native: 优化 build script 及 actions,修复 ECDH 相关资源释放问题 (#2110)

* build: fix build on Windows

* test: fix ContentEqualsTest on native

* build: allow disabling targets with property

* fix: free ECDH-related resources properly on native
avoid memory leaking (usually on *nix) or crash (usually on Windows)

Signed-off-by: AdoptOSS <adoptoss@outlook.com>

* build(workflow): remove invalid options

* fix(styling): `mirai.target` property

* build(workflow): try to limit memory usage during mirai-console:tools:gradle-plugin:integTest

* enhance(test): use buildList

* build(workflow): retry

Co-authored-by: ArcticLampyrid <arcticlampyrid@outlook.com>
This commit is contained in:
AdoptOSS 2022-06-25 10:40:29 +08:00 committed by Him188
parent afedbdad7b
commit 7325c1f7e2
12 changed files with 201 additions and 166 deletions

View File

@ -13,7 +13,7 @@ jobs:
- windows-2022
- macos-12
env:
gradleArgs: --scan "-Pkotlin.compiler.execution.strategy=in-process" "-Dorg.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 --illegal-access=permit -Dkotlin.daemon.jvm.options=--illegal-access=permit --add-opens java.base/java.util=ALL-UNNAMED"
gradleArgs: --scan "-Dmirai.target=jvm;android;!other" "-Pkotlin.compiler.execution.strategy=in-process" "-Dorg.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8"
isMac: ${{ startsWith(matrix.os, 'macos') }}
isWindows: ${{ startsWith(matrix.os, 'windows') }}
isUbuntu: ${{ startsWith(matrix.os, 'ubuntu') }}
@ -34,6 +34,13 @@ jobs:
- if: ${{ env.isUnix == 'true' }}
run: chmod -R 777 *
- if: ${{ env.isWindows == 'true' }}
name: Setup Memory Environment on Windows
run: >
wmic pagefileset where name="D:\\pagefile.sys" set InitialSize=1024,MaximumSize=9216 &
net stop mongodb
shell: cmd
continue-on-error: true
- name: Clean and download dependencies
run: ./gradlew clean ${{ env.gradleArgs }}
@ -295,22 +302,24 @@ jobs:
- macos-11
include:
- os: windows-2022
testTaskName: mingwX64Test
targetName: mingwX64
- os: ubuntu-20.04
testTaskName: linuxX64Test
targetName: linuxX64
- os: ubuntu-18.04
testTaskName: linuxX64Test
targetName: linuxX64
- os: macos-12
testTaskName: macosX64Test
targetName: macosX64
- os: macos-11
testTaskName: macosX64Test
targetName: macosX64
env:
gradleArgs: --scan "-Pkotlin.compiler.execution.strategy=in-process" "-Dorg.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 --illegal-access=permit -Dkotlin.daemon.jvm.options=--illegal-access=permit --add-opens java.base/java.util=ALL-UNNAMED"
# FIXME there must be two or more targets, or we'll get error on `@OptionalExpectation`
# > Declaration annotated with '@OptionalExpectation' can only be used in common module sources
gradleArgs: --scan "-Dmirai.target=jvm;${{ matrix.targetName }};!other" "-Pkotlin.compiler.execution.strategy=in-process" "-Dorg.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8"
isMac: ${{ startsWith(matrix.os, 'macos') }}
isWindows: ${{ startsWith(matrix.os, 'windows') }}
isUbuntu: ${{ startsWith(matrix.os, 'ubuntu') }}
isUnix: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') }}
VCPKG_DEFAULT_BINARY_CACHE: ${{ startsWith(matrix.os, 'windows') && 'C:\\vcpkg\\binary_cache' || '/usr/local/share/vcpkg/binary_cache' }}
VCPKG_DEFAULT_BINARY_CACHE: ${{ startsWith(matrix.os, 'windows') && 'C:\vcpkg\binary_cache' || '/usr/local/share/vcpkg/binary_cache' }}
steps:
- uses: actions/checkout@v2
with:
@ -373,12 +382,6 @@ jobs:
shell: cmd
continue-on-error: true
- if: ${{ env.isWindows == 'true' }}
name: Setup C++ Toolchain Environment on Windows
run: |
'echo "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.32.31326\bin\Hostx64\x64" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append'
'echo "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\Llvm\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append'
- if: ${{ env.isWindows == 'true' }}
name: Install OpenSSL & cURL on Windows
run: |
@ -393,10 +396,10 @@ jobs:
run: ./gradlew clean ${{ env.gradleArgs }}
- name: "Test mirai-core-utils for ${{ matrix.os }}"
run: ./gradlew :mirai-core-utils:${{ matrix.testTaskName }} ${{ env.gradleArgs }}
run: ./gradlew :mirai-core-utils:${{ matrix.targetName }}Test ${{ env.gradleArgs }}
- name: "Test mirai-core-api for ${{ matrix.os }}"
run: ./gradlew :mirai-core-api:${{ matrix.testTaskName }} ${{ env.gradleArgs }}
run: ./gradlew :mirai-core-api:${{ matrix.targetName }}Test ${{ env.gradleArgs }}
- name: "Test mirai-core for ${{ matrix.os }}"
run: ./gradlew :mirai-core:${{ matrix.testTaskName }} ${{ env.gradleArgs }}
run: ./gradlew :mirai-core:${{ matrix.targetName }}Test ${{ env.gradleArgs }}

View File

@ -21,7 +21,7 @@ import java.io.File
*/
object BinaryCompatibilityConfigurator {
fun Project.configureBinaryValidators(vararg targetNames: String) {
fun Project.configureBinaryValidators(targetNames: Set<String>) {
targetNames.forEach { configureBinaryValidator(it) }
}

View File

@ -30,10 +30,6 @@ private val miraiPlatform = Attribute.of(
)
val IDEA_ACTIVE = System.getProperty("idea.active") == "true" && System.getProperty("publication.test") != "true"
val WINDOWS_TARGET_ENABLED = System.getProperty("mirai.windows.target") != "false"
val NATIVE_ENABLED = System.getProperty("mirai.enable.native", "true").toBoolean()
val ANDROID_ENABLED = System.getProperty("mirai.enable.android", "true").toBoolean()
val OS_NAME = System.getProperty("os.name").toLowerCase()
@ -50,7 +46,7 @@ sealed class HostKind(
val targetName: String
) {
object LINUX : HostKind("linuxX64")
object WINDOWS : HostKind("windowsX64")
object WINDOWS : HostKind("mingwX64")
abstract class MACOS(targetName: String) : HostKind(targetName)
@ -68,6 +64,7 @@ val HOST_KIND by lazy {
HostKind.MACOS_X64
}
}
else -> HostKind.LINUX
}
}
@ -76,8 +73,31 @@ enum class HostArch {
X86, X64, ARM32, ARM64
}
/// eg. "!a;!b" means to enable all targets but a or b
/// eg. "a;b;!other" means to disable all targets but a or b
val ENABLED_TARGETS by lazy {
System.getProperty(
"mirai.target",
if (IDEA_ACTIVE)
"jvm;android;${HOST_KIND.targetName};!other"
else
""
).split(';').toSet()
}
fun isTargetEnabled(name: String): Boolean {
return when {
name in ENABLED_TARGETS -> true
"!$name" in ENABLED_TARGETS -> false
else -> "!other" !in ENABLED_TARGETS
}
}
fun Set<String>.filterTargets() =
this.filter { isTargetEnabled(it) }.toSet()
val MAC_TARGETS: Set<String> by lazy {
if (!IDEA_ACTIVE) setOf(
setOf(
// "watchosX86",
"macosX64",
"macosArm64",
@ -98,23 +118,22 @@ val MAC_TARGETS: Set<String> by lazy {
// "tvosX64",
// "tvosArm64",
// "tvosSimulatorArm64",
) else setOf(
// IDEA active, reduce load
HOST_KIND.targetName
)
).filterTargets()
}
val WIN_TARGETS = if (WINDOWS_TARGET_ENABLED) setOf("mingwX64") else emptySet()
val WIN_TARGETS by lazy { setOf("mingwX64").filterTargets() }
val LINUX_TARGETS = setOf("linuxX64")
val LINUX_TARGETS by lazy { setOf("linuxX64").filterTargets() }
val UNIX_LIKE_TARGETS by lazy { LINUX_TARGETS + MAC_TARGETS }
val NATIVE_TARGETS by lazy { UNIX_LIKE_TARGETS + WIN_TARGETS }
fun Project.configureJvmTargetsHierarchical() {
extensions.getByType(KotlinMultiplatformExtension::class.java).apply {
val commonMain by sourceSets.getting
val commonTest by sourceSets.getting
if (IDEA_ACTIVE) {
jvm("jvmBase") { // dummy target for resolution, not published
compilations.all {
@ -125,19 +144,42 @@ fun Project.configureJvmTargetsHierarchical() {
}
}
if (isAndroidSDKAvailable && ANDROID_ENABLED) {
jvm("android") {
attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm)
if (IDEA_ACTIVE) {
attributes.attribute(miraiPlatform, "android") // avoid resolution
}
val jvmBaseMain by lazy {
sourceSets.maybeCreate("jvmBaseMain").apply {
dependsOn(commonMain)
}
}
val jvmBaseTest by lazy {
sourceSets.maybeCreate("jvmBaseTest").apply {
dependsOn(commonTest)
}
} else {
printAndroidNotInstalled()
}
jvm("jvm") {
if (isTargetEnabled("android")) {
if (isAndroidSDKAvailable) {
jvm("android") {
attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm)
if (IDEA_ACTIVE) {
attributes.attribute(miraiPlatform, "android") // avoid resolution
}
}
val androidMain by sourceSets.getting
val androidTest by sourceSets.getting
androidMain.dependsOn(jvmBaseMain)
androidTest.dependsOn(jvmBaseTest)
} else {
printAndroidNotInstalled()
}
}
if (isTargetEnabled("jvm")) {
jvm("jvm") {
}
val jvmMain by sourceSets.getting
val jvmTest by sourceSets.getting
jvmMain.dependsOn(jvmBaseMain)
jvmTest.dependsOn(jvmBaseTest)
}
}
}
@ -188,21 +230,39 @@ fun KotlinMultiplatformExtension.configureNativeTargetsHierarchical(
val commonMain by sourceSets.getting
val commonTest by sourceSets.getting
val jvmBaseMain = this.sourceSets.maybeCreate("jvmBaseMain")
val jvmBaseTest = this.sourceSets.maybeCreate("jvmBaseTest")
val jvmMain by sourceSets.getting
val jvmTest by sourceSets.getting
val androidMain by sourceSets.getting
val androidTest by sourceSets.getting
val nativeMain = this.sourceSets.maybeCreate("nativeMain")
val nativeTest = this.sourceSets.maybeCreate("nativeTest")
val nativeMain by lazy {
this.sourceSets.maybeCreate("nativeMain").apply {
dependsOn(commonMain)
}
}
val nativeTest by lazy {
this.sourceSets.maybeCreate("nativeTest").apply {
dependsOn(commonTest)
}
}
val unixMain = this.sourceSets.maybeCreate("unixMain")
val unixTest = this.sourceSets.maybeCreate("unixTest")
val unixMain by lazy {
this.sourceSets.maybeCreate("unixMain").apply {
dependsOn(nativeMain)
}
}
val unixTest by lazy {
this.sourceSets.maybeCreate("unixTest").apply {
dependsOn(nativeTest)
}
}
val darwinMain = this.sourceSets.maybeCreate("darwinMain")
val darwinTest = this.sourceSets.maybeCreate("darwinTest")
val darwinMain by lazy {
this.sourceSets.maybeCreate("darwinMain").apply {
dependsOn(unixMain)
}
}
val darwinTest by lazy {
this.sourceSets.maybeCreate("darwinTest").apply {
dependsOn(unixTest)
}
}
presets.filter { it.name in MAC_TARGETS }.forEach { preset ->
addNativeTarget(preset).run {
@ -293,24 +353,6 @@ fun KotlinMultiplatformExtension.configureNativeTargetsHierarchical(
}
}
}
jvmBaseMain.dependsOn(commonMain)
jvmBaseTest.dependsOn(commonTest)
nativeMain.dependsOn(commonMain)
nativeTest.dependsOn(commonTest)
unixMain.dependsOn(nativeMain)
unixTest.dependsOn(nativeTest)
darwinMain.dependsOn(unixMain)
darwinTest.dependsOn(unixTest)
jvmMain.dependsOn(jvmBaseMain)
jvmTest.dependsOn(jvmBaseTest)
androidMain.dependsOn(jvmBaseMain)
androidTest.dependsOn(jvmBaseTest)
}
private fun KotlinNativeTarget.findOrCreateTest(buildType: NativeBuildType, configure: TestExecutable.() -> Unit) =

View File

@ -29,14 +29,21 @@ abstract class AbstractTest {
lateinit var propertiesFile: File
fun gradleRunner(): GradleRunner {
println(PluginUnderTestMetadataReading.readImplementationClasspath())
return GradleRunner.create()
@OptIn(ExperimentalStdlibApi::class)
fun runGradle(vararg arguments: String) {
System.gc()
GradleRunner.create()
.withProjectDir(tempDir)
.withPluginClasspath()
.withGradleVersion("7.2")
.forwardOutput()
.withEnvironment(System.getenv())
.withArguments(buildList {
addAll(arguments)
add("-Pkotlin.compiler.execution.strategy=in-process")
add("-Dorg.gradle.jvmargs=-Xmx256m -Dfile.encoding=UTF-8")
})
.build()
}
@BeforeEach

View File

@ -88,9 +88,7 @@ class TestBuildPlugin : AbstractTest() {
""".trimIndent()
)
gradleRunner()
.withArguments(":buildPlugin", "--stacktrace", "--info")
.build()
runGradle(":buildPlugin", "--stacktrace", "--info")
ZipFile(findJar()).use { zipFile ->
@ -174,9 +172,7 @@ class TestBuildPlugin : AbstractTest() {
""".trimIndent()
)
gradleRunner()
.withArguments(":buildPlugin", "--stacktrace", "--info")
.build()
runGradle(":buildPlugin", "--stacktrace", "--info")
ZipFile(findJar()).use { zipFile ->
@ -242,9 +238,7 @@ class TestBuildPlugin : AbstractTest() {
""".trimIndent()
)
gradleRunner()
.withArguments(":buildPlugin", "--stacktrace", "--info")
.build()
runGradle(":buildPlugin", "--stacktrace", "--info")
ZipFile(findJar()).use { zipFile ->
@ -311,9 +305,7 @@ class TestBuildPlugin : AbstractTest() {
""".trimIndent()
)
gradleRunner()
.withArguments(":buildPlugin", "dependencies", "--stacktrace", "--info")
.build()
runGradle(":buildPlugin", "dependencies", "--stacktrace", "--info")
checkOutput()
ZipFile(findJar()).use { zipFile ->
@ -334,9 +326,7 @@ class TestBuildPlugin : AbstractTest() {
}
""".trimIndent()
)
gradleRunner()
.withArguments("buildPlugin", "dependencies", "--stacktrace", "--info")
.build()
runGradle("buildPlugin", "dependencies", "--stacktrace", "--info")
checkOutput()
}
@ -349,9 +339,7 @@ class TestBuildPlugin : AbstractTest() {
}
""".trimIndent()
)
gradleRunner()
.withArguments("buildPlugin", "dependencies", "--stacktrace", "--info")
.build()
runGradle("buildPlugin", "dependencies", "--stacktrace", "--info")
ZipFile(findJar()).use { zipFile ->
@ -372,9 +360,7 @@ class TestBuildPlugin : AbstractTest() {
}
""".trimIndent()
)
gradleRunner()
.withArguments("buildPlugin", "dependencies", "--stacktrace", "--info")
.build()
runGradle("buildPlugin", "dependencies", "--stacktrace", "--info")
ZipFile(findJar()).use { zipFile ->
@ -397,9 +383,7 @@ class TestBuildPlugin : AbstractTest() {
}
""".trimIndent()
)
gradleRunner()
.withArguments("buildPlugin", "dependencies", "--stacktrace", "--info")
.build()
runGradle("buildPlugin", "dependencies", "--stacktrace", "--info")
ZipFile(findJar()).use { zipFile ->
@ -423,9 +407,7 @@ class TestBuildPlugin : AbstractTest() {
}
""".trimIndent()
)
gradleRunner()
.withArguments("buildPlugin", "dependencies", "--stacktrace", "--info")
.build()
runGradle("buildPlugin", "dependencies", "--stacktrace", "--info")
ZipFile(findJar()).use { zipFile ->
assertNotNull(zipFile.getEntry("cn/hutool/core/annotation/Alias.class"))

View File

@ -15,8 +15,6 @@ class TestPluginApply : AbstractTest() {
@Test
fun `can apply plugin`() {
gradleRunner()
.withArguments("clean", "--stacktrace")
.build()
runGradle("clean", "--stacktrace")
}
}

View File

@ -55,7 +55,7 @@ kotlin {
}
}
val jvmBaseMain by getting {
findByName("jvmBaseMain")?.apply {
dependencies {
api(`kotlinx-coroutines-jdk8`) // use -jvm modules for this magic target 'jvmBase'
implementation(`jetbrains-annotations`)
@ -64,34 +64,32 @@ kotlin {
}
}
if (isAndroidSDKAvailable) {
val androidMain by getting {
dependsOn(commonMain)
dependencies {
compileOnly(`android-runtime`)
findByName("androidMain")?.apply {
dependsOn(commonMain)
dependencies {
compileOnly(`android-runtime`)
// api(`ktor-client-android`)
}
}
}
val jvmMain by getting {
findByName("jvmMain")?.apply {
}
val jvmTest by getting {
findByName("jvmTest")?.apply {
dependencies {
runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE
}
}
val nativeMain by getting {
findByName("nativeMain")?.apply {
dependencies {
}
}
}
}
if (isAndroidSDKAvailable) {
if (tasks.findByName("androidMainClasses") != null) {
tasks.register("checkAndroidApiLevel") {
doFirst {
analyzes.AndroidApiLevelCheck.check(
@ -107,4 +105,4 @@ if (isAndroidSDKAvailable) {
}
configureMppPublishing()
configureBinaryValidators("jvm", "android")
configureBinaryValidators(setOf("jvm", "android").filterTargets())

View File

@ -12,7 +12,6 @@ package net.mamoe.mirai.message.data
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.FileSupported
import net.mamoe.mirai.contact.file.AbsoluteFile
@ -42,7 +41,6 @@ import net.mamoe.mirai.utils.safeCast
@Serializable(FileMessage.Serializer::class)
@SerialName(FileMessage.SERIAL_NAME)
@NotStableForInheritance
@JvmBlockingBridge
public actual interface FileMessage : MessageContent, ConstrainSingle, CodableMessage {
/**
* 服务器需要的某种 ID.

View File

@ -47,31 +47,31 @@ kotlin {
}
}
val jvmBaseMain by getting {
findByName("jvmBaseMain")?.apply {
dependencies {
implementation(`jetbrains-annotations`)
}
}
if (isAndroidSDKAvailable) {
val androidMain by getting {
//
dependencies {
compileOnly(`android-runtime`)
findByName("androidMain")?.apply {
//
dependencies {
compileOnly(`android-runtime`)
// api1(`ktor-client-android`)
}
}
}
val jvmMain by getting
findByName("jvmMain")?.apply {
val jvmTest by getting {
}
findByName("jvmTest")?.apply {
dependencies {
runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE
}
}
val nativeMain by getting {
findByName("nativeMain")?.apply {
dependencies {
// implementation("com.soywiz.korlibs.krypto:krypto:2.4.12") // ':mirai-core-utils:compileNativeMainKotlinMetadata' fails because compiler cannot find reference
}
@ -79,7 +79,7 @@ kotlin {
}
}
if (isAndroidSDKAvailable) {
if (tasks.findByName("androidMainClasses") != null) {
tasks.register("checkAndroidApiLevel") {
doFirst {
analyzes.AndroidApiLevelCheck.check(

View File

@ -55,7 +55,7 @@ kotlin {
}
}
val jvmBaseMain by getting {
findByName("jvmBaseMain")?.apply {
dependencies {
implementation(bouncycastle)
implementation(`log4j-api`)
@ -65,45 +65,43 @@ kotlin {
}
}
val jvmBaseTest by getting {
findByName("jvmBaseTest")?.apply {
dependencies {
implementation(`kotlinx-coroutines-debug`)
}
}
if (isAndroidSDKAvailable) {
val androidMain by getting {
dependsOn(commonMain)
dependencies {
compileOnly(`android-runtime`)
}
findByName("androidMain")?.apply {
dependsOn(commonMain)
dependencies {
compileOnly(`android-runtime`)
}
val androidTest by getting {
dependencies {
implementation(kotlin("test", Versions.kotlinCompiler))
implementation(kotlin("test-junit5", Versions.kotlinCompiler))
implementation(kotlin("test-annotations-common"))
implementation(kotlin("test-common"))
//implementation("org.bouncycastle:bcprov-jdk15on:1.64")
}
}
findByName("androidTest")?.apply {
dependencies {
implementation(kotlin("test", Versions.kotlinCompiler))
implementation(kotlin("test-junit5", Versions.kotlinCompiler))
implementation(kotlin("test-annotations-common"))
implementation(kotlin("test-common"))
//implementation("org.bouncycastle:bcprov-jdk15on:1.64")
}
}
val jvmMain by getting {
findByName("jvmMain")?.apply {
dependencies {
//implementation("org.bouncycastle:bcprov-jdk15on:1.64")
// api(kotlinx("coroutines-debug", Versions.coroutines))
}
}
val jvmTest by getting {
findByName("jvmTest")?.apply {
dependencies {
api(`kotlinx-coroutines-debug`)
// implementation("net.mamoe:mirai-login-solver-selenium:1.0-dev-14")
}
}
val nativeMain by getting {
findByName("nativeMain")?.apply {
dependencies {
}
}
@ -151,7 +149,7 @@ kotlin {
}
}
val darwinMain by getting {
findByName("darwinMain")?.apply {
dependencies {
implementation(`ktor-client-darwin`)
}
@ -188,7 +186,7 @@ afterEvaluate {
}
}
if (isAndroidSDKAvailable) {
if (tasks.findByName("androidMainClasses") != null) {
tasks.register("checkAndroidApiLevel") {
doFirst {
analyzes.AndroidApiLevelCheck.check(
@ -204,4 +202,4 @@ if (isAndroidSDKAvailable) {
}
configureMppPublishing()
configureBinaryValidators("jvm", "android")
configureBinaryValidators(setOf("jvm", "android").filterTargets())

View File

@ -9,6 +9,7 @@
package net.mamoe.mirai.internal.message.data
import net.mamoe.mirai.internal.test.AbstractTest
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.safeCast
import kotlin.test.Test
@ -26,7 +27,7 @@ internal class TestConstrainSingleMessage : ConstrainSingle, Any() {
get() = Key
}
internal class ContentEqualsTest {
internal class ContentEqualsTest: AbstractTest() {
@Test
fun testContentEquals() {

View File

@ -15,7 +15,6 @@ import net.mamoe.mirai.utils.md5
import net.mamoe.mirai.utils.toUHexString
import openssl.*
import platform.posix.errno
import platform.posix.free
private const val curveId = NID_X9_62_prime256v1
@ -27,13 +26,26 @@ private val convForm by lazy { EC_GROUP_get_point_conversion_form(group) }
// shared, not freed!
private val bnCtx by lazy { BN_CTX_new() }
// ====ATTENTION====
// Do not use [platform.posix.free] easily
// For anything allocated by OpenSSL, <type>_free or CRYPTO_free
// (the underlying of OPENSSL_free macro) should be called.
// It's more than dangerous to assume OpenSSL uses the same memory manager as general posix functions,
// easily causing memory leaking (usually on *nix) or crash (usually on Windows)
internal actual interface ECDHPublicKey : OpenSSLKey {
val encoded: ByteArray
/**
* @return It is the caller's responsibility to free this memory with a subsequent call to [EC_POINT_free]
*/
fun toPoint(): CPointer<EC_POINT>
}
internal actual interface ECDHPrivateKey : OpenSSLKey {
/**
* @return It is the caller's responsibility to free this memory with a subsequent call to [BN_free]
*/
fun toBignum(): CPointer<BIGNUM>
}
@ -52,16 +64,14 @@ internal class OpenSslPrivateKey(
companion object {
fun fromKey(key: CPointer<EC_KEY>): OpenSslPrivateKey {
// Note that the private key (bignum) is associated with the key
// We can't free it, or it'll crash when EC_KEY_free
val bn = EC_KEY_get0_private_key(key) ?: error("Failed EC_KEY_get0_private_key")
val ptr = BN_bn2hex(bn) ?: error("Failed EC_POINT_bn2point")
val hex = try {
val ptr = BN_bn2hex(bn) ?: error("Failed EC_POINT_bn2point")
try {
ptr.toKString()
} finally {
free(ptr)
}
ptr.toKString()
} finally {
BN_free(bn)
CRYPTO_free(ptr, "OpenSslPrivateKey.Companion.fromKey(key: CPointer<EC_KEY>)", -1)
}
return OpenSslPrivateKey(hex)
}
@ -115,7 +125,7 @@ private fun CPointer<EC_POINT>.toKtHex(): String {
return try {
ptr.toKString()
} finally {
free(ptr)
CRYPTO_free(ptr, "CPointer<EC_POINT>.toKtHex()", -1)
}
}
@ -159,15 +169,13 @@ internal actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
actual fun generateKeyPair(initialPublicKey: ECDHPublicKey): ECDHKeyPair {
val key: CPointer<EC_KEY> = EC_KEY_new_by_curve_name(curveId)
?: throw IllegalStateException("Failed to create key curve, $errno")
if (1 != EC_KEY_generate_key(key)) {
throw IllegalStateException("Failed to generate key, $errno")
}
try {
if (1 != EC_KEY_generate_key(key)) {
throw IllegalStateException("Failed to generate key, $errno")
}
return ECDHKeyPairImpl.fromKey(key, initialPublicKey)
} finally {
free(key) // TODO: THIS MAY CAUSE MEMORY LEAK. But EC_KEY_free() will terminate the process for unknown reason.
EC_KEY_free(key)
}
}