diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift index 74d38b050b..054f7fc6aa 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift @@ -235,11 +235,16 @@ struct AdvancedNetworkSettings: View { intSettingPicker("TCP_KEEPIDLE", selection: $keepAliveOpts.keepIdle, values: [15, 30, 60, 120, 180], label: secondsLabel) intSettingPicker("TCP_KEEPINTVL", selection: $keepAliveOpts.keepIntvl, values: [5, 10, 15, 30, 60], label: secondsLabel) intSettingPicker("TCP_KEEPCNT", selection: $keepAliveOpts.keepCnt, values: [1, 2, 4, 6, 8], label: "") + Toggle("TCP_USER_TIMEOUT", isOn: Binding( + get: { keepAliveOpts.unackedDataTimeout == true }, + set: { keepAliveOpts.unackedDataTimeout = $0 } + )) } else { Group { Text("TCP_KEEPIDLE") Text("TCP_KEEPINTVL") Text("TCP_KEEPCNT") + Text("TCP_USER_TIMEOUT") } .foregroundColor(theme.colors.secondary) } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 75d66ebb6f..a09518b860 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -438,8 +438,11 @@ public struct KeepAliveOpts: Codable, Equatable { public var keepIdle: Int // seconds public var keepIntvl: Int // seconds public var keepCnt: Int // times + // a connection with unacknowledged data gets no keep-alive probes, and the OS fails it only when it + // gives up retransmitting - ~15 minutes; when this is on it is failed after the keep-alive detection time + public var unackedDataTimeout: Bool? = nil - public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4) + public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4, unackedDataTimeout: nil) } public struct NetworkProxy: Equatable, Codable { diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index d8543735b0..3084efe2c5 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -57,6 +57,7 @@ let GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE = "networkEnableKeepAlive" let GROUP_DEFAULT_NETWORK_TCP_KEEP_IDLE = "networkTCPKeepIdle" let GROUP_DEFAULT_NETWORK_TCP_KEEP_INTVL = "networkTCPKeepIntvl" let GROUP_DEFAULT_NETWORK_TCP_KEEP_CNT = "networkTCPKeepCnt" +let GROUP_DEFAULT_NETWORK_TCP_UNACKED_DATA_TIMEOUT = "networkTCPUnackedDataTimeout" public let GROUP_DEFAULT_INCOGNITO = "incognito" let GROUP_DEFAULT_STORE_DB_PASSPHRASE = "storeDBPassphrase" public let GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE = "initialRandomDBPassphrase" @@ -90,6 +91,7 @@ public let groupAppDefaults: [String: Any] = [ GROUP_DEFAULT_NETWORK_TCP_KEEP_IDLE: KeepAliveOpts.defaults.keepIdle, GROUP_DEFAULT_NETWORK_TCP_KEEP_INTVL: KeepAliveOpts.defaults.keepIntvl, GROUP_DEFAULT_NETWORK_TCP_KEEP_CNT: KeepAliveOpts.defaults.keepCnt, + GROUP_DEFAULT_NETWORK_TCP_UNACKED_DATA_TIMEOUT: KeepAliveOpts.defaults.unackedDataTimeout == true, GROUP_DEFAULT_INCOGNITO: false, GROUP_DEFAULT_STORE_DB_PASSPHRASE: true, GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE: false, @@ -383,7 +385,8 @@ public func getNetCfg() -> NetCfg { let keepIdle = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_KEEP_IDLE) let keepIntvl = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_KEEP_INTVL) let keepCnt = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_KEEP_CNT) - tcpKeepAlive = KeepAliveOpts(keepIdle: keepIdle, keepIntvl: keepIntvl, keepCnt: keepCnt) + let unackedDataTimeout = groupDefaults.bool(forKey: GROUP_DEFAULT_NETWORK_TCP_UNACKED_DATA_TIMEOUT) + tcpKeepAlive = KeepAliveOpts(keepIdle: keepIdle, keepIntvl: keepIntvl, keepCnt: keepCnt, unackedDataTimeout: unackedDataTimeout) } else { tcpKeepAlive = nil } @@ -427,6 +430,7 @@ public func setNetCfg(_ cfg: NetCfg, networkProxy: NetworkProxy?) { groupDefaults.set(tcpKeepAlive.keepIdle, forKey: GROUP_DEFAULT_NETWORK_TCP_KEEP_IDLE) groupDefaults.set(tcpKeepAlive.keepIntvl, forKey: GROUP_DEFAULT_NETWORK_TCP_KEEP_INTVL) groupDefaults.set(tcpKeepAlive.keepCnt, forKey: GROUP_DEFAULT_NETWORK_TCP_KEEP_CNT) + groupDefaults.set(tcpKeepAlive.unackedDataTimeout == true, forKey: GROUP_DEFAULT_NETWORK_TCP_UNACKED_DATA_TIMEOUT) } else { groupDefaults.set(false, forKey: GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index e3bea95c03..25285f0970 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -5398,11 +5398,14 @@ enum class TransportSessionMode { data class KeepAliveOpts( val keepIdle: Int, // seconds val keepIntvl: Int, // seconds - val keepCnt: Int // times + val keepCnt: Int, // times + // a connection with unacknowledged data gets no keep-alive probes, and the OS fails it only when it gives + // up retransmitting - ~15 minutes; when this is on it is failed after the keep-alive detection time instead + val unackedDataTimeout: Boolean? = null ) { companion object { val defaults: KeepAliveOpts = - KeepAliveOpts(keepIdle = 30, keepIntvl = 15, keepCnt = 4) + KeepAliveOpts(keepIdle = 30, keepIntvl = 15, keepCnt = 4, unackedDataTimeout = null) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt index 42746006a3..48d319b4fd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/AdvancedNetworkSettings.kt @@ -61,14 +61,17 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (@Composable ModalData.() - val networkTCPKeepIdle: MutableState val networkTCPKeepIntvl: MutableState val networkTCPKeepCnt: MutableState + val networkTCPUserTimeout: MutableState if (currentCfgVal.tcpKeepAlive != null) { networkTCPKeepIdle = remember { mutableStateOf(currentCfgVal.tcpKeepAlive.keepIdle) } networkTCPKeepIntvl = remember { mutableStateOf(currentCfgVal.tcpKeepAlive.keepIntvl) } networkTCPKeepCnt = remember { mutableStateOf(currentCfgVal.tcpKeepAlive.keepCnt) } + networkTCPUserTimeout = remember { mutableStateOf(currentCfgVal.tcpKeepAlive.unackedDataTimeout == true) } } else { networkTCPKeepIdle = remember { mutableStateOf(KeepAliveOpts.defaults.keepIdle) } networkTCPKeepIntvl = remember { mutableStateOf(KeepAliveOpts.defaults.keepIntvl) } networkTCPKeepCnt = remember { mutableStateOf(KeepAliveOpts.defaults.keepCnt) } + networkTCPUserTimeout = remember { mutableStateOf(KeepAliveOpts.defaults.unackedDataTimeout == true) } } fun buildCfg(): NetCfg { @@ -77,7 +80,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (@Composable ModalData.() - val keepIdle = networkTCPKeepIdle.value val keepIntvl = networkTCPKeepIntvl.value val keepCnt = networkTCPKeepCnt.value - KeepAliveOpts(keepIdle = keepIdle, keepIntvl = keepIntvl, keepCnt = keepCnt) + KeepAliveOpts(keepIdle = keepIdle, keepIntvl = keepIntvl, keepCnt = keepCnt, unackedDataTimeout = networkTCPUserTimeout.value) } else { null } @@ -123,10 +126,12 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (@Composable ModalData.() - networkTCPKeepIdle.value = cfg.tcpKeepAlive.keepIdle networkTCPKeepIntvl.value = cfg.tcpKeepAlive.keepIntvl networkTCPKeepCnt.value = cfg.tcpKeepAlive.keepCnt + networkTCPUserTimeout.value = cfg.tcpKeepAlive.unackedDataTimeout == true } else { networkTCPKeepIdle.value = KeepAliveOpts.defaults.keepIdle networkTCPKeepIntvl.value = KeepAliveOpts.defaults.keepIntvl networkTCPKeepCnt.value = KeepAliveOpts.defaults.keepCnt + networkTCPUserTimeout.value = KeepAliveOpts.defaults.unackedDataTimeout == true } } @@ -180,6 +185,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (@Composable ModalData.() - networkTCPKeepIdle, networkTCPKeepIntvl, networkTCPKeepCnt, + networkTCPUserTimeout, updateSessionMode = { sessionMode.value = it; currentCfg.value = currentCfg.value.copy(sessionMode = it) }, updateSMPProxyMode = { smpProxyMode.value = it; currentCfg.value = currentCfg.value.copy(smpProxyMode = it) }, updateSMPProxyFallback = { smpProxyFallback.value = it; currentCfg.value = currentCfg.value.copy(smpProxyFallback = it) }, @@ -215,6 +221,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (@Composable ModalData.() - networkTCPKeepIdle: MutableState, networkTCPKeepIntvl: MutableState, networkTCPKeepCnt: MutableState, + networkTCPUserTimeout: MutableState, updateSessionMode: (TransportSessionMode) -> Unit, updateSMPProxyMode: (SMPProxyMode) -> Unit, updateSMPProxyFallback: (SMPProxyFallback) -> Unit, @@ -318,6 +325,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (@Composable ModalData.() - SectionItemView { IntSettingRow("TCP_KEEPCNT", networkTCPKeepCnt, listOf(1, 2, 4, 6, 8), "") } + PreferenceToggle("TCP_USER_TIMEOUT", checked = networkTCPUserTimeout.value) { networkTCPUserTimeout.value = it } } else { SectionItemView { Text("TCP_KEEPIDLE", color = MaterialTheme.colors.secondary) @@ -328,6 +336,9 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (@Composable ModalData.() - SectionItemView { Text("TCP_KEEPCNT", color = MaterialTheme.colors.secondary) } + SectionItemView { + Text("TCP_USER_TIMEOUT", color = MaterialTheme.colors.secondary) + } } } } @@ -595,6 +606,7 @@ fun PreviewAdvancedNetworkSettingsLayout() { networkTCPKeepIdle = remember { mutableStateOf(10) }, networkTCPKeepIntvl = remember { mutableStateOf(10) }, networkTCPKeepCnt = remember { mutableStateOf(10) }, + networkTCPUserTimeout = remember { mutableStateOf(false) }, updateSessionMode = {}, updateSMPProxyMode = {}, updateSMPProxyFallback = {},