diff --git a/bin/main/logback.xml b/bin/main/logback.xml new file mode 100644 index 0000000..c8b7060 --- /dev/null +++ b/bin/main/logback.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 0330431..27034c9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,8 +16,6 @@ plugins { kotlin("multiplatform") version Versions.kotlin kotlin("plugin.serialization") version Versions.kotlin id("app.cash.sqldelight") version Versions.sqlDelight - `java-library` - id("org.bytedeco.gradle-javacpp-platform").version("1.5.10") application } @@ -75,6 +73,9 @@ val buildVersionsTask by tasks.registering(Sync::class) { kotlin { jvm { withJava() + compilations["main"].defaultSourceSet { + resources.srcDirs("src/jvmMain/kotlin/fr/acinq/lightning/vsock/") + } } fun KotlinNativeTargetWithHostTests.phoenixBinaries() { @@ -83,10 +84,10 @@ kotlin { entryPoint = "fr.acinq.lightning.bin.main" optimized = false // without this, release mode throws 'Index 0 out of bounds for length 0' in StaticInitializersOptimization.kt } - executable("phoenix-cli") { + /*executable("phoenix-cli") { entryPoint = "fr.acinq.lightning.cli.main" optimized = false // without this, release mode throws 'Index 0 out of bounds for length 0' in StaticInitializersOptimization.kt - } + }*/ } } @@ -111,20 +112,14 @@ kotlin { } sourceSets { - val jvmMain by getting { - // Include the native source directory if needed - resources.srcDirs("src/commonMain/kotlin/fr/acinq/lightning/vsock/native") - } commonMain { kotlin.srcDir(buildVersionsTask.map { it.destinationDir }) dependencies { implementation("com.github.raymond98.lightning-kmp:lightning-kmp:v1.6.2-FEECREDIT-8") - implementation("org.bytedeco:javacpp:1.5.10") - implementation(kotlin("stdlib-jdk8")) - implementation("org.bytedeco:javacv-platform:1.5.10") - implementation("org.bytedeco:javacpp-presets:1.5.10") + implementation("org.jetbrains.kotlinx:atomicfu:0.25.0") + implementation("org.jetbrains.kotlin:kotlin-stdlib:1.8.20") api("fr.acinq.bitcoin:bitcoin-kmp:${Versions.bitcoinKmpVersion}") api("co.touchlab:kermit:${Versions.kermitLoggerVersion}") api("org.jetbrains.kotlinx:kotlinx-datetime:${Versions.datetimeVersion}") @@ -157,6 +152,8 @@ kotlin { implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-jvm:${Versions.secpJniJvmVersion}") implementation(ktor("client-okhttp")) implementation("ch.qos.logback:logback-classic:1.2.3") + implementation("org.bytedeco:javacpp:1.5.10") + implementation("org.bytedeco:javacpp-presets:1.5.10") } } nativeMain { @@ -220,7 +217,8 @@ application { mainClass = "fr.acinq.lightning.bin.MainKt" // Set java.library.path to include the directory where the shared library is generated - applicationDefaultJvmArgs = listOf("-Djava.library.path=${project.buildDir}/libs") + applicationDefaultJvmArgs = listOf("-Djava.library.path=${layout.buildDirectory.dir("libs").get().asFile}") + applicationDefaultJvmArgs = listOf("-DLIBS_PATH=${layout.buildDirectory.dir("libs").get().asFile.absolutePath.replace("\\", "/")}") } val cliScripts by tasks.register("cliScripts", CreateStartScripts::class) { @@ -234,11 +232,27 @@ tasks.startScripts { dependsOn(cliScripts) } +val setLibPath by tasks.register("runLinuxExecutable") { + val libsDir = layout.buildDirectory.dir("libs").get().asFile + val linuxLibsPath = libsDir.absolutePath.replace("\\", "/") + + // Set the environment variable + environment("LIBS_PATH", linuxLibsPath) + + // Mark the task as always out-of-date + outputs.upToDateWhen { false } + + // Execute the command + commandLine("sh", "-c", "LIBS_PATH=$linuxLibsPath; export LIBS_PATH=$linuxLibsPath") +} + + + val compileNative by tasks.register("compileNative") { group = "build" description = "Compile the native C++ code into a shared library" - val outputDir = file("${project.buildDir}/libs") + val outputDir = layout.buildDirectory.dir("libs").get().asFile val nativeSourceDir = file("src/commonMain/kotlin/fr/acinq/lightning/vsock/native") // Locate the JNI headers - adjust these paths based on your actual JDK location @@ -252,13 +266,14 @@ val compileNative by tasks.register("compileNative") { commandLine("g++", "-I$jniIncludeDir", "-I$jniPlatformIncludeDir", "-shared", "-o", outputDir.resolve("libjniVSockImpl.so"), nativeSourceDir.resolve("VSockImpl.cpp"), "-fPIC") } -// Ensure the native library is compiled before creating the distribution tasks.withType { dependsOn(compileNative) + dependsOn(setLibPath) } tasks.withType { dependsOn(compileNative) + dependsOn(setLibPath) } distributions { @@ -271,7 +286,6 @@ distributions { // forward std input when app is run via gradle (otherwise keyboard input will return EOF) tasks.withType { standardInput = System.`in` - dependsOn(compileNative) //This should not be the case for all platforms } sqldelight { diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 7a499ef..6c8fd8e 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,7 +1,6 @@ object Versions { val kotlin = "1.9.23" val lightningKmp = "1.7.0-FEECREDIT-8" - val lightningKmpTag = "v1.6.2-FEECREDIT-8" val sqlDelight = "2.0.1" val okio = "3.8.0" val clikt = "4.2.2" diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/Api.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/Api.kt index b2bb8e1..8cc9100 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/Api.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/Api.kt @@ -372,7 +372,7 @@ class Api(private val nodeParams: NodeParams, private val peer: Peer, private va else -> call.respondText("Splice-in failed: unexpected response type", status = HttpStatusCode.InternalServerError) } } catch (e: Exception) { - call.respond(HttpStatusCode.InternalServerError, "Failed to process splice-in: ${e.localizedMessage}") + call.respond(HttpStatusCode.InternalServerError, "Failed to process splice-in: ${e.message}") } } post("closechannel") { diff --git a/src/commonMain/kotlin/fr/acinq/lightning/vsock/BaseVSock.kt b/src/commonMain/kotlin/fr/acinq/lightning/vsock/BaseVSock.kt index da725ad..cad6fc2 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/vsock/BaseVSock.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/vsock/BaseVSock.kt @@ -1,67 +1,64 @@ package fr.acinq.lightning.vsock +import fr.acinq.lightning.logging.LoggerFactory +import fr.acinq.lightning.logging.error +import fr.acinq.lightning.logging.warning import fr.acinq.lightning.vsock.native.VSockImpl -import java.io.Closeable -import java.io.IOException -import java.net.SocketException +import kotlinx.atomicfu.locks.SynchronizedObject +import kotlinx.atomicfu.locks.synchronized +import kotlin.jvm.Synchronized -abstract class BaseVSock : Closeable { - protected val closeLock: Any = Any() +abstract class BaseVSock(loggerfactory: LoggerFactory) : Closeable { + private val closeLock = SynchronizedObject() + val logger = loggerfactory.newLogger(this::class) protected var isClosed: Boolean = false protected var created: Boolean = false protected var bound: Boolean = false - private var implementation: VSockImpl? = null + private lateinit var implementation: VSockImpl @Throws(SocketException::class) private fun createImplementation() { implementation = VSockImpl() - implementation!!.create() + implementation.create() created = true } @Throws(SocketException::class) - fun getImplementation(): VSockImpl? { + fun getImplementation(): VSockImpl { if (!created) createImplementation() return implementation } @Throws(SocketException::class) fun setImplementation(): VSockImpl { - if (implementation == null) { - implementation = VSockImpl() - } - return implementation!! + return implementation } - @get:Throws(IOException::class) - val localCid: Int - get() = getImplementation()!!.getLocalCid() - - @JvmOverloads @Throws(IOException::class) fun bind(address: VSockAddress?, backlog: Int = DEFAULT_BACKLOG) { - var backlog = backlog + var backlogs = backlog if (isClosed) { - throw SocketException("Socket closed") + logger.warning { "Socket closed" } } if (bound) { - throw SocketException("Socket already bound") + logger.warning { "Socket already bound" } } - if (backlog <= 0) { - backlog = DEFAULT_BACKLOG + if (backlogs <= 0) { + backlogs = DEFAULT_BACKLOG } - getImplementation()!!.bind(address) - getImplementation()!!.listen(backlog) + if (address != null) { + getImplementation().bind(address) + } + getImplementation().listen(backlogs) bound = true } @Synchronized - @Throws(IOException::class) override fun close() { synchronized(closeLock) { if (isClosed) return - if (created) getImplementation()!!.close() + if (created) getImplementation().close() isClosed = true } } @@ -70,3 +67,9 @@ abstract class BaseVSock : Closeable { private const val DEFAULT_BACKLOG = 42 } } + +// Custom Closeable interface for Kotlin/Native +interface Closeable { + fun close() +} + diff --git a/src/commonMain/kotlin/fr/acinq/lightning/vsock/ServerVSock.kt b/src/commonMain/kotlin/fr/acinq/lightning/vsock/ServerVSock.kt index b6ec846..2cdebad 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/vsock/ServerVSock.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/vsock/ServerVSock.kt @@ -1,17 +1,20 @@ package fr.acinq.lightning.vsock -import java.io.IOException -import java.net.SocketException +import fr.acinq.lightning.logging.LoggerFactory +import fr.acinq.lightning.logging.debug -class ServerVSock : BaseVSock() { - @Throws(IOException::class) +class ServerVSock(private val loggerfactory: LoggerFactory) : BaseVSock(loggerfactory) { + + @Throws(SocketException::class) fun accept(): VSock { - if (isClosed) throw SocketException("Socket closed") - if (!bound) throw SocketException("Socket not bound") - val socket = VSock() + if (isClosed) logger.debug { "Socket closed" } + if (!bound) logger.debug { "Socket not bound"} + + val socket = VSock(logger = loggerfactory) socket.setImplementation() - socket.getImplementation()?.let { getImplementation()!!.accept(it) } + socket.getImplementation().let { getImplementation().accept(it) } socket.postAccept() + return socket } } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSock.kt b/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSock.kt index b35e36f..d5c88cc 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSock.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSock.kt @@ -1,71 +1,81 @@ package fr.acinq.lightning.vsock -import java.io.Closeable -import java.io.IOException -import java.net.SocketException +import fr.acinq.lightning.logging.LoggerFactory +import kotlin.jvm.Synchronized + +class VSock(address: VSockAddress? = null, logger: LoggerFactory) : BaseVSock(logger) { + private val loggerInstance = logger.newLogger(this::class) -class VSock : BaseVSock, Closeable { private var connected = false - - @get:Throws(IOException::class) - @get:Synchronized var outputStream: VSockOutputStream? = null - get() { - if (isClosed) { - throw SocketException("VSock is closed") - } - if (field == null) { - field = getImplementation()?.let { VSockOutputStream(it) } - } - return field - } private set - @get:Throws(IOException::class) - @get:Synchronized var inputStream: VSockInputStream? = null - get() { - if (isClosed) { - throw SocketException("VSock is closed") - } - if (field == null) { - field = getImplementation()?.let { VSockInputStream(it) } - } - return field - } private set - constructor() - - constructor(address: VSockAddress?) { - try { - getImplementation()!!.connect(address) - } catch (e: Exception) { + init { + if (address != null) { try { - close() - } catch (ce: Exception) { - e.addSuppressed(ce) + getImplementation().connect(address) + } catch (e: Exception) { + try { + close() + } catch (ce: Exception) { + e.addSuppressed(ce) + } + throw IllegalStateException(e.message, e) } - throw IllegalStateException(e.message, e) } } @Throws(SocketException::class) - fun connect(address: VSockAddress?) { + fun connect(address: VSockAddress) { if (isClosed) { throw SocketException("Socket closed") } if (connected) { throw SocketException("Socket already connected") } - getImplementation()!!.connect(address) + getImplementation().connect(address) connected = true bound = true } + @Synchronized + @Throws(IOException::class) + fun initializeOutputStream(): VSockOutputStream? { + if (isClosed) { + throw SocketException("VSock is closed") + } + if (outputStream == null) { + outputStream = VSockOutputStream(getImplementation()) + } + return outputStream + } + + @Synchronized + @Throws(IOException::class) + fun initializeInputStream(): VSockInputStream? { + if (isClosed) { + throw SocketException("VSock is closed") + } + if (inputStream == null) { + inputStream = VSockInputStream(getImplementation()) + } + return inputStream + } + fun postAccept() { created = true bound = true connected = true } } + +// Custom exception classes for Kotlin/Native +class SocketException(message: String) : Exception(message) +class IOException(message: String) : Exception(message) + + + + diff --git a/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockAddress.kt b/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockAddress.kt index 80829a1..a75dfcd 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockAddress.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockAddress.kt @@ -1,35 +1,36 @@ package fr.acinq.lightning.vsock -import java.net.SocketAddress -import java.util.Objects +class VSockAddress(val cid: Int, val port: Int) { -class VSockAddress(val cid: Int, val port: Int) : SocketAddress() { - override fun equals(o: Any?): Boolean { - if (this === o) return true - if (o == null || javaClass != o.javaClass) return false - val that = o as VSockAddress - return cid == that.cid && - port == that.port + companion object { + const val VMADDR_CID_ANY = -1 + const val VMADDR_CID_HYPERVISOR = 0 + const val VMADDR_CID_RESERVED = 1 + const val VMADDR_CID_HOST = 2 + const val VMADDR_CID_PARENT = 3 + + const val VMADDR_PORT_ANY = -1 + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as VSockAddress + + if (cid != other.cid) return false + if (port != other.port) return false + + return true } override fun hashCode(): Int { - return Objects.hash(cid, port) + var result = cid + result = 31 * result + port + return result } override fun toString(): String { - return "VSockAddress{" + - "cid=" + cid + - ", port=" + port + - '}' - } - - companion object { - const val VMADDR_CID_ANY: Int = -1 - const val VMADDR_CID_HYPERVISOR: Int = 0 - const val VMADDR_CID_RESERVED: Int = 1 - const val VMADDR_CID_HOST: Int = 2 - const val VMADDR_CID_PARENT: Int = 3 - - const val VMADDR_PORT_ANY: Int = -1 + return "VSockAddress(cid=$cid, port=$port)" } } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockInputStream.kt b/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockInputStream.kt index b07275b..4717ab8 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockInputStream.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockInputStream.kt @@ -1,30 +1,29 @@ package fr.acinq.lightning.vsock import fr.acinq.lightning.vsock.native.VSockImpl -import java.io.IOException -import java.io.InputStream -class VSockInputStream(private val vSock: VSockImpl) : InputStream() { - private lateinit var temp: ByteArray +class VSockInputStream(private val vSock: VSockImpl) : Closeable { + + private var temp: ByteArray? = null @Throws(IOException::class) - override fun read(b: ByteArray, off: Int, len: Int): Int { + fun read(b: ByteArray, off: Int, len: Int): Int { return vSock.read(b, off, len) } @Throws(IOException::class) - override fun read(): Int { + fun read(): Int { temp = ByteArray(1) - val n = read(temp, 0, 1) - if (n <= 0) { - return -1 + val n = read(temp!!, 0, 1) + return if (n <= 0) { + -1 + } else { + temp!![0].toInt() } - return temp[0].toInt() } - @Throws(IOException::class) override fun close() { vSock.close() - super.close() } } + diff --git a/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockLibraryLoader.kt b/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockLibraryLoader.kt new file mode 100644 index 0000000..0b2c050 --- /dev/null +++ b/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockLibraryLoader.kt @@ -0,0 +1,24 @@ +package fr.acinq.lightning.vsock + +import fr.acinq.lightning.vsock.native.VSockImpl + +expect object VSockImplLib { + + fun loadLibrary() + + fun socketCreate() + + fun connect(address: VSockAddress) + + fun close() + + fun write(b: ByteArray, off: Int, len: Int) + + fun read(b: ByteArray, off: Int, len: Int): Int + + fun bind(address: VSockAddress) + + fun listen(backlog: Int) + + fun accept(peerVSock: VSockImplLib) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockOutputStream.kt b/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockOutputStream.kt index 83c9690..817bef4 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockOutputStream.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/vsock/VSockOutputStream.kt @@ -1,26 +1,23 @@ package fr.acinq.lightning.vsock import fr.acinq.lightning.vsock.native.VSockImpl -import java.io.IOException -import java.io.OutputStream -class VSockOutputStream internal constructor(private val vSock: VSockImpl) : OutputStream() { +class VSockOutputStream(private val vSock: VSockImpl) : Closeable { + private val temp = ByteArray(1) @Throws(IOException::class) - override fun write(b: Int) { - temp[0] = b.toByte() + fun write(b: ByteArray) { + temp[0] = b[0] this.write(temp, 0, 1) } @Throws(IOException::class) - override fun write(b: ByteArray, off: Int, len: Int) { + fun write(b: ByteArray, off: Int, len: Int) { vSock.write(b, off, len) } - @Throws(IOException::class) override fun close() { vSock.close() - super.close() } } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/vsock/VsockMain.kt b/src/commonMain/kotlin/fr/acinq/lightning/vsock/VsockMain.kt index 893ecd9..73b0974 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/vsock/VsockMain.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/vsock/VsockMain.kt @@ -1,6 +1,6 @@ package fr.acinq.lightning.vsock -import fr.acinq.lightning.bin.json.ApiType.* +import fr.acinq.lightning.bin.json.ApiType import fr.acinq.lightning.logging.LoggerFactory import fr.acinq.lightning.logging.debug import fr.acinq.lightning.logging.error @@ -8,66 +8,73 @@ import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* +import io.ktor.utils.io.charsets.Charsets +import io.ktor.utils.io.core.toByteArray import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.serialization.json.Json -import java.io.IOException -import java.nio.charset.StandardCharsets -import java.util.Base64 +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi -class VsockServer(private val CID: Int, private val port: Int, httpBindPort: Int, host: String, loggerFactory: LoggerFactory) { +class VsockServer(private val CID: Int, private val port: Int, httpBindPort: Int, host: String, + private val loggerFactory: LoggerFactory +) { private var server: ServerVSock? = null private val logger = loggerFactory.newLogger(this::class) private val client = HttpClient() - private val apiBaseUrl: String = "${host}:${httpBindPort}" + private val apiBaseUrl: String = "$host:$httpBindPort" private val bufferSize: Int = 4096 @OptIn(DelicateCoroutinesApi::class) fun start() { - server = ServerVSock() + server = ServerVSock(loggerFactory) + var peerVSock: VSock? = null try { - server?.bind(VSockAddress(CID, port)) //For any CID use VSockAddress.VMADDR_CID_ANY - logger.debug { "Vsock Bound on Cid: ${server?.localCid}" } + server?.bind(VSockAddress(CID, port)) // For any CID, use VSockAddress.VMADDR_CID_ANY + logger.debug { "Vsock Bound on CID: ${CID}" } - server?.accept()?.use { peerVSock -> + peerVSock = server?.accept() + + if (peerVSock != null) { + logger.debug { "Vsock start did do it" } val buffer = ByteArray(bufferSize) val bytesRead = peerVSock.inputStream?.read(buffer, 0, bufferSize) - if (bytesRead != null) { - if (bytesRead > 0) { - val receivedData = String(buffer, 0, bytesRead, StandardCharsets.UTF_8).trim() - logger.debug { "Received Data: $receivedData" } + if (bytesRead != null && bytesRead > 0) { + val receivedData = buffer.decodeToString(0, bytesRead).trim() + logger.debug { "Received Data: $receivedData" } - // Parse the received data into a http request - val apiRequest = try { - Json.decodeFromString(receivedData) - } catch (e: Exception) { - logger.error { "Failed to parse JSON: ${e.message}" } - peerVSock.outputStream?.write("Invalid JSON format".toByteArray(StandardCharsets.UTF_8)) - return - } + // Parse the received data into an API request + val apiRequest = try { + Json.decodeFromString(receivedData) + } catch (e: Exception) { + logger.error { "Failed to parse JSON: ${e.message}" } + peerVSock.outputStream?.write("Invalid JSON format".toByteArray(Charsets.UTF_8)) + return + } - // Handle the API call - GlobalScope.launch { - val response = handleApiCall(apiRequest) - peerVSock.outputStream?.write(response.toByteArray(StandardCharsets.UTF_8)) - } + // Handle the API call + GlobalScope.launch { + val response = handleApiCall(apiRequest) + peerVSock.outputStream?.write(response.toByteArray(Charsets.UTF_8)) } } } } catch (ex: IOException) { logger.error { "Error starting Vsock: ${ex.message}" } } finally { + peerVSock?.close() stop() } } - private suspend fun handleApiCall(request: VsockApiRequest): String { + @OptIn(ExperimentalEncodingApi::class) + private suspend fun handleApiCall(request: ApiType.VsockApiRequest): String { return try { val url = "$apiBaseUrl/${request.method}" val response: HttpResponse = client.get(url) { headers { - append(HttpHeaders.Authorization, "Basic ${Base64.getEncoder().encodeToString("user:${request.httpPassword}".toByteArray())}") + append(HttpHeaders.Authorization, "Basic ${Base64.encode("user:${request.httpPassword}".toByteArray(Charsets.UTF_8))}") } url { parameters.appendAll(Parameters.build { diff --git a/src/commonMain/kotlin/fr/acinq/lightning/vsock/native/VSockImpl.kt b/src/commonMain/kotlin/fr/acinq/lightning/vsock/native/VSockImpl.kt index fa60369..3be1184 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/vsock/native/VSockImpl.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/vsock/native/VSockImpl.kt @@ -1,59 +1,38 @@ package fr.acinq.lightning.vsock.native import fr.acinq.lightning.vsock.VSockAddress -import org.bytedeco.javacpp.Loader -import org.bytedeco.javacpp.Pointer -import org.bytedeco.javacpp.annotation.Namespace -import org.bytedeco.javacpp.annotation.Platform -import java.net.SocketException +import fr.acinq.lightning.vsock.VSockImplLib - -@Platform(include = [ - "", - "", - "", - "", - "" -]) -@Namespace("vsock") -class VSockImpl() : Pointer() { +class VSockImpl { init { - Loader.load() //load the native library + VSockImplLib.loadLibrary() } - var fd: Int = -1 - - @Throws(SocketException::class) fun create() { - socketCreate() + VSockImplLib.socketCreate() } - private external fun allocate() + fun connect(address: VSockAddress){ + VSockImplLib.connect(address) + } - private external fun socketCreate() - - @Throws(Exception::class) - external fun connect(address: VSockAddress?) - - @Throws(Exception::class) - external override fun close() - - @Throws(Exception::class) - external fun write(b: ByteArray, offset: Int, len: Int) - - @Throws(Exception::class) - external fun read(b: ByteArray, offset: Int, len: Int): Int - - @Throws(Exception::class) - external fun bind(addr: fr.acinq.lightning.vsock.VSockAddress?) - - @Throws(Exception::class) - external fun listen(backlog: Int) - - @Throws(Exception::class) - external fun accept(peerVSock: VSockImpl) - - @Throws(Exception::class) - external fun getLocalCid(): Int -} + fun close(){ + VSockImplLib.close() + } + fun write(b: ByteArray, off: Int, len: Int){ + VSockImplLib.write(b, off, len) + } + fun read(b: ByteArray, off: Int, len: Int): Int{ + return VSockImplLib.read(b, off, len) + } + fun bind(address: VSockAddress){ + VSockImplLib.bind(address) + } + fun listen(backlog: Int){ + VSockImplLib.listen(backlog) + } + fun accept(peerVSock: VSockImpl){ + VSockImplLib.accept(peerVSock) + } +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/fr/acinq/lightning/vsock/VSockLibraryLoader.jvm.kt b/src/jvmMain/kotlin/fr/acinq/lightning/vsock/VSockLibraryLoader.jvm.kt new file mode 100644 index 0000000..7777ba4 --- /dev/null +++ b/src/jvmMain/kotlin/fr/acinq/lightning/vsock/VSockLibraryLoader.jvm.kt @@ -0,0 +1,22 @@ +package fr.acinq.lightning.vsock + +import fr.acinq.lightning.vsock.native.VSockImpl + +actual object VSockImplLib { + actual fun loadLibrary() { + val libsPath = System.getenv("LIBS_PATH") + ?: throw IllegalStateException("LIBS_PATH environment variable not set") + + val libPath = "$libsPath/libjniVSockImpl.so" + System.load(libPath) + } + + actual external fun socketCreate() + actual external fun connect(address: VSockAddress) + actual external fun close() + actual external fun write(b: ByteArray, off: Int, len: Int) + actual external fun read(b: ByteArray, off: Int, len: Int): Int + actual external fun bind(address: VSockAddress) + actual external fun listen(backlog: Int) + actual external fun accept(peerVSock: VSockImplLib) +} diff --git a/src/linuxX64Main/kotlin/fr/acinq/lightning/vsock/VSockLibraryLoader.linuxX64.kt b/src/linuxX64Main/kotlin/fr/acinq/lightning/vsock/VSockLibraryLoader.linuxX64.kt new file mode 100644 index 0000000..e4e2074 --- /dev/null +++ b/src/linuxX64Main/kotlin/fr/acinq/lightning/vsock/VSockLibraryLoader.linuxX64.kt @@ -0,0 +1,91 @@ +package fr.acinq.lightning.vsock + +import kotlinx.cinterop.* +import platform.posix.* +import kotlin.experimental.ExperimentalNativeApi + +@OptIn(ExperimentalForeignApi::class) +fun getEnvVariable(name: String): String? { + return getenv(name)?.toKString() +} + +actual object VSockImplLib { + @OptIn(ExperimentalForeignApi::class) + val handle: COpaquePointer? by lazy { + val libsPath = getEnvVariable("LIBS_PATH") + ?: throw IllegalStateException("LIBS_PATH environment variable not set") + + val libPath = "$libsPath/libjniVSockImpl.so" + dlopen(libPath, RTLD_LAZY)?.also { + println("Library loaded successfully") + } ?: run { + val error = dlerror() ?: "Unknown error" + throw IllegalStateException("Failed to load library: $error") + } + } + + @OptIn(ExperimentalForeignApi::class) + actual fun loadLibrary() { + handle + } + + // Function to retrieve the symbol (function pointer) and cast it + @OptIn(ExperimentalNativeApi::class) + inline fun > getSymbol(name: String): T { + val symbol = dlsym(handle, name) + ?: throw IllegalStateException("Symbol $name not found: ${dlerror()}") + return symbol.reinterpret() + } + + @OptIn(ExperimentalNativeApi::class) + actual fun socketCreate(){ + val socketCreatePtr = getSymbol Int>>("socketCreate") + } + + @OptIn(ExperimentalNativeApi::class) + actual fun connect(address: VSockAddress) { + val connectPtr = getSymbol Unit>>("connect") + connectPtr(address) + } + + @OptIn(ExperimentalNativeApi::class) + actual fun close() { + val closePtr = getSymbol Unit>>("close") + closePtr() + } + + @OptIn(ExperimentalNativeApi::class) + actual fun write(b: ByteArray, off: Int, len: Int) { + val writePtr = getSymbol, Int, Int) -> Unit>>("write") + b.usePinned { + writePtr(it.addressOf(off), off, len) + } + } + + @OptIn(ExperimentalNativeApi::class) + actual fun read(b: ByteArray, off: Int, len: Int): Int { + val readPtr = getSymbol, Int, Int) -> Int>>("read") + return b.usePinned { + readPtr(it.addressOf(off), off, len) + } + } + + @OptIn(ExperimentalNativeApi::class) + actual fun bind(address: VSockAddress) { + val bindPtr = getSymbol Unit>>("bind") + bindPtr(address) + } + + @OptIn(ExperimentalNativeApi::class) + actual fun listen(backlog: Int) { + val listenPtr = getSymbol Unit>>("listen") + listenPtr(backlog) + } + + @OptIn(ExperimentalNativeApi::class) + actual fun accept(peerVSock: VSockImplLib) { + @OptIn(ExperimentalNativeApi::class) + val acceptPtr = getSymbol Unit>>("accept") + acceptPtr(peerVSock) + } +}