ios: advanced network settings (#873)

* ios: advanced network settings

* save network config

* update network settins, set in NSE

* update UI, update simplexmq

* show advanced network settings only with dev tools on
This commit is contained in:
Evgeny Poberezkin
2022-08-03 12:36:51 +01:00
committed by GitHub
parent e39f9bc251
commit 0fe7e64989
12 changed files with 322 additions and 11 deletions

View File

@@ -296,8 +296,10 @@ func getNetworkConfig() async throws -> NetCfg? {
throw r
}
func setNetworkConfig(cfg: NetCfg) async throws {
try await sendCommandOkResp(.apiSetNetworkConfig(networkConfig: cfg))
func setNetworkConfig(_ cfg: NetCfg) throws {
let r = chatSendCmdSync(.apiSetNetworkConfig(networkConfig: cfg))
if case .cmdOk = r { return }
throw r
}
func apiContactInfo(contactId: Int64) async throws -> ConnectionStats? {
@@ -657,6 +659,7 @@ func initializeChat(start: Bool) throws {
func startChat() throws {
logger.debug("startChat")
let m = ChatModel.shared
try setNetworkConfig(getNetCfg())
let justStarted = try apiStartChat()
if justStarted {
m.userAddress = try apiGetUserAddress()

View File

@@ -26,6 +26,7 @@ struct SimpleXApp: App {
hs_init(0, nil)
UserDefaults.standard.register(defaults: appDefaults)
setGroupDefaults()
registerGroupDefaults()
setDbContainer()
BGManager.shared.register()
NtfManager.shared.registerCategories()

View File

@@ -0,0 +1,164 @@
//
// AdvancedNetworkSettings.swift
// SimpleX (iOS)
//
// Created by Evgeny on 02/08/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
private let secondsLabel = NSLocalizedString("sec", comment: "network option")
enum NetworkSettingsAlert: Identifiable {
case update
case error(err: String)
var id: String {
switch self {
case .update: return "update"
case let .error(err): return "error \(err)"
}
}
}
struct AdvancedNetworkSettings: View {
@State private var netCfg = NetCfg.defaults
@State private var currentNetCfg = NetCfg.defaults
@State private var cfgLoaded = false
@State private var enableKeepAlive = true
@State private var keepAliveOpts = KeepAliveOpts.defaults
@State private var showSettingsAlert: NetworkSettingsAlert?
var body: some View {
VStack {
List {
Section {
Button {
updateNetCfgView(NetCfg.defaults)
showSettingsAlert = .update
} label: {
Text("Reset to defaults")
}
.disabled(currentNetCfg == NetCfg.defaults)
Button {
updateNetCfgView(NetCfg.proxyDefaults)
showSettingsAlert = .update
} label: {
Text("Set timeouts for proxy/VPN")
}
.disabled(currentNetCfg == NetCfg.proxyDefaults)
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [2_500000, 5_000000, 7_500000, 10_000000, 15_000000, 20_000000], label: secondsLabel)
timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [1_500000, 3_000000, 5_000000, 7_000000, 10_000000, 15_000000], label: secondsLabel)
timeoutSettingPicker("PING interval", selection: $netCfg.smpPingInterval, values: [120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000], label: secondsLabel)
Toggle("Enable TCP keep-alive", isOn: $enableKeepAlive)
if enableKeepAlive {
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: "")
} else {
Group {
Text("TCP_KEEPIDLE")
Text("TCP_KEEPINTVL")
Text("TCP_KEEPCNT")
}
.foregroundColor(.secondary)
}
} header: {
Text("")
} footer: {
HStack {
Button {
updateNetCfgView(currentNetCfg)
} label: {
Label("Revert", systemImage: "arrow.counterclockwise").font(.callout)
}
Spacer()
Button {
showSettingsAlert = .update
} label: {
Label("Save", systemImage: "checkmark").font(.callout)
}
}
.disabled(netCfg == currentNetCfg)
}
}
}
.onChange(of: keepAliveOpts) { opts in
netCfg.tcpKeepAlive = keepAliveOpts
}
.onChange(of: enableKeepAlive) { on in
netCfg.tcpKeepAlive = on ? (currentNetCfg.tcpKeepAlive ?? KeepAliveOpts.defaults) : nil
}
.onAppear {
if cfgLoaded { return }
cfgLoaded = true
currentNetCfg = getNetCfg()
updateNetCfgView(currentNetCfg)
}
.alert(item: $showSettingsAlert) { a in
switch a {
case .update:
return Alert(
title: Text("Update network settings?"),
message: Text("Updating settings will re-connect the client to all servers."),
primaryButton: .default(Text("Ok")) {
saveNetCfg()
},
secondaryButton: .cancel()
)
case let .error(err):
return Alert(
title: Text("Error updating settings"),
message: Text(err)
)
}
}
}
private func updateNetCfgView(_ cfg: NetCfg) {
netCfg = cfg
enableKeepAlive = currentNetCfg.enableKeepAlive
keepAliveOpts = currentNetCfg.tcpKeepAlive ?? KeepAliveOpts.defaults
}
private func saveNetCfg() {
do {
try setNetworkConfig(netCfg)
currentNetCfg = netCfg
setNetCfg(netCfg)
} catch let error {
let err = responseError(error)
showSettingsAlert = .error(err: err)
logger.error("\(err)")
}
}
private func intSettingPicker(_ title: LocalizedStringKey, selection: Binding<Int>, values: [Int], label: String) -> some View {
Picker(title, selection: selection) {
ForEach(values, id: \.self) { value in
Text("\(value) \(label)")
}
}
}
private func timeoutSettingPicker(_ title: LocalizedStringKey, selection: Binding<Int>, values: [Int], label: String) -> some View {
Picker(title, selection: selection) {
ForEach(values, id: \.self) { value in
Text("\(String(format: "%g", (Double(value) / 1000000))) \(secondsLabel)")
}
}
}
}
struct AdvancedNetworkSettings_Previews: PreviewProvider {
static var previews: some View {
AdvancedNetworkSettings()
}
}

View File

@@ -0,0 +1,43 @@
//
// NetworkServersView.swift
// SimpleX (iOS)
//
// Created by Evgeny on 02/08/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct NetworkAndServers: View {
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
var body: some View {
VStack {
List {
Section("") {
NavigationLink {
SMPServers()
.navigationTitle("Your SMP servers")
} label: {
settingsRow("server.rack") { Text("SMP servers") }
}
if developerTools {
NavigationLink {
AdvancedNetworkSettings()
.navigationTitle("Network settings")
} label: {
settingsRow("app.connected.to.app.below.fill") { Text("Advanced network settings") }
}
}
}
}
}
}
}
struct NetworkServersView_Previews: PreviewProvider {
static var previews: some View {
NetworkAndServers()
}
}

View File

@@ -121,10 +121,10 @@ struct SettingsView: View {
Toggle("Show pending connections", isOn: $pendingConnections)
}
NavigationLink {
SMPServers()
.navigationTitle("Your SMP servers")
NetworkAndServers()
.navigationTitle("Network & servers")
} label: {
settingsRow("server.rack") { Text("SMP servers") }
settingsRow("externaldrive.connected.to.line.below") { Text("Network & servers") }
}
}
.disabled(chatModel.chatRunning != true)

View File

@@ -150,6 +150,7 @@ func startChat() -> User? {
if let user = apiGetActiveUser() {
logger.debug("active user \(String(describing: user))")
do {
try setNetworkConfig(getNetCfg())
let justStarted = try apiStartChat()
if justStarted {
try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path)
@@ -257,6 +258,12 @@ func apiReceiveFile(fileId: Int64) -> AChatItem? {
return nil
}
func setNetworkConfig(_ cfg: NetCfg) throws {
let r = sendSimpleXCmd(.apiSetNetworkConfig(networkConfig: cfg))
if case .cmdOk = r { return }
throw r
}
struct NtfMessages {
var connEntity: ConnectionEntity?
var msgTs: Date?

View File

@@ -56,6 +56,8 @@
5C9C2DA228929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9C2D9D28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a */; };
5C9C2DA328929B6900CC63B1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9C2D9E28929B6900CC63B1 /* libgmpxx.a */; };
5C9C2DA52894777E00CC63B1 /* GroupProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9C2DA42894777E00CC63B1 /* GroupProfileView.swift */; };
5C9C2DA7289957AE00CC63B1 /* AdvancedNetworkSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */; };
5C9C2DA92899DA6F00CC63B1 /* NetworkAndServers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */; };
5C9D13A3282187BB00AB8B43 /* WebRTC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9D13A2282187BB00AB8B43 /* WebRTC.swift */; };
5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */; };
5CA059DC279559F40002BEB4 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059DB279559F40002BEB4 /* Tests_iOS.swift */; };
@@ -236,6 +238,8 @@
5C9C2D9D28929B6900CC63B1 /* libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-FNUbBjLYHjnDjt6ldpTolw-ghc8.10.7.a"; sourceTree = "<group>"; };
5C9C2D9E28929B6900CC63B1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C9C2DA42894777E00CC63B1 /* GroupProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupProfileView.swift; sourceTree = "<group>"; };
5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedNetworkSettings.swift; sourceTree = "<group>"; };
5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkAndServers.swift; sourceTree = "<group>"; };
5C9D13A2282187BB00AB8B43 /* WebRTC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTC.swift; sourceTree = "<group>"; };
5C9FD96A27A56D4D0075386C /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageView.swift; sourceTree = "<group>"; };
@@ -531,6 +535,8 @@
5CB924D327A853F100ACCCDD /* SettingsButton.swift */,
5CB924D627A8563F00ACCCDD /* SettingsView.swift */,
5CB346E62868D76D001FD2EF /* NotificationsView.swift */,
5C9C2DA6289957AE00CC63B1 /* AdvancedNetworkSettings.swift */,
5C9C2DA82899DA6F00CC63B1 /* NetworkAndServers.swift */,
5C05DF522840AA1D00C683F9 /* CallSettings.swift */,
5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */,
5CB924E327A8683A00ACCCDD /* UserAddress.swift */,
@@ -886,6 +892,7 @@
5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */,
6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */,
5C5F2B6D27EBC3FE006A9D5F /* ImagePicker.swift in Sources */,
5C9C2DA92899DA6F00CC63B1 /* NetworkAndServers.swift in Sources */,
5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */,
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */,
5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */,
@@ -912,6 +919,7 @@
5C3F1D5A2844B4DE00EC8A82 /* ExperimentalFeaturesView.swift in Sources */,
5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */,
64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */,
5C9C2DA7289957AE00CC63B1 /* AdvancedNetworkSettings.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -500,9 +500,38 @@ public struct ArchiveConfig: Encodable {
}
}
public struct NetCfg: Codable {
var socksProxy: String? = nil
var tcpTimeout: Int
public struct NetCfg: Codable, Equatable {
public var socksProxy: String? = nil
public var tcpConnectTimeout: Int // microseconds
public var tcpTimeout: Int // microseconds
public var tcpKeepAlive: KeepAliveOpts?
public var smpPingInterval: Int // microseconds
public static let defaults: NetCfg = NetCfg(
socksProxy: nil,
tcpConnectTimeout: 7_500_000,
tcpTimeout: 5_000_000,
tcpKeepAlive: KeepAliveOpts.defaults,
smpPingInterval: 600_000_000
)
public static let proxyDefaults: NetCfg = NetCfg(
socksProxy: nil,
tcpConnectTimeout: 15_000_000,
tcpTimeout: 10_000_000,
tcpKeepAlive: KeepAliveOpts.defaults,
smpPingInterval: 600_000_000
)
public var enableKeepAlive: Bool { tcpKeepAlive != nil }
}
public struct KeepAliveOpts: Codable, Equatable {
public var keepIdle: Int // seconds
public var keepIntvl: Int // seconds
public var keepCnt: Int // times
public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4)
}
public struct ConnectionStats: Codable {

View File

@@ -15,11 +15,30 @@ public let GROUP_DEFAULT_CHAT_LAST_START = "chatLastStart"
let GROUP_DEFAULT_NTF_PREVIEW_MODE = "ntfPreviewMode"
let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages"
let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount"
let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT = "networkTCPConnectTimeout"
let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT = "networkTCPTimeout"
let GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL = "networkSMPPingInterval"
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 APP_GROUP_NAME = "group.chat.simplex.app"
public let groupDefaults = UserDefaults(suiteName: APP_GROUP_NAME)!
public func registerGroupDefaults() {
groupDefaults.register(defaults: [
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,
GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE: NetCfg.defaults.enableKeepAlive,
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
])
}
public enum AppState: String {
case active
case bgRefresh
@@ -136,3 +155,40 @@ public class Default<T> {
defaults.synchronize()
}
}
public func getNetCfg() -> NetCfg {
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)
let enableKeepAlive = groupDefaults.bool(forKey: GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE)
var tcpKeepAlive: KeepAliveOpts?
if enableKeepAlive {
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)
} else {
tcpKeepAlive = nil
}
return NetCfg(
tcpConnectTimeout: tcpConnectTimeout,
tcpTimeout: tcpTimeout,
tcpKeepAlive: tcpKeepAlive,
smpPingInterval: smpPingInterval
)
}
public func setNetCfg(_ cfg: NetCfg) {
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)
if let tcpKeepAlive = cfg.tcpKeepAlive {
groupDefaults.set(true, forKey: GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE)
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)
} else {
groupDefaults.set(false, forKey: GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE)
}
groupDefaults.synchronize()
}

View File

@@ -5,7 +5,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
tag: e9db0a1162a0858b0acb1880a6ef261242adc356
tag: 7d99c4b35cf2dc531219bc83146b714c9bae429c
source-repository-package
type: git

View File

@@ -1,5 +1,5 @@
{
"https://github.com/simplex-chat/simplexmq.git"."e9db0a1162a0858b0acb1880a6ef261242adc356" = "1klakvm93qsgwbg00xbq6s32sqcqww8x4ln4m8bjss0mracgrnki";
"https://github.com/simplex-chat/simplexmq.git"."7d99c4b35cf2dc531219bc83146b714c9bae429c" = "037a0p7cdi4lrsbh21b4gldwdcj1sk8wz4wjsph6bnv2jyid11gk";
"https://github.com/simplex-chat/aeson.git"."3eb66f9a68f103b5f1489382aad89f5712a64db7" = "0kilkx59fl6c3qy3kjczqvm8c3f4n3p0bdk9biyflf51ljnzp4yp";
"https://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj";
"https://github.com/zw3rk/android-support.git"."3c3a5ab0b8b137a072c98d3d0937cbdc96918ddb" = "1r6jyxbim3dsvrmakqfyxbd6ms6miaghpbwyl0sr6dzwpgaprz97";

View File

@@ -49,7 +49,7 @@ extra-deps:
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
# - ../simplexmq
- github: simplex-chat/simplexmq
commit: e9db0a1162a0858b0acb1880a6ef261242adc356
commit: 7d99c4b35cf2dc531219bc83146b714c9bae429c
# - terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977
- github: simplex-chat/aeson
commit: 3eb66f9a68f103b5f1489382aad89f5712a64db7