diff --git a/library/runtime-core/api/runtime-core.api b/library/runtime-core/api/runtime-core.api index a80c6ce4c..de1f9cf40 100644 --- a/library/runtime-core/api/runtime-core.api +++ b/library/runtime-core/api/runtime-core.api @@ -707,13 +707,15 @@ public final class io/matthewnelson/kmp/tor/runtime/core/config/IntervalUnit$WEE public fun toString ()Ljava/lang/String; } -public final class io/matthewnelson/kmp/tor/runtime/core/config/TorConfig { +public final class io/matthewnelson/kmp/tor/runtime/core/config/TorConfig : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { public static final field Companion Lio/matthewnelson/kmp/tor/runtime/core/config/TorConfig$Companion; public final field settings Ljava/util/Set; public synthetic fun (Ljava/util/Set;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public static final fun Builder (Lio/matthewnelson/kmp/tor/runtime/core/ThisBlock;)Lio/matthewnelson/kmp/tor/runtime/core/config/TorConfig; public fun equals (Ljava/lang/Object;)Z public fun hashCode ()I + public fun iterator ()Ljava/util/Iterator; + public static final fun toConfig (Lio/matthewnelson/kmp/tor/runtime/core/config/TorSetting;)Lio/matthewnelson/kmp/tor/runtime/core/config/TorConfig; public fun toString ()Ljava/lang/String; } @@ -735,6 +737,7 @@ protected final class io/matthewnelson/kmp/tor/runtime/core/config/TorConfig$Bui public final class io/matthewnelson/kmp/tor/runtime/core/config/TorConfig$Companion { public final fun Builder (Lio/matthewnelson/kmp/tor/runtime/core/ThisBlock;)Lio/matthewnelson/kmp/tor/runtime/core/config/TorConfig; + public final fun toConfig (Lio/matthewnelson/kmp/tor/runtime/core/config/TorSetting;)Lio/matthewnelson/kmp/tor/runtime/core/config/TorConfig; } public abstract class io/matthewnelson/kmp/tor/runtime/core/config/TorOption : java/lang/CharSequence, java/lang/Comparable { @@ -3901,8 +3904,9 @@ public final class io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmd$Config$Get } public final class io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmd$Config$Load : io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmd$Privileged { - public final field configText Ljava/lang/String; - public fun (Ljava/lang/String;)V + public final field config Lio/matthewnelson/kmp/tor/runtime/core/config/TorConfig; + public fun (Lio/matthewnelson/kmp/tor/runtime/core/ThisBlock;)V + public fun (Lio/matthewnelson/kmp/tor/runtime/core/config/TorConfig;)V } public final class io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmd$Config$Reset : io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmd$Unprivileged { @@ -3919,7 +3923,7 @@ public final class io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmd$Config$Save } public final class io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmd$Config$Set : io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmd$Unprivileged { - public final field settings Ljava/util/Set; + public final field config Lio/matthewnelson/kmp/tor/runtime/core/config/TorConfig; public fun (Lio/matthewnelson/kmp/tor/runtime/core/ThisBlock;)V public fun (Lio/matthewnelson/kmp/tor/runtime/core/config/TorConfig;)V public fun (Lio/matthewnelson/kmp/tor/runtime/core/config/TorSetting;)V diff --git a/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/config/TorConfig.kt b/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/config/TorConfig.kt index a19a1c307..9cf96c17d 100644 --- a/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/config/TorConfig.kt +++ b/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/config/TorConfig.kt @@ -17,6 +17,7 @@ package io.matthewnelson.kmp.tor.runtime.core.config +import io.matthewnelson.immutable.collections.immutableSetOf import io.matthewnelson.immutable.collections.toImmutableSet import io.matthewnelson.kmp.file.File import io.matthewnelson.kmp.tor.core.api.annotation.InternalKmpTorApi @@ -34,7 +35,7 @@ import kotlin.jvm.JvmSynthetic * @see [TorSetting.filterByAttribute] * @see [TorSetting.filterByOption] * */ -public class TorConfig private constructor(settings: Set) { +public class TorConfig private constructor(settings: Set): Iterable { /** * All [TorSetting] which make up this configuration. @@ -42,6 +43,8 @@ public class TorConfig private constructor(settings: Set) { @JvmField public val settings: Set = settings.toImmutableSet() + public override fun iterator(): Iterator = settings.iterator() + public companion object { /** @@ -56,6 +59,14 @@ public class TorConfig private constructor(settings: Set) { @OptIn(InternalKmpTorApi::class) return RealBuilderScopeTorConfig.build(::TorConfig, block) } + + /** + * Wraps a single [TorSetting] in [TorConfig]. + * */ + @JvmStatic + public fun TorSetting.toConfig(): TorConfig { + return TorConfig(immutableSetOf(this)) + } } /** @@ -314,6 +325,7 @@ public class TorConfig private constructor(settings: Set) { public override fun equals(other: Any?): Boolean = other is TorConfig && other.settings == settings /** @suppress */ public override fun hashCode(): Int = 5 * 42 + settings.hashCode() + // TODO: Extras as code comments. Issue #526 /** @suppress */ public override fun toString(): String = buildString { settings.joinTo(this, separator = "\n") } } diff --git a/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/config/TorSetting.kt b/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/config/TorSetting.kt index 72cfb02c2..03e026b38 100644 --- a/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/config/TorSetting.kt +++ b/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/config/TorSetting.kt @@ -40,8 +40,8 @@ import kotlin.jvm.* * Comparison of settings is done such that only the first [LineItem] (or * "root" item) within [items] is considered. * - * @see [Iterable.filterByAttribute] - * @see [Iterable.filterByOption] + * @see [filterByAttribute] + * @see [filterByOption] * */ public class TorSetting private constructor( @@ -260,24 +260,6 @@ public class TorSetting private constructor( public companion object { - /** - * Returns a list containing all elements of [TorSetting] within - * the [Iterable] which contain a [LineItem], and is configured - * for, attribute [A]. - * - * For example, if [TorOption.Attribute.UNIX_SOCKET] is parameter - * [A] and 2 declarations of [TorOption.ControlPort] are present - * (one configured as a Unix Socket, and the other as a TCP port), - * then only the one configured as a Unix Socket will be present - * in the returned list. - * - * @see [Iterable.filterByAttribute] - * */ - @JvmStatic - public inline fun TorConfig.filterByAttribute(): List { - return settings.filterByAttribute() - } - /** * Returns a list containing all elements of [TorSetting] within * the [Iterable] which contain a [LineItem], and is configured @@ -329,17 +311,6 @@ public class TorSetting private constructor( } } - /** - * Returns a list containing all elements of [TorSetting] within - * the [TorConfig] which contain a [LineItem] for option [O]. - * - * @see [Iterable.filterByOption] - * */ - @JvmStatic - public inline fun TorConfig.filterByOption(): List { - return settings.filterByOption() - } - /** * Returns a list containing all elements of [TorSetting] within * the [Iterable] which contain a [LineItem] for option [O]. diff --git a/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/config/builder/BuilderScopeVirtualAddr.kt b/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/config/builder/BuilderScopeVirtualAddr.kt index c2d129eaf..91ec27560 100644 --- a/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/config/builder/BuilderScopeVirtualAddr.kt +++ b/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/config/builder/BuilderScopeVirtualAddr.kt @@ -121,7 +121,7 @@ public abstract class BuilderScopeVirtualAddr: TorSetting.BuilderScope { @JvmField protected var _address: IPAddress = argument.toIPAddress() @JvmField - protected var _bits: Byte = argument.substringAfter('/').toByte() + protected var _bits: Byte = argument.substringAfterLast('/').toByte() @JvmSynthetic internal final override fun build(): TorSetting { diff --git a/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmd.kt b/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmd.kt index 04369db25..f94ac8213 100644 --- a/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmd.kt +++ b/library/runtime-core/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmd.kt @@ -13,8 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -@file:Suppress("UNUSED_PARAMETER") - package io.matthewnelson.kmp.tor.runtime.core.ctrl import io.matthewnelson.encoding.base16.Base16 @@ -30,6 +28,7 @@ import io.matthewnelson.kmp.tor.runtime.core.ctrl.builder.BuilderScopeOnionAdd import io.matthewnelson.kmp.tor.runtime.core.ctrl.builder.BuilderScopeOnionAdd.Companion.configure import io.matthewnelson.kmp.tor.runtime.core.ctrl.builder.BuilderScopeClientAuthAdd import io.matthewnelson.kmp.tor.runtime.core.config.TorConfig +import io.matthewnelson.kmp.tor.runtime.core.config.TorConfig.Companion.toConfig import io.matthewnelson.kmp.tor.runtime.core.config.TorOption import io.matthewnelson.kmp.tor.runtime.core.config.TorSetting import io.matthewnelson.kmp.tor.runtime.core.key.* @@ -124,10 +123,16 @@ public sealed class TorCmd private constructor( /** * [control-spec#LOADCONF](https://spec.torproject.org/control-spec/commands.html#loadconf) * */ - public class Load( + public class Load: Privileged { + @JvmField - public val configText: String, - ): Privileged("LOADCONF") + public val config: TorConfig + + public constructor(block: ThisBlock): this(TorConfig.Builder(block)) + public constructor(config: TorConfig): super("LOADCONF") { + this.config = config + } + } /** * [control-spec#RESETCONF](https://spec.torproject.org/control-spec/commands.html#resetconf) @@ -165,17 +170,15 @@ public sealed class TorCmd private constructor( public class Set: Unprivileged { @JvmField - public val settings: kotlin.collections.Set + public val config: TorConfig - public constructor(setting: TorSetting): this(immutableSetOf(setting), null) + public constructor(setting: TorSetting): this(setting.toConfig()) public constructor(vararg settings: TorSetting): this(ThisBlock { settings.forEach { put(it) } }) public constructor(settings: Collection): this(ThisBlock { putAll(settings) }) public constructor(block: ThisBlock): this(TorConfig.Builder(block)) - public constructor(config: TorConfig): this(config.settings, null) - - private constructor(settings: kotlin.collections.Set, any: Any?): super("SETCONF") { - this.settings = settings + public constructor(config: TorConfig): super("SETCONF") { + this.config = config } } } diff --git a/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmdConfigSetUnitTest.kt b/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmdConfigSetUnitTest.kt index 820f7b166..b946e6fb2 100644 --- a/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmdConfigSetUnitTest.kt +++ b/library/runtime-core/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/core/ctrl/TorCmdConfigSetUnitTest.kt @@ -31,10 +31,10 @@ class TorCmdConfigSetUnitTest { assertEquals(2, set.size) val cmd = TorCmd.Config.Set(set) - assertEquals(1, cmd.settings.size) + assertEquals(1, cmd.config.settings.size) // When all items in the set were added to TorConfig.BuilderScope, the // final argument should have overridden the first one. - assertEquals(false.byte.toString(), cmd.settings.first().items.first().argument) + assertEquals(false.byte.toString(), cmd.config.settings.first().items.first().argument) } } diff --git a/library/runtime-ctrl/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/internal/-TorCmd.kt b/library/runtime-ctrl/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/internal/-TorCmd.kt index 48d7d2f61..c587ab377 100644 --- a/library/runtime-ctrl/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/internal/-TorCmd.kt +++ b/library/runtime-ctrl/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/internal/-TorCmd.kt @@ -89,31 +89,27 @@ private fun TorCmd.Config.Get.encode(LOG: Debugger?): ByteArray { @Throws(IllegalArgumentException::class) private fun TorCmd.Config.Load.encode(LOG: Debugger?): ByteArray { + require(config.settings.isNotEmpty()) { "A minimum of 1 setting is required" } + return StringBuilder().apply { append('+').append(keyword) - var hasLine = false - for (line in configText.lines()) { - val isCommentOrBlank = run { - val i = line.indexOfFirst { !it.isWhitespace() } - if (i == -1) true else line[i] == '#' - } - if (isCommentOrBlank) continue - - if (!hasLine) { - hasLine = true - LOG.d { ">> ${toString()}" } - CRLF() - } else { - appendLine() - } + var crlf = false + for (setting in config) { + for (line in setting.items) { + if (!crlf) { + crlf = true + LOG.d { ">> ${toString()}" } + CRLF() + } else { + appendLine() + } - append(line) - LOG.d { ">> $line" } + append(line) + LOG.d { ">> $line" } + } } - require(hasLine) { "configText must contain at least 1 setting" } - CRLF() LOG.d { ">> ." } append('.') @@ -146,28 +142,23 @@ private fun TorCmd.Config.Save.encode(LOG: Debugger?): ByteArray { @Throws(IllegalArgumentException::class) private fun TorCmd.Config.Set.encode(LOG: Debugger?): ByteArray { - require(settings.isNotEmpty()) { "A minimum of 1 setting is required" } + require(config.settings.isNotEmpty()) { "A minimum of 1 setting is required" } return StringBuilder(keyword).apply { - for (setting in settings) { + for (setting in config) { for (line in setting.items) { SP().append(line.option).append('=').append('"') run { var argument = line.argument - with(line.option.attributes) { - when { - contains(Attribute.UNIX_SOCKET) -> { - if (argument.startsWith("unix:")) { - argument = argument.replace("\"", "\\\"") - } - } - contains(Attribute.FILE) || contains(Attribute.DIRECTORY) -> { - if (SysDirSep == '\\') { - argument = argument.replace("\\", "\\\\") - } - } + // Escapes + when { + line.isUnixSocket -> { + argument = argument.replace("\"", "\\\"") + } + (line.isDirectory || line.isFile) && SysDirSep == '\\' -> { + argument = argument.replace("\\", "\\\\") } } diff --git a/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/internal/TorCmdUnitTest.kt b/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/internal/TorCmdUnitTest.kt deleted file mode 100644 index 11ff0d4e4..000000000 --- a/library/runtime-ctrl/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/ctrl/internal/TorCmdUnitTest.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2024 Matthew Nelson - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ -package io.matthewnelson.kmp.tor.runtime.ctrl.internal - -import io.matthewnelson.kmp.tor.runtime.core.ctrl.TorCmd -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue - -class TorCmdUnitTest { - - @Test - fun givenConfigLoad_whenEncode_thenThrowsWhenNoSettings() { - var invocationLOG = 0 - val LOG = Debugger.of("") { invocationLOG++ } - - val blank = " " - - listOf( - "", - """ - # comment - """.trimIndent(), - """ - # comment - - $blank - # indented comment - """.trimIndent() - ).forEach { text -> - assertFailsWith { - TorCmd.Config.Load(text).encodeToByteArray(LOG) - } - } - - assertEquals(0, invocationLOG) - } - - @Test - fun givenConfigLoad_whenEncode_thenRemovesComments() { - val expected = "SocksPort 9050" - val lines = TorCmd.Config.Load(configText = """ - # comment - # indented comment - - $expected - """.trimIndent()) - .encodeToByteArray(null) - .decodeToString() - .lines() - - // +LOADCONF - // SocksPort 9050 - // . - // - assertEquals(4, lines.size) - assertTrue(lines.contains(expected)) - } -} diff --git a/library/runtime/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/TorListeners.kt b/library/runtime/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/TorListeners.kt index 5829b3518..a65f32bae 100644 --- a/library/runtime/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/TorListeners.kt +++ b/library/runtime/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/TorListeners.kt @@ -569,8 +569,8 @@ public class TorListeners private constructor( protected open fun onConfigChangeJob(cmd: TorCmd.Config.Set, job: EnqueuedJob) { job.invokeOnErrorRecovery(recovery = { val changes = run { - val eSocks = cmd.settings.filterByOption() - val socks = cmd.settings.filterByOption() + val eSocks = cmd.config.filterByOption() + val socks = cmd.config.filterByOption() eSocks + socks }.takeIf { it.isNotEmpty() }?.let { settings -> settings.mapTo(LinkedHashSet(settings.size, 1.0F)) { setting -> diff --git a/library/runtime/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/internal/process/TorDaemon.kt b/library/runtime/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/internal/process/TorDaemon.kt index 6768854db..bb9e36707 100644 --- a/library/runtime/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/internal/process/TorDaemon.kt +++ b/library/runtime/src/commonMain/kotlin/io/matthewnelson/kmp/tor/runtime/internal/process/TorDaemon.kt @@ -104,7 +104,7 @@ internal class TorDaemon private constructor( this, "Starting Tor with the following settings:\n" + "------------------------------------------------------------------------\n" - + startArgs.load.configText + + startArgs.load.config.toString() + "\n------------------------------------------------------------------------" ) @@ -490,7 +490,7 @@ internal class TorDaemon private constructor( } } - return StartArgs(cmdLine.toImmutableList(), TorCmd.Config.Load(toString())) + return StartArgs(cmdLine.toImmutableList(), TorCmd.Config.Load(this)) } } } diff --git a/library/runtime/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/internal/TorConfigGeneratorUnitTest.kt b/library/runtime/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/internal/TorConfigGeneratorUnitTest.kt index 13833433d..8ea7e0344 100644 --- a/library/runtime/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/internal/TorConfigGeneratorUnitTest.kt +++ b/library/runtime/src/commonTest/kotlin/io/matthewnelson/kmp/tor/runtime/internal/TorConfigGeneratorUnitTest.kt @@ -102,18 +102,18 @@ class TorConfigGeneratorUnitTest { @Test fun givenUnavailablePort_whenGenerate_thenPortRemovedAndReplaced() = runTest { // socks port at 9050 is automatically added - val settings = newGenerator( + val config = newGenerator( config = setOf( ConfigBuilderCallback { TorOption.__DNSPort.configure { port(1080.toPortEphemeral()) } } ), isPortAvailable = { _, _ -> false } - ).generate(notifier).first.settings + ).generate(notifier).first - val socks = settings.filterByOption().first() + val socks = config.filterByOption().first() assertEquals("auto", socks.items.first().argument) - val dns = settings.filterByOption().first() + val dns = config.filterByOption().first() assertEquals("auto", dns.items.first().argument) } @@ -144,7 +144,6 @@ class TorConfigGeneratorUnitTest { ) ).generate(notifier) .first - .settings .filterByOption() .first() @@ -165,7 +164,6 @@ class TorConfigGeneratorUnitTest { ) ).generate(notifier) .first - .settings .filterByOption() .firstOrNull() @@ -194,7 +192,6 @@ class TorConfigGeneratorUnitTest { ) ).generate(notifier) .first - .settings .filterByOption() .first() .items