An attempt to build linux binary with native vsock code

This commit is contained in:
Raymond Sebetoa 2024-08-16 01:52:25 +02:00
parent 86c21cbab3
commit 003419a84c
15 changed files with 364 additions and 211 deletions

4
bin/main/logback.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<root level="OFF" />
</configuration>

View File

@ -16,8 +16,6 @@ plugins {
kotlin("multiplatform") version Versions.kotlin kotlin("multiplatform") version Versions.kotlin
kotlin("plugin.serialization") version Versions.kotlin kotlin("plugin.serialization") version Versions.kotlin
id("app.cash.sqldelight") version Versions.sqlDelight id("app.cash.sqldelight") version Versions.sqlDelight
`java-library`
id("org.bytedeco.gradle-javacpp-platform").version("1.5.10")
application application
} }
@ -75,6 +73,9 @@ val buildVersionsTask by tasks.registering(Sync::class) {
kotlin { kotlin {
jvm { jvm {
withJava() withJava()
compilations["main"].defaultSourceSet {
resources.srcDirs("src/jvmMain/kotlin/fr/acinq/lightning/vsock/")
}
} }
fun KotlinNativeTargetWithHostTests.phoenixBinaries() { fun KotlinNativeTargetWithHostTests.phoenixBinaries() {
@ -83,10 +84,10 @@ kotlin {
entryPoint = "fr.acinq.lightning.bin.main" entryPoint = "fr.acinq.lightning.bin.main"
optimized = false // without this, release mode throws 'Index 0 out of bounds for length 0' in StaticInitializersOptimization.kt 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" entryPoint = "fr.acinq.lightning.cli.main"
optimized = false // without this, release mode throws 'Index 0 out of bounds for length 0' in StaticInitializersOptimization.kt optimized = false // without this, release mode throws 'Index 0 out of bounds for length 0' in StaticInitializersOptimization.kt
} }*/
} }
} }
@ -111,20 +112,14 @@ kotlin {
} }
sourceSets { sourceSets {
val jvmMain by getting {
// Include the native source directory if needed
resources.srcDirs("src/commonMain/kotlin/fr/acinq/lightning/vsock/native")
}
commonMain { commonMain {
kotlin.srcDir(buildVersionsTask.map { it.destinationDir }) kotlin.srcDir(buildVersionsTask.map { it.destinationDir })
dependencies { dependencies {
implementation("com.github.raymond98.lightning-kmp:lightning-kmp:v1.6.2-FEECREDIT-8") implementation("com.github.raymond98.lightning-kmp:lightning-kmp:v1.6.2-FEECREDIT-8")
implementation("org.bytedeco:javacpp:1.5.10") implementation("org.jetbrains.kotlinx:atomicfu:0.25.0")
implementation(kotlin("stdlib-jdk8"))
implementation("org.bytedeco:javacv-platform:1.5.10")
implementation("org.bytedeco:javacpp-presets:1.5.10")
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.8.20")
api("fr.acinq.bitcoin:bitcoin-kmp:${Versions.bitcoinKmpVersion}") api("fr.acinq.bitcoin:bitcoin-kmp:${Versions.bitcoinKmpVersion}")
api("co.touchlab:kermit:${Versions.kermitLoggerVersion}") api("co.touchlab:kermit:${Versions.kermitLoggerVersion}")
api("org.jetbrains.kotlinx:kotlinx-datetime:${Versions.datetimeVersion}") api("org.jetbrains.kotlinx:kotlinx-datetime:${Versions.datetimeVersion}")
@ -157,6 +152,8 @@ kotlin {
implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-jvm:${Versions.secpJniJvmVersion}") implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-jvm:${Versions.secpJniJvmVersion}")
implementation(ktor("client-okhttp")) implementation(ktor("client-okhttp"))
implementation("ch.qos.logback:logback-classic:1.2.3") 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 { nativeMain {
@ -220,7 +217,8 @@ application {
mainClass = "fr.acinq.lightning.bin.MainKt" mainClass = "fr.acinq.lightning.bin.MainKt"
// Set java.library.path to include the directory where the shared library is generated // 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) { val cliScripts by tasks.register("cliScripts", CreateStartScripts::class) {
@ -234,11 +232,27 @@ tasks.startScripts {
dependsOn(cliScripts) dependsOn(cliScripts)
} }
val setLibPath by tasks.register<Exec>("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<Exec>("compileNative") { val compileNative by tasks.register<Exec>("compileNative") {
group = "build" group = "build"
description = "Compile the native C++ code into a shared library" 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") val nativeSourceDir = file("src/commonMain/kotlin/fr/acinq/lightning/vsock/native")
// Locate the JNI headers - adjust these paths based on your actual JDK location // Locate the JNI headers - adjust these paths based on your actual JDK location
@ -252,13 +266,14 @@ val compileNative by tasks.register<Exec>("compileNative") {
commandLine("g++", "-I$jniIncludeDir", "-I$jniPlatformIncludeDir", "-shared", "-o", outputDir.resolve("libjniVSockImpl.so"), nativeSourceDir.resolve("VSockImpl.cpp"), "-fPIC") 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<Tar> { tasks.withType<Tar> {
dependsOn(compileNative) dependsOn(compileNative)
dependsOn(setLibPath)
} }
tasks.withType<Zip> { tasks.withType<Zip> {
dependsOn(compileNative) dependsOn(compileNative)
dependsOn(setLibPath)
} }
distributions { distributions {
@ -271,7 +286,6 @@ distributions {
// forward std input when app is run via gradle (otherwise keyboard input will return EOF) // forward std input when app is run via gradle (otherwise keyboard input will return EOF)
tasks.withType<JavaExec> { tasks.withType<JavaExec> {
standardInput = System.`in` standardInput = System.`in`
dependsOn(compileNative) //This should not be the case for all platforms
} }
sqldelight { sqldelight {

View File

@ -1,7 +1,6 @@
object Versions { object Versions {
val kotlin = "1.9.23" val kotlin = "1.9.23"
val lightningKmp = "1.7.0-FEECREDIT-8" val lightningKmp = "1.7.0-FEECREDIT-8"
val lightningKmpTag = "v1.6.2-FEECREDIT-8"
val sqlDelight = "2.0.1" val sqlDelight = "2.0.1"
val okio = "3.8.0" val okio = "3.8.0"
val clikt = "4.2.2" val clikt = "4.2.2"

View File

@ -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) else -> call.respondText("Splice-in failed: unexpected response type", status = HttpStatusCode.InternalServerError)
} }
} catch (e: Exception) { } 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") { post("closechannel") {

View File

@ -1,67 +1,64 @@
package fr.acinq.lightning.vsock 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 fr.acinq.lightning.vsock.native.VSockImpl
import java.io.Closeable import kotlinx.atomicfu.locks.SynchronizedObject
import java.io.IOException import kotlinx.atomicfu.locks.synchronized
import java.net.SocketException import kotlin.jvm.Synchronized
abstract class BaseVSock : Closeable { abstract class BaseVSock(loggerfactory: LoggerFactory) : Closeable {
protected val closeLock: Any = Any() private val closeLock = SynchronizedObject()
val logger = loggerfactory.newLogger(this::class)
protected var isClosed: Boolean = false protected var isClosed: Boolean = false
protected var created: Boolean = false protected var created: Boolean = false
protected var bound: Boolean = false protected var bound: Boolean = false
private var implementation: VSockImpl? = null private lateinit var implementation: VSockImpl
@Throws(SocketException::class) @Throws(SocketException::class)
private fun createImplementation() { private fun createImplementation() {
implementation = VSockImpl() implementation = VSockImpl()
implementation!!.create() implementation.create()
created = true created = true
} }
@Throws(SocketException::class) @Throws(SocketException::class)
fun getImplementation(): VSockImpl? { fun getImplementation(): VSockImpl {
if (!created) createImplementation() if (!created) createImplementation()
return implementation return implementation
} }
@Throws(SocketException::class) @Throws(SocketException::class)
fun setImplementation(): VSockImpl { fun setImplementation(): VSockImpl {
if (implementation == null) { return implementation
implementation = VSockImpl()
}
return implementation!!
} }
@get:Throws(IOException::class)
val localCid: Int
get() = getImplementation()!!.getLocalCid()
@JvmOverloads
@Throws(IOException::class) @Throws(IOException::class)
fun bind(address: VSockAddress?, backlog: Int = DEFAULT_BACKLOG) { fun bind(address: VSockAddress?, backlog: Int = DEFAULT_BACKLOG) {
var backlog = backlog var backlogs = backlog
if (isClosed) { if (isClosed) {
throw SocketException("Socket closed") logger.warning { "Socket closed" }
} }
if (bound) { if (bound) {
throw SocketException("Socket already bound") logger.warning { "Socket already bound" }
} }
if (backlog <= 0) { if (backlogs <= 0) {
backlog = DEFAULT_BACKLOG backlogs = DEFAULT_BACKLOG
} }
getImplementation()!!.bind(address) if (address != null) {
getImplementation()!!.listen(backlog) getImplementation().bind(address)
}
getImplementation().listen(backlogs)
bound = true bound = true
} }
@Synchronized @Synchronized
@Throws(IOException::class)
override fun close() { override fun close() {
synchronized(closeLock) { synchronized(closeLock) {
if (isClosed) return if (isClosed) return
if (created) getImplementation()!!.close() if (created) getImplementation().close()
isClosed = true isClosed = true
} }
} }
@ -70,3 +67,9 @@ abstract class BaseVSock : Closeable {
private const val DEFAULT_BACKLOG = 42 private const val DEFAULT_BACKLOG = 42
} }
} }
// Custom Closeable interface for Kotlin/Native
interface Closeable {
fun close()
}

View File

@ -1,17 +1,20 @@
package fr.acinq.lightning.vsock package fr.acinq.lightning.vsock
import java.io.IOException import fr.acinq.lightning.logging.LoggerFactory
import java.net.SocketException import fr.acinq.lightning.logging.debug
class ServerVSock : BaseVSock() { class ServerVSock(private val loggerfactory: LoggerFactory) : BaseVSock(loggerfactory) {
@Throws(IOException::class)
@Throws(SocketException::class)
fun accept(): VSock { fun accept(): VSock {
if (isClosed) throw SocketException("Socket closed") if (isClosed) logger.debug { "Socket closed" }
if (!bound) throw SocketException("Socket not bound") if (!bound) logger.debug { "Socket not bound"}
val socket = VSock()
val socket = VSock(logger = loggerfactory)
socket.setImplementation() socket.setImplementation()
socket.getImplementation()?.let { getImplementation()!!.accept(it) } socket.getImplementation().let { getImplementation().accept(it) }
socket.postAccept() socket.postAccept()
return socket return socket
} }
} }

View File

@ -1,45 +1,22 @@
package fr.acinq.lightning.vsock package fr.acinq.lightning.vsock
import java.io.Closeable import fr.acinq.lightning.logging.LoggerFactory
import java.io.IOException import kotlin.jvm.Synchronized
import java.net.SocketException
class VSock(address: VSockAddress? = null, logger: LoggerFactory) : BaseVSock(logger) {
private val loggerInstance = logger.newLogger(this::class)
class VSock : BaseVSock, Closeable {
private var connected = false private var connected = false
@get:Throws(IOException::class)
@get:Synchronized
var outputStream: VSockOutputStream? = null var outputStream: VSockOutputStream? = null
get() {
if (isClosed) {
throw SocketException("VSock is closed")
}
if (field == null) {
field = getImplementation()?.let { VSockOutputStream(it) }
}
return field
}
private set private set
@get:Throws(IOException::class)
@get:Synchronized
var inputStream: VSockInputStream? = null var inputStream: VSockInputStream? = null
get() {
if (isClosed) {
throw SocketException("VSock is closed")
}
if (field == null) {
field = getImplementation()?.let { VSockInputStream(it) }
}
return field
}
private set private set
constructor() init {
if (address != null) {
constructor(address: VSockAddress?) {
try { try {
getImplementation()!!.connect(address) getImplementation().connect(address)
} catch (e: Exception) { } catch (e: Exception) {
try { try {
close() close()
@ -49,23 +26,56 @@ class VSock : BaseVSock, Closeable {
throw IllegalStateException(e.message, e) throw IllegalStateException(e.message, e)
} }
} }
}
@Throws(SocketException::class) @Throws(SocketException::class)
fun connect(address: VSockAddress?) { fun connect(address: VSockAddress) {
if (isClosed) { if (isClosed) {
throw SocketException("Socket closed") throw SocketException("Socket closed")
} }
if (connected) { if (connected) {
throw SocketException("Socket already connected") throw SocketException("Socket already connected")
} }
getImplementation()!!.connect(address) getImplementation().connect(address)
connected = true connected = true
bound = 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() { fun postAccept() {
created = true created = true
bound = true bound = true
connected = true connected = true
} }
} }
// Custom exception classes for Kotlin/Native
class SocketException(message: String) : Exception(message)
class IOException(message: String) : Exception(message)

View File

@ -1,35 +1,36 @@
package fr.acinq.lightning.vsock package fr.acinq.lightning.vsock
import java.net.SocketAddress class VSockAddress(val cid: Int, val port: Int) {
import java.util.Objects
class VSockAddress(val cid: Int, val port: Int) : SocketAddress() { companion object {
override fun equals(o: Any?): Boolean { const val VMADDR_CID_ANY = -1
if (this === o) return true const val VMADDR_CID_HYPERVISOR = 0
if (o == null || javaClass != o.javaClass) return false const val VMADDR_CID_RESERVED = 1
val that = o as VSockAddress const val VMADDR_CID_HOST = 2
return cid == that.cid && const val VMADDR_CID_PARENT = 3
port == that.port
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 { override fun hashCode(): Int {
return Objects.hash(cid, port) var result = cid
result = 31 * result + port
return result
} }
override fun toString(): String { override fun toString(): String {
return "VSockAddress{" + return "VSockAddress(cid=$cid, port=$port)"
"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
} }
} }

View File

@ -1,30 +1,29 @@
package fr.acinq.lightning.vsock package fr.acinq.lightning.vsock
import fr.acinq.lightning.vsock.native.VSockImpl import fr.acinq.lightning.vsock.native.VSockImpl
import java.io.IOException
import java.io.InputStream
class VSockInputStream(private val vSock: VSockImpl) : InputStream() { class VSockInputStream(private val vSock: VSockImpl) : Closeable {
private lateinit var temp: ByteArray
private var temp: ByteArray? = null
@Throws(IOException::class) @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) return vSock.read(b, off, len)
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun read(): Int { fun read(): Int {
temp = ByteArray(1) temp = ByteArray(1)
val n = read(temp, 0, 1) val n = read(temp!!, 0, 1)
if (n <= 0) { return if (n <= 0) {
return -1 -1
} else {
temp!![0].toInt()
} }
return temp[0].toInt()
} }
@Throws(IOException::class)
override fun close() { override fun close() {
vSock.close() vSock.close()
super.close()
} }
} }

View File

@ -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)
}

View File

@ -1,26 +1,23 @@
package fr.acinq.lightning.vsock package fr.acinq.lightning.vsock
import fr.acinq.lightning.vsock.native.VSockImpl 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) private val temp = ByteArray(1)
@Throws(IOException::class) @Throws(IOException::class)
override fun write(b: Int) { fun write(b: ByteArray) {
temp[0] = b.toByte() temp[0] = b[0]
this.write(temp, 0, 1) this.write(temp, 0, 1)
} }
@Throws(IOException::class) @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) vSock.write(b, off, len)
} }
@Throws(IOException::class)
override fun close() { override fun close() {
vSock.close() vSock.close()
super.close()
} }
} }

View File

@ -1,6 +1,6 @@
package fr.acinq.lightning.vsock 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.LoggerFactory
import fr.acinq.lightning.logging.debug import fr.acinq.lightning.logging.debug
import fr.acinq.lightning.logging.error import fr.acinq.lightning.logging.error
@ -8,66 +8,73 @@ import io.ktor.client.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import io.ktor.http.* 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.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.IOException import kotlin.io.encoding.Base64
import java.nio.charset.StandardCharsets import kotlin.io.encoding.ExperimentalEncodingApi
import java.util.Base64
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 var server: ServerVSock? = null
private val logger = loggerFactory.newLogger(this::class) private val logger = loggerFactory.newLogger(this::class)
private val client = HttpClient() private val client = HttpClient()
private val apiBaseUrl: String = "${host}:${httpBindPort}" private val apiBaseUrl: String = "$host:$httpBindPort"
private val bufferSize: Int = 4096 private val bufferSize: Int = 4096
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
fun start() { fun start() {
server = ServerVSock() server = ServerVSock(loggerFactory)
var peerVSock: VSock? = null
try { try {
server?.bind(VSockAddress(CID, port)) //For any CID use VSockAddress.VMADDR_CID_ANY server?.bind(VSockAddress(CID, port)) // For any CID, use VSockAddress.VMADDR_CID_ANY
logger.debug { "Vsock Bound on Cid: ${server?.localCid}" } 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 buffer = ByteArray(bufferSize)
val bytesRead = peerVSock.inputStream?.read(buffer, 0, bufferSize) val bytesRead = peerVSock.inputStream?.read(buffer, 0, bufferSize)
if (bytesRead != null) { if (bytesRead != null && bytesRead > 0) {
if (bytesRead > 0) { val receivedData = buffer.decodeToString(0, bytesRead).trim()
val receivedData = String(buffer, 0, bytesRead, StandardCharsets.UTF_8).trim()
logger.debug { "Received Data: $receivedData" } logger.debug { "Received Data: $receivedData" }
// Parse the received data into a http request // Parse the received data into an API request
val apiRequest = try { val apiRequest = try {
Json.decodeFromString<VsockApiRequest>(receivedData) Json.decodeFromString<ApiType.VsockApiRequest>(receivedData)
} catch (e: Exception) { } catch (e: Exception) {
logger.error { "Failed to parse JSON: ${e.message}" } logger.error { "Failed to parse JSON: ${e.message}" }
peerVSock.outputStream?.write("Invalid JSON format".toByteArray(StandardCharsets.UTF_8)) peerVSock.outputStream?.write("Invalid JSON format".toByteArray(Charsets.UTF_8))
return return
} }
// Handle the API call // Handle the API call
GlobalScope.launch { GlobalScope.launch {
val response = handleApiCall(apiRequest) val response = handleApiCall(apiRequest)
peerVSock.outputStream?.write(response.toByteArray(StandardCharsets.UTF_8)) peerVSock.outputStream?.write(response.toByteArray(Charsets.UTF_8))
}
} }
} }
} }
} catch (ex: IOException) { } catch (ex: IOException) {
logger.error { "Error starting Vsock: ${ex.message}" } logger.error { "Error starting Vsock: ${ex.message}" }
} finally { } finally {
peerVSock?.close()
stop() stop()
} }
} }
private suspend fun handleApiCall(request: VsockApiRequest): String { @OptIn(ExperimentalEncodingApi::class)
private suspend fun handleApiCall(request: ApiType.VsockApiRequest): String {
return try { return try {
val url = "$apiBaseUrl/${request.method}" val url = "$apiBaseUrl/${request.method}"
val response: HttpResponse = client.get(url) { val response: HttpResponse = client.get(url) {
headers { 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 { url {
parameters.appendAll(Parameters.build { parameters.appendAll(Parameters.build {

View File

@ -1,59 +1,38 @@
package fr.acinq.lightning.vsock.native package fr.acinq.lightning.vsock.native
import fr.acinq.lightning.vsock.VSockAddress import fr.acinq.lightning.vsock.VSockAddress
import org.bytedeco.javacpp.Loader import fr.acinq.lightning.vsock.VSockImplLib
import org.bytedeco.javacpp.Pointer
import org.bytedeco.javacpp.annotation.Namespace
import org.bytedeco.javacpp.annotation.Platform
import java.net.SocketException
class VSockImpl {
@Platform(include = [
"<sys/socket.h>",
"<sys/ioctl.h>",
"<linux/vm_sockets.h>",
"<unistd.h>",
"<errno.h>"
])
@Namespace("vsock")
class VSockImpl() : Pointer() {
init { init {
Loader.load() //load the native library VSockImplLib.loadLibrary()
} }
var fd: Int = -1
@Throws(SocketException::class)
fun create() { fun create() {
socketCreate() VSockImplLib.socketCreate()
} }
private external fun allocate() fun connect(address: VSockAddress){
VSockImplLib.connect(address)
private external fun socketCreate() }
@Throws(Exception::class) fun close(){
external fun connect(address: VSockAddress?) VSockImplLib.close()
}
@Throws(Exception::class) fun write(b: ByteArray, off: Int, len: Int){
external override fun close() VSockImplLib.write(b, off, len)
}
@Throws(Exception::class) fun read(b: ByteArray, off: Int, len: Int): Int{
external fun write(b: ByteArray, offset: Int, len: Int) return VSockImplLib.read(b, off, len)
}
@Throws(Exception::class) fun bind(address: VSockAddress){
external fun read(b: ByteArray, offset: Int, len: Int): Int VSockImplLib.bind(address)
}
@Throws(Exception::class) fun listen(backlog: Int){
external fun bind(addr: fr.acinq.lightning.vsock.VSockAddress?) VSockImplLib.listen(backlog)
}
@Throws(Exception::class) fun accept(peerVSock: VSockImpl){
external fun listen(backlog: Int) VSockImplLib.accept(peerVSock)
}
@Throws(Exception::class)
external fun accept(peerVSock: VSockImpl)
@Throws(Exception::class)
external fun getLocalCid(): Int
} }

View File

@ -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)
}

View File

@ -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 <reified T : CPointer<*>> 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<CFunction<() -> Int>>("socketCreate")
}
@OptIn(ExperimentalNativeApi::class)
actual fun connect(address: VSockAddress) {
val connectPtr = getSymbol<CFunction<(VSockAddress) -> Unit>>("connect")
connectPtr(address)
}
@OptIn(ExperimentalNativeApi::class)
actual fun close() {
val closePtr = getSymbol<CFunction<() -> Unit>>("close")
closePtr()
}
@OptIn(ExperimentalNativeApi::class)
actual fun write(b: ByteArray, off: Int, len: Int) {
val writePtr = getSymbol<CFunction<(CPointer<ByteVar>, 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<CFunction<(CPointer<ByteVar>, Int, Int) -> Int>>("read")
return b.usePinned {
readPtr(it.addressOf(off), off, len)
}
}
@OptIn(ExperimentalNativeApi::class)
actual fun bind(address: VSockAddress) {
val bindPtr = getSymbol<CFunction<(VSockAddress) -> Unit>>("bind")
bindPtr(address)
}
@OptIn(ExperimentalNativeApi::class)
actual fun listen(backlog: Int) {
val listenPtr = getSymbol<CFunction<(Int) -> Unit>>("listen")
listenPtr(backlog)
}
@OptIn(ExperimentalNativeApi::class)
actual fun accept(peerVSock: VSockImplLib) {
@OptIn(ExperimentalNativeApi::class)
val acceptPtr = getSymbol<CFunction<(VSockImplLib) -> Unit>>("accept")
acceptPtr(peerVSock)
}
}