Make Android UDP log forwarding opt-in with a persisted target

This commit is contained in:
evgeny committed 2026-10-28 12:08:07 +01:00
1 parent 917a2a2b28
commit 0a36dc5ac6
3 files changed
+91 -12

No files matched your search

@@ -55,6 +55,7 @@ class ConfigProvider(private val context: Context) {
private val debugCategories = stringPreferencesKey("debug_categories")
private val logUdpIp = stringPreferencesKey("log_udp_ip")
private val logUdpPort = intPreferencesKey("log_udp_port")
private val logUdpEnabled = booleanPreferencesKey("log_udp_enabled")
private val chatStorageAutoload = intPreferencesKey("chat_storage_autoload")
private val chatStorageAutoloadMaxsizeMb = intPreferencesKey("chat_storage_autoload_maxsize_mb")
private val chatStorageMaxsizeGb = intPreferencesKey("chat_storage_maxsize_gb")
@@ -184,11 +185,27 @@ class ConfigProvider(private val context: Context) {
"debug.console_level" to (prefs[debugConsoleLevel] ?: "info"),
"debug.log_file" to (prefs[debugLogFile] ?: "utun.log"),
"debug.categories" to (prefs[debugCategories] ?: ""),
"log_udp.ip" to (prefs[logUdpIp] ?: ""),
"log_udp.port" to (prefs[logUdpPort]?.toString() ?: "0")
"log_udp.enabled" to if (prefs[logUdpEnabled] ?: false) "1" else "0",
"log_udp.ip" to (prefs[logUdpIp] ?: UdpLogSettings.DEFAULT_IP),
"log_udp.port" to (prefs[logUdpPort]?.toString() ?: UdpLogSettings.DEFAULT_PORT.toString())
)
}
private fun udpLogSettings(prefs: Preferences): UdpLogSettings = UdpLogSettings(prefs[logUdpEnabled] ?: false,
prefs[logUdpIp] ?: UdpLogSettings.DEFAULT_IP, prefs[logUdpPort] ?: UdpLogSettings.DEFAULT_PORT)
suspend fun getUdpLogSettings(): UdpLogSettings = configFiles.submit("load UDP log settings") {
udpLogSettings(context.dataStore.data.first())
}.await()
fun saveUdpLogSettings(settings: UdpLogSettings): Deferred<Unit> = configFiles.submit("save UDP log settings") {
context.dataStore.edit { prefs ->
prefs[logUdpEnabled] = settings.enabled; prefs[logUdpIp] = settings.ip; prefs[logUdpPort] = settings.port
}
LogManager.addLog("INFO", "UDPLog", "Settings saved enabled=${settings.enabled} active=${settings.active} " +
"target=${settings.ip}:${settings.port}")
}
fun getCallVideoBitrateRange(): CallVideoBitrateRange = runBlocking {
configFiles.submit("load call video bitrate") { videoBitrateRange(context.dataStore.data.first()) }.await()
}
@@ -316,7 +333,7 @@ class ConfigProvider(private val context: Context) {
"debug.console_level" -> prefs[debugConsoleLevel] ?: "info"
"debug.log_file" -> prefs[debugLogFile] ?: "utun.log"
"debug.categories" -> prefs[debugCategories] ?: ""
"log_udp.ip" -> prefs[logUdpIp] ?: "192.168.40.250"
"log_udp.ip" -> prefs[logUdpIp] ?: UdpLogSettings.DEFAULT_IP
else -> null
}
@@ -329,7 +346,8 @@ class ConfigProvider(private val context: Context) {
"gui.utun_enabled" -> prefs[guiUtunEnabled] ?: 0
"node.listen_port" -> prefs[nodeListenPort] ?: 12345
"control.port" -> prefs[controlPort] ?: 9999
"log_udp.port" -> prefs[logUdpPort] ?: 9999
"log_udp.enabled" -> if (prefs[logUdpEnabled] ?: false) 1 else 0
"log_udp.port" -> prefs[logUdpPort] ?: UdpLogSettings.DEFAULT_PORT
"chat.storage_autoload" -> prefs[chatStorageAutoload] ?: 1
"chat.storage_autoload_maxsize_mb" -> prefs[chatStorageAutoloadMaxsizeMb] ?: 100
"chat.storage_maxsize_gb" -> prefs[chatStorageMaxsizeGb] ?: 1
@@ -376,6 +394,7 @@ class ConfigProvider(private val context: Context) {
"debug.categories" -> prefs[debugCategories] = value
"log_udp.ip" -> prefs[logUdpIp] = value
"log_udp.port" -> prefs[logUdpPort] = value.toIntOrNull() ?: 0
"log_udp.enabled" -> prefs[logUdpEnabled] = value == "1" || value == "yes" || value == "true"
"chat.storage_autoload" -> prefs[chatStorageAutoload] = value.toIntOrNull() ?: 1
"chat.storage_autoload_maxsize_mb" -> prefs[chatStorageAutoloadMaxsizeMb] = value.toIntOrNull() ?: 100
"chat.storage_maxsize_gb" -> prefs[chatStorageMaxsizeGb] = value.toIntOrNull() ?: 1
@@ -881,14 +900,10 @@ class ConfigProvider(private val context: Context) {
if (trimmed.isNotEmpty()) appendLine(trimmed)
}
}
val udpIp = stringValue("log_udp.ip", prefs, snapshot.keys) ?: ""
val udpPort = intValue("log_udp.port", prefs)
if (udpIp.isNotEmpty() && udpPort > 0) {
appendLine()
appendLine("[log_udp]")
appendLine("ip=$udpIp")
appendLine("port=$udpPort")
}
val udpLog = udpLogSettings(prefs)
udpLog.appendConfig(this)
LogManager.addLog(if (udpLog.enabled && !udpLog.active) "WARN" else "INFO", "UDPLog",
"Startup network logging enabled=${udpLog.enabled} active=${udpLog.active} target=${udpLog.ip}:${udpLog.port}")
}
}
@@ -0,0 +1,24 @@
package com.utun.chat.data
/** Адрес logreceiver сохраняется при выключении; наличие адреса само по себе не включает отправку. */
data class UdpLogSettings(val enabled: Boolean = false, val ip: String = DEFAULT_IP, val port: Int = DEFAULT_PORT) {
val validAddress: Boolean get() = ip.split('.').let { parts -> parts.size == 4 && parts.all { part ->
part.toIntOrNull()?.let { it in 0..255 && part == it.toString() } == true
} }
val active: Boolean get() = enabled && validAddress && port in 1..65535
/** Добавить сетевой target в INI только при явном включении и заполненном адресе/порте. */
fun appendConfig(config: StringBuilder) {
if (active) {
config.appendLine()
config.appendLine("[log_udp]")
config.appendLine("ip=$ip")
config.appendLine("port=$port")
}
}
companion object {
const val DEFAULT_IP = "192.168.40.250"
const val DEFAULT_PORT = 9999
}
}
@@ -0,0 +1,40 @@
package com.utun.chat.data
import org.junit.Assert.*
import org.junit.Test
class UdpLogSettingsTest {
private fun config(settings: UdpLogSettings): String = buildString { appendLine("[global]"); settings.appendConfig(this) }
@Test fun defaultAddressDoesNotEnableNetworkLogging() {
val settings = UdpLogSettings()
assertFalse(settings.enabled)
assertFalse(settings.active)
assertEquals("[global]\n", config(settings))
}
@Test fun explicitEnableWritesTargetIntoStartupConfig() {
val settings = UdpLogSettings(true, "192.168.40.250", 9999)
assertTrue(settings.active)
assertEquals("[global]\n\n[log_udp]\nip=192.168.40.250\nport=9999\n", config(settings))
}
@Test fun disabledTargetRetainsAddressWithoutConfiguringSender() {
val enabled = UdpLogSettings(true, "10.20.30.40", 12345)
val disabled = enabled.copy(enabled = false)
assertEquals(enabled.ip, disabled.ip)
assertEquals(enabled.port, disabled.port)
assertEquals("[global]\n", config(disabled))
assertEquals(config(enabled), config(disabled.copy(enabled = true)))
}
@Test fun malformedTargetsNeverEnableSender() {
for (ip in listOf("", "localhost", "127.1", "1.2.3.256", "01.2.3.4", "1.2.3.4 ")) {
val settings = UdpLogSettings(true, ip, 9999)
assertFalse(ip, settings.active)
assertEquals("[global]\n", config(settings))
}
for (port in listOf(-1, 0, 65536)) assertFalse(UdpLogSettings(true, "127.0.0.1", port).active)
assertTrue(UdpLogSettings(true, "127.0.0.1", 65535).active)
}
}