diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml new file mode 100644 index 0000000..366acc3 --- /dev/null +++ b/.github/workflows/code_quality.yml @@ -0,0 +1,28 @@ +name: Qodana +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + - 'feature/*' + +jobs: + qodana: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit + fetch-depth: 0 # a full history is required for pull request analysis + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2025.3 + with: + pr-mode: false + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} + QODANA_ENDPOINT: 'https://qodana.cloud' \ No newline at end of file diff --git a/.github/workflows/security-and-static-analysis.yml b/.github/workflows/security-and-static-analysis.yml index 4cb0876..e1dcd68 100644 --- a/.github/workflows/security-and-static-analysis.yml +++ b/.github/workflows/security-and-static-analysis.yml @@ -2,7 +2,6 @@ name: Security & Static Analysis on: push: - branches: [ "master", "develop" ] pull_request: branches: [ "master", "develop" ] schedule: @@ -24,6 +23,11 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + # 서브모듈을 재귀호출 + submodules: recursive + # 비공개 저장소의 경우 토큰 설정 필요 + # token: ${{ secrets.GITHUB_TOKEN }} # Rust 툴체인 설치 (clippy, rustfmt 포함) - name: Install Rust toolchain @@ -47,9 +51,15 @@ jobs: run: cargo fmt -- --check # 정적 분석 및 린트 (Clippy) - 보안 취약점 및 잠재적 버그 탐지 + # 벤치마킹 모듈은 검사 강도 하향 가능 - name: Run Clippy (Linting) working-directory: ./${{ matrix.project-path }} - run: cargo clippy --all-targets --all-features -- -D warnings + run: | + if [ "${{ matrix.project-path }}" == "native-benchmark" ]; then + cargo clippy --all-targets --all-features + else + cargo clippy --all-targets --all-features -- -D warnings + fi # 의존성 보안 검사 (Audit) - RustSec 데이터베이스 기반 - name: Run Security Audit @@ -75,6 +85,9 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + # Java 빌드 시에도 entlib-native 참조 가능성을 위해 서브모듈 체크아웃 + submodules: recursive # Java 25 환경 설정 (프로젝트 요구사항) - name: Set up JDK 25 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9541551 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "entlib-native"] + path = entlib-native + url = https://github.com/Quant-Off/entlib-native.git diff --git a/build.gradle.kts b/build.gradle.kts index 132bf4c..6ac1a06 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,25 +9,59 @@ plugins { id("me.champeau.jmh") version "0.7.3" } +val commonGroupId = project.findProperty("commonGroupId") as? String ?: "space.qu4nt" +val quantPublicDir = project.findProperty("quantPublicDir") as? String + ?: layout.buildDirectory.dir("dummy-resources").get().asFile.absolutePath + val lombokVersion = "org.projectlombok:lombok:1.18.42" -val quantPublicDir: String by project -val commonGroupId: String by project val bouncyCastleVer = "1.83" -val entLibVersion = "1.1.2-Alpha2" +val entLibVersion = "1.1.2-Alpha3" group = commonGroupId version = entLibVersion sourceSets { main { + java { + srcDirs("src/main/java") + } resources { - srcDirs += File("${quantPublicDir}/entanglementlib") + srcDirs("src/main/resources") + + if (quantPublicDir.isNotEmpty()) { + val extraResourceDir = File("${quantPublicDir}/entanglementlib") + if (extraResourceDir.exists()) { + srcDir(extraResourceDir) + } else { + logger.warn("Warning: External resource directory not found: $extraResourceDir. Skipping...") + } + } } } + test { + java { + srcDirs("src/test/java") + } + resources { + srcDirs("src/test/resources") + + if (quantPublicDir.isNotEmpty()) { + val extraTestResourceDir = File("${quantPublicDir}/entanglementlib-test") + if (extraTestResourceDir.exists()) { + srcDir(extraTestResourceDir) + } + } + } + } + + named("jmh") { + java { + srcDirs("src/benchmark/java") + } resources { - srcDirs += File("${quantPublicDir}/entanglementlib-test") + srcDirs("src/benchmark/resources") } } } @@ -96,6 +130,7 @@ dependencies { testImplementation("org.openjdk.jmh:jmh-core:1.37") // Source: https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess testImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.37") + jmhAnnotationProcessor(lombokVersion) } tasks.test { @@ -111,6 +146,10 @@ tasks.jar { } } +tasks.withType { + duplicatesStrategy = DuplicatesStrategy.INCLUDE +} + mavenPublishing { signAllPublications() @@ -160,7 +199,7 @@ jmh { // 테스트 벤치마킹 클래스 등록 includeTests.set(true) - includes.set(listOf(".*Benchmark")) + includes.set(listOf(".*_JMHBenchmark")) // 벤치마크 실행 시 필요한 jvm 인자 중앙 제어 jvmArgs.set(listOf( diff --git a/entlib-native b/entlib-native index a2ca93a..988dd79 160000 --- a/entlib-native +++ b/entlib-native @@ -1 +1 @@ -Subproject commit a2ca93a6ef616f5ac97143baa8c4f4ae989ff34f +Subproject commit 988dd79552f2afddd47dda3404f7db106639e569 diff --git a/native-benchmark/src/lib.rs b/native-benchmark/src/lib.rs index bd79178..d6aceea 100644 --- a/native-benchmark/src/lib.rs +++ b/native-benchmark/src/lib.rs @@ -1,7 +1,7 @@ use jni::sys::*; use std::arch::x86_64::{ __m256i, _mm256_add_epi32, _mm256_and_si256, _mm256_cmpgt_epi32, _mm256_loadu_si256, - _mm256_set1_epi32, _mm256_set1_epi8, _mm256_storeu_si256, _mm256_sub_epi32, _mm256_xor_si256, + _mm256_set1_epi8, _mm256_set1_epi32, _mm256_storeu_si256, _mm256_sub_epi32, _mm256_xor_si256, }; use std::slice; @@ -119,7 +119,8 @@ pub unsafe extern "C" fn bless_poly_modular_add( /// jni 단순 ++++ #[unsafe(no_mangle)] -#[unsafe(export_name = "Java_space_qu4nt_entanglementlib_benchmarks_NativeCallBenchmark_jni_1add_1numbers")] +// TODO: 사실 이 JNI 테스트가 작동되는지 모르겠음. 아마 안 될거임. 클래스명까지 "_1"로 언더스코어를 포함할 수 없는걸로 알고 있긴 함. +#[unsafe(export_name = "Java_space_qu4nt_entanglementlib_benchmark_NativeCall_1JMHBenchmark_jni_1add_1numbers")] pub extern "C" fn jni_add_numbers_impl( mut _env: JNIEnv, _class: jclass, diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 0000000..8bc8ec1 --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,10 @@ +#################################################################################################################### +# WARNING: Do not store sensitive information in this file, as its contents will be included in the Qodana report. # +#################################################################################################################### + +version: "1.0" +linter: jetbrains/qodana-jvm-community:2025.3 +profile: + name: qodana.recommended +include: + - name: CheckDependencyLicenses \ No newline at end of file diff --git a/src/test/java/space/qu4nt/entanglementlib/benchmarks/NativeAVXCallBenchmark.java b/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java similarity index 98% rename from src/test/java/space/qu4nt/entanglementlib/benchmarks/NativeAVXCallBenchmark.java rename to src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java index 2999ea2..6a0bab6 100644 --- a/src/test/java/space/qu4nt/entanglementlib/benchmarks/NativeAVXCallBenchmark.java +++ b/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java @@ -1,10 +1,8 @@ -/* +package space.qu4nt.entanglementlib.benchmark;/* * Copyright © 2025-2026 Quant. * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.benchmarks; - import org.openjdk.jmh.annotations.*; import java.lang.foreign.*; @@ -23,7 +21,7 @@ }) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) -public class NativeAVXCallBenchmark { +public class NativeAVXCall_JMHBenchmark { private static final Linker LINKER = Linker.nativeLinker(); diff --git a/src/test/java/space/qu4nt/entanglementlib/benchmarks/NativeCallBenchmark.java b/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java similarity index 98% rename from src/test/java/space/qu4nt/entanglementlib/benchmarks/NativeCallBenchmark.java rename to src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java index 7580fac..82f5979 100644 --- a/src/test/java/space/qu4nt/entanglementlib/benchmarks/NativeCallBenchmark.java +++ b/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java @@ -1,10 +1,8 @@ -/* +package space.qu4nt.entanglementlib.benchmark;/* * Copyright © 2025-2026 Quant. * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.benchmarks; - import org.openjdk.jmh.annotations.*; import java.lang.foreign.*; @@ -19,7 +17,7 @@ @Fork(value = 1, jvmArgs = {"--enable-native-access=ALL-UNNAMED", "--enable-preview"}) @Warmup(iterations = 3, time = 1) @Measurement(iterations = 5, time = 1) -public class NativeCallBenchmark { +public class NativeCall_JMHBenchmark { private static final Linker LINKER = Linker.nativeLinker(); diff --git a/src/benchmark/resources/logback.xml b/src/benchmark/resources/logback.xml new file mode 100644 index 0000000..5f5b457 --- /dev/null +++ b/src/benchmark/resources/logback.xml @@ -0,0 +1,49 @@ + + + + + + + + + + ${LOG_PATTERN_CONSOLE} + + + + + ${LOG_DIR}/benchmark.log + + ${LOG_DIR}/benchmark-%d{yyyy-MM-dd}.log + 30 + + + ${LOG_PATTERN_FILE} + + + + + + 512 + 0 false + + + + 1024 + 0 + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/EntanglementLibEnvs.java b/src/main/java/space/qu4nt/entanglementlib/EntanglementLibEnvs.java index 7c73113..6b303f3 100644 --- a/src/main/java/space/qu4nt/entanglementlib/EntanglementLibEnvs.java +++ b/src/main/java/space/qu4nt/entanglementlib/EntanglementLibEnvs.java @@ -28,7 +28,7 @@ sealed class EntanglementLibEnvs extends WrapEnv permits InternalFactory { static { entanglementPublicDir = new EntanglementLibEnvs("ENTANGLEMENT_PUBLIC_DIR" , true); entanglementHomeDir = new EntanglementLibEnvs("ENTANGLEMENT_HOME_DIR" , true); - entLibNativeDir = new EntanglementLibEnvs("ENTLIB_NATIVE_DIR" , false, "native"); + entLibNativeDir = new EntanglementLibEnvs("ENTLIB_NATIVE_BIN" , false, "native"); } EntanglementLibEnvs() { diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Session.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Session.java index d363220..03c813f 100644 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Session.java +++ b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Session.java @@ -13,6 +13,7 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; @@ -94,7 +95,8 @@ private Session(String sessionId, SessionConfig config) { this.participants = new ConcurrentHashMap<>(); this.participantsByRole = new ConcurrentHashMap<>(); this.participantsLock = new ReentrantReadWriteLock(); - this.eventListeners = Collections.synchronizedList(new ArrayList<>()); + // 순회 시 락이 필요 없는 CopyOnWrite 병렬 컬렉션 사용 변경 + this.eventListeners = new CopyOnWriteArrayList<>(); log.debug("세션 생성됨: {}", sessionId); } @@ -318,21 +320,27 @@ public void close() { if (state.compareAndSet(current, SessionState.CLOSING)) { try { - // 모든 참여자에게 종료 알림 notifyListeners(listener -> listener.onSessionClosing(this)); - // 참여자 정리 + // 방어적 복사 로컬 리스트 + List participantsToClose; + + // lock scope 최소화 -> 내부 컬렉션 상태만 조작 participantsLock.writeLock().lock(); try { - participants.values().forEach(p -> - p.transitionState(ConnectionState.CLOSING)); + // 스냅샷 생성 + participantsToClose = new ArrayList<>(participants.values()); participants.clear(); participantsByRole.clear(); } finally { participantsLock.writeLock().unlock(); } - // 보안 컨텍스트 정리 + // 락이 해제된 안전한 상태에서 외부 메소드(alien method) 호출 + participantsToClose.forEach(p -> + p.transitionState(ConnectionState.CLOSING) + ); + if (sessionSecurityContext != null) { sessionSecurityContext.clear(); }