Compare commits

..

3 Commits

17 changed files with 273 additions and 402 deletions

View File

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

View File

@ -16,6 +16,8 @@ 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
} }
@ -73,15 +75,12 @@ 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() {
binaries { binaries {
executable("phoenixd") { executable("phoenixd") {
entryPoint = "fr.acinq.lightning.bin.main" entryPoint = "fr.acinq.lightning.bin.MainKt"
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") {
@ -117,12 +116,12 @@ kotlin {
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.jetbrains.kotlinx:atomicfu:0.25.0") //implementation("org.bytedeco:javacpp:1.5.10")
//implementation("com.github.maven-nar:nar-maven-plugin:3.10.1")
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}")
api(ktor("network")) api(ktor("network"))
api(ktor("network-tls")) api(ktor("network-tls"))
@ -149,11 +148,9 @@ kotlin {
jvmMain { jvmMain {
dependencies { dependencies {
implementation("app.cash.sqldelight:sqlite-driver:${Versions.sqlDelight}") implementation("app.cash.sqldelight:sqlite-driver:${Versions.sqlDelight}")
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 {
@ -187,10 +184,10 @@ kotlin {
include("*.kexe") include("*.kexe")
rename("phoenixd.kexe", "phoenixd") rename("phoenixd.kexe", "phoenixd")
} }
from("$projectDir/build/bin/$dir/phoenix-cliReleaseExecutable") { /*from("$projectDir/build/bin/$dir/phoenix-cliReleaseExecutable") {
include("*.kexe") include("*.kexe")
rename("phoenix-cli.kexe", "phoenix-cli") rename("phoenix-cli.kexe", "phoenix-cli")
} }*/
into("${archiveBaseName.get()}-${archiveVersion.get()}-${archiveClassifier.get()}") into("${archiveBaseName.get()}-${archiveVersion.get()}-${archiveClassifier.get()}")
} }
@ -217,11 +214,10 @@ 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=${layout.buildDirectory.dir("libs").get().asFile}") applicationDefaultJvmArgs = listOf("-Djava.library.path=${layout.buildDirectory.dir("libs").get().asFile.absolutePath}")
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) {
mainClass.set("fr.acinq.lightning.cli.PhoenixCliKt") mainClass.set("fr.acinq.lightning.cli.PhoenixCliKt")
outputDir = tasks.startScripts.get().outputDir outputDir = tasks.startScripts.get().outputDir
classpath = tasks.startScripts.get().classpath classpath = tasks.startScripts.get().classpath
@ -230,27 +226,11 @@ val cliScripts by tasks.register("cliScripts", CreateStartScripts::class) {
tasks.startScripts { 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 and package it into a .nar file"
val outputDir = layout.buildDirectory.dir("libs").get().asFile 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")
@ -263,17 +243,18 @@ val compileNative by tasks.register<Exec>("compileNative") {
inputs.dir(nativeSourceDir) inputs.dir(nativeSourceDir)
outputs.dir(outputDir) outputs.dir(outputDir)
// Compile the shared library
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 {
@ -286,6 +267,7 @@ 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,6 +1,7 @@
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.message}") call.respond(HttpStatusCode.InternalServerError, "Failed to process splice-in: ${e.localizedMessage}")
} }
} }
post("closechannel") { post("closechannel") {

View File

@ -133,8 +133,8 @@ class Phoenixd : CliktCommand() {
private val electrumServerPort by option("--electrum-server-port", help = "Port for the electrum server").int().default(50002) private val electrumServerPort by option("--electrum-server-port", help = "Port for the electrum server").int().default(50002)
private val startVsock by option("--start-vsock-server", help = "Start the vsock server for API calls").boolean().default(true) private val startVsock by option("--start-vsock-server", help = "Start the vsock server for API calls").boolean().default(true)
private val vsockCID by option("--vsock-server-cid", help = "CID for the Vsock server").int().default(4) private val vsockCID by option("--vsock-server-cid", help = "CID for the Vsock server").int().default(4) //enclave cid is 4 i think
private val vsockPort by option("--vsock-server-port", help = "Port for the Vsock server").int().default(9001) private val vsockPort by option("--vsock-server-port", help = "Port for the Vsock server").int().default(9002)
class LiquidityOptions : OptionGroup(name = "Liquidity Options") { class LiquidityOptions : OptionGroup(name = "Liquidity Options") {
val autoLiquidity by option("--auto-liquidity", help = "Amount automatically requested when inbound liquidity is needed").choice( val autoLiquidity by option("--auto-liquidity", help = "Amount automatically requested when inbound liquidity is needed").choice(
@ -393,9 +393,12 @@ class Phoenixd : CliktCommand() {
} }
var vsockServer: VsockServer? = null var vsockServer: VsockServer? = null
if(startVsock){ if (startVsock) {
vsockServer = VsockServer(vsockCID, vsockPort, httpBindPort, httpBindIp, loggerFactory) vsockServer = VsockServer(vsockCID, vsockPort, httpBindPort, httpBindIp, loggerFactory)
vsockServer.start() GlobalScope.launch {
consoleLog(yellow("Vsock Server Binding to Port: $vsockPort"))
vsockServer.start()
}
} }
val server = embeddedServer(CIO, port = httpBindPort, host = httpBindIp, val server = embeddedServer(CIO, port = httpBindPort, host = httpBindIp,

View File

@ -1,64 +1,67 @@
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 kotlinx.atomicfu.locks.SynchronizedObject import java.io.Closeable
import kotlinx.atomicfu.locks.synchronized import java.io.IOException
import kotlin.jvm.Synchronized import java.net.SocketException
abstract class BaseVSock(loggerfactory: LoggerFactory) : Closeable { abstract class BaseVSock : Closeable {
private val closeLock = SynchronizedObject() protected val closeLock: Any = Any()
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 lateinit var implementation: VSockImpl private var implementation: VSockImpl? = null
@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 {
return implementation if (implementation == null) {
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 backlogs = backlog var backlog = backlog
if (isClosed) { if (isClosed) {
logger.warning { "Socket closed" } throw SocketException("Socket closed thrown in Base Vsock")
} }
if (bound) { if (bound) {
logger.warning { "Socket already bound" } throw SocketException("Socket already bound")
} }
if (backlogs <= 0) { if (backlog <= 0) {
backlogs = DEFAULT_BACKLOG backlog = DEFAULT_BACKLOG
} }
if (address != null) { getImplementation()!!.bind(address)
getImplementation().bind(address) getImplementation()!!.listen(backlog)
}
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
} }
} }
@ -67,9 +70,3 @@ abstract class BaseVSock(loggerfactory: LoggerFactory) : 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,20 +1,19 @@
package fr.acinq.lightning.vsock package fr.acinq.lightning.vsock
import fr.acinq.lightning.logging.LoggerFactory import java.io.IOException
import fr.acinq.lightning.logging.debug import java.net.SocketException
class ServerVSock(private val loggerfactory: LoggerFactory) : BaseVSock(loggerfactory) { class ServerVSock : BaseVSock() {
@Throws(IOException::class)
@Throws(SocketException::class)
fun accept(): VSock { fun accept(): VSock {
if (isClosed) logger.debug { "Socket closed" } if (isClosed) {
if (!bound) logger.debug { "Socket not bound"} throw SocketException("Socket closed")
}
val socket = VSock(logger = loggerfactory) if (!bound) throw SocketException("Socket not bound")
val socket = VSock()
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,81 +1,73 @@
package fr.acinq.lightning.vsock package fr.acinq.lightning.vsock
import fr.acinq.lightning.logging.LoggerFactory import java.io.Closeable
import kotlin.jvm.Synchronized import java.io.IOException
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
private set get() {
if (isClosed) {
var inputStream: VSockInputStream? = null throw SocketException("VSock is closed thrown in Vsock")
private set
init {
if (address != null) {
try {
getImplementation().connect(address)
} catch (e: Exception) {
try {
close()
} catch (ce: Exception) {
e.addSuppressed(ce)
}
throw IllegalStateException(e.message, e)
} }
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) {
getImplementation()!!.create()
isClosed = false;
//throw SocketException("VSock is closed thrown in Vsock")
}
if (field == null) {
field = getImplementation()?.let { VSockInputStream(it) }
}
return field
}
private set
constructor()
constructor(address: VSockAddress?) {
try {
getImplementation()!!.connect(address)
} catch (e: Exception) {
try {
close()
} catch (ce: Exception) {
e.addSuppressed(ce)
}
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,36 +1,35 @@
package fr.acinq.lightning.vsock package fr.acinq.lightning.vsock
class VSockAddress(val cid: Int, val port: Int) { import java.net.SocketAddress
import java.util.Objects
companion object { class VSockAddress(val cid: Int, val port: Int) : SocketAddress() {
const val VMADDR_CID_ANY = -1 override fun equals(o: Any?): Boolean {
const val VMADDR_CID_HYPERVISOR = 0 if (this === o) return true
const val VMADDR_CID_RESERVED = 1 if (o == null || javaClass != o.javaClass) return false
const val VMADDR_CID_HOST = 2 val that = o as VSockAddress
const val VMADDR_CID_PARENT = 3 return cid == that.cid &&
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 {
var result = cid return Objects.hash(cid, port)
result = 31 * result + port
return result
} }
override fun toString(): String { override fun toString(): String {
return "VSockAddress(cid=$cid, port=$port)" 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
} }
} }

View File

@ -1,29 +1,30 @@
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) : Closeable { class VSockInputStream(private val vSock: VSockImpl) : InputStream() {
private lateinit var temp: ByteArray
private var temp: ByteArray? = null
@Throws(IOException::class) @Throws(IOException::class)
fun read(b: ByteArray, off: Int, len: Int): Int { override 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)
fun read(): Int { override fun read(): Int {
temp = ByteArray(1) temp = ByteArray(1)
val n = read(temp!!, 0, 1) val n = read(temp, 0, 1)
return if (n <= 0) { if (n <= 0) {
-1 return -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

@ -1,24 +0,0 @@
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,23 +1,26 @@
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(private val vSock: VSockImpl) : Closeable { class VSockOutputStream internal constructor(private val vSock: VSockImpl) : OutputStream() {
private val temp = ByteArray(1) private val temp = ByteArray(1)
@Throws(IOException::class) @Throws(IOException::class)
fun write(b: ByteArray) { override fun write(b: Int) {
temp[0] = b[0] temp[0] = b.toByte()
this.write(temp, 0, 1) this.write(temp, 0, 1)
} }
@Throws(IOException::class) @Throws(IOException::class)
fun write(b: ByteArray, off: Int, len: Int) { override 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,80 +1,75 @@
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.info
import fr.acinq.lightning.logging.error import fr.acinq.lightning.logging.error
import io.ktor.client.* 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 kotlin.io.encoding.Base64 import java.io.IOException
import kotlin.io.encoding.ExperimentalEncodingApi import java.nio.charset.StandardCharsets
import java.util.Base64
class VsockServer(private val CID: Int, private val port: Int, httpBindPort: Int, host: String, class VsockServer(private val cid: Int, private val port: Int, httpBindPort: Int, host: String, loggerFactory: LoggerFactory) {
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(loggerFactory) server = ServerVSock()
var peerVSock: VSock? = null
try { try {
server?.bind(VSockAddress(CID, port)) // For any CID, use VSockAddress.VMADDR_CID_ANY server?.bind(VSockAddress(VSockAddress.VMADDR_CID_ANY, port)) //For any CID use VSockAddress.VMADDR_CID_ANY
logger.debug { "Vsock Bound on CID: ${CID}" } logger.info { "Vsock Bound on Cid: ${server?.localCid}" }
peerVSock = server?.accept() server?.accept()?.use { peerVSock ->
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 && bytesRead > 0) { if (bytesRead != null) {
val receivedData = buffer.decodeToString(0, bytesRead).trim() if (bytesRead > 0) {
logger.debug { "Received Data: $receivedData" } val receivedData = String(buffer, 0, bytesRead, StandardCharsets.UTF_8).trim()
logger.info { "Received Data: $receivedData" }
// Parse the received data into an API request // Parse the received data into a http request
val apiRequest = try { val apiRequest = try {
Json.decodeFromString<ApiType.VsockApiRequest>(receivedData) Json.decodeFromString<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(Charsets.UTF_8)) val errorMessage = "{\"error\":\"Invalid JSON format\", \"received\":\"${receivedData}\"}"
return peerVSock.outputStream?.write(errorMessage.toByteArray(StandardCharsets.UTF_8))
} 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(Charsets.UTF_8)) peerVSock.outputStream?.write(response.toByteArray(StandardCharsets.UTF_8))
}
} }
} }
} }
} catch (ex: IOException) { } catch (ex: IOException) {
logger.error { "Error starting Vsock: ${ex.message}" } logger.error { "Error starting Vsock: ${ex.message}" }
} finally {
peerVSock?.close()
stop()
} }
/*finally { // We have to keep the server running
stop()
}*/
} }
@OptIn(ExperimentalEncodingApi::class) private suspend fun handleApiCall(request: VsockApiRequest): String {
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.encode("user:${request.httpPassword}".toByteArray(Charsets.UTF_8))}") append(HttpHeaders.Authorization, "Basic ${Base64.getEncoder().encodeToString("user:${request.httpPassword}".toByteArray())}")
} }
url { url {
parameters.appendAll(Parameters.build { parameters.appendAll(Parameters.build {
@ -97,7 +92,7 @@ class VsockServer(private val CID: Int, private val port: Int, httpBindPort: Int
} }
fun stop() { fun stop() {
logger.debug { "Stopping Vsock Server" } logger.info { "Stopping Vsock Server" }
server?.close() server?.close()
} }
} }

View File

@ -12,20 +12,28 @@
#define BUFFER_LEN 65536 #define BUFFER_LEN 65536
#define min(a, b) ((a) < (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b))
// Make sure to wrap all your JNI functions with extern "C" to avoid name mangling
extern "C" { extern "C" {
// Native method implementations matching the JNI header
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_fr_acinq_lightning_vsock_native_VSockImpl_socketCreate(JNIEnv *env, jobject thisObj) { Java_fr_acinq_lightning_vsock_native_VSockImpl_socketCreate(JNIEnv *env, jobject thisObj) {
int fd = socket(AF_VSOCK, SOCK_STREAM, 0); int fd = socket(AF_VSOCK, SOCK_STREAM, 0);
// Optionally store the socket descriptor in the Java object's field if (fd < 0) {
env->ThrowNew(env->FindClass("java/net/SocketException"), "Failed to create socket");
return;
}
// Store the socket descriptor in the Java object's field
jclass thisClass = env->GetObjectClass(thisObj);
jfieldID fdField = env->GetFieldID(thisClass, "fd", "I");
env->SetIntField(thisObj, fdField, fd);
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_fr_acinq_lightning_vsock_native_VSockImpl_connect(JNIEnv *env, jobject thisObj, jobject addr) { Java_fr_acinq_lightning_vsock_native_VSockImpl_connect(JNIEnv *env, jobject thisObj, jobject addr) {
int fd = -1; // Assuming you have stored the fd somewhere accessible jclass thisClass = env->GetObjectClass(thisObj);
jfieldID fdField = env->GetFieldID(thisClass, "fd", "I");
int fd = env->GetIntField(thisObj, fdField);
if (fd == -1) { if (fd == -1) {
env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed"); env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed");
return; return;
@ -41,6 +49,7 @@ std::memset(&sock_addr, 0, sizeof(struct sockaddr_vm));
sock_addr.svm_family = AF_VSOCK; sock_addr.svm_family = AF_VSOCK;
sock_addr.svm_port = env->GetIntField(addr, portField); sock_addr.svm_port = env->GetIntField(addr, portField);
sock_addr.svm_cid = env->GetIntField(addr, cidField); sock_addr.svm_cid = env->GetIntField(addr, cidField);
int status = ::connect(fd, (struct sockaddr *)&sock_addr, sizeof(struct sockaddr_vm)); int status = ::connect(fd, (struct sockaddr *)&sock_addr, sizeof(struct sockaddr_vm));
if (status != 0) { if (status != 0) {
@ -51,23 +60,30 @@ env->ThrowNew(env->FindClass("java/net/ConnectException"),
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_fr_acinq_lightning_vsock_native_VSockImpl_close(JNIEnv *env, jobject thisObj) { Java_fr_acinq_lightning_vsock_native_VSockImpl_close(JNIEnv *env, jobject thisObj) {
int fd = -1; // Assuming you have stored the fd somewhere accessible jclass thisClass = env->GetObjectClass(thisObj);
jfieldID fdField = env->GetFieldID(thisClass, "fd", "I");
int fd = env->GetIntField(thisObj, fdField);
if (fd == -1) { if (fd == -1) {
return; return; // Socket is already closed
} }
int status = ::close(fd); int status = ::close(fd);
fd = -1;
if (status != 0) { if (status != 0) {
env->ThrowNew(env->FindClass("java/net/SocketException"), env->ThrowNew(env->FindClass("java/net/SocketException"),
("Close failed with error no: " + std::to_string(errno)).c_str()); ("Close failed with error no: " + std::to_string(errno)).c_str());
} }
// Mark the socket as closed
env->SetIntField(thisObj, fdField, -1);
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_fr_acinq_lightning_vsock_native_VSockImpl_write(JNIEnv *env, jobject thisObj, jbyteArray b, jint offset, jint len) { Java_fr_acinq_lightning_vsock_native_VSockImpl_write(JNIEnv *env, jobject thisObj, jbyteArray b, jint offset, jint len) {
int fd = -1; // Assuming you have stored the fd somewhere accessible jclass thisClass = env->GetObjectClass(thisObj);
jfieldID fdField = env->GetFieldID(thisClass, "fd", "I");
int fd = env->GetIntField(thisObj, fdField);
if (fd == -1) { if (fd == -1) {
env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed"); env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed");
return; return;
@ -91,7 +107,10 @@ offset += chunkLen;
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_fr_acinq_lightning_vsock_native_VSockImpl_read(JNIEnv *env, jobject thisObj, jbyteArray b, jint offset, jint len) { Java_fr_acinq_lightning_vsock_native_VSockImpl_read(JNIEnv *env, jobject thisObj, jbyteArray b, jint offset, jint len) {
int fd = -1; // Assuming you have stored the fd somewhere accessible jclass thisClass = env->GetObjectClass(thisObj);
jfieldID fdField = env->GetFieldID(thisClass, "fd", "I");
int fd = env->GetIntField(thisObj, fdField);
if (fd == -1) { if (fd == -1) {
env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed"); env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed");
return -1; return -1;
@ -114,7 +133,10 @@ return nread;
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_fr_acinq_lightning_vsock_native_VSockImpl_bind(JNIEnv *env, jobject thisObj, jobject addr) { Java_fr_acinq_lightning_vsock_native_VSockImpl_bind(JNIEnv *env, jobject thisObj, jobject addr) {
int fd = -1; // Assuming you have stored the fd somewhere accessible jclass thisClass = env->GetObjectClass(thisObj);
jfieldID fdField = env->GetFieldID(thisClass, "fd", "I");
int fd = env->GetIntField(thisObj, fdField);
if (fd == -1) { if (fd == -1) {
env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed"); env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed");
return; return;
@ -141,7 +163,10 @@ env->ThrowNew(env->FindClass("java/net/BindException"),
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_fr_acinq_lightning_vsock_native_VSockImpl_listen(JNIEnv *env, jobject thisObj, jint backlog) { Java_fr_acinq_lightning_vsock_native_VSockImpl_listen(JNIEnv *env, jobject thisObj, jint backlog) {
int fd = -1; // Assuming you have stored the fd somewhere accessible jclass thisClass = env->GetObjectClass(thisObj);
jfieldID fdField = env->GetFieldID(thisClass, "fd", "I");
int fd = env->GetIntField(thisObj, fdField);
if (fd == -1) { if (fd == -1) {
env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed"); env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed");
return; return;
@ -157,7 +182,10 @@ env->ThrowNew(env->FindClass("java/net/SocketException"),
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_fr_acinq_lightning_vsock_native_VSockImpl_accept(JNIEnv *env, jobject thisObj, jobject connectionVSock) { Java_fr_acinq_lightning_vsock_native_VSockImpl_accept(JNIEnv *env, jobject thisObj, jobject connectionVSock) {
int fd = -1; // Assuming you have stored the fd somewhere accessible jclass thisClass = env->GetObjectClass(thisObj);
jfieldID fdField = env->GetFieldID(thisClass, "fd", "I");
int fd = env->GetIntField(thisObj, fdField);
if (fd == -1) { if (fd == -1) {
env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed"); env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed");
return; return;
@ -175,13 +203,16 @@ return;
// Set the peer_fd in the Java connectionVSock object // Set the peer_fd in the Java connectionVSock object
jclass VSockImplClass = env->GetObjectClass(connectionVSock); jclass VSockImplClass = env->GetObjectClass(connectionVSock);
jfieldID fdField = env->GetFieldID(VSockImplClass, "fd", "I"); jfieldID peerFdField = env->GetFieldID(VSockImplClass, "fd", "I");
env->SetIntField(connectionVSock, fdField, peer_fd); env->SetIntField(connectionVSock, peerFdField, peer_fd);
} }
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_fr_acinq_lightning_vsock_native_VSockImpl_getLocalCid(JNIEnv *env, jobject thisObj) { Java_fr_acinq_lightning_vsock_native_VSockImpl_getLocalCid(JNIEnv *env, jobject thisObj) {
int fd = -1; // Assuming you have stored the fd somewhere accessible jclass thisClass = env->GetObjectClass(thisObj);
jfieldID fdField = env->GetFieldID(thisClass, "fd", "I");
int fd = env->GetIntField(thisObj, fdField);
if (fd == -1) { if (fd == -1) {
env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed"); env->ThrowNew(env->FindClass("java/net/SocketException"), "Socket is closed");
return -1; return -1;

View File

@ -1,38 +1,47 @@
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 fr.acinq.lightning.vsock.VSockImplLib import java.net.SocketException
class VSockImpl { class VSockImpl() {
init { init {
VSockImplLib.loadLibrary() System.load("${System.getProperty("user.dir")}/build/libs/libjniVSockImpl.so")
//NarSystem.loadLibrary() // Load the native library from the .nar file
} }
var fd: Int = -1
@Throws(SocketException::class)
fun create() { fun create() {
VSockImplLib.socketCreate() socketCreate()
} }
fun connect(address: VSockAddress){ private external fun allocate()
VSockImplLib.connect(address)
}
fun close(){ private external fun socketCreate()
VSockImplLib.close()
} @Throws(Exception::class)
fun write(b: ByteArray, off: Int, len: Int){ external fun connect(address: VSockAddress?)
VSockImplLib.write(b, off, len)
} @Throws(Exception::class)
fun read(b: ByteArray, off: Int, len: Int): Int{ external fun close()
return VSockImplLib.read(b, off, len)
} @Throws(Exception::class)
fun bind(address: VSockAddress){ external fun write(b: ByteArray, offset: Int, len: Int)
VSockImplLib.bind(address)
} @Throws(Exception::class)
fun listen(backlog: Int){ external fun read(b: ByteArray, offset: Int, len: Int): Int
VSockImplLib.listen(backlog)
} @Throws(Exception::class)
fun accept(peerVSock: VSockImpl){ external fun bind(addr: fr.acinq.lightning.vsock.VSockAddress?)
VSockImplLib.accept(peerVSock)
} @Throws(Exception::class)
} external fun listen(backlog: Int)
@Throws(Exception::class)
external fun accept(peerVSock: VSockImpl)
@Throws(Exception::class)
external fun getLocalCid(): Int
}

View File

@ -1,22 +0,0 @@
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

@ -1,91 +0,0 @@
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)
}
}