diff --git a/.gitignore b/.gitignore index 088ba6ba7..29c34dbec 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk + +*.dylib \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..26d33521a --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 000000000..1d9864d6f --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000..a55e7a179 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/ldk-node.iml b/.idea/ldk-node.iml new file mode 100644 index 000000000..9b4cf845b --- /dev/null +++ b/.idea/ldk-node.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..5a0adcddb --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..830674470 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 70026985e..3acdf312a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,30 @@ # ldk-lite A Simplified API for LDK. + +## Build and publish to local Maven repository +```shell +source uniffi_bindgen_generate_kotlin.sh +./gradlew publishToMavenLocal +``` + +## How to Use +To use the Kotlin language bindings for [`ldk-node`] in your JVM project, add the following to your gradle dependencies: +```kotlin +repositories { + mavenCentral() +} + +dependencies { + implementation("org.ldk:ldk-node:0.0.1") +} +``` + +You may then import and use the `org.ldk_node` library in your Kotlin code. For example: +```kotlin +import uniffi.ldk_node.Builder +import uniffi.ldk_node.Node + +fun main() { + val node: Node = Builder().build() +} +``` diff --git a/ldk-node-jvm/.gitattributes b/ldk-node-jvm/.gitattributes new file mode 100644 index 000000000..00a51aff5 --- /dev/null +++ b/ldk-node-jvm/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/ldk-node-jvm/.gitignore b/ldk-node-jvm/.gitignore new file mode 100644 index 000000000..67c4583a2 --- /dev/null +++ b/ldk-node-jvm/.gitignore @@ -0,0 +1,7 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +.idea/ diff --git a/ldk-node-jvm/gradle.properties b/ldk-node-jvm/gradle.properties new file mode 100644 index 000000000..a6c229201 --- /dev/null +++ b/ldk-node-jvm/gradle.properties @@ -0,0 +1 @@ +libraryVersion=0.0.1 diff --git a/ldk-node-jvm/gradle/wrapper/gradle-wrapper.jar b/ldk-node-jvm/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..41d9927a4 Binary files /dev/null and b/ldk-node-jvm/gradle/wrapper/gradle-wrapper.jar differ diff --git a/ldk-node-jvm/gradle/wrapper/gradle-wrapper.properties b/ldk-node-jvm/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..00e33edef --- /dev/null +++ b/ldk-node-jvm/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/ldk-node-jvm/gradlew b/ldk-node-jvm/gradlew new file mode 100755 index 000000000..1b6c78733 --- /dev/null +++ b/ldk-node-jvm/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/ldk-node-jvm/gradlew.bat b/ldk-node-jvm/gradlew.bat new file mode 100644 index 000000000..107acd32c --- /dev/null +++ b/ldk-node-jvm/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/ldk-node-jvm/lib/build.gradle.kts b/ldk-node-jvm/lib/build.gradle.kts new file mode 100644 index 000000000..2903e0367 --- /dev/null +++ b/ldk-node-jvm/lib/build.gradle.kts @@ -0,0 +1,100 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat.* +import org.gradle.api.tasks.testing.logging.TestLogEvent.* + +// library version is defined in gradle.properties +val libraryVersion: String by project + +plugins { + id("org.jetbrains.kotlin.jvm") version "1.5.31" + id("java-library") + id("maven-publish") +} + +repositories { + mavenCentral() +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + withSourcesJar() + withJavadocJar() +} + +tasks.withType { + useJUnitPlatform() + + testLogging { + events(PASSED, SKIPPED, FAILED, STANDARD_OUT, STANDARD_ERROR) + exceptionFormat = FULL + showExceptions = true + showCauses = true + showStackTraces = true + } +} + +dependencies { + // Align versions of all Kotlin components + implementation(platform("org.jetbrains.kotlin:kotlin-bom")) + + // Use the Kotlin JDK 8 standard library. + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + + implementation("net.java.dev.jna:jna:5.8.0") +} + +afterEvaluate { + publishing { + publications { + create("maven") { + groupId = "org.ldk" + artifactId = "ldk-node" + version = libraryVersion + + from(components["java"]) + pom { + name.set("ldk-node") + description.set("Lightning Dev Kit Node language bindings for Kotlin.") + url.set("https://lightningdevkit.org") + licenses { + license { + name.set("APACHE 2.0") + url.set("https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-APACHE") + } + license { + name.set("MIT") + url.set("https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT") + } + } + developers { + developer { + id.set("") + name.set("") + email.set("") + } + developer { + id.set("") + name.set("") + email.set("") + } + } + scm { + connection.set("scm:git:github.com/lightningdevkit/ldk-node.git") + developerConnection.set("scm:git:ssh://github.com/lightningdevkit/ldk-node.git") + url.set("https://github.com/lightningdevkit/ldk-node/tree/master") + } + } + } + } + } +} + +testing { + suites { + // Configure the built-in test suite + val test by getting(JvmTestSuite::class) { + // Use Kotlin Test test framework + useKotlinTest() + } + } +} diff --git a/ldk-node-jvm/lib/src/main/kotlin/ldk/node/ldk_node.kt b/ldk-node-jvm/lib/src/main/kotlin/ldk/node/ldk_node.kt new file mode 100644 index 000000000..56452d287 --- /dev/null +++ b/ldk-node-jvm/lib/src/main/kotlin/ldk/node/ldk_node.kt @@ -0,0 +1,1203 @@ +// 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_node; + +// 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_node_ad4b_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_node_ad4b_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_node" +} + +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_node") + + } + } + + fun ffi_ldk_node_ad4b_Builder_object_free(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_node_ad4b_Builder_new( + _uniffi_out_err: RustCallStatus + ): Pointer + + fun ldk_node_ad4b_Builder_build(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Pointer + + fun ffi_ldk_node_ad4b_Node_object_free(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_node_ad4b_Node_start(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_node_ad4b_Node_stop(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_node_ad4b_Node_next_event(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_node_ad4b_Node_event_handled(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_node_ad4b_Node_node_id(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_node_ad4b_Node_new_funding_address(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_node_ad4b_Node_connect_open_channel(`ptr`: Pointer,`nodePubkeyAndAddress`: RustBuffer.ByValue,`channelAmountSats`: Long,`announceChannel`: Byte, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_node_ad4b_Node_send_payment(`ptr`: Pointer,`invoice`: RustBuffer.ByValue, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_node_ad4b_Node_send_spontaneous_payment(`ptr`: Pointer,`amountMsat`: Long,`nodeId`: RustBuffer.ByValue, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_node_ad4b_Node_receive_payment(`ptr`: Pointer,`amountMsat`: RustBuffer.ByValue,`description`: RustBuffer.ByValue,`expirySecs`: Int, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_ldk_node_ad4b_rustbuffer_alloc(`size`: Int, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_ldk_node_ad4b_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_ldk_node_ad4b_rustbuffer_free(`buf`: RustBuffer.ByValue, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_ldk_node_ad4b_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) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun 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`(): Node + +} + +class Builder( + pointer: Pointer +) : FFIObject(pointer), BuilderInterface { + constructor() : + this( + rustCall() { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_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. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_ldk_node_ad4b_Builder_object_free(this.pointer, status) + } + } + + override fun `build`(): Node = + callWithPointer { + rustCall() { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Builder_build(it, _status) +} + }.let { + FfiConverterTypeNode.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 NodeInterface { + + @Throws(NodeException::class) + fun `start`() + + @Throws(NodeException::class) + fun `stop`() + + fun `nextEvent`(): Event + + fun `eventHandled`() + + @Throws(NodeException::class) + fun `nodeId`(): PublicKey + + @Throws(NodeException::class) + fun `newFundingAddress`(): Address + + @Throws(NodeException::class) + fun `connectOpenChannel`(`nodePubkeyAndAddress`: String, `channelAmountSats`: ULong, `announceChannel`: Boolean) + + @Throws(NodeException::class) + fun `sendPayment`(`invoice`: Invoice): PaymentHash + + @Throws(NodeException::class) + fun `sendSpontaneousPayment`(`amountMsat`: ULong, `nodeId`: String): PaymentHash + + @Throws(NodeException::class) + fun `receivePayment`(`amountMsat`: ULong?, `description`: String, `expirySecs`: UInt): Invoice + +} + +class Node( + pointer: Pointer +) : FFIObject(pointer), NodeInterface { + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_ldk_node_ad4b_Node_object_free(this.pointer, status) + } + } + + + @Throws(NodeException::class)override fun `start`() = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_start(it, _status) +} + } + + + @Throws(NodeException::class)override fun `stop`() = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_stop(it, _status) +} + } + + override fun `nextEvent`(): Event = + callWithPointer { + rustCall() { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_next_event(it, _status) +} + }.let { + FfiConverterTypeEvent.lift(it) + } + override fun `eventHandled`() = + callWithPointer { + rustCall() { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_event_handled(it, _status) +} + } + + + @Throws(NodeException::class)override fun `nodeId`(): PublicKey = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_node_id(it, _status) +} + }.let { + FfiConverterTypePublicKey.lift(it) + } + + @Throws(NodeException::class)override fun `newFundingAddress`(): Address = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_new_funding_address(it, _status) +} + }.let { + FfiConverterTypeAddress.lift(it) + } + + @Throws(NodeException::class)override fun `connectOpenChannel`(`nodePubkeyAndAddress`: String, `channelAmountSats`: ULong, `announceChannel`: Boolean) = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_connect_open_channel(it, FfiConverterString.lower(`nodePubkeyAndAddress`), FfiConverterULong.lower(`channelAmountSats`), FfiConverterBoolean.lower(`announceChannel`), _status) +} + } + + + @Throws(NodeException::class)override fun `sendPayment`(`invoice`: Invoice): PaymentHash = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_send_payment(it, FfiConverterTypeInvoice.lower(`invoice`), _status) +} + }.let { + FfiConverterTypePaymentHash.lift(it) + } + + @Throws(NodeException::class)override fun `sendSpontaneousPayment`(`amountMsat`: ULong, `nodeId`: String): PaymentHash = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_send_spontaneous_payment(it, FfiConverterULong.lower(`amountMsat`), FfiConverterString.lower(`nodeId`), _status) +} + }.let { + FfiConverterTypePaymentHash.lift(it) + } + + @Throws(NodeException::class)override fun `receivePayment`(`amountMsat`: ULong?, `description`: String, `expirySecs`: UInt): Invoice = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_receive_payment(it, FfiConverterOptionalULong.lower(`amountMsat`), FfiConverterString.lower(`description`), FfiConverterUInt.lower(`expirySecs`), _status) +} + }.let { + FfiConverterTypeInvoice.lift(it) + } + + + +} + +public object FfiConverterTypeNode: FfiConverter { + override fun lower(value: Node): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): Node { + return Node(value) + } + + override fun read(buf: ByteBuffer): Node { + // 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: Node) = 8 + + override fun write(value: Node, 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 ChannelReady( + val `channelId`: ChannelId, + val `userChannelId`: UserChannelId + ) : Event() + data class ChannelClosed( + val `channelId`: ChannelId, + val `userChannelId`: UserChannelId + ) : 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.ChannelReady( + FfiConverterTypeChannelId.read(buf), + FfiConverterTypeUserChannelId.read(buf), + ) + 5 -> Event.ChannelClosed( + FfiConverterTypeChannelId.read(buf), + FfiConverterTypeUserChannelId.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.ChannelReady -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4 + + FfiConverterTypeChannelId.allocationSize(value.`channelId`) + + FfiConverterTypeUserChannelId.allocationSize(value.`userChannelId`) + ) + } + 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`) + + FfiConverterTypeUserChannelId.allocationSize(value.`userChannelId`) + ) + } + } + + 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.ChannelReady -> { + buf.putInt(4) + FfiConverterTypeChannelId.write(value.`channelId`, buf) + FfiConverterTypeUserChannelId.write(value.`userChannelId`, buf) + Unit + } + is Event.ChannelClosed -> { + buf.putInt(5) + FfiConverterTypeChannelId.write(value.`channelId`, buf) + FfiConverterTypeUserChannelId.write(value.`userChannelId`, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + + + + + + + +sealed class NodeException(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) : NodeException(message) + class NotRunning(message: String) : NodeException(message) + class FundingTxCreationFailed(message: String) : NodeException(message) + class ConnectionFailed(message: String) : NodeException(message) + class AddressInvalid(message: String) : NodeException(message) + class PublicKeyInvalid(message: String) : NodeException(message) + class PaymentHashInvalid(message: String) : NodeException(message) + class NonUniquePaymentHash(message: String) : NodeException(message) + class InvoiceInvalid(message: String) : NodeException(message) + class InvoiceCreationFailed(message: String) : NodeException(message) + class ChannelIdInvalid(message: String) : NodeException(message) + class RoutingFailed(message: String) : NodeException(message) + class PeerInfoParseFailed(message: String) : NodeException(message) + class ChannelCreationFailed(message: String) : NodeException(message) + class ChannelClosingFailed(message: String) : NodeException(message) + class PersistenceFailed(message: String) : NodeException(message) + class WalletOperationFailed(message: String) : NodeException(message) + class WalletSigningFailed(message: String) : NodeException(message) + class TxSyncFailed(message: String) : NodeException(message) + + + companion object ErrorHandler : CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): NodeException = FfiConverterTypeNodeError.lift(error_buf) + } +} + +public object FfiConverterTypeNodeError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): NodeException { + + return when(buf.getInt()) { + 1 -> NodeException.AlreadyRunning(FfiConverterString.read(buf)) + 2 -> NodeException.NotRunning(FfiConverterString.read(buf)) + 3 -> NodeException.FundingTxCreationFailed(FfiConverterString.read(buf)) + 4 -> NodeException.ConnectionFailed(FfiConverterString.read(buf)) + 5 -> NodeException.AddressInvalid(FfiConverterString.read(buf)) + 6 -> NodeException.PublicKeyInvalid(FfiConverterString.read(buf)) + 7 -> NodeException.PaymentHashInvalid(FfiConverterString.read(buf)) + 8 -> NodeException.NonUniquePaymentHash(FfiConverterString.read(buf)) + 9 -> NodeException.InvoiceInvalid(FfiConverterString.read(buf)) + 10 -> NodeException.InvoiceCreationFailed(FfiConverterString.read(buf)) + 11 -> NodeException.ChannelIdInvalid(FfiConverterString.read(buf)) + 12 -> NodeException.RoutingFailed(FfiConverterString.read(buf)) + 13 -> NodeException.PeerInfoParseFailed(FfiConverterString.read(buf)) + 14 -> NodeException.ChannelCreationFailed(FfiConverterString.read(buf)) + 15 -> NodeException.ChannelClosingFailed(FfiConverterString.read(buf)) + 16 -> NodeException.PersistenceFailed(FfiConverterString.read(buf)) + 17 -> NodeException.WalletOperationFailed(FfiConverterString.read(buf)) + 18 -> NodeException.WalletSigningFailed(FfiConverterString.read(buf)) + 19 -> NodeException.TxSyncFailed(FfiConverterString.read(buf)) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + + } + + override fun allocationSize(value: NodeException): Int { + return 4 + } + + override fun write(value: NodeException, buf: ByteBuffer) { + when(value) { + is NodeException.AlreadyRunning -> { + buf.putInt(1) + Unit + } + is NodeException.NotRunning -> { + buf.putInt(2) + Unit + } + is NodeException.FundingTxCreationFailed -> { + buf.putInt(3) + Unit + } + is NodeException.ConnectionFailed -> { + buf.putInt(4) + Unit + } + is NodeException.AddressInvalid -> { + buf.putInt(5) + Unit + } + is NodeException.PublicKeyInvalid -> { + buf.putInt(6) + Unit + } + is NodeException.PaymentHashInvalid -> { + buf.putInt(7) + Unit + } + is NodeException.NonUniquePaymentHash -> { + buf.putInt(8) + Unit + } + is NodeException.InvoiceInvalid -> { + buf.putInt(9) + Unit + } + is NodeException.InvoiceCreationFailed -> { + buf.putInt(10) + Unit + } + is NodeException.ChannelIdInvalid -> { + buf.putInt(11) + Unit + } + is NodeException.RoutingFailed -> { + buf.putInt(12) + Unit + } + is NodeException.PeerInfoParseFailed -> { + buf.putInt(13) + Unit + } + is NodeException.ChannelCreationFailed -> { + buf.putInt(14) + Unit + } + is NodeException.ChannelClosingFailed -> { + buf.putInt(15) + Unit + } + is NodeException.PersistenceFailed -> { + buf.putInt(16) + Unit + } + is NodeException.WalletOperationFailed -> { + buf.putInt(17) + Unit + } + is NodeException.WalletSigningFailed -> { + buf.putInt(18) + Unit + } + is NodeException.TxSyncFailed -> { + buf.putInt(19) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + +} + + + + +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 + + + +/** + * 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 UserChannelId = String +public typealias FfiConverterTypeUserChannelId = FfiConverterString + + diff --git a/ldk-node-jvm/lib/src/test/kotlin/ldk/node/LibraryTest.kt b/ldk-node-jvm/lib/src/test/kotlin/ldk/node/LibraryTest.kt new file mode 100644 index 000000000..3914cb939 --- /dev/null +++ b/ldk-node-jvm/lib/src/test/kotlin/ldk/node/LibraryTest.kt @@ -0,0 +1,14 @@ +/* + * This Kotlin source file was generated by the Gradle 'init' task. + */ +package ldk.node + +import kotlin.test.Test +import kotlin.test.assertTrue + +class LibraryTest { + @Test fun someLibraryMethodReturnsTrue() { + val classUnderTest = Library() + assertTrue(classUnderTest.someLibraryMethod(), "someLibraryMethod should return 'true'") + } +} diff --git a/ldk-node-jvm/settings.gradle.kts b/ldk-node-jvm/settings.gradle.kts new file mode 100644 index 000000000..75e57139a --- /dev/null +++ b/ldk-node-jvm/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "ldk-node-jvm" +include("lib") diff --git a/uniffi/uniffi/ldk_node/ldk_node.kt b/uniffi/uniffi/ldk_node/ldk_node.kt new file mode 100644 index 000000000..56452d287 --- /dev/null +++ b/uniffi/uniffi/ldk_node/ldk_node.kt @@ -0,0 +1,1203 @@ +// 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_node; + +// 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_node_ad4b_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_node_ad4b_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_node" +} + +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_node") + + } + } + + fun ffi_ldk_node_ad4b_Builder_object_free(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_node_ad4b_Builder_new( + _uniffi_out_err: RustCallStatus + ): Pointer + + fun ldk_node_ad4b_Builder_build(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Pointer + + fun ffi_ldk_node_ad4b_Node_object_free(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_node_ad4b_Node_start(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_node_ad4b_Node_stop(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_node_ad4b_Node_next_event(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_node_ad4b_Node_event_handled(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_node_ad4b_Node_node_id(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_node_ad4b_Node_new_funding_address(`ptr`: Pointer, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_node_ad4b_Node_connect_open_channel(`ptr`: Pointer,`nodePubkeyAndAddress`: RustBuffer.ByValue,`channelAmountSats`: Long,`announceChannel`: Byte, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ldk_node_ad4b_Node_send_payment(`ptr`: Pointer,`invoice`: RustBuffer.ByValue, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_node_ad4b_Node_send_spontaneous_payment(`ptr`: Pointer,`amountMsat`: Long,`nodeId`: RustBuffer.ByValue, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ldk_node_ad4b_Node_receive_payment(`ptr`: Pointer,`amountMsat`: RustBuffer.ByValue,`description`: RustBuffer.ByValue,`expirySecs`: Int, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_ldk_node_ad4b_rustbuffer_alloc(`size`: Int, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_ldk_node_ad4b_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue, + _uniffi_out_err: RustCallStatus + ): RustBuffer.ByValue + + fun ffi_ldk_node_ad4b_rustbuffer_free(`buf`: RustBuffer.ByValue, + _uniffi_out_err: RustCallStatus + ): Unit + + fun ffi_ldk_node_ad4b_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) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun 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`(): Node + +} + +class Builder( + pointer: Pointer +) : FFIObject(pointer), BuilderInterface { + constructor() : + this( + rustCall() { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_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. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_ldk_node_ad4b_Builder_object_free(this.pointer, status) + } + } + + override fun `build`(): Node = + callWithPointer { + rustCall() { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Builder_build(it, _status) +} + }.let { + FfiConverterTypeNode.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 NodeInterface { + + @Throws(NodeException::class) + fun `start`() + + @Throws(NodeException::class) + fun `stop`() + + fun `nextEvent`(): Event + + fun `eventHandled`() + + @Throws(NodeException::class) + fun `nodeId`(): PublicKey + + @Throws(NodeException::class) + fun `newFundingAddress`(): Address + + @Throws(NodeException::class) + fun `connectOpenChannel`(`nodePubkeyAndAddress`: String, `channelAmountSats`: ULong, `announceChannel`: Boolean) + + @Throws(NodeException::class) + fun `sendPayment`(`invoice`: Invoice): PaymentHash + + @Throws(NodeException::class) + fun `sendSpontaneousPayment`(`amountMsat`: ULong, `nodeId`: String): PaymentHash + + @Throws(NodeException::class) + fun `receivePayment`(`amountMsat`: ULong?, `description`: String, `expirySecs`: UInt): Invoice + +} + +class Node( + pointer: Pointer +) : FFIObject(pointer), NodeInterface { + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.ffi_ldk_node_ad4b_Node_object_free(this.pointer, status) + } + } + + + @Throws(NodeException::class)override fun `start`() = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_start(it, _status) +} + } + + + @Throws(NodeException::class)override fun `stop`() = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_stop(it, _status) +} + } + + override fun `nextEvent`(): Event = + callWithPointer { + rustCall() { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_next_event(it, _status) +} + }.let { + FfiConverterTypeEvent.lift(it) + } + override fun `eventHandled`() = + callWithPointer { + rustCall() { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_event_handled(it, _status) +} + } + + + @Throws(NodeException::class)override fun `nodeId`(): PublicKey = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_node_id(it, _status) +} + }.let { + FfiConverterTypePublicKey.lift(it) + } + + @Throws(NodeException::class)override fun `newFundingAddress`(): Address = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_new_funding_address(it, _status) +} + }.let { + FfiConverterTypeAddress.lift(it) + } + + @Throws(NodeException::class)override fun `connectOpenChannel`(`nodePubkeyAndAddress`: String, `channelAmountSats`: ULong, `announceChannel`: Boolean) = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_connect_open_channel(it, FfiConverterString.lower(`nodePubkeyAndAddress`), FfiConverterULong.lower(`channelAmountSats`), FfiConverterBoolean.lower(`announceChannel`), _status) +} + } + + + @Throws(NodeException::class)override fun `sendPayment`(`invoice`: Invoice): PaymentHash = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_send_payment(it, FfiConverterTypeInvoice.lower(`invoice`), _status) +} + }.let { + FfiConverterTypePaymentHash.lift(it) + } + + @Throws(NodeException::class)override fun `sendSpontaneousPayment`(`amountMsat`: ULong, `nodeId`: String): PaymentHash = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_send_spontaneous_payment(it, FfiConverterULong.lower(`amountMsat`), FfiConverterString.lower(`nodeId`), _status) +} + }.let { + FfiConverterTypePaymentHash.lift(it) + } + + @Throws(NodeException::class)override fun `receivePayment`(`amountMsat`: ULong?, `description`: String, `expirySecs`: UInt): Invoice = + callWithPointer { + rustCallWithError(NodeException) { _status -> + _UniFFILib.INSTANCE.ldk_node_ad4b_Node_receive_payment(it, FfiConverterOptionalULong.lower(`amountMsat`), FfiConverterString.lower(`description`), FfiConverterUInt.lower(`expirySecs`), _status) +} + }.let { + FfiConverterTypeInvoice.lift(it) + } + + + +} + +public object FfiConverterTypeNode: FfiConverter { + override fun lower(value: Node): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): Node { + return Node(value) + } + + override fun read(buf: ByteBuffer): Node { + // 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: Node) = 8 + + override fun write(value: Node, 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 ChannelReady( + val `channelId`: ChannelId, + val `userChannelId`: UserChannelId + ) : Event() + data class ChannelClosed( + val `channelId`: ChannelId, + val `userChannelId`: UserChannelId + ) : 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.ChannelReady( + FfiConverterTypeChannelId.read(buf), + FfiConverterTypeUserChannelId.read(buf), + ) + 5 -> Event.ChannelClosed( + FfiConverterTypeChannelId.read(buf), + FfiConverterTypeUserChannelId.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.ChannelReady -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4 + + FfiConverterTypeChannelId.allocationSize(value.`channelId`) + + FfiConverterTypeUserChannelId.allocationSize(value.`userChannelId`) + ) + } + 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`) + + FfiConverterTypeUserChannelId.allocationSize(value.`userChannelId`) + ) + } + } + + 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.ChannelReady -> { + buf.putInt(4) + FfiConverterTypeChannelId.write(value.`channelId`, buf) + FfiConverterTypeUserChannelId.write(value.`userChannelId`, buf) + Unit + } + is Event.ChannelClosed -> { + buf.putInt(5) + FfiConverterTypeChannelId.write(value.`channelId`, buf) + FfiConverterTypeUserChannelId.write(value.`userChannelId`, buf) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + + + + + + + +sealed class NodeException(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) : NodeException(message) + class NotRunning(message: String) : NodeException(message) + class FundingTxCreationFailed(message: String) : NodeException(message) + class ConnectionFailed(message: String) : NodeException(message) + class AddressInvalid(message: String) : NodeException(message) + class PublicKeyInvalid(message: String) : NodeException(message) + class PaymentHashInvalid(message: String) : NodeException(message) + class NonUniquePaymentHash(message: String) : NodeException(message) + class InvoiceInvalid(message: String) : NodeException(message) + class InvoiceCreationFailed(message: String) : NodeException(message) + class ChannelIdInvalid(message: String) : NodeException(message) + class RoutingFailed(message: String) : NodeException(message) + class PeerInfoParseFailed(message: String) : NodeException(message) + class ChannelCreationFailed(message: String) : NodeException(message) + class ChannelClosingFailed(message: String) : NodeException(message) + class PersistenceFailed(message: String) : NodeException(message) + class WalletOperationFailed(message: String) : NodeException(message) + class WalletSigningFailed(message: String) : NodeException(message) + class TxSyncFailed(message: String) : NodeException(message) + + + companion object ErrorHandler : CallStatusErrorHandler { + override fun lift(error_buf: RustBuffer.ByValue): NodeException = FfiConverterTypeNodeError.lift(error_buf) + } +} + +public object FfiConverterTypeNodeError : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): NodeException { + + return when(buf.getInt()) { + 1 -> NodeException.AlreadyRunning(FfiConverterString.read(buf)) + 2 -> NodeException.NotRunning(FfiConverterString.read(buf)) + 3 -> NodeException.FundingTxCreationFailed(FfiConverterString.read(buf)) + 4 -> NodeException.ConnectionFailed(FfiConverterString.read(buf)) + 5 -> NodeException.AddressInvalid(FfiConverterString.read(buf)) + 6 -> NodeException.PublicKeyInvalid(FfiConverterString.read(buf)) + 7 -> NodeException.PaymentHashInvalid(FfiConverterString.read(buf)) + 8 -> NodeException.NonUniquePaymentHash(FfiConverterString.read(buf)) + 9 -> NodeException.InvoiceInvalid(FfiConverterString.read(buf)) + 10 -> NodeException.InvoiceCreationFailed(FfiConverterString.read(buf)) + 11 -> NodeException.ChannelIdInvalid(FfiConverterString.read(buf)) + 12 -> NodeException.RoutingFailed(FfiConverterString.read(buf)) + 13 -> NodeException.PeerInfoParseFailed(FfiConverterString.read(buf)) + 14 -> NodeException.ChannelCreationFailed(FfiConverterString.read(buf)) + 15 -> NodeException.ChannelClosingFailed(FfiConverterString.read(buf)) + 16 -> NodeException.PersistenceFailed(FfiConverterString.read(buf)) + 17 -> NodeException.WalletOperationFailed(FfiConverterString.read(buf)) + 18 -> NodeException.WalletSigningFailed(FfiConverterString.read(buf)) + 19 -> NodeException.TxSyncFailed(FfiConverterString.read(buf)) + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + + } + + override fun allocationSize(value: NodeException): Int { + return 4 + } + + override fun write(value: NodeException, buf: ByteBuffer) { + when(value) { + is NodeException.AlreadyRunning -> { + buf.putInt(1) + Unit + } + is NodeException.NotRunning -> { + buf.putInt(2) + Unit + } + is NodeException.FundingTxCreationFailed -> { + buf.putInt(3) + Unit + } + is NodeException.ConnectionFailed -> { + buf.putInt(4) + Unit + } + is NodeException.AddressInvalid -> { + buf.putInt(5) + Unit + } + is NodeException.PublicKeyInvalid -> { + buf.putInt(6) + Unit + } + is NodeException.PaymentHashInvalid -> { + buf.putInt(7) + Unit + } + is NodeException.NonUniquePaymentHash -> { + buf.putInt(8) + Unit + } + is NodeException.InvoiceInvalid -> { + buf.putInt(9) + Unit + } + is NodeException.InvoiceCreationFailed -> { + buf.putInt(10) + Unit + } + is NodeException.ChannelIdInvalid -> { + buf.putInt(11) + Unit + } + is NodeException.RoutingFailed -> { + buf.putInt(12) + Unit + } + is NodeException.PeerInfoParseFailed -> { + buf.putInt(13) + Unit + } + is NodeException.ChannelCreationFailed -> { + buf.putInt(14) + Unit + } + is NodeException.ChannelClosingFailed -> { + buf.putInt(15) + Unit + } + is NodeException.PersistenceFailed -> { + buf.putInt(16) + Unit + } + is NodeException.WalletOperationFailed -> { + buf.putInt(17) + Unit + } + is NodeException.WalletSigningFailed -> { + buf.putInt(18) + Unit + } + is NodeException.TxSyncFailed -> { + buf.putInt(19) + Unit + } + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + +} + + + + +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 + + + +/** + * 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 UserChannelId = String +public typealias FfiConverterTypeUserChannelId = FfiConverterString + + diff --git a/uniffi_bindgen_generate_kotlin.sh b/uniffi_bindgen_generate_kotlin.sh new file mode 100644 index 000000000..398a5406c --- /dev/null +++ b/uniffi_bindgen_generate_kotlin.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# make sure you're using the uniffi-bindgen tool version 0.21.0 + +# 1. Generate the glue file +# 2. Build the native binaries +# 3. Move them both in the jvm lib +uniffi-bindgen generate uniffi/ldk_node.udl --language kotlin +cargo +nightly build --target aarch64-apple-darwin +cp ./uniffi/uniffi/ldk_node/ldk_node.kt ./ldk-node-jvm/lib/src/main/kotlin/ldk/node/ +cp ./target/debug/libldk_node.dylib ./ldk-node-jvm/lib/src/main/resources/darwin-aarch64/libuniffi_ldk_node.dylib