1 Commits

Author SHA1 Message Date
003419a84c An attempt to build linux binary with native vsock code 2024-08-16 01:52:25 +02:00
17 changed files with 396 additions and 267 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("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,12 +73,15 @@ 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() {
binaries {
executable("phoenixd") {
entryPoint = "fr.acinq.lightning.bin.MainKt"
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") {
@@ -116,12 +117,12 @@ kotlin {
dependencies {
implementation("com.github.raymond98.lightning-kmp:lightning-kmp:v1.6.2-FEECREDIT-8")
//implementation("org.bytedeco:javacpp:1.5.10")
//implementation("com.github.maven-nar:nar-maven-plugin:3.10.1")
implementation("org.jetbrains.kotlinx:atomicfu:0.25.0")
//api("fr.acinq.bitcoin:bitcoin-kmp:${Versions.bitcoinKmpVersion}")
//api("co.touchlab:kermit:${Versions.kermitLoggerVersion}")
//api("org.jetbrains.kotlinx:kotlinx-datetime:${Versions.datetimeVersion}")
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}")
api(ktor("network"))
api(ktor("network-tls"))
@@ -148,9 +149,11 @@ kotlin {
jvmMain {
dependencies {
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("ch.qos.logback:logback-classic:1.2.3")
implementation("org.bytedeco:javacpp:1.5.10")
implementation("org.bytedeco:javacpp-presets:1.5.10")
}
}
nativeMain {
@@ -184,10 +187,10 @@ kotlin {
include("*.kexe")
rename("phoenixd.kexe", "phoenixd")
}
/*from("$projectDir/build/bin/$dir/phoenix-cliReleaseExecutable") {
from("$projectDir/build/bin/$dir/phoenix-cliReleaseExecutable") {
include("*.kexe")
rename("phoenix-cli.kexe", "phoenix-cli")
}*/
}
into("${archiveBaseName.get()}-${archiveVersion.get()}-${archiveClassifier.get()}")
}
@@ -214,10 +217,11 @@ 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=${layout.buildDirectory.dir("libs").get().asFile.absolutePath}")
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) {
mainClass.set("fr.acinq.lightning.cli.PhoenixCliKt")
outputDir = tasks.startScripts.get().outputDir
classpath = tasks.startScripts.get().classpath
@@ -226,11 +230,27 @@ application {
tasks.startScripts {
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") {
group = "build"
description = "Compile the native C++ code into a shared library and package it into a .nar file"
description = "Compile the native C++ code into a shared library"
val outputDir = layout.buildDirectory.dir("libs").get().asFile
val nativeSourceDir = file("src/commonMain/kotlin/fr/acinq/lightning/vsock/native")
@@ -243,18 +263,17 @@ val compileNative by tasks.register<Exec>("compileNative") {
inputs.dir(nativeSourceDir)
outputs.dir(outputDir)
// Compile the shared library
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> {
dependsOn(compileNative)
dependsOn(setLibPath)
}
tasks.withType<Zip> {
dependsOn(compileNative)
dependsOn(setLibPath)
}
distributions {
@@ -267,7 +286,6 @@ distributions {
// forward std input when app is run via gradle (otherwise keyboard input will return EOF)
tasks.withType<JavaExec> {
standardInput = System.`in`
dependsOn(compileNative) //This should not be the case for all platforms
}
sqldelight {

View File

@@ -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"

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)
}
} 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") {

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

View File

@@ -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 thrown in Base Vsock")
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()
}

View File

@@ -1,19 +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
}
}

View File

@@ -1,73 +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 thrown in Vsock")
}
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) {
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)

View File

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

View File

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

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

View File

@@ -1,75 +1,80 @@
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.info
import fr.acinq.lightning.logging.debug
import fr.acinq.lightning.logging.error
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(VSockAddress.VMADDR_CID_ANY, port)) //For any CID use VSockAddress.VMADDR_CID_ANY
logger.info { "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.info { "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<VsockApiRequest>(receivedData)
} catch (e: Exception) {
logger.error { "Failed to parse JSON: ${e.message}" }
val errorMessage = "{\"error\":\"Invalid JSON format\", \"received\":\"${receivedData}\"}"
peerVSock.outputStream?.write(errorMessage.toByteArray(StandardCharsets.UTF_8))
return
}
// Parse the received data into an API request
val apiRequest = try {
Json.decodeFromString<ApiType.VsockApiRequest>(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 { // We have to keep the server running
} 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 {
@@ -92,7 +97,7 @@ class VsockServer(private val cid: Int, private val port: Int, httpBindPort: Int
}
fun stop() {
logger.info { "Stopping Vsock Server" }
logger.debug { "Stopping Vsock Server" }
server?.close()
}
}

View File

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

View File

@@ -1,47 +1,38 @@
package fr.acinq.lightning.vsock.native
import fr.acinq.lightning.vsock.VSockAddress
import java.net.SocketException
import fr.acinq.lightning.vsock.VSockImplLib
class VSockImpl() {
class VSockImpl {
init {
System.load("${System.getProperty("user.dir")}/build/libs/libjniVSockImpl.so")
//NarSystem.loadLibrary() // Load the native library from the .nar file
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 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)
}
}

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