diff --git a/build.gradle b/build.gradle index a943bb4..9369a62 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,7 @@ allprojects { subproject -> withSourcesJar() modularity.inferModulePath = true toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(23) vendor = JvmVendorSpec.ORACLE } } diff --git a/docker/Dockerfile b/docker/Dockerfile index 3bbbbfd..26b7c48 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -15,3 +15,7 @@ RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distri WORKDIR /home/luser ADD logback.xml . ENTRYPOINT ["java", "-Dlogback.configurationFile=logback.xml", "-XX:+UseSerialGC", "-XX:GCTimeRatio=24", "-jar", "/home/luser/rbcs.jar", "server"] + +FROM scratch AS release-native +ADD rbcs-cli.upx rbcs-cli +ENTRYPOINT rbcs-cli diff --git a/rbcs-cli/build.gradle b/rbcs-cli/build.gradle index 42a59b6..41d0236 100644 --- a/rbcs-cli/build.gradle +++ b/rbcs-cli/build.gradle @@ -13,10 +13,20 @@ import net.woggioni.gradle.envelope.EnvelopePlugin import net.woggioni.gradle.envelope.EnvelopeJarTask import net.woggioni.gradle.graalvm.NativeImageConfigurationTask import net.woggioni.gradle.graalvm.NativeImagePlugin -import net.woggioni.gradle.graalvm.NativeImageTask +import net.woggioni.gradle.graalvm.UpxTask import net.woggioni.gradle.graalvm.JlinkPlugin import net.woggioni.gradle.graalvm.JlinkTask +sourceSets { + configureNativeImage { + java { + } + kotlin { + + } + } +} + configurations { release { transitive = false @@ -24,9 +34,25 @@ configurations { canBeResolved = true visible = true } + + configureNativeImageImplementation { + extendsFrom implementation + } + + configureNativeImageRuntimeOnly { + extendsFrom runtimeOnly + } + + nativeImage { + extendsFrom runtimeClasspath + } + } dependencies { + configureNativeImageImplementation project + configureNativeImageImplementation project(':rbcs-server-memcache') + implementation catalog.jwo implementation catalog.slf4j.api implementation catalog.picocli @@ -37,6 +63,8 @@ dependencies { // runtimeOnly catalog.slf4j.jdk14 runtimeOnly catalog.logback.classic // runtimeOnly catalog.slf4j.simple + nativeImage project(':rbcs-server-memcache') + } @@ -49,6 +77,8 @@ tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) { options.javaModuleMainClass = mainClassName } +Provider jarTaskProvider = tasks.named(JavaPlugin.JAR_TASK_NAME, Jar) + Provider envelopeJarTaskProvider = tasks.named(EnvelopePlugin.ENVELOPE_JAR_TASK_NAME, EnvelopeJarTask.class) { mainModule = mainModuleName mainClass = mainClassName @@ -60,15 +90,28 @@ Provider envelopeJarTaskProvider = tasks.named(EnvelopePlugin.E } tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) { - mainClass = mainClassName - mainModule = mainModuleName + mainClass = "net.woggioni.rbcs.cli.graal.GraalNativeImageConfiguration" + setClasspath(configurations.configureNativeImageRuntimeClasspath + sourceSets.graal.output.classesDirs) + mergeConfiguration = false + systemProperty('logback.configurationFile', 'classpath:net/woggioni/rbcs/cli/logback.xml') + systemProperty('io.netty.leakDetectionLevel', 'DISABLED') + modularity.inferModulePath = false + enabled = false } -tasks.named(NativeImagePlugin.NATIVE_IMAGE_TASK_NAME, NativeImageTask) { +nativeImage { mainClass = mainClassName - mainModule = mainModuleName +// mainModule = mainModuleName useMusl = true buildStaticImage = true + linkAtBuildTime = false + classpath = project.files(jarTaskProvider, configurations.nativeImage) + compressExecutable = true + compressionLevel = 10 + useLZMA = false +} + +Provider upxTaskProvider = tasks.named(NativeImagePlugin.UPX_TASK_NAME, UpxTask) { } tasks.named(JlinkPlugin.JLINK_TASK_NAME, JlinkTask) { @@ -92,8 +135,13 @@ publishing { publications { maven(MavenPublication) { artifact envelopeJar + artifact(upxTaskProvider) { + classifier = "linux-x86_64" + extension = "exe" + } } } } + diff --git a/rbcs-cli/native-image/jni-config.json b/rbcs-cli/native-image/jni-config.json new file mode 100644 index 0000000..8b4e417 --- /dev/null +++ b/rbcs-cli/native-image/jni-config.json @@ -0,0 +1,6 @@ +[ +{ + "name":"java.lang.Boolean", + "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] +} +] diff --git a/rbcs-cli/native-image/native-image.properties b/rbcs-cli/native-image/native-image.properties index 52d1cbf..9e1f8de 100644 --- a/rbcs-cli/native-image/native-image.properties +++ b/rbcs-cli/native-image/native-image.properties @@ -1,2 +1,2 @@ -Args=-H:Optimize=3 --gc=serial --initialize-at-run-time=io.netty +Args=-O3 --gc=serial --initialize-at-run-time=io.netty --enable-url-protocols=jpms --initialize-at-build-time=net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory,net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory$JpmsHandler #-H:TraceClassInitialization=io.netty.handler.ssl.BouncyCastleAlpnSslUtils \ No newline at end of file diff --git a/rbcs-cli/native-image/predefined-classes-config.json b/rbcs-cli/native-image/predefined-classes-config.json new file mode 100644 index 0000000..0e79b2c --- /dev/null +++ b/rbcs-cli/native-image/predefined-classes-config.json @@ -0,0 +1,8 @@ +[ + { + "type":"agent-extracted", + "classes":[ + ] + } +] + diff --git a/rbcs-cli/native-image/proxy-config.json b/rbcs-cli/native-image/proxy-config.json new file mode 100644 index 0000000..0d4f101 --- /dev/null +++ b/rbcs-cli/native-image/proxy-config.json @@ -0,0 +1,2 @@ +[ +] diff --git a/rbcs-cli/native-image/reflect-config.json b/rbcs-cli/native-image/reflect-config.json new file mode 100644 index 0000000..bafe953 --- /dev/null +++ b/rbcs-cli/native-image/reflect-config.json @@ -0,0 +1,756 @@ +[ +{ + "name":"android.os.Build$VERSION" +}, +{ + "name":"ch.qos.logback.classic.encoder.PatternLayoutEncoder", + "queryAllPublicMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.joran.SerializedModelConfigurator", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.classic.util.DefaultJoranConfigurator", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"ch.qos.logback.core.ConsoleAppender", + "queryAllPublicMethods":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"setTarget","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"ch.qos.logback.core.OutputStreamAppender", + "methods":[{"name":"setEncoder","parameterTypes":["ch.qos.logback.core.encoder.Encoder"] }] +}, +{ + "name":"ch.qos.logback.core.encoder.Encoder", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"ch.qos.logback.core.encoder.LayoutWrappingEncoder", + "methods":[{"name":"setParent","parameterTypes":["ch.qos.logback.core.spi.ContextAware"] }] +}, +{ + "name":"ch.qos.logback.core.pattern.PatternLayoutEncoderBase", + "methods":[{"name":"setPattern","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"ch.qos.logback.core.spi.ContextAware", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"com.aayushatharva.brotli4j.Brotli4jLoader" +}, +{ + "name":"com.github.luben.zstd.Zstd" +}, +{ + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ARCFOURCipher", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESCipher", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DESedeCipher", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DHParameters", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.HmacCore$HmacSHA512", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.PBKDF2Core$HmacSHA512", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.TlsMasterSecretGenerator", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.org.apache.xerces.internal.impl.dv.xs.ExtendedSchemaDVFactoryImpl", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.org.apache.xerces.internal.impl.dv.xs.SchemaDVFactoryImpl", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"groovy.lang.Closure" +}, +{ + "name":"io.netty.bootstrap.ServerBootstrap$1" +}, +{ + "name":"io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor", + "methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }] +}, +{ + "name":"io.netty.buffer.AbstractByteBufAllocator", + "queryAllDeclaredMethods":true +}, +{ + "name":"io.netty.buffer.AbstractReferenceCountedByteBuf", + "fields":[{"name":"refCnt"}] +}, +{ + "name":"io.netty.channel.AbstractChannelHandlerContext", + "fields":[{"name":"handlerState"}] +}, +{ + "name":"io.netty.channel.ChannelDuplexHandler", + "methods":[{"name":"bind","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.net.SocketAddress","io.netty.channel.ChannelPromise"] }, {"name":"close","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"connect","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.net.SocketAddress","java.net.SocketAddress","io.netty.channel.ChannelPromise"] }, {"name":"deregister","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"disconnect","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"flush","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"read","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"write","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object","io.netty.channel.ChannelPromise"] }] +}, +{ + "name":"io.netty.channel.ChannelHandlerAdapter", + "methods":[{"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }] +}, +{ + "name":"io.netty.channel.ChannelInboundHandlerAdapter", + "methods":[{"name":"channelActive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"channelReadComplete","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRegistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelUnregistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelWritabilityChanged","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }, {"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }] +}, +{ + "name":"io.netty.channel.ChannelInitializer", + "methods":[{"name":"channelRegistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }] +}, +{ + "name":"io.netty.channel.ChannelOutboundBuffer", + "fields":[{"name":"totalPendingSize"}, {"name":"unwritable"}] +}, +{ + "name":"io.netty.channel.ChannelOutboundHandlerAdapter", + "methods":[{"name":"bind","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.net.SocketAddress","io.netty.channel.ChannelPromise"] }, {"name":"connect","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.net.SocketAddress","java.net.SocketAddress","io.netty.channel.ChannelPromise"] }, {"name":"deregister","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"disconnect","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"flush","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"read","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }] +}, +{ + "name":"io.netty.channel.CombinedChannelDuplexHandler", + "methods":[{"name":"bind","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.net.SocketAddress","io.netty.channel.ChannelPromise"] }, {"name":"channelActive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"channelReadComplete","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRegistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelUnregistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelWritabilityChanged","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"close","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"connect","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.net.SocketAddress","java.net.SocketAddress","io.netty.channel.ChannelPromise"] }, {"name":"deregister","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"disconnect","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }, {"name":"flush","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"read","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"write","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object","io.netty.channel.ChannelPromise"] }] +}, +{ + "name":"io.netty.channel.DefaultChannelConfig", + "fields":[{"name":"autoRead"}, {"name":"writeBufferWaterMark"}] +}, +{ + "name":"io.netty.channel.DefaultChannelPipeline", + "fields":[{"name":"estimatorHandle"}] +}, +{ + "name":"io.netty.channel.DefaultChannelPipeline$HeadContext", + "methods":[{"name":"bind","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.net.SocketAddress","io.netty.channel.ChannelPromise"] }, {"name":"channelActive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"channelReadComplete","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRegistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelUnregistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelWritabilityChanged","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"close","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"connect","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.net.SocketAddress","java.net.SocketAddress","io.netty.channel.ChannelPromise"] }, {"name":"deregister","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"disconnect","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }, {"name":"flush","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"read","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"write","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object","io.netty.channel.ChannelPromise"] }] +}, +{ + "name":"io.netty.channel.DefaultChannelPipeline$TailContext", + "methods":[{"name":"channelActive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"channelReadComplete","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRegistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelUnregistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelWritabilityChanged","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }, {"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }] +}, +{ + "name":"io.netty.channel.SimpleChannelInboundHandler", + "methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }] +}, +{ + "name":"io.netty.channel.embedded.EmbeddedChannel$2" +}, +{ + "name":"io.netty.channel.pool.SimpleChannelPool$1" +}, +{ + "name":"io.netty.channel.socket.nio.NioSocketChannel", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.netty.handler.codec.ByteToMessageDecoder", + "methods":[{"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"channelReadComplete","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }] +}, +{ + "name":"io.netty.handler.codec.MessageAggregator", + "methods":[{"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelReadComplete","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }] +}, +{ + "name":"io.netty.handler.codec.MessageToByteEncoder", + "methods":[{"name":"write","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object","io.netty.channel.ChannelPromise"] }] +}, +{ + "name":"io.netty.handler.codec.MessageToMessageCodec", + "methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"channelReadComplete","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }] +}, +{ + "name":"io.netty.handler.codec.MessageToMessageDecoder", + "methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }] +}, +{ + "name":"io.netty.handler.codec.compression.JdkZlibDecoder" +}, +{ + "name":"io.netty.handler.codec.compression.JdkZlibEncoder", + "methods":[{"name":"close","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }] +}, +{ + "name":"io.netty.handler.codec.http.HttpClientCodec" +}, +{ + "name":"io.netty.handler.codec.http.HttpContentDecoder", + "methods":[{"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelReadComplete","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }] +}, +{ + "name":"io.netty.handler.codec.http.HttpContentDecompressor" +}, +{ + "name":"io.netty.handler.codec.http.HttpContentEncoder", + "methods":[{"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }] +}, +{ + "name":"io.netty.handler.codec.http.HttpObjectAggregator" +}, +{ + "name":"io.netty.handler.codec.http.HttpServerCodec" +}, +{ + "name":"io.netty.handler.codec.memcache.binary.BinaryMemcacheClientCodec" +}, +{ + "name":"io.netty.handler.stream.ChunkedWriteHandler", + "methods":[{"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelWritabilityChanged","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"flush","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"write","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object","io.netty.channel.ChannelPromise"] }] +}, +{ + "name":"io.netty.handler.timeout.IdleStateHandler", + "methods":[{"name":"channelActive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"channelReadComplete","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRegistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"write","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object","io.netty.channel.ChannelPromise"] }] +}, +{ + "name":"io.netty.internal.tcnative.SSLContext" +}, +{ + "name":"io.netty.util.AbstractReferenceCounted", + "fields":[{"name":"refCnt"}] +}, +{ + "name":"io.netty.util.DefaultAttributeMap", + "fields":[{"name":"attributes"}] +}, +{ + "name":"io.netty.util.DefaultAttributeMap$DefaultAttribute", + "fields":[{"name":"attributeMap"}] +}, +{ + "name":"io.netty.util.Recycler$DefaultHandle", + "fields":[{"name":"state"}] +}, +{ + "name":"io.netty.util.ReferenceCountUtil", + "queryAllDeclaredMethods":true +}, +{ + "name":"io.netty.util.concurrent.DefaultPromise", + "fields":[{"name":"result"}] +}, +{ + "name":"io.netty.util.concurrent.SingleThreadEventExecutor", + "fields":[{"name":"state"}, {"name":"threadProperties"}] +}, +{ + "name":"io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueColdProducerFields", + "fields":[{"name":"producerLimit"}] +}, +{ + "name":"io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueConsumerFields", + "fields":[{"name":"consumerIndex"}] +}, +{ + "name":"io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueProducerFields", + "fields":[{"name":"producerIndex"}] +}, +{ + "name":"io.netty.util.internal.shaded.org.jctools.queues.unpadded.MpscUnpaddedArrayQueueConsumerIndexField", + "fields":[{"name":"consumerIndex"}] +}, +{ + "name":"io.netty.util.internal.shaded.org.jctools.queues.unpadded.MpscUnpaddedArrayQueueProducerIndexField", + "fields":[{"name":"producerIndex"}] +}, +{ + "name":"io.netty.util.internal.shaded.org.jctools.queues.unpadded.MpscUnpaddedArrayQueueProducerLimitField", + "fields":[{"name":"producerLimit"}] +}, +{ + "name":"java.io.FilePermission" +}, +{ + "name":"java.lang.Object", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.lang.ProcessHandle", + "methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }] +}, +{ + "name":"java.lang.RuntimePermission" +}, +{ + "name":"java.lang.System", + "methods":[{"name":"console","parameterTypes":[] }] +}, +{ + "name":"java.lang.Thread", + "fields":[{"name":"threadLocalRandomProbe"}] +}, +{ + "name":"java.net.NetPermission" +}, +{ + "name":"java.net.SocketPermission" +}, +{ + "name":"java.net.URLPermission", + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }] +}, +{ + "name":"java.nio.Bits", + "fields":[{"name":"MAX_MEMORY"}, {"name":"UNALIGNED"}] +}, +{ + "name":"java.nio.Buffer", + "fields":[{"name":"address"}] +}, +{ + "name":"java.nio.ByteBuffer", + "methods":[{"name":"alignedSlice","parameterTypes":["int"] }] +}, +{ + "name":"java.nio.DirectByteBuffer", + "methods":[{"name":"","parameterTypes":["long","long"] }] +}, +{ + "name":"java.nio.channels.spi.SelectorProvider", + "methods":[{"name":"openServerSocketChannel","parameterTypes":["java.net.ProtocolFamily"] }, {"name":"openSocketChannel","parameterTypes":["java.net.ProtocolFamily"] }] +}, +{ + "name":"java.nio.file.Path" +}, +{ + "name":"java.nio.file.Paths", + "methods":[{"name":"get","parameterTypes":["java.lang.String","java.lang.String[]"] }] +}, +{ + "name":"java.security.AlgorithmParametersSpi" +}, +{ + "name":"java.security.AllPermission" +}, +{ + "name":"java.security.KeyStoreSpi" +}, +{ + "name":"java.security.SecureRandomParameters" +}, +{ + "name":"java.security.SecurityPermission" +}, +{ + "name":"java.sql.Connection" +}, +{ + "name":"java.sql.Driver" +}, +{ + "name":"java.sql.DriverManager", + "methods":[{"name":"getConnection","parameterTypes":["java.lang.String"] }, {"name":"getDriver","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.sql.Time", + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"java.sql.Timestamp", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.time.Duration", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.Instant", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.LocalDate", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.LocalDateTime", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.LocalTime", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.MonthDay", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.OffsetDateTime", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.OffsetTime", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.Period", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.Year", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.YearMonth", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.ZoneId", + "methods":[{"name":"of","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.time.ZoneOffset", + "methods":[{"name":"of","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.time.ZonedDateTime", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.util.PropertyPermission" +}, +{ + "name":"java.util.concurrent.ForkJoinTask", + "fields":[{"name":"aux"}, {"name":"status"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicBoolean", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.AtomicReference", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.concurrent.atomic.Striped64", + "fields":[{"name":"base"}, {"name":"cellsBusy"}] +}, +{ + "name":"java.util.concurrent.atomic.Striped64$Cell", + "fields":[{"name":"value"}] +}, +{ + "name":"java.util.zip.Adler32", + "methods":[{"name":"update","parameterTypes":["java.nio.ByteBuffer"] }] +}, +{ + "name":"java.util.zip.CRC32", + "methods":[{"name":"update","parameterTypes":["java.nio.ByteBuffer"] }] +}, +{ + "name":"javax.security.auth.x500.X500Principal", + "fields":[{"name":"thisX500Name"}], + "methods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }] +}, +{ + "name":"javax.smartcardio.CardPermission" +}, +{ + "name":"jdk.internal.misc.Unsafe", + "methods":[{"name":"getUnsafe","parameterTypes":[] }] +}, +{ + "name":"net.woggioni.rbcs.cli.RemoteBuildCacheServerCli", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"net.woggioni.rbcs.cli.RemoteBuildCacheServerCli$VersionProvider", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"net.woggioni.rbcs.cli.impl.RbcsCommand", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"net.woggioni.rbcs.cli.impl.commands.BenchmarkCommand", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"net.woggioni.rbcs.cli.impl.commands.ClientCommand", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"net.woggioni.rbcs.cli.impl.commands.GetCommand", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"net.woggioni.rbcs.cli.impl.commands.HealthCheckCommand", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"net.woggioni.rbcs.cli.impl.commands.PasswordHashCommand", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"net.woggioni.rbcs.cli.impl.commands.PutCommand", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"net.woggioni.rbcs.cli.impl.commands.ServerCommand", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"net.woggioni.rbcs.cli.impl.converters.ByteSizeConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"net.woggioni.rbcs.cli.impl.converters.DurationConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"net.woggioni.rbcs.cli.impl.converters.OutputStreamConverter", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"net.woggioni.rbcs.client.RemoteBuildCacheClient$sendRequest$1$operationComplete$responseHandler$1", + "methods":[{"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }] +}, +{ + "name":"net.woggioni.rbcs.client.RemoteBuildCacheClient$sendRequest$1$operationComplete$timeoutHandler$1", + "methods":[{"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }] +}, +{ + "name":"net.woggioni.rbcs.server.RemoteBuildCacheServer$HttpChunkContentCompressor", + "methods":[{"name":"write","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object","io.netty.channel.ChannelPromise"] }] +}, +{ + "name":"net.woggioni.rbcs.server.RemoteBuildCacheServer$NettyHttpBasicAuthenticator" +}, +{ + "name":"net.woggioni.rbcs.server.RemoteBuildCacheServer$ServerInitializer" +}, +{ + "name":"net.woggioni.rbcs.server.RemoteBuildCacheServer$ServerInitializer$initChannel$4", + "methods":[{"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }] +}, +{ + "name":"net.woggioni.rbcs.server.auth.AbstractNettyHttpAuthenticator", + "methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }] +}, +{ + "name":"net.woggioni.rbcs.server.cache.FileSystemCacheHandler", + "methods":[{"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }] +}, +{ + "name":"net.woggioni.rbcs.server.cache.InMemoryCacheHandler", + "methods":[{"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }] +}, +{ + "name":"net.woggioni.rbcs.server.exception.ExceptionHandler", + "methods":[{"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }] +}, +{ + "name":"net.woggioni.rbcs.server.handler.CacheContentHandler", + "methods":[{"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }] +}, +{ + "name":"net.woggioni.rbcs.server.handler.MaxRequestSizeHandler", + "methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }] +}, +{ + "name":"net.woggioni.rbcs.server.handler.ServerHandler", + "methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }, {"name":"write","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object","io.netty.channel.ChannelPromise"] }] +}, +{ + "name":"net.woggioni.rbcs.server.handler.TraceHandler", + "methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }] +}, +{ + "name":"net.woggioni.rbcs.server.memcache.MemcacheCacheHandler", + "methods":[{"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }] +}, +{ + "name":"net.woggioni.rbcs.server.memcache.client.MemcacheClient$sendRequest$1$operationComplete$handler$1", + "methods":[{"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }] +}, +{ + "name":"net.woggioni.rbcs.server.throttling.ThrottlingHandler", + "methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }] +}, +{ + "name":"sun.misc.Unsafe", + "fields":[{"name":"theUnsafe"}], + "methods":[{"name":"copyMemory","parameterTypes":["java.lang.Object","long","java.lang.Object","long","long"] }, {"name":"getAndAddLong","parameterTypes":["java.lang.Object","long","long"] }, {"name":"getAndSetObject","parameterTypes":["java.lang.Object","long","java.lang.Object"] }, {"name":"invokeCleaner","parameterTypes":["java.nio.ByteBuffer"] }, {"name":"storeFence","parameterTypes":[] }] +}, +{ + "name":"sun.nio.ch.SelectorImpl", + "fields":[{"name":"publicSelectedKeys"}, {"name":"selectedKeys"}] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.DSA$SHA224withDSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.DSA$SHA256withDSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.JavaKeyStore$JKS", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.MD5", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.NativePRNG$NonBlocking", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.SHA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA2$SHA224", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA2$SHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA5$SHA384", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA5$SHA512", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.X509Factory", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.PSSParameters", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAKeyFactory$Legacy", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAPSSSignature", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSASignature$SHA224withRSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.KeyManagerFactoryImpl$SunX509", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.SSLContextImpl$DefaultSSLContext", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.SSLContextImpl$TLSContext", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.x509.AuthorityInfoAccessExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.AuthorityKeyIdentifierExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.BasicConstraintsExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CRLDistributionPointsExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CertificatePoliciesExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.KeyUsageExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.NetscapeCertTypeExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.PrivateKeyUsageExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectAlternativeNameExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectKeyIdentifierExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +} +] diff --git a/rbcs-cli/native-image/resource-config.json b/rbcs-cli/native-image/resource-config.json new file mode 100644 index 0000000..5ddf972 --- /dev/null +++ b/rbcs-cli/native-image/resource-config.json @@ -0,0 +1,74 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\QMETA-INF/MANIFEST.MF\\E" + }, + { + "pattern": "\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E" + }, + { + "pattern": "\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, + { + "pattern": "\\QMETA-INF/services/java.net.spi.InetAddressResolverProvider\\E" + }, + { + "pattern": "\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" + }, + { + "pattern": "\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E" + }, + { + "pattern": "\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, + { + "pattern": "\\QMETA-INF/services/javax.xml.parsers.DocumentBuilderFactory\\E" + }, + { + "pattern": "\\QMETA-INF/services/javax.xml.parsers.SAXParserFactory\\E" + }, + { + "pattern": "\\QMETA-INF/services/net.woggioni.rbcs.api.CacheProvider\\E" + }, + { + "pattern": "\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" + }, + { + "pattern": "\\Qclasspath:net/woggioni/rbcs/cli/logback.xml\\E" + }, + { + "pattern": "\\Qlogback-test.scmo\\E" + }, + { + "pattern": "\\Qlogback.scmo\\E" + }, + { + "pattern": "\\Qnet/woggioni/rbcs/cli/logback.xml\\E" + }, + { + "pattern": "\\Qnet/woggioni/rbcs/server/rbcs-default.xml\\E" + }, + { + "pattern": "\\Qnet/woggioni/rbcs/server/schema/rbcs.xsd\\E" + }, + { + "pattern": "\\Qnet/woggioni/rbcs/client/schema/rbcs-client.xsd\\E" + }, + { + "pattern": "\\Q/net/woggioni/rbcs/server/memcache/schema/rbcs-memcache.xsd\\E" + }, + { + "pattern": "java.base:\\Qsun/text/resources/LineBreakIteratorData\\E" + } + ] + }, + "bundles": [ + { + "name": "com.sun.org.apache.xerces.internal.impl.xpath.regex.message", + "locales": [ + "" + ] + } + ] +} diff --git a/rbcs-cli/native-image/serialization-config.json b/rbcs-cli/native-image/serialization-config.json new file mode 100644 index 0000000..d82df4f --- /dev/null +++ b/rbcs-cli/native-image/serialization-config.json @@ -0,0 +1,11 @@ +{ + "types":[ + { + "name":"net.woggioni.rbcs.api.CacheValueMetadata" + } + ], + "lambdaCapturingTypes":[ + ], + "proxies":[ + ] +} diff --git a/rbcs-cli/src/graal/kotlin/net/woggioni/rbcs/cli/graal/GraalNativeImageConfiguration.kt b/rbcs-cli/src/graal/kotlin/net/woggioni/rbcs/cli/graal/GraalNativeImageConfiguration.kt new file mode 100644 index 0000000..fb10590 --- /dev/null +++ b/rbcs-cli/src/graal/kotlin/net/woggioni/rbcs/cli/graal/GraalNativeImageConfiguration.kt @@ -0,0 +1,161 @@ +package net.woggioni.rbcs.cli.graal + +import net.woggioni.rbcs.api.Configuration +import net.woggioni.rbcs.api.Configuration.User +import net.woggioni.rbcs.api.Role +import net.woggioni.rbcs.cli.RemoteBuildCacheServerCli +import net.woggioni.rbcs.cli.impl.commands.BenchmarkCommand +import net.woggioni.rbcs.cli.impl.commands.HealthCheckCommand +import net.woggioni.rbcs.client.RemoteBuildCacheClient +import net.woggioni.rbcs.common.HostAndPort +import net.woggioni.rbcs.common.PasswordSecurity.hashPassword +import net.woggioni.rbcs.common.RBCS +import net.woggioni.rbcs.common.Xml +import net.woggioni.rbcs.server.RemoteBuildCacheServer +import net.woggioni.rbcs.server.cache.FileSystemCacheConfiguration +import net.woggioni.rbcs.server.cache.InMemoryCacheConfiguration +import net.woggioni.rbcs.server.configuration.Parser +import net.woggioni.rbcs.server.memcache.MemcacheCacheConfiguration +import java.net.URI +import java.nio.file.Path +import java.time.Duration +import java.time.temporal.ChronoUnit +import java.util.concurrent.ExecutionException +import java.util.zip.Deflater + +object GraalNativeImageConfiguration { + @JvmStatic + fun main(vararg args : String) { + + val serverDoc = RemoteBuildCacheServer.DEFAULT_CONFIGURATION_URL.openStream().use { + Xml.parseXml(RemoteBuildCacheServer.DEFAULT_CONFIGURATION_URL, it) + } + Parser.parse(doc) + + val clientDoc = RemoteBuildCacheClient.Configuration.openStream().use { + Xml.parseXml(RemoteBuildCacheServer.DEFAULT_CONFIGURATION_URL, it) + } + Parser.parse(doc) + + val PASSWORD = "password" + val readersGroup = Configuration.Group("readers", setOf(Role.Reader), null, null) + val writersGroup = Configuration.Group("writers", setOf(Role.Writer), null, null) + + + val users = listOf( + User("user1", hashPassword(PASSWORD), setOf(readersGroup), null), + User("user2", hashPassword(PASSWORD), setOf(writersGroup), null), + User("user3", hashPassword(PASSWORD), setOf(readersGroup, writersGroup), null), + User("", null, setOf(readersGroup), null), + User("user4", hashPassword(PASSWORD), setOf(readersGroup), + Configuration.Quota(1, Duration.of(1, ChronoUnit.DAYS), 0, 1) + ), + User("user5", hashPassword(PASSWORD), setOf(readersGroup), + Configuration.Quota(1, Duration.of(5, ChronoUnit.SECONDS), 0, 1) + ) + ) + + val serverPort = RBCS.getFreePort() + + val caches = listOf( + InMemoryCacheConfiguration( + maxAge = Duration.ofSeconds(3600), + digestAlgorithm = "MD5", + compressionLevel = Deflater.DEFAULT_COMPRESSION, + compressionEnabled = false, + maxSize = 0x1000000, + chunkSize = 0x1000 + ), + FileSystemCacheConfiguration( + Path.of(System.getProperty("java.io.tmpdir")).resolve("rbcs"), + maxAge = Duration.ofSeconds(3600), + digestAlgorithm = "MD5", + compressionLevel = Deflater.DEFAULT_COMPRESSION, + compressionEnabled = false, + chunkSize = 0x1000 + ), + MemcacheCacheConfiguration( + listOf(MemcacheCacheConfiguration.Server( + HostAndPort("127.0.0.1", 11211), + 1000, + 4) + ), + Duration.ofSeconds(60), + "MD5", + null, + 1, + 0x1000 + ) + ) + + for (cache in caches) { + val serverConfiguration = Configuration( + "127.0.0.1", + serverPort, + 100, + null, + Configuration.EventExecutor(true), + Configuration.Connection( + Duration.ofSeconds(10), + Duration.ofSeconds(15), + Duration.ofSeconds(15), + 0x10000, + ), + users.asSequence().map { it.name to it }.toMap(), + sequenceOf(writersGroup, readersGroup).map { it.name to it }.toMap(), + cache, + Configuration.BasicAuthentication(), + null, + ) + + MemcacheCacheConfiguration( + listOf( + MemcacheCacheConfiguration.Server( + HostAndPort("127.0.0.1", 11211), + 1000, + 4 + ) + ), + Duration.ofSeconds(60), + "MD5", + null, + 1, + 0x1000 + ) + + val serverHandle = RemoteBuildCacheServer(serverConfiguration).run() + + + val clientProfile = RemoteBuildCacheClient.Configuration.Profile( + URI.create("http://127.0.0.1:$serverPort/"), + null, + RemoteBuildCacheClient.Configuration.Authentication.BasicAuthenticationCredentials("user3", PASSWORD), + Duration.ofSeconds(3), + 10, + true, + RemoteBuildCacheClient.Configuration.RetryPolicy( + 3, + 1000, + 1.2 + ), + RemoteBuildCacheClient.Configuration.TrustStore(null, null, false, false) + ) + + HealthCheckCommand.run(clientProfile) + + BenchmarkCommand.run( + clientProfile, + 1000, + 0x100, + true + ) + + serverHandle.sendShutdownSignal() + try { + serverHandle.get() + } catch (ee : ExecutionException) { + } + } + RemoteBuildCacheServerCli.main("--help") + } +} \ No newline at end of file diff --git a/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/RemoteBuildCacheServerCli.kt b/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/RemoteBuildCacheServerCli.kt index 6ee25cd..6977526 100644 --- a/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/RemoteBuildCacheServerCli.kt +++ b/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/RemoteBuildCacheServerCli.kt @@ -23,8 +23,13 @@ class RemoteBuildCacheServerCli : RbcsCommand() { class VersionProvider : AbstractVersionProvider() companion object { + private fun setPropertyIfNotPresent(key: String, value: String) { + System.getProperty(key) ?: System.setProperty(key, value) + } @JvmStatic fun main(vararg args: String) { + setPropertyIfNotPresent("logback.configurationFile", "net/woggioni/rbcs/cli/logback.xml") + setPropertyIfNotPresent("io.netty.leakDetectionLevel", "DISABLED") val currentClassLoader = RemoteBuildCacheServerCli::class.java.classLoader Thread.currentThread().contextClassLoader = currentClassLoader if(currentClassLoader.javaClass.name == "net.woggioni.envelope.loader.ModuleClassLoader") { diff --git a/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/BenchmarkCommand.kt b/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/BenchmarkCommand.kt index 47f3ad0..bb54f6d 100644 --- a/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/BenchmarkCommand.kt +++ b/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/BenchmarkCommand.kt @@ -26,8 +26,123 @@ import kotlin.random.Random showDefaultValues = true ) class BenchmarkCommand : RbcsCommand() { - companion object{ + companion object { private val log = createLogger() + + fun run(profile : RemoteBuildCacheClient.Configuration.Profile, + numberOfEntries : Int, + entrySize : Int, + useRandomValue : Boolean, + ) { + val progressThreshold = LongMath.ceilDiv(numberOfEntries.toLong(), 20) + RemoteBuildCacheClient(profile).use { client -> + val entryGenerator = sequence { + val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong()) + while (true) { + val key = JWO.bytesToHex(random.nextBytes(16)) + val value = if (useRandomValue) { + random.nextBytes(entrySize) + } else { + val byteValue = random.nextInt().toByte() + ByteArray(entrySize) { _ -> byteValue } + } + yield(key to value) + } + } + + log.info { + "Starting insertion" + } + val entries = let { + val completionCounter = AtomicLong(0) + val completionQueue = LinkedBlockingQueue>(numberOfEntries) + val start = Instant.now() + val semaphore = Semaphore(profile.maxConnections * 5) + val iterator = entryGenerator.take(numberOfEntries).iterator() + while (completionCounter.get() < numberOfEntries) { + if (iterator.hasNext()) { + val entry = iterator.next() + semaphore.acquire() + val future = + client.put(entry.first, entry.second, CacheValueMetadata(null, null)).thenApply { entry } + future.whenComplete { result, ex -> + if (ex != null) { + log.error(ex.message, ex) + } else { + completionQueue.put(result) + } + semaphore.release() + val completed = completionCounter.incrementAndGet() + if (completed.mod(progressThreshold) == 0L) { + log.debug { + "Inserted $completed / $numberOfEntries" + } + } + } + } else { + Thread.sleep(Duration.of(500, ChronoUnit.MILLIS)) + } + } + + val inserted = completionQueue.toList() + val end = Instant.now() + log.info { + val elapsed = Duration.between(start, end).toMillis() + val opsPerSecond = String.format("%.2f", numberOfEntries.toDouble() / elapsed * 1000) + "Insertion rate: $opsPerSecond ops/s" + } + inserted + } + log.info { + "Inserted ${entries.size} entries" + } + log.info { + "Starting retrieval" + } + if (entries.isNotEmpty()) { + val completionCounter = AtomicLong(0) + val semaphore = Semaphore(profile.maxConnections * 5) + val start = Instant.now() + val it = entries.iterator() + while (completionCounter.get() < entries.size) { + if (it.hasNext()) { + val entry = it.next() + semaphore.acquire() + val future = client.get(entry.first).thenApply { + if (it == null) { + log.error { + "Missing entry for key '${entry.first}'" + } + } else if (!entry.second.contentEquals(it)) { + log.error { + "Retrieved a value different from what was inserted for key '${entry.first}'" + } + } + } + future.whenComplete { _, _ -> + val completed = completionCounter.incrementAndGet() + if (completed.mod(progressThreshold) == 0L) { + log.debug { + "Retrieved $completed / ${entries.size}" + } + } + semaphore.release() + } + } else { + Thread.sleep(Duration.of(500, ChronoUnit.MILLIS)) + } + } + val end = Instant.now() + log.info { + val elapsed = Duration.between(start, end).toMillis() + val opsPerSecond = String.format("%.2f", entries.size.toDouble() / elapsed * 1000) + "Retrieval rate: $opsPerSecond ops/s" + } + } else { + log.error("Skipping retrieval benchmark as it was not possible to insert any entry in the cache") + } + } + } } @CommandLine.Spec @@ -60,113 +175,11 @@ class BenchmarkCommand : RbcsCommand() { clientCommand.configuration.profiles[profileName] ?: throw IllegalArgumentException("Profile $profileName does not exist in configuration") } - val progressThreshold = LongMath.ceilDiv(numberOfEntries.toLong(), 20) - RemoteBuildCacheClient(profile).use { client -> - - val entryGenerator = sequence { - val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong()) - while (true) { - val key = JWO.bytesToHex(random.nextBytes(16)) - val value = if(randomValues) { - random.nextBytes(size) - } else { - val byteValue = random.nextInt().toByte() - ByteArray(size) {_ -> byteValue} - } - yield(key to value) - } - } - - log.info { - "Starting insertion" - } - val entries = let { - val completionCounter = AtomicLong(0) - val completionQueue = LinkedBlockingQueue>(numberOfEntries) - val start = Instant.now() - val semaphore = Semaphore(profile.maxConnections * 5) - val iterator = entryGenerator.take(numberOfEntries).iterator() - while (completionCounter.get() < numberOfEntries) { - if (iterator.hasNext()) { - val entry = iterator.next() - semaphore.acquire() - val future = client.put(entry.first, entry.second, CacheValueMetadata(null, null)).thenApply { entry } - future.whenComplete { result, ex -> - if (ex != null) { - log.error(ex.message, ex) - } else { - completionQueue.put(result) - } - semaphore.release() - val completed = completionCounter.incrementAndGet() - if(completed.mod(progressThreshold) == 0L) { - log.debug { - "Inserted $completed / $numberOfEntries" - } - } - } - } else { - Thread.sleep(Duration.of(500, ChronoUnit.MILLIS)) - } - } - - val inserted = completionQueue.toList() - val end = Instant.now() - log.info { - val elapsed = Duration.between(start, end).toMillis() - val opsPerSecond = String.format("%.2f", numberOfEntries.toDouble() / elapsed * 1000) - "Insertion rate: $opsPerSecond ops/s" - } - inserted - } - log.info { - "Inserted ${entries.size} entries" - } - log.info { - "Starting retrieval" - } - if (entries.isNotEmpty()) { - val completionCounter = AtomicLong(0) - val semaphore = Semaphore(profile.maxConnections * 5) - val start = Instant.now() - val it = entries.iterator() - while (completionCounter.get() < entries.size) { - if (it.hasNext()) { - val entry = it.next() - semaphore.acquire() - val future = client.get(entry.first).thenApply { - if (it == null) { - log.error { - "Missing entry for key '${entry.first}'" - } - } else if (!entry.second.contentEquals(it)) { - log.error { - "Retrieved a value different from what was inserted for key '${entry.first}'" - } - } - } - future.whenComplete { _, _ -> - val completed = completionCounter.incrementAndGet() - if(completed.mod(progressThreshold) == 0L) { - log.debug { - "Retrieved $completed / ${entries.size}" - } - } - semaphore.release() - } - } else { - Thread.sleep(Duration.of(500, ChronoUnit.MILLIS)) - } - } - val end = Instant.now() - log.info { - val elapsed = Duration.between(start, end).toMillis() - val opsPerSecond = String.format("%.2f", entries.size.toDouble() / elapsed * 1000) - "Retrieval rate: $opsPerSecond ops/s" - } - } else { - log.error("Skipping retrieval benchmark as it was not possible to insert any entry in the cache") - } - } + run( + profile, + numberOfEntries, + size, + randomValues + ) } } \ No newline at end of file diff --git a/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/HealthCheckCommand.kt b/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/HealthCheckCommand.kt index 0156f86..4b224f9 100644 --- a/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/HealthCheckCommand.kt +++ b/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/HealthCheckCommand.kt @@ -15,6 +15,27 @@ import kotlin.random.Random class HealthCheckCommand : RbcsCommand() { companion object{ private val log = createLogger() + + fun run(profile : RemoteBuildCacheClient.Configuration.Profile) { + RemoteBuildCacheClient(profile).use { client -> + val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong()) + val nonce = ByteArray(0xa0) + random.nextBytes(nonce) + client.healthCheck(nonce).thenApply { value -> + if(value == null) { + throw IllegalStateException("Empty response from server") + } + val offset = value.size - nonce.size + for(i in 0 until nonce.size) { + val a = nonce[i] + val b = value[offset + i] + if(a != b) { + throw IllegalStateException("Server nonce does not match") + } + } + }.get() + } + } } @CommandLine.Spec @@ -26,23 +47,6 @@ class HealthCheckCommand : RbcsCommand() { clientCommand.configuration.profiles[profileName] ?: throw IllegalArgumentException("Profile $profileName does not exist in configuration") } - RemoteBuildCacheClient(profile).use { client -> - val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong()) - val nonce = ByteArray(0xa0) - random.nextBytes(nonce) - client.healthCheck(nonce).thenApply { value -> - if(value == null) { - throw IllegalStateException("Empty response from server") - } - val offset = value.size - nonce.size - for(i in 0 until nonce.size) { - val a = nonce[i] - val b = value[offset + i] - if(a != b) { - throw IllegalStateException("Server nonce does not match") - } - } - }.get() - } + run(profile) } } \ No newline at end of file diff --git a/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/Client.kt b/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/Client.kt index b125050..521ace1 100644 --- a/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/Client.kt +++ b/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/Client.kt @@ -37,6 +37,7 @@ import io.netty.util.concurrent.Future import io.netty.util.concurrent.GenericFutureListener import net.woggioni.rbcs.api.CacheValueMetadata import net.woggioni.rbcs.client.impl.Parser +import net.woggioni.rbcs.common.RBCS.loadKeystore import net.woggioni.rbcs.common.Xml import net.woggioni.rbcs.common.createLogger import net.woggioni.rbcs.common.debug @@ -54,6 +55,8 @@ import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.AtomicInteger +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager import kotlin.random.Random import io.netty.util.concurrent.Future as NettyFuture @@ -63,7 +66,7 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC } private val group: NioEventLoopGroup - private var sslContext: SslContext + private val sslContext: SslContext private val pool: ChannelPool data class Configuration( @@ -78,6 +81,13 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC data class BasicAuthenticationCredentials(val username: String, val password: String) : Authentication() } + class TrustStore ( + var file: Path?, + var password: String?, + var checkCertificateStatus: Boolean = false, + var verifyServerCertificate: Boolean = true, + ) + class RetryPolicy( val maxAttempts: Int, val initialDelayMillis: Long, @@ -100,6 +110,7 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC val maxConnections: Int, val compressionEnabled: Boolean, val retryPolicy: RetryPolicy?, + val tlsTruststore : TrustStore? ) companion object { @@ -115,10 +126,33 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC group = NioEventLoopGroup() sslContext = SslContextBuilder.forClient().also { builder -> (profile.authentication as? Configuration.Authentication.TlsClientAuthenticationCredentials)?.let { tlsClientAuthenticationCredentials -> - builder.keyManager( - tlsClientAuthenticationCredentials.key, - *tlsClientAuthenticationCredentials.certificateChain - ) + builder.apply { + keyManager( + tlsClientAuthenticationCredentials.key, + *tlsClientAuthenticationCredentials.certificateChain + ) + profile.tlsTruststore?.let { trustStore -> + if(!trustStore.verifyServerCertificate) { + trustManager(object : X509TrustManager { + override fun checkClientTrusted(certChain: Array, p1: String?) { + } + + override fun checkServerTrusted(certChain: Array, p1: String?) { + } + + override fun getAcceptedIssuers() = null + }) + } else { + trustStore.file?.let { + val ts = loadKeystore(it, trustStore.password) + val trustManagerFactory: TrustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + trustManagerFactory.init(ts) + trustManager(trustManagerFactory) + } + } + } + } } }.build() diff --git a/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/impl/Parser.kt b/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/impl/Parser.kt index 2e8a328..6120a22 100644 --- a/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/impl/Parser.kt +++ b/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/impl/Parser.kt @@ -31,6 +31,7 @@ object Parser { var authentication: RemoteBuildCacheClient.Configuration.Authentication? = null var retryPolicy: RemoteBuildCacheClient.Configuration.RetryPolicy? = null var connection : RemoteBuildCacheClient.Configuration.Connection? = null + var trustStore : RemoteBuildCacheClient.Configuration.TrustStore? = null for (gchild in child.asIterable()) { when (gchild.localName) { "tls-client-auth" -> { @@ -108,6 +109,17 @@ object Parser { writeIdleTimeout, ) } + + "tls-trust-store" -> { + val file = gchild.renderAttribute("file") + ?.let(Path::of) + val password = gchild.renderAttribute("password") + val checkCertificateStatus = gchild.renderAttribute("check-certificate-status") + ?.let(String::toBoolean) ?: false + val verifyServerCertificate = gchild.renderAttribute("verify-server-certificate") + ?.let(String::toBoolean) ?: true + trustStore = RemoteBuildCacheClient.Configuration.TrustStore(file, password, checkCertificateStatus, verifyServerCertificate) + } } } val maxConnections = child.renderAttribute("max-connections") @@ -126,7 +138,8 @@ object Parser { connectionTimeout, maxConnections, compressionEnabled, - retryPolicy + retryPolicy, + trustStore ) } } diff --git a/rbcs-client/src/main/resources/net/woggioni/rbcs/client/schema/rbcs-client.xsd b/rbcs-client/src/main/resources/net/woggioni/rbcs/client/schema/rbcs-client.xsd index e6feb53..5623fb5 100644 --- a/rbcs-client/src/main/resources/net/woggioni/rbcs/client/schema/rbcs-client.xsd +++ b/rbcs-client/src/main/resources/net/woggioni/rbcs/client/schema/rbcs-client.xsd @@ -21,6 +21,7 @@ + @@ -57,4 +58,34 @@ + + + + + Path to the trustore file + + + + + + + Trustore file password + + + + + + + Whether or not check the certificate validity using CRL/OCSP + + + + + + + If false, the client will blindly trust the provided server certificate + + + + diff --git a/rbcs-client/src/test/resources/net/woggioni/rbcs/client/test/rbcs-client.xml b/rbcs-client/src/test/resources/net/woggioni/rbcs/client/test/rbcs-client.xml index 96465ee..5828d87 100644 --- a/rbcs-client/src/test/resources/net/woggioni/rbcs/client/test/rbcs-client.xml +++ b/rbcs-client/src/test/resources/net/woggioni/rbcs/client/test/rbcs-client.xml @@ -9,6 +9,8 @@ key-store-password="password" key-alias="woggioni@c962475fa38" key-password="key-password"/> + + diff --git a/rbcs-common/src/main/kotlin/net/woggioni/rbcs/common/RBCS.kt b/rbcs-common/src/main/kotlin/net/woggioni/rbcs/common/RBCS.kt index 201c2f3..a9c534a 100644 --- a/rbcs-common/src/main/kotlin/net/woggioni/rbcs/common/RBCS.kt +++ b/rbcs-common/src/main/kotlin/net/woggioni/rbcs/common/RBCS.kt @@ -1,9 +1,26 @@ package net.woggioni.rbcs.common import net.woggioni.jwo.JWO +import net.woggioni.jwo.Tuple2 +import java.io.IOException +import java.net.InetAddress +import java.net.ServerSocket import java.net.URI import java.net.URL +import java.nio.file.Files +import java.nio.file.Path +import java.security.KeyStore import java.security.MessageDigest +import java.security.cert.CertPathValidator +import java.security.cert.CertPathValidatorException +import java.security.cert.CertificateException +import java.security.cert.CertificateFactory +import java.security.cert.PKIXParameters +import java.security.cert.PKIXRevocationChecker +import java.security.cert.X509Certificate +import java.util.EnumSet +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager object RBCS { fun String.toUrl() : URL = URL.of(URI(this), null) @@ -58,4 +75,86 @@ object RBCS { null } } + + fun getFreePort(): Int { + var count = 0 + while (count < 50) { + try { + ServerSocket(0, 50, InetAddress.getLocalHost()).use { serverSocket -> + val candidate = serverSocket.localPort + if (candidate > 0) { + return candidate + } else { + throw RuntimeException("Got invalid port number: $candidate") + } + } + } catch (ignored: IOException) { + ++count + } + } + throw RuntimeException("Error trying to find an open port") + } + + fun loadKeystore(file: Path, password: String?): KeyStore { + val ext = JWO.splitExtension(file) + .map(Tuple2::get_2) + .orElseThrow { + IllegalArgumentException( + "Keystore file '${file}' must have .jks, .p12, .pfx extension" + ) + } + val keystore = when (ext.substring(1).lowercase()) { + "jks" -> KeyStore.getInstance("JKS") + "p12", "pfx" -> KeyStore.getInstance("PKCS12") + else -> throw IllegalArgumentException( + "Keystore file '${file}' must have .jks, .p12, .pfx extension" + ) + } + Files.newInputStream(file).use { + keystore.load(it, password?.let(String::toCharArray)) + } + return keystore + } + + fun getTrustManager(trustStore: KeyStore?, certificateRevocationEnabled: Boolean): X509TrustManager { + return if (trustStore != null) { + val certificateFactory = CertificateFactory.getInstance("X.509") + val validator = CertPathValidator.getInstance("PKIX").apply { + val rc = revocationChecker as PKIXRevocationChecker + rc.options = EnumSet.of( + PKIXRevocationChecker.Option.NO_FALLBACK + ) + } + val params = PKIXParameters(trustStore).apply { + isRevocationEnabled = certificateRevocationEnabled + } + object : X509TrustManager { + override fun checkClientTrusted(chain: Array, authType: String) { + val clientCertificateChain = certificateFactory.generateCertPath(chain.toList()) + try { + validator.validate(clientCertificateChain, params) + } catch (ex: CertPathValidatorException) { + throw CertificateException(ex) + } + } + + override fun checkServerTrusted(chain: Array, authType: String) { + throw NotImplementedError() + } + + private val acceptedIssuers = trustStore.aliases().asSequence() + .filter(trustStore::isCertificateEntry) + .map(trustStore::getCertificate) + .map { it as X509Certificate } + .toList() + .toTypedArray() + + override fun getAcceptedIssuers() = acceptedIssuers + } + } else { + val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + trustManagerFactory.trustManagers.asSequence().filter { it is X509TrustManager } + .single() as X509TrustManager + } + } } \ No newline at end of file diff --git a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/RemoteBuildCacheServer.kt b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/RemoteBuildCacheServer.kt index 88ae5e3..e80ab99 100644 --- a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/RemoteBuildCacheServer.kt +++ b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/RemoteBuildCacheServer.kt @@ -35,13 +35,13 @@ import io.netty.handler.timeout.IdleStateHandler import io.netty.util.AttributeKey import io.netty.util.concurrent.DefaultEventExecutorGroup import io.netty.util.concurrent.EventExecutorGroup -import net.woggioni.jwo.JWO -import net.woggioni.jwo.Tuple2 import net.woggioni.rbcs.api.AsyncCloseable import net.woggioni.rbcs.api.Configuration import net.woggioni.rbcs.api.exception.ConfigurationException import net.woggioni.rbcs.common.PasswordSecurity.decodePasswordHash import net.woggioni.rbcs.common.PasswordSecurity.hashPassword +import net.woggioni.rbcs.common.RBCS.getTrustManager +import net.woggioni.rbcs.common.RBCS.loadKeystore import net.woggioni.rbcs.common.RBCS.toUrl import net.woggioni.rbcs.common.Xml import net.woggioni.rbcs.common.createLogger @@ -49,7 +49,6 @@ import net.woggioni.rbcs.common.debug import net.woggioni.rbcs.common.info import net.woggioni.rbcs.server.auth.AbstractNettyHttpAuthenticator import net.woggioni.rbcs.server.auth.Authorizer -import net.woggioni.rbcs.server.auth.ClientCertificateValidator import net.woggioni.rbcs.server.auth.RoleAuthorizer import net.woggioni.rbcs.server.configuration.Parser import net.woggioni.rbcs.server.configuration.Serializer @@ -63,7 +62,6 @@ import java.io.OutputStream import java.net.InetSocketAddress import java.nio.file.Files import java.nio.file.Path -import java.security.KeyStore import java.security.PrivateKey import java.security.cert.X509Certificate import java.time.Duration @@ -230,7 +228,7 @@ class RemoteBuildCacheServer(private val cfg: Configuration) { val clientAuth = tls.trustStore?.let { trustStore -> val ts = loadKeystore(trustStore.file, trustStore.password) trustManager( - ClientCertificateValidator.getTrustManager(ts, trustStore.isCheckCertificateStatus) + getTrustManager(ts, trustStore.isCheckCertificateStatus) ) if (trustStore.isRequireClientCertificate) ClientAuth.REQUIRE else ClientAuth.OPTIONAL @@ -240,27 +238,6 @@ class RemoteBuildCacheServer(private val cfg: Configuration) { } } - fun loadKeystore(file: Path, password: String?): KeyStore { - val ext = JWO.splitExtension(file) - .map(Tuple2::get_2) - .orElseThrow { - IllegalArgumentException( - "Keystore file '${file}' must have .jks, .p12, .pfx extension" - ) - } - val keystore = when (ext.substring(1).lowercase()) { - "jks" -> KeyStore.getInstance("JKS") - "p12", "pfx" -> KeyStore.getInstance("PKCS12") - else -> throw IllegalArgumentException( - "Keystore file '${file}' must have .jks, .p12, .pfx extension" - ) - } - Files.newInputStream(file).use { - keystore.load(it, password?.let(String::toCharArray)) - } - return keystore - } - private val log = createLogger() } diff --git a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/auth/ClientCertificateValidator.kt b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/auth/ClientCertificateValidator.kt deleted file mode 100644 index fe24d17..0000000 --- a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/auth/ClientCertificateValidator.kt +++ /dev/null @@ -1,90 +0,0 @@ -package net.woggioni.rbcs.server.auth - -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.ChannelInboundHandlerAdapter -import io.netty.handler.ssl.SslHandler -import io.netty.handler.ssl.SslHandshakeCompletionEvent -import java.security.KeyStore -import java.security.cert.CertPathValidator -import java.security.cert.CertPathValidatorException -import java.security.cert.CertificateException -import java.security.cert.CertificateFactory -import java.security.cert.PKIXParameters -import java.security.cert.PKIXRevocationChecker -import java.security.cert.X509Certificate -import java.util.EnumSet -import javax.net.ssl.SSLSession -import javax.net.ssl.TrustManagerFactory -import javax.net.ssl.X509TrustManager - - -class ClientCertificateValidator private constructor( - private val sslHandler: SslHandler, - private val x509TrustManager: X509TrustManager -) : ChannelInboundHandlerAdapter() { - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - if (evt is SslHandshakeCompletionEvent) { - if (evt.isSuccess) { - val session: SSLSession = sslHandler.engine().session - val clientCertificateChain = session.peerCertificates as Array - val authType: String = clientCertificateChain[0].publicKey.algorithm - x509TrustManager.checkClientTrusted(clientCertificateChain, authType) - } else { - // Handle the failure, for example by closing the channel. - } - } - super.userEventTriggered(ctx, evt) - } - - companion object { - fun getTrustManager(trustStore: KeyStore?, certificateRevocationEnabled: Boolean): X509TrustManager { - return if (trustStore != null) { - val certificateFactory = CertificateFactory.getInstance("X.509") - val validator = CertPathValidator.getInstance("PKIX").apply { - val rc = revocationChecker as PKIXRevocationChecker - rc.options = EnumSet.of( - PKIXRevocationChecker.Option.NO_FALLBACK - ) - } - val params = PKIXParameters(trustStore).apply { - isRevocationEnabled = certificateRevocationEnabled - } - object : X509TrustManager { - override fun checkClientTrusted(chain: Array, authType: String) { - val clientCertificateChain = certificateFactory.generateCertPath(chain.toList()) - try { - validator.validate(clientCertificateChain, params) - } catch (ex: CertPathValidatorException) { - throw CertificateException(ex) - } - } - - override fun checkServerTrusted(chain: Array, authType: String) { - throw NotImplementedError() - } - - private val acceptedIssuers = trustStore.aliases().asSequence() - .filter(trustStore::isCertificateEntry) - .map(trustStore::getCertificate) - .map { it as X509Certificate } - .toList() - .toTypedArray() - - override fun getAcceptedIssuers() = acceptedIssuers - } - } else { - val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) - trustManagerFactory.trustManagers.asSequence().filter { it is X509TrustManager } - .single() as X509TrustManager - } - } - - fun of( - sslHandler: SslHandler, - trustStore: KeyStore?, - certificateRevocationEnabled: Boolean - ): ClientCertificateValidator { - return ClientCertificateValidator(sslHandler, getTrustManager(trustStore, certificateRevocationEnabled)) - } - } -} \ No newline at end of file diff --git a/rbcs-server/src/test/java/net/woggioni/rbcs/server/test/utils/NetworkUtils.java b/rbcs-server/src/test/java/net/woggioni/rbcs/server/test/utils/NetworkUtils.java deleted file mode 100644 index 520dcb7..0000000 --- a/rbcs-server/src/test/java/net/woggioni/rbcs/server/test/utils/NetworkUtils.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.woggioni.rbcs.server.test.utils; - -import net.woggioni.jwo.JWO; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.ServerSocket; - -public class NetworkUtils { - - private static final int MAX_ATTEMPTS = 50; - - public static int getFreePort() { - int count = 0; - while(count < MAX_ATTEMPTS) { - try (ServerSocket serverSocket = new ServerSocket(0, 50, InetAddress.getLocalHost())) { - final var candidate = serverSocket.getLocalPort(); - if (candidate > 0) { - return candidate; - } else { - JWO.newThrowable(RuntimeException.class, "Got invalid port number: %d", candidate); - throw new RuntimeException("Error trying to find an open port"); - } - } catch (IOException ignored) { - ++count; - } - } - throw new RuntimeException("Error trying to find an open port"); - } -} diff --git a/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/AbstractBasicAuthServerTest.kt b/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/AbstractBasicAuthServerTest.kt index 6a599f3..2ebc8c2 100644 --- a/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/AbstractBasicAuthServerTest.kt +++ b/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/AbstractBasicAuthServerTest.kt @@ -2,10 +2,10 @@ package net.woggioni.rbcs.server.test import net.woggioni.rbcs.api.Configuration import net.woggioni.rbcs.api.Role +import net.woggioni.rbcs.common.RBCS.getFreePort import net.woggioni.rbcs.common.Xml import net.woggioni.rbcs.server.cache.FileSystemCacheConfiguration import net.woggioni.rbcs.server.configuration.Serializer -import net.woggioni.rbcs.server.test.utils.NetworkUtils import java.net.URI import java.net.http.HttpRequest import java.nio.charset.StandardCharsets @@ -33,7 +33,7 @@ abstract class AbstractBasicAuthServerTest : AbstractServerTest() { this.cacheDir = testDir.resolve("cache") cfg = Configuration.of( "127.0.0.1", - NetworkUtils.getFreePort(), + getFreePort(), 50, serverPath, Configuration.EventExecutor(false), diff --git a/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/AbstractTlsServerTest.kt b/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/AbstractTlsServerTest.kt index 0f3462f..5c5d6c1 100644 --- a/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/AbstractTlsServerTest.kt +++ b/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/AbstractTlsServerTest.kt @@ -2,12 +2,12 @@ package net.woggioni.rbcs.server.test import net.woggioni.rbcs.api.Configuration import net.woggioni.rbcs.api.Role +import net.woggioni.rbcs.common.RBCS.getFreePort import net.woggioni.rbcs.common.Xml import net.woggioni.rbcs.server.cache.FileSystemCacheConfiguration import net.woggioni.rbcs.server.configuration.Serializer import net.woggioni.rbcs.server.test.utils.CertificateUtils import net.woggioni.rbcs.server.test.utils.CertificateUtils.X509Credentials -import net.woggioni.rbcs.server.test.utils.NetworkUtils import org.bouncycastle.asn1.x500.X500Name import java.net.URI import java.net.http.HttpClient @@ -138,7 +138,7 @@ abstract class AbstractTlsServerTest : AbstractServerTest() { createKeyStoreAndTrustStore() cfg = Configuration( "127.0.0.1", - NetworkUtils.getFreePort(), + getFreePort(), 100, serverPath, Configuration.EventExecutor(false), diff --git a/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/NoAuthServerTest.kt b/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/NoAuthServerTest.kt index 71cc801..5d222e1 100644 --- a/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/NoAuthServerTest.kt +++ b/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/NoAuthServerTest.kt @@ -2,10 +2,10 @@ package net.woggioni.rbcs.server.test import io.netty.handler.codec.http.HttpResponseStatus import net.woggioni.rbcs.api.Configuration +import net.woggioni.rbcs.common.RBCS.getFreePort import net.woggioni.rbcs.common.Xml import net.woggioni.rbcs.server.cache.InMemoryCacheConfiguration import net.woggioni.rbcs.server.configuration.Serializer -import net.woggioni.rbcs.server.test.utils.NetworkUtils import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Order import org.junit.jupiter.api.Test @@ -33,7 +33,7 @@ class NoAuthServerTest : AbstractServerTest() { this.cacheDir = testDir.resolve("cache") cfg = Configuration( "127.0.0.1", - NetworkUtils.getFreePort(), + getFreePort(), 100, serverPath, Configuration.EventExecutor(false),