mobile: UI to choose transport isolation mode (#1813)

* mobile: UI to choose transport isolation mode

* typo

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* ios: update alerts

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
This commit is contained in:
Evgeny Poberezkin
2023-01-21 16:05:09 +00:00
committed by GitHub
parent c337a6888d
commit 8bec0004cc
7 changed files with 200 additions and 29 deletions

View File

@@ -109,6 +109,18 @@ class AppPreferences(val context: Context) {
val chatLastStart = mkDatePreference(SHARED_PREFS_CHAT_LAST_START, null)
val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false)
val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false)
private val _networkSessionMode = mkStrPreference(SHARED_PREFS_NETWORK_SESSION_MODE, TransportSessionMode.default.name)
val networkSessionMode: SharedPreference<TransportSessionMode> = SharedPreference(
get = fun(): TransportSessionMode {
val value = _networkSessionMode.get() ?: return TransportSessionMode.default
return try {
TransportSessionMode.valueOf(value)
} catch (e: Error) {
TransportSessionMode.default
}
},
set = fun(mode: TransportSessionMode) { _networkSessionMode.set(mode.name) }
)
val networkHostMode = mkStrPreference(SHARED_PREFS_NETWORK_HOST_MODE, HostMode.OnionViaSocks.name)
val networkRequiredHostMode = mkBoolPreference(SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE, false)
val networkTCPConnectTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT, NetCfg.defaults.tcpConnectTimeout, NetCfg.proxyDefaults.tcpConnectTimeout)
@@ -206,6 +218,7 @@ class AppPreferences(val context: Context) {
private const val SHARED_PREFS_CHAT_LAST_START = "ChatLastStart"
private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools"
private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy"
private const val SHARED_PREFS_NETWORK_SESSION_MODE = "NetworkSessionMode"
private const val SHARED_PREFS_NETWORK_HOST_MODE = "NetworkHostMode"
private const val SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE = "NetworkRequiredHostMode"
private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT = "NetworkTCPConnectTimeout"
@@ -254,6 +267,8 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
apiSetNetworkConfig(getNetCfg())
val justStarted = apiStartChat()
if (justStarted) {
chatModel.currentUser.value = user
chatModel.userCreated.value = true
apiSetFilesFolder(getAppFilesDirectory(appContext))
apiSetIncognito(chatModel.incognito.value)
chatModel.userAddress.value = apiGetUserAddress()
@@ -263,8 +278,6 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
chatModel.chatItemTTL.value = getChatItemTTL()
val chats = apiGetChats()
chatModel.updateChats(chats)
chatModel.currentUser.value = user
chatModel.userCreated.value = true
chatModel.onboardingStage.value = OnboardingStage.OnboardingComplete
chatModel.controller.appPrefs.chatLastStart.set(Clock.System.now())
chatModel.chatRunning.value = true
@@ -1533,6 +1546,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
val socksProxy = if (useSocksProxy) ":9050" else null
val hostMode = HostMode.valueOf(appPrefs.networkHostMode.get()!!)
val requiredHostMode = appPrefs.networkRequiredHostMode.get()
val sessionMode = appPrefs.networkSessionMode.get()
val tcpConnectTimeout = appPrefs.networkTCPConnectTimeout.get()
val tcpTimeout = appPrefs.networkTCPTimeout.get()
val smpPingInterval = appPrefs.networkSMPPingInterval.get()
@@ -1550,6 +1564,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
socksProxy = socksProxy,
hostMode = hostMode,
requiredHostMode = requiredHostMode,
sessionMode = sessionMode,
tcpConnectTimeout = tcpConnectTimeout,
tcpTimeout = tcpTimeout,
tcpKeepAlive = tcpKeepAlive,
@@ -1562,6 +1577,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
appPrefs.networkUseSocksProxy.set(cfg.useSocksProxy)
appPrefs.networkHostMode.set(cfg.hostMode.name)
appPrefs.networkRequiredHostMode.set(cfg.requiredHostMode)
appPrefs.networkSessionMode.set(cfg.sessionMode)
appPrefs.networkTCPConnectTimeout.set(cfg.tcpConnectTimeout)
appPrefs.networkTCPTimeout.set(cfg.tcpTimeout)
appPrefs.networkSMPPingInterval.set(cfg.smpPingInterval)
@@ -2000,9 +2016,10 @@ data class ParsedServerAddress (
@Serializable
data class NetCfg(
val socksProxy: String? = null,
val hostMode: HostMode = HostMode.OnionViaSocks,
val requiredHostMode: Boolean = false,
val socksProxy: String?,
val hostMode: HostMode,
val requiredHostMode: Boolean,
val sessionMode: TransportSessionMode,
val tcpConnectTimeout: Long, // microseconds
val tcpTimeout: Long, // microseconds
val tcpKeepAlive: KeepAliveOpts?,
@@ -2017,6 +2034,9 @@ data class NetCfg(
val defaults: NetCfg =
NetCfg(
socksProxy = null,
hostMode = HostMode.OnionViaSocks,
requiredHostMode = false,
sessionMode = TransportSessionMode.User,
tcpConnectTimeout = 10_000_000,
tcpTimeout = 7_000_000,
tcpKeepAlive = KeepAliveOpts.defaults,
@@ -2027,6 +2047,9 @@ data class NetCfg(
val proxyDefaults: NetCfg =
NetCfg(
socksProxy = ":9050",
hostMode = HostMode.OnionViaSocks,
requiredHostMode = false,
sessionMode = TransportSessionMode.User,
tcpConnectTimeout = 20_000_000,
tcpTimeout = 15_000_000,
tcpKeepAlive = KeepAliveOpts.defaults,
@@ -2063,6 +2086,16 @@ enum class HostMode {
@SerialName("public") Public;
}
@Serializable
enum class TransportSessionMode {
@SerialName("user") User,
@SerialName("entity") Entity;
companion object {
val default = User
}
}
@Serializable
data class KeepAliveOpts(
val keepIdle: Int, // seconds

View File

@@ -60,6 +60,9 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
}
return NetCfg(
socksProxy = currentCfg.value.socksProxy,
hostMode = currentCfg.value.hostMode,
requiredHostMode = currentCfg.value.requiredHostMode,
sessionMode = currentCfg.value.sessionMode,
tcpConnectTimeout = networkTCPConnectTimeout.value,
tcpTimeout = networkTCPTimeout.value,
tcpKeepAlive = tcpKeepAlive,

View File

@@ -31,6 +31,7 @@ fun NetworkAndServersView(
val networkUseSocksProxy: MutableState<Boolean> = remember { mutableStateOf(netCfg.useSocksProxy) }
val developerTools = chatModel.controller.appPrefs.developerTools.get()
val onionHosts = remember { mutableStateOf(netCfg.onionHosts) }
val sessionMode = remember { mutableStateOf(netCfg.sessionMode) }
LaunchedEffect(Unit) {
chatModel.userSMPServersUnsaved.value = null
@@ -40,6 +41,7 @@ fun NetworkAndServersView(
developerTools = developerTools,
networkUseSocksProxy = networkUseSocksProxy,
onionHosts = onionHosts,
sessionMode = sessionMode,
showModal = showModal,
showSettingsModal = showSettingsModal,
toggleSocksProxy = { enable ->
@@ -82,9 +84,13 @@ fun NetworkAndServersView(
OnionHosts.PREFER -> generalGetString(R.string.network_use_onion_hosts_prefer_desc_in_alert)
OnionHosts.REQUIRED -> generalGetString(R.string.network_use_onion_hosts_required_desc_in_alert)
}
updateOnionHostsDialog(startsWith, onDismiss = {
onionHosts.value = prevValue
}) {
updateNetworkSettingsDialog(
title = generalGetString(R.string.update_onion_hosts_settings_question),
startsWith,
onDismiss = {
onionHosts.value = prevValue
}
) {
withApi {
val newCfg = chatModel.controller.getNetCfg().withOnionHosts(it)
val res = chatModel.controller.apiSetNetworkConfig(newCfg)
@@ -96,6 +102,31 @@ fun NetworkAndServersView(
}
}
}
},
updateSessionMode = {
if (sessionMode.value == it) return@NetworkAndServersLayout
val prevValue = sessionMode.value
sessionMode.value = it
val startsWith = when (it) {
TransportSessionMode.User -> generalGetString(R.string.network_session_mode_user_description)
TransportSessionMode.Entity -> generalGetString(R.string.network_session_mode_entity_description)
}
updateNetworkSettingsDialog(
title = generalGetString(R.string.update_network_session_mode_question),
startsWith,
onDismiss = { sessionMode.value = prevValue }
) {
withApi {
val newCfg = chatModel.controller.getNetCfg().copy(sessionMode = it)
val res = chatModel.controller.apiSetNetworkConfig(newCfg)
if (res) {
chatModel.controller.setNetCfg(newCfg)
sessionMode.value = it
} else {
sessionMode.value = prevValue
}
}
}
}
)
}
@@ -104,10 +135,12 @@ fun NetworkAndServersView(
developerTools: Boolean,
networkUseSocksProxy: MutableState<Boolean>,
onionHosts: MutableState<OnionHosts>,
sessionMode: MutableState<TransportSessionMode>,
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
toggleSocksProxy: (Boolean) -> Unit,
useOnion: (OnionHosts) -> Unit,
updateSessionMode: (TransportSessionMode) -> Unit,
) {
Column(
Modifier.fillMaxWidth(),
@@ -124,6 +157,10 @@ fun NetworkAndServersView(
SectionDivider()
UseOnionHosts(onionHosts, networkUseSocksProxy, showSettingsModal, useOnion)
SectionDivider()
if (developerTools) {
SessionModePicker(sessionMode, showSettingsModal, updateSessionMode)
SectionDivider()
}
SettingsActionItem(Icons.Outlined.Cable, stringResource(R.string.network_settings), showSettingsModal { AdvancedNetworkSettingsView(it) })
}
Spacer(Modifier.height(8.dp))
@@ -183,7 +220,6 @@ private fun UseOnionHosts(
}
}
val onSelected = showModal {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
@@ -203,14 +239,47 @@ private fun UseOnionHosts(
)
}
private fun updateOnionHostsDialog(
@Composable
private fun SessionModePicker(
sessionMode: MutableState<TransportSessionMode>,
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
updateSessionMode: (TransportSessionMode) -> Unit,
) {
val values = remember {
TransportSessionMode.values().map {
when (it) {
TransportSessionMode.User -> ValueTitleDesc(TransportSessionMode.User, generalGetString(R.string.network_session_mode_user), generalGetString(R.string.network_session_mode_user_description))
TransportSessionMode.Entity -> ValueTitleDesc(TransportSessionMode.Entity, generalGetString(R.string.network_session_mode_entity), generalGetString(R.string.network_session_mode_entity_description))
}
}
}
SectionItemWithValue(
generalGetString(R.string.network_session_mode_transport_isolation),
sessionMode,
values,
icon = Icons.Outlined.SafetyDivider,
onSelected = showModal {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.network_session_mode_transport_isolation))
SectionViewSelectable(null, sessionMode, values, updateSessionMode)
}
}
)
}
private fun updateNetworkSettingsDialog(
title: String,
startsWith: String = "",
message: String = generalGetString(R.string.updating_settings_will_reconnect_client_to_all_servers),
onDismiss: () -> Unit,
onConfirm: () -> Unit
) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.update_onion_hosts_settings_question),
title = title,
text = startsWith + "\n\n" + message,
confirmText = generalGetString(R.string.update_network_settings_confirmation),
onDismiss = onDismiss,
@@ -230,7 +299,9 @@ fun PreviewNetworkAndServersLayout() {
showSettingsModal = { {} },
toggleSocksProxy = {},
onionHosts = remember { mutableStateOf(OnionHosts.PREFER) },
sessionMode = remember { mutableStateOf(TransportSessionMode.User) },
useOnion = {},
updateSessionMode = {},
)
}
}

View File

@@ -478,6 +478,12 @@
<string name="network_use_onion_hosts_prefer_desc_in_alert">Onion hosts will be used when available.</string>
<string name="network_use_onion_hosts_no_desc_in_alert">Onion hosts will not be used.</string>
<string name="network_use_onion_hosts_required_desc_in_alert">Onion hosts will be required for connection.</string>
<string name="network_session_mode_transport_isolation">Transport isolation</string>
<string name="network_session_mode_user">Chat profile</string>
<string name="network_session_mode_entity">Connection</string>
<string name="network_session_mode_user_description">A separate TCP connection (and SOCKS credential) will be used <b>for each chat profile you have in the app</b>.</string>
<string name="network_session_mode_entity_description">A separate TCP connection (and SOCKS credential) will be used <b>for each contact and group member</b>.\n<b>Please note</b>: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail.</string>
<string name="update_network_session_mode_question">Update transport isolation mode?</string>
<string name="appearance_settings">Appearance</string>
<!-- Address Items - UserAddressView.kt -->

View File

@@ -9,13 +9,15 @@
import SwiftUI
import SimpleXChat
enum OnionHostsAlert: Identifiable {
case update(hosts: OnionHosts)
private enum NetworkAlert: Identifiable {
case updateOnionHosts(hosts: OnionHosts)
case updateSessionMode(mode: TransportSessionMode)
case error(err: String)
var id: String {
switch self {
case let .update(hosts): return "update \(hosts)"
case let .updateOnionHosts(hosts): return "updateOnionHosts \(hosts)"
case let .updateSessionMode(mode): return "updateSessionMode \(mode)"
case let .error(err): return "error \(err)"
}
}
@@ -27,7 +29,8 @@ struct NetworkAndServers: View {
@State private var currentNetCfg = NetCfg.defaults
@State private var netCfg = NetCfg.defaults
@State private var onionHosts: OnionHosts = .no
@State private var showOnionHostsAlert: OnionHostsAlert?
@State private var sessionMode: TransportSessionMode = .user
@State private var alert: NetworkAlert?
var body: some View {
VStack {
@@ -45,6 +48,12 @@ struct NetworkAndServers: View {
}
.frame(height: 36)
if developerTools {
Picker("Transport isolation", selection: $sessionMode) {
ForEach(TransportSessionMode.values, id: \.self) { Text($0.text) }
}
}
NavigationLink {
AdvancedNetworkSettings()
.navigationTitle("Network settings")
@@ -75,17 +84,37 @@ struct NetworkAndServers: View {
}
.onChange(of: onionHosts) { _ in
if onionHosts != OnionHosts(netCfg: currentNetCfg) {
showOnionHostsAlert = .update(hosts: onionHosts)
alert = .updateOnionHosts(hosts: onionHosts)
}
}
.alert(item: $showOnionHostsAlert) { a in
.onChange(of: sessionMode) { _ in
if sessionMode != netCfg.sessionMode {
alert = .updateSessionMode(mode: sessionMode)
}
}
.alert(item: $alert) { a in
switch a {
case let .update(hosts):
case let .updateOnionHosts(hosts):
return Alert(
title: Text("Update .onion hosts setting?"),
message: Text(onionHostsInfo()) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
message: Text(onionHostsInfo(hosts)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
primaryButton: .default(Text("Ok")) {
saveNetCfg(hosts)
let (hostMode, requiredHostMode) = hosts.hostMode
netCfg.hostMode = hostMode
netCfg.requiredHostMode = requiredHostMode
saveNetCfg()
},
secondaryButton: .cancel() {
resetNetCfgView()
}
)
case let .updateSessionMode(mode):
return Alert(
title: Text("Update transport isolation mode?"),
message: Text(sessionModeInfo(mode)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."),
primaryButton: .default(Text("Ok")) {
netCfg.sessionMode = mode
saveNetCfg()
},
secondaryButton: .cancel() {
resetNetCfgView()
@@ -100,11 +129,8 @@ struct NetworkAndServers: View {
}
}
private func saveNetCfg(_ hosts: OnionHosts) {
private func saveNetCfg() {
do {
let (hostMode, requiredHostMode) = hosts.hostMode
netCfg.hostMode = hostMode
netCfg.requiredHostMode = requiredHostMode
let def = netCfg.hostMode == .onionHost ? NetCfg.proxyDefaults : NetCfg.defaults
netCfg.tcpConnectTimeout = def.tcpConnectTimeout
netCfg.tcpTimeout = def.tcpTimeout
@@ -114,7 +140,7 @@ struct NetworkAndServers: View {
} catch let error {
let err = responseError(error)
resetNetCfgView()
showOnionHostsAlert = .error(err: err)
alert = .error(err: err)
logger.error("\(err)")
}
}
@@ -122,15 +148,23 @@ struct NetworkAndServers: View {
private func resetNetCfgView() {
netCfg = currentNetCfg
onionHosts = OnionHosts(netCfg: netCfg)
sessionMode = netCfg.sessionMode
}
private func onionHostsInfo() -> LocalizedStringKey {
switch onionHosts {
private func onionHostsInfo(_ hosts: OnionHosts) -> LocalizedStringKey {
switch hosts {
case .no: return "Onion hosts will not be used."
case .prefer: return "Onion hosts will be used when available. Requires enabling VPN."
case .require: return "Onion hosts will be required for connection. Requires enabling VPN."
}
}
private func sessionModeInfo(_ mode: TransportSessionMode) -> LocalizedStringKey {
switch mode {
case .user: return "A separate TCP connection will be used **for each chat profile you have in the app**."
case .entity: return "A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail."
}
}
}
struct NetworkServersView_Previews: PreviewProvider {

View File

@@ -824,7 +824,7 @@ public struct NetCfg: Codable, Equatable {
public var socksProxy: String? = nil
public var hostMode: HostMode = .publicHost
public var requiredHostMode = true
public var sessionMode = TransportSessionMode.user
public var sessionMode: TransportSessionMode
public var tcpConnectTimeout: Int // microseconds
public var tcpTimeout: Int // microseconds
public var tcpKeepAlive: KeepAliveOpts?
@@ -834,6 +834,7 @@ public struct NetCfg: Codable, Equatable {
public static let defaults: NetCfg = NetCfg(
socksProxy: nil,
sessionMode: TransportSessionMode.user,
tcpConnectTimeout: 10_000_000,
tcpTimeout: 7_000_000,
tcpKeepAlive: KeepAliveOpts.defaults,
@@ -844,6 +845,7 @@ public struct NetCfg: Codable, Equatable {
public static let proxyDefaults: NetCfg = NetCfg(
socksProxy: nil,
sessionMode: TransportSessionMode.user,
tcpConnectTimeout: 20_000_000,
tcpTimeout: 15_000_000,
tcpKeepAlive: KeepAliveOpts.defaults,
@@ -895,9 +897,20 @@ public enum OnionHosts: String, Identifiable {
public static let values: [OnionHosts] = [.no, .prefer, .require]
}
public enum TransportSessionMode: String, Codable {
public enum TransportSessionMode: String, Codable, Identifiable {
case user
case entity
public var text: LocalizedStringKey {
switch self {
case .user: return "User profile"
case .entity: return "Connection"
}
}
public var id: TransportSessionMode { self }
public static let values: [TransportSessionMode] = [.user, .entity]
}
public struct KeepAliveOpts: Codable, Equatable {

View File

@@ -17,6 +17,7 @@ let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages"
public let GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE = "privacyTransferImagesInline"
let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount"
let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts"
let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode"
let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT = "networkTCPConnectTimeout"
let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT = "networkTCPTimeout"
let GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL = "networkSMPPingInterval"
@@ -36,6 +37,7 @@ public let groupDefaults = UserDefaults(suiteName: APP_GROUP_NAME)!
public func registerGroupDefaults() {
groupDefaults.register(defaults: [
GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS: OnionHosts.no.rawValue,
GROUP_DEFAULT_NETWORK_SESSION_MODE: TransportSessionMode.user.rawValue,
GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT: NetCfg.defaults.tcpConnectTimeout,
GROUP_DEFAULT_NETWORK_TCP_TIMEOUT: NetCfg.defaults.tcpTimeout,
GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL: NetCfg.defaults.smpPingInterval,
@@ -107,6 +109,12 @@ public let networkUseOnionHostsGroupDefault = EnumDefault<OnionHosts>(
withDefault: .no
)
public let networkSessionModeGroupDefault = EnumDefault<TransportSessionMode>(
defaults: groupDefaults,
forKey: GROUP_DEFAULT_NETWORK_SESSION_MODE,
withDefault: .user
)
public let storeDBPassphraseGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_STORE_DB_PASSPHRASE)
public let initialRandomDBPassphraseGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE)
@@ -186,6 +194,7 @@ public class Default<T> {
public func getNetCfg() -> NetCfg {
let onionHosts = networkUseOnionHostsGroupDefault.get()
let (hostMode, requiredHostMode) = onionHosts.hostMode
let sessionMode = networkSessionModeGroupDefault.get()
let tcpConnectTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT)
let tcpTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT)
let smpPingInterval = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL)
@@ -203,6 +212,7 @@ public func getNetCfg() -> NetCfg {
return NetCfg(
hostMode: hostMode,
requiredHostMode: requiredHostMode,
sessionMode: sessionMode,
tcpConnectTimeout: tcpConnectTimeout,
tcpTimeout: tcpTimeout,
tcpKeepAlive: tcpKeepAlive,
@@ -214,6 +224,7 @@ public func getNetCfg() -> NetCfg {
public func setNetCfg(_ cfg: NetCfg) {
networkUseOnionHostsGroupDefault.set(OnionHosts(netCfg: cfg))
networkSessionModeGroupDefault.set(cfg.sessionMode)
groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT)
groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT)
groupDefaults.set(cfg.smpPingInterval, forKey: GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL)