import Foundation import Postbox import TelegramApi import SwiftSignalKit import MtProtoKit import NetworkLogging #if os(iOS) import CloudData #endif import SyncCore import EncryptionProvider public enum ConnectionStatus: Equatable { case waitingForNetwork case connecting(proxyAddress: String?, proxyHasConnectionIssues: Bool) case updating(proxyAddress: String?) case online(proxyAddress: String?) } private struct MTProtoConnectionFlags: OptionSet { let rawValue: Int static let NetworkAvailable = MTProtoConnectionFlags(rawValue: 1) static let Connected = MTProtoConnectionFlags(rawValue: 2) static let UpdatingConnectionContext = MTProtoConnectionFlags(rawValue: 4) static let PerformingServiceTasks = MTProtoConnectionFlags(rawValue: 8) static let ProxyHasConnectionIssues = MTProtoConnectionFlags(rawValue: 16) } private struct MTProtoConnectionInfo: Equatable { var flags: MTProtoConnectionFlags var proxyAddress: String? } final class WrappedFunctionDescription: CustomStringConvertible { private let desc: FunctionDescription init(_ desc: FunctionDescription) { self.desc = desc } var description: String { return apiFunctionDescription(of: self.desc) } } final class WrappedShortFunctionDescription: CustomStringConvertible { private let desc: FunctionDescription init(_ desc: FunctionDescription) { self.desc = desc } var description: String { return apiShortFunctionDescription(of: self.desc) } } class WrappedRequestMetadata: NSObject { let metadata: CustomStringConvertible let tag: NetworkRequestDependencyTag? init(metadata: CustomStringConvertible, tag: NetworkRequestDependencyTag?) { self.metadata = metadata self.tag = tag } override var description: String { return self.metadata.description } } class WrappedRequestShortMetadata: NSObject { let shortMetadata: CustomStringConvertible init(shortMetadata: CustomStringConvertible) { self.shortMetadata = shortMetadata } override var description: String { return self.shortMetadata.description } } public protocol NetworkRequestDependencyTag { func shouldDependOn(other: NetworkRequestDependencyTag) -> Bool } private class MTProtoConnectionStatusDelegate: NSObject, MTProtoDelegate { var action: (MTProtoConnectionInfo) -> () = { _ in } let info = Atomic(value: MTProtoConnectionInfo(flags: [], proxyAddress: nil)) @objc func mtProtoNetworkAvailabilityChanged(_ mtProto: MTProto!, isNetworkAvailable: Bool) { self.action(self.info.modify { info in var info = info if isNetworkAvailable { info.flags = info.flags.union([.NetworkAvailable]) } else { info.flags = info.flags.subtracting([.NetworkAvailable]) } return info }) } @objc func mtProtoConnectionStateChanged(_ mtProto: MTProto!, state: MTProtoConnectionState!) { self.action(self.info.modify { info in var info = info if let state = state { if state.isConnected { info.flags.insert(.Connected) info.flags.remove(.ProxyHasConnectionIssues) } else { info.flags.remove(.Connected) if state.proxyHasConnectionIssues { info.flags.insert(.ProxyHasConnectionIssues) } else { info.flags.remove(.ProxyHasConnectionIssues) } } } else { info.flags.remove(.Connected) info.flags.remove(.ProxyHasConnectionIssues) } info.proxyAddress = state?.proxyAddress return info }) } @objc func mtProtoConnectionContextUpdateStateChanged(_ mtProto: MTProto!, isUpdatingConnectionContext: Bool) { self.action(self.info.modify { info in var info = info if isUpdatingConnectionContext { info.flags = info.flags.union([.UpdatingConnectionContext]) } else { info.flags = info.flags.subtracting([.UpdatingConnectionContext]) } return info }) } @objc func mtProtoServiceTasksStateChanged(_ mtProto: MTProto!, isPerformingServiceTasks: Bool) { self.action(self.info.modify { info in var info = info if isPerformingServiceTasks { info.flags = info.flags.union([.PerformingServiceTasks]) } else { info.flags = info.flags.subtracting([.PerformingServiceTasks]) } return info }) } } private var registeredLoggingFunctions: Void = { NetworkRegisterLoggingFunction() registerLoggingFunctions() }() private enum UsageCalculationConnection: Int32 { case cellular = 0 case wifi = 1 } private enum UsageCalculationDirection: Int32 { case incoming = 0 case outgoing = 1 } private struct UsageCalculationTag { let connection: UsageCalculationConnection let direction: UsageCalculationDirection let category: MediaResourceStatsCategory var key: Int32 { switch category { case .generic: return 0 * 4 + self.connection.rawValue * 2 + self.direction.rawValue * 1 case .image: return 1 * 4 + self.connection.rawValue * 2 + self.direction.rawValue * 1 case .video: return 2 * 4 + self.connection.rawValue * 2 + self.direction.rawValue * 1 case .audio: return 3 * 4 + self.connection.rawValue * 2 + self.direction.rawValue * 1 case .file: return 4 * 4 + self.connection.rawValue * 2 + self.direction.rawValue * 1 case .call: return 5 * 4 + self.connection.rawValue * 2 + self.direction.rawValue * 1 } } } private enum UsageCalculationResetKey: Int32 { case wifi = 80 //20 * 4 + 0 case cellular = 81 //20 * 4 + 2 } private func usageCalculationInfo(basePath: String, category: MediaResourceStatsCategory?) -> MTNetworkUsageCalculationInfo { let categoryValue: MediaResourceStatsCategory if let category = category { categoryValue = category } else { categoryValue = .generic } return MTNetworkUsageCalculationInfo(filePath: basePath + "/network-stats", incomingWWANKey: UsageCalculationTag(connection: .cellular, direction: .incoming, category: categoryValue).key, outgoingWWANKey: UsageCalculationTag(connection: .cellular, direction: .outgoing, category: categoryValue).key, incomingOtherKey: UsageCalculationTag(connection: .wifi, direction: .incoming, category: categoryValue).key, outgoingOtherKey: UsageCalculationTag(connection: .wifi, direction: .outgoing, category: categoryValue).key) } public struct NetworkUsageStatsDirectionsEntry: Equatable { public let incoming: Int64 public let outgoing: Int64 public init(incoming: Int64, outgoing: Int64) { self.incoming = incoming self.outgoing = outgoing } public static func ==(lhs: NetworkUsageStatsDirectionsEntry, rhs: NetworkUsageStatsDirectionsEntry) -> Bool { return lhs.incoming == rhs.incoming && lhs.outgoing == rhs.outgoing } } public struct NetworkUsageStatsConnectionsEntry: Equatable { public let cellular: NetworkUsageStatsDirectionsEntry public let wifi: NetworkUsageStatsDirectionsEntry public init(cellular: NetworkUsageStatsDirectionsEntry, wifi: NetworkUsageStatsDirectionsEntry) { self.cellular = cellular self.wifi = wifi } public static func ==(lhs: NetworkUsageStatsConnectionsEntry, rhs: NetworkUsageStatsConnectionsEntry) -> Bool { return lhs.cellular == rhs.cellular && lhs.wifi == rhs.wifi } } public struct NetworkUsageStats: Equatable { public let generic: NetworkUsageStatsConnectionsEntry public let image: NetworkUsageStatsConnectionsEntry public let video: NetworkUsageStatsConnectionsEntry public let audio: NetworkUsageStatsConnectionsEntry public let file: NetworkUsageStatsConnectionsEntry public let call: NetworkUsageStatsConnectionsEntry public let resetWifiTimestamp: Int32 public let resetCellularTimestamp: Int32 public static func ==(lhs: NetworkUsageStats, rhs: NetworkUsageStats) -> Bool { return lhs.generic == rhs.generic && lhs.image == rhs.image && lhs.video == rhs.video && lhs.audio == rhs.audio && lhs.file == rhs.file && lhs.call == rhs.call && lhs.resetWifiTimestamp == rhs.resetWifiTimestamp && lhs.resetCellularTimestamp == rhs.resetCellularTimestamp } } public struct ResetNetworkUsageStats: OptionSet { public var rawValue: Int32 public init(rawValue: Int32) { self.rawValue = rawValue } public init() { self.rawValue = 0 } public static let wifi = ResetNetworkUsageStats(rawValue: 1 << 0) public static let cellular = ResetNetworkUsageStats(rawValue: 1 << 1) } private func interfaceForConnection(_ connection: UsageCalculationConnection) -> MTNetworkUsageManagerInterface { return MTNetworkUsageManagerInterface(rawValue: UInt32(connection.rawValue)) } func updateNetworkUsageStats(basePath: String, category: MediaResourceStatsCategory, delta: NetworkUsageStatsConnectionsEntry) { let info = usageCalculationInfo(basePath: basePath, category: category) let manager = MTNetworkUsageManager(info: info)! manager.addIncomingBytes(UInt(clamping: delta.wifi.incoming), interface: interfaceForConnection(.wifi)) manager.addOutgoingBytes(UInt(clamping: delta.wifi.outgoing), interface: interfaceForConnection(.wifi)) manager.addIncomingBytes(UInt(clamping: delta.cellular.incoming), interface: interfaceForConnection(.cellular)) manager.addOutgoingBytes(UInt(clamping: delta.cellular.outgoing), interface: interfaceForConnection(.cellular)) } func networkUsageStats(basePath: String, reset: ResetNetworkUsageStats) -> Signal { return ((Signal { subscriber in let info = usageCalculationInfo(basePath: basePath, category: nil) let manager = MTNetworkUsageManager(info: info)! let rawKeys: [UsageCalculationTag] = [ UsageCalculationTag(connection: .cellular, direction: .incoming, category: .generic), UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .generic), UsageCalculationTag(connection: .wifi, direction: .incoming, category: .generic), UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .generic), UsageCalculationTag(connection: .cellular, direction: .incoming, category: .image), UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .image), UsageCalculationTag(connection: .wifi, direction: .incoming, category: .image), UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .image), UsageCalculationTag(connection: .cellular, direction: .incoming, category: .video), UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .video), UsageCalculationTag(connection: .wifi, direction: .incoming, category: .video), UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .video), UsageCalculationTag(connection: .cellular, direction: .incoming, category: .audio), UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .audio), UsageCalculationTag(connection: .wifi, direction: .incoming, category: .audio), UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .audio), UsageCalculationTag(connection: .cellular, direction: .incoming, category: .file), UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .file), UsageCalculationTag(connection: .wifi, direction: .incoming, category: .file), UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .file), UsageCalculationTag(connection: .cellular, direction: .incoming, category: .call), UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .call), UsageCalculationTag(connection: .wifi, direction: .incoming, category: .call), UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .call) ] var keys: [NSNumber] = rawKeys.map { $0.key as NSNumber } var resetKeys: [NSNumber] = [] var resetAddKeys: [NSNumber: NSNumber] = [:] let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) if reset.contains(.wifi) { resetKeys = rawKeys.filter({ $0.connection == .wifi }).map({ $0.key as NSNumber }) resetAddKeys[UsageCalculationResetKey.wifi.rawValue as NSNumber] = Int64(timestamp) as NSNumber } if reset.contains(.cellular) { resetKeys = rawKeys.filter({ $0.connection == .cellular }).map({ $0.key as NSNumber }) resetAddKeys[UsageCalculationResetKey.cellular.rawValue as NSNumber] = Int64(timestamp) as NSNumber } if !resetKeys.isEmpty { manager.resetKeys(resetKeys, setKeys: resetAddKeys, completion: {}) } keys.append(UsageCalculationResetKey.cellular.rawValue as NSNumber) keys.append(UsageCalculationResetKey.wifi.rawValue as NSNumber) let disposable = manager.currentStats(forKeys: keys).start(next: { next in var dict: [Int32: Int64] = [:] for key in keys { dict[key.int32Value] = 0 } (next as! NSDictionary).enumerateKeysAndObjects({ key, value, _ in dict[(key as! NSNumber).int32Value] = (value as! NSNumber).int64Value }) subscriber.putNext(NetworkUsageStats( generic: NetworkUsageStatsConnectionsEntry( cellular: NetworkUsageStatsDirectionsEntry( incoming: dict[UsageCalculationTag(connection: .cellular, direction: .incoming, category: .generic).key]!, outgoing: dict[UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .generic).key]!), wifi: NetworkUsageStatsDirectionsEntry( incoming: dict[UsageCalculationTag(connection: .wifi, direction: .incoming, category: .generic).key]!, outgoing: dict[UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .generic).key]!)), image: NetworkUsageStatsConnectionsEntry( cellular: NetworkUsageStatsDirectionsEntry( incoming: dict[UsageCalculationTag(connection: .cellular, direction: .incoming, category: .image).key]!, outgoing: dict[UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .image).key]!), wifi: NetworkUsageStatsDirectionsEntry( incoming: dict[UsageCalculationTag(connection: .wifi, direction: .incoming, category: .image).key]!, outgoing: dict[UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .image).key]!)), video: NetworkUsageStatsConnectionsEntry( cellular: NetworkUsageStatsDirectionsEntry( incoming: dict[UsageCalculationTag(connection: .cellular, direction: .incoming, category: .video).key]!, outgoing: dict[UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .video).key]!), wifi: NetworkUsageStatsDirectionsEntry( incoming: dict[UsageCalculationTag(connection: .wifi, direction: .incoming, category: .video).key]!, outgoing: dict[UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .video).key]!)), audio: NetworkUsageStatsConnectionsEntry( cellular: NetworkUsageStatsDirectionsEntry( incoming: dict[UsageCalculationTag(connection: .cellular, direction: .incoming, category: .audio).key]!, outgoing: dict[UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .audio).key]!), wifi: NetworkUsageStatsDirectionsEntry( incoming: dict[UsageCalculationTag(connection: .wifi, direction: .incoming, category: .audio).key]!, outgoing: dict[UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .audio).key]!)), file: NetworkUsageStatsConnectionsEntry( cellular: NetworkUsageStatsDirectionsEntry( incoming: dict[UsageCalculationTag(connection: .cellular, direction: .incoming, category: .file).key]!, outgoing: dict[UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .file).key]!), wifi: NetworkUsageStatsDirectionsEntry( incoming: dict[UsageCalculationTag(connection: .wifi, direction: .incoming, category: .file).key]!, outgoing: dict[UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .file).key]!)), call: NetworkUsageStatsConnectionsEntry( cellular: NetworkUsageStatsDirectionsEntry( incoming: dict[UsageCalculationTag(connection: .cellular, direction: .incoming, category: .call).key]!, outgoing: dict[UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .call).key]!), wifi: NetworkUsageStatsDirectionsEntry( incoming: dict[UsageCalculationTag(connection: .wifi, direction: .incoming, category: .call).key]!, outgoing: dict[UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .call).key]!)), resetWifiTimestamp: Int32(dict[UsageCalculationResetKey.wifi.rawValue]!), resetCellularTimestamp: Int32(dict[UsageCalculationResetKey.cellular.rawValue]!) )) })! return ActionDisposable { disposable.dispose() } }) |> then(Signal.complete() |> delay(5.0, queue: Queue.concurrentDefaultQueue()))) |> restart } public struct NetworkInitializationArguments { public let apiId: Int32 public let apiHash: String public let languagesCategory: String public let appVersion: String public let voipMaxLayer: Int32 public let appData: Signal public let autolockDeadine: Signal public let encryptionProvider: EncryptionProvider public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, appData: Signal, autolockDeadine: Signal, encryptionProvider: EncryptionProvider) { self.apiId = apiId self.apiHash = apiHash self.languagesCategory = languagesCategory self.appVersion = appVersion self.voipMaxLayer = voipMaxLayer self.appData = appData self.autolockDeadine = autolockDeadine self.encryptionProvider = encryptionProvider } } #if os(iOS) private let cloudDataContext = Atomic(value: nil) #endif func initializedNetwork(arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?) -> Signal { return Signal { subscriber in let queue = Queue() queue.async { let _ = registeredLoggingFunctions let serialization = Serialization() var apiEnvironment = MTApiEnvironment() apiEnvironment.apiId = arguments.apiId apiEnvironment.langPack = arguments.languagesCategory apiEnvironment.layer = NSNumber(value: Int(serialization.currentLayer())) apiEnvironment.disableUpdates = supplementary apiEnvironment = apiEnvironment.withUpdatedLangPackCode(languageCode ?? "en") if let effectiveActiveServer = proxySettings?.effectiveActiveServer { apiEnvironment = apiEnvironment.withUpdatedSocksProxySettings(effectiveActiveServer.mtProxySettings) } apiEnvironment = apiEnvironment.withUpdatedNetworkSettings((networkSettings ?? NetworkSettings.defaultSettings).mtNetworkSettings) apiEnvironment.accessHostOverride = networkSettings?.backupHostOverride var appDataUpdatedImpl: ((Data?) -> Void)? let syncValue = Atomic(value: nil) let appDataDisposable = (arguments.appData |> deliverOn(queue)).start(next: { value in let _ = syncValue.swap(value) appDataUpdatedImpl?(value) }) if let currentAppData = syncValue.swap(Data()) { if let jsonData = JSON(data: currentAppData) { if let value = apiJson(jsonData) { let buffer = Buffer() value.serialize(buffer, true) apiEnvironment = apiEnvironment.withUpdatedSystemCode(buffer.makeData()) } } } let context = MTContext(serialization: serialization, encryptionProvider: arguments.encryptionProvider, apiEnvironment: apiEnvironment, isTestingEnvironment: testingEnvironment, useTempAuthKeys: false)! let seedAddressList: [Int: [String]] if testingEnvironment { seedAddressList = [ 1: ["149.154.175.10"], 2: ["149.154.167.40"] ] } else { seedAddressList = [ 1: ["149.154.175.50", "2001:b28:f23d:f001::a"], 2: ["149.154.167.50", "2001:67c:4e8:f002::a"], 3: ["149.154.175.100", "2001:b28:f23d:f003::a"], 4: ["149.154.167.91", "2001:67c:4e8:f004::a"], 5: ["149.154.171.5", "2001:b28:f23f:f005::a"] ] } for (id, ips) in seedAddressList { context.setSeedAddressSetForDatacenterWithId(id, seedAddressSet: MTDatacenterAddressSet(addressList: ips.map { MTDatacenterAddress(ip: $0, port: 443, preferForMedia: false, restrictToTcp: false, cdn: false, preferForProxy: false, secret: nil) })) } context.keychain = keychain var wrappedAdditionalSource: MTSignal? #if os(iOS) if #available(iOS 10.0, *), !supplementary { var cloudDataContextValue: CloudDataContext? if let value = cloudDataContext.with({ $0 }) { cloudDataContextValue = value } else { cloudDataContextValue = makeCloudDataContext(encryptionProvider: arguments.encryptionProvider) let _ = cloudDataContext.swap(cloudDataContextValue) } if let cloudDataContext = cloudDataContextValue { wrappedAdditionalSource = MTSignal(generator: { subscriber in let disposable = cloudDataContext.get(phoneNumber: .single(phoneNumber)).start(next: { value in subscriber?.putNext(value) }, completed: { subscriber?.putCompletion() }) return MTBlockDisposable(block: { disposable.dispose() }) }) } } #endif context.setDiscoverBackupAddressListSignal(MTBackupAddressSignals.fetchBackupIps(testingEnvironment, currentContext: context, additionalSource: wrappedAdditionalSource, phoneNumber: phoneNumber)) #if DEBUG //let _ = MTBackupAddressSignals.fetchBackupIps(testingEnvironment, currentContext: context, additionalSource: wrappedAdditionalSource, phoneNumber: phoneNumber).start(next: nil) #endif let mtProto = MTProto(context: context, datacenterId: datacenterId, usageCalculationInfo: usageCalculationInfo(basePath: basePath, category: nil), requiredAuthToken: nil, authTokenMasterDatacenterId: 0)! mtProto.useTempAuthKeys = context.useTempAuthKeys mtProto.checkForProxyConnectionIssues = true let connectionStatus = Promise(.waitingForNetwork) let requestService = MTRequestMessageService(context: context)! let connectionStatusDelegate = MTProtoConnectionStatusDelegate() connectionStatusDelegate.action = { [weak connectionStatus] info in if info.flags.contains(.Connected) { if !info.flags.intersection([.UpdatingConnectionContext, .PerformingServiceTasks]).isEmpty { connectionStatus?.set(.single(.updating(proxyAddress: info.proxyAddress))) } else { connectionStatus?.set(.single(.online(proxyAddress: info.proxyAddress))) } } else { if !info.flags.contains(.NetworkAvailable) { connectionStatus?.set(.single(ConnectionStatus.waitingForNetwork)) } else if !info.flags.contains(.Connected) { connectionStatus?.set(.single(.connecting(proxyAddress: info.proxyAddress, proxyHasConnectionIssues: info.flags.contains(.ProxyHasConnectionIssues)))) } else if !info.flags.intersection([.UpdatingConnectionContext, .PerformingServiceTasks]).isEmpty { connectionStatus?.set(.single(.updating(proxyAddress: info.proxyAddress))) } else { connectionStatus?.set(.single(.online(proxyAddress: info.proxyAddress))) } } } mtProto.delegate = connectionStatusDelegate mtProto.add(requestService) let network = Network(queue: queue, datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath, appDataDisposable: appDataDisposable, encryptionProvider: arguments.encryptionProvider) appDataUpdatedImpl = { [weak network] data in guard let data = data else { return } guard let jsonData = JSON(data: data) else { return } guard let value = apiJson(jsonData) else { return } let buffer = Buffer() value.serialize(buffer, true) let systemCode = buffer.makeData() network?.context.updateApiEnvironment { environment in let current = environment?.systemCode let updateNetwork: Bool if let current = current { updateNetwork = systemCode != current } else { updateNetwork = true } if updateNetwork { return environment?.withUpdatedSystemCode(systemCode) } else { return nil } } } subscriber.putNext(network) subscriber.putCompletion() } return EmptyDisposable } } private final class NetworkHelper: NSObject, MTContextChangeListener { private let requestPublicKeys: (Int) -> Signal private let isContextNetworkAccessAllowedImpl: () -> Signal private let contextProxyIdUpdated: (NetworkContextProxyId?) -> Void private let contextLoggedOutUpdated: () -> Void init(requestPublicKeys: @escaping (Int) -> Signal, isContextNetworkAccessAllowed: @escaping () -> Signal, contextProxyIdUpdated: @escaping (NetworkContextProxyId?) -> Void, contextLoggedOutUpdated: @escaping () -> Void) { self.requestPublicKeys = requestPublicKeys self.isContextNetworkAccessAllowedImpl = isContextNetworkAccessAllowed self.contextProxyIdUpdated = contextProxyIdUpdated self.contextLoggedOutUpdated = contextLoggedOutUpdated } func fetchContextDatacenterPublicKeys(_ context: MTContext!, datacenterId: Int) -> MTSignal! { return MTSignal { subscriber in let disposable = self.requestPublicKeys(datacenterId).start(next: { next in subscriber?.putNext(next) subscriber?.putCompletion() }) return MTBlockDisposable(block: { disposable.dispose() }) } } func isContextNetworkAccessAllowed(_ context: MTContext!) -> MTSignal! { return MTSignal { subscriber in let disposable = self.isContextNetworkAccessAllowedImpl().start(next: { next in subscriber?.putNext(next as NSNumber) subscriber?.putCompletion() }) return MTBlockDisposable(block: { disposable.dispose() }) } } func contextApiEnvironmentUpdated(_ context: MTContext!, apiEnvironment: MTApiEnvironment!) { let settings: MTSocksProxySettings? = apiEnvironment.socksProxySettings self.contextProxyIdUpdated(settings.flatMap(NetworkContextProxyId.init(settings:))) } func contextLoggedOut(_ context: MTContext!) { self.contextLoggedOutUpdated() } } struct NetworkContextProxyId: Equatable { private let ip: String private let port: Int private let secret: Data } private extension NetworkContextProxyId { init?(settings: MTSocksProxySettings) { if let secret = settings.secret, !secret.isEmpty { self.init(ip: settings.ip, port: Int(settings.port), secret: secret) } else { return nil } } } public struct NetworkRequestAdditionalInfo: OptionSet { public var rawValue: Int32 public init(rawValue: Int32) { self.rawValue = rawValue } public static let acknowledgement = NetworkRequestAdditionalInfo(rawValue: 1 << 0) public static let progress = NetworkRequestAdditionalInfo(rawValue: 1 << 1) } public enum NetworkRequestResult { case result(T) case acknowledged case progress(Float, Int32) } public final class Network: NSObject, MTRequestMessageServiceDelegate { public let encryptionProvider: EncryptionProvider private let queue: Queue public let datacenterId: Int public let context: MTContext let mtProto: MTProto let requestService: MTRequestMessageService let basePath: String private let connectionStatusDelegate: MTProtoConnectionStatusDelegate private let appDataDisposable: Disposable private var _multiplexedRequestManager: MultiplexedRequestManager? var multiplexedRequestManager: MultiplexedRequestManager { return self._multiplexedRequestManager! } private let _contextProxyId: ValuePromise var contextProxyId: Signal { return self._contextProxyId.get() } private let _connectionStatus: Promise public var connectionStatus: Signal { return self._connectionStatus.get() |> distinctUntilChanged } public func dropConnectionStatus() { _connectionStatus.set(.single(.waitingForNetwork)) } public let shouldKeepConnection = Promise(false) private let shouldKeepConnectionDisposable = MetaDisposable() public let shouldExplicitelyKeepWorkerConnections = Promise(false) public let shouldKeepBackgroundDownloadConnections = Promise(false) public var mockConnectionStatus: ConnectionStatus? { didSet { if let mockConnectionStatus = self.mockConnectionStatus { self._connectionStatus.set(.single(mockConnectionStatus)) } } } var loggedOut: (() -> Void)? var didReceiveSoftAuthResetError: (() -> Void)? override public var description: String { return "Network context: \(self.context)" } fileprivate init(queue: Queue, datacenterId: Int, context: MTContext, mtProto: MTProto, requestService: MTRequestMessageService, connectionStatusDelegate: MTProtoConnectionStatusDelegate, _connectionStatus: Promise, basePath: String, appDataDisposable: Disposable, encryptionProvider: EncryptionProvider) { self.encryptionProvider = encryptionProvider self.queue = queue self.datacenterId = datacenterId self.context = context self._contextProxyId = ValuePromise((context.apiEnvironment.socksProxySettings as MTSocksProxySettings?).flatMap(NetworkContextProxyId.init(settings:)), ignoreRepeated: true) self.mtProto = mtProto self.requestService = requestService self.connectionStatusDelegate = connectionStatusDelegate self._connectionStatus = _connectionStatus self.appDataDisposable = appDataDisposable self.basePath = basePath super.init() self.requestService.didReceiveSoftAuthResetError = { [weak self] in self?.didReceiveSoftAuthResetError?() } let _contextProxyId = self._contextProxyId context.add(NetworkHelper(requestPublicKeys: { [weak self] id in if let strongSelf = self { return strongSelf.request(Api.functions.help.getCdnConfig()) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } |> map { result -> NSArray in let array = NSMutableArray() if let result = result { switch result { case let .cdnConfig(publicKeys): for key in publicKeys { switch key { case let .cdnPublicKey(dcId, publicKey): if id == Int(dcId) { let dict = NSMutableDictionary() dict["key"] = publicKey dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey) array.add(dict) } } } } } return array } } else { return .never() } }, isContextNetworkAccessAllowed: { [weak self] in if let strongSelf = self { return strongSelf.shouldKeepConnection.get() |> distinctUntilChanged } else { return .single(false) } }, contextProxyIdUpdated: { value in _contextProxyId.set(value) }, contextLoggedOutUpdated: { [weak self] in Logger.shared.log("Network", "contextLoggedOut") self?.loggedOut?() })) requestService.delegate = self self._multiplexedRequestManager = MultiplexedRequestManager(takeWorker: { [weak self] target, tag, continueInBackground in if let strongSelf = self { let datacenterId: Int let isCdn: Bool let isMedia: Bool = true switch target { case let .main(id): datacenterId = id isCdn = false case let .cdn(id): datacenterId = id isCdn = true } return strongSelf.makeWorker(datacenterId: datacenterId, isCdn: isCdn, isMedia: isMedia, tag: tag, continueInBackground: continueInBackground) } return nil }) let shouldKeepConnectionSignal = self.shouldKeepConnection.get() |> distinctUntilChanged |> deliverOn(queue) self.shouldKeepConnectionDisposable.set(shouldKeepConnectionSignal.start(next: { [weak self] value in if let strongSelf = self { if value { Logger.shared.log("Network", "Resume network connection") strongSelf.mtProto.resume() } else { Logger.shared.log("Network", "Pause network connection") strongSelf.mtProto.pause() } } })) } deinit { self.shouldKeepConnectionDisposable.dispose() self.appDataDisposable.dispose() } public var globalTime: TimeInterval { return self.context.globalTime() } public var currentGlobalTime: Signal { return Signal { subscriber in self.context.performBatchUpdates({ subscriber.putNext(self.context.globalTime()) subscriber.putCompletion() }) return EmptyDisposable } } public func requestMessageServiceAuthorizationRequired(_ requestMessageService: MTRequestMessageService!) { Logger.shared.log("Network", "requestMessageServiceAuthorizationRequired") self.loggedOut?() } func download(datacenterId: Int, isMedia: Bool, isCdn: Bool = false, tag: MediaResourceFetchTag?) -> Signal { return self.worker(datacenterId: datacenterId, isCdn: isCdn, isMedia: isMedia, tag: tag) } func upload(tag: MediaResourceFetchTag?) -> Signal { return self.worker(datacenterId: self.datacenterId, isCdn: false, isMedia: false, tag: tag) } func background() -> Signal { return self.worker(datacenterId: self.datacenterId, isCdn: false, isMedia: false, tag: nil) } private func makeWorker(datacenterId: Int, isCdn: Bool, isMedia: Bool, tag: MediaResourceFetchTag?, continueInBackground: Bool = false) -> Download { let queue = Queue.mainQueue() let shouldKeepWorkerConnection: Signal = combineLatest(queue: queue, self.shouldKeepConnection.get(), self.shouldExplicitelyKeepWorkerConnections.get(), self.shouldKeepBackgroundDownloadConnections.get()) |> map { shouldKeepConnection, shouldExplicitelyKeepWorkerConnections, shouldKeepBackgroundDownloadConnections -> Bool in return shouldKeepConnection || shouldExplicitelyKeepWorkerConnections || (continueInBackground && shouldKeepBackgroundDownloadConnections) } |> distinctUntilChanged return Download(queue: self.queue, datacenterId: datacenterId, isMedia: isMedia, isCdn: isCdn, context: self.context, masterDatacenterId: self.datacenterId, usageInfo: usageCalculationInfo(basePath: self.basePath, category: (tag as? TelegramMediaResourceFetchTag)?.statsCategory), shouldKeepConnection: shouldKeepWorkerConnection) } private func worker(datacenterId: Int, isCdn: Bool, isMedia: Bool, tag: MediaResourceFetchTag?) -> Signal { return Signal { [weak self] subscriber in if let strongSelf = self { subscriber.putNext(strongSelf.makeWorker(datacenterId: datacenterId, isCdn: isCdn, isMedia: isMedia, tag: tag)) } subscriber.putCompletion() return ActionDisposable { } } } public func getApproximateRemoteTimestamp() -> Int32 { return Int32(self.context.globalTime()) } public func mergeBackupDatacenterAddress(datacenterId: Int32, host: String, port: Int32, secret: Data?) { self.context.performBatchUpdates { let address = MTDatacenterAddress(ip: host, port: UInt16(port), preferForMedia: false, restrictToTcp: false, cdn: false, preferForProxy: false, secret: secret) self.context.addAddressForDatacenter(withId: Int(datacenterId), address: address) /*let currentScheme = self.context.transportSchemeForDatacenter(withId: Int(datacenterId), media: false, isProxy: false) if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) { } else { let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false) self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: false) }*/ let currentSchemes = self.context.transportSchemesForDatacenter(withId: Int(datacenterId), media: false, enforceMedia: false, isProxy: false) var found = false for scheme in currentSchemes { if scheme.address.isEqual(to: address) { found = true break } } if !found { let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false) self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: false) } } } public func requestWithAdditionalInfo(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal, MTRpcError> { let requestService = self.requestService return Signal { subscriber in let request = MTRequest() request.setPayload(data.1.makeData() as Data, metadata: WrappedRequestMetadata(metadata: WrappedFunctionDescription(data.0), tag: tag), shortMetadata: WrappedRequestShortMetadata(shortMetadata: WrappedShortFunctionDescription(data.0)), responseParser: { response in if let result = data.2.parse(Buffer(data: response)) { return BoxedMessage(result) } return nil }) request.dependsOnPasswordEntry = false request.shouldContinueExecutionWithErrorContext = { errorContext in guard let errorContext = errorContext else { return true } if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { return false } return true } request.acknowledgementReceived = { if info.contains(.acknowledgement) { subscriber.putNext(.acknowledged) } } request.progressUpdated = { progress, packetSize in if info.contains(.progress) { subscriber.putNext(.progress(progress, Int32(clamping: packetSize))) } } request.completed = { (boxedResponse, timestamp, error) -> () in if let error = error { subscriber.putError(error) } else { if let result = (boxedResponse as! BoxedMessage).body as? T { subscriber.putNext(.result(result)) subscriber.putCompletion() } else { subscriber.putError(MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR")) } } } if let tag = tag { request.shouldDependOnRequest = { other in if let other = other, let metadata = other.metadata as? WrappedRequestMetadata, let otherTag = metadata.tag { return tag.shouldDependOn(other: otherTag) } return false } } let internalId: Any! = request.internalId requestService.add(request) return ActionDisposable { [weak requestService] in requestService?.removeRequest(byInternalId: internalId) } } } public func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal { let requestService = self.requestService return Signal { subscriber in let request = MTRequest() request.setPayload(data.1.makeData() as Data, metadata: WrappedRequestMetadata(metadata: WrappedFunctionDescription(data.0), tag: tag), shortMetadata: WrappedRequestShortMetadata(shortMetadata: WrappedShortFunctionDescription(data.0)), responseParser: { response in if let result = data.2.parse(Buffer(data: response)) { return BoxedMessage(result) } return nil }) request.dependsOnPasswordEntry = false request.shouldContinueExecutionWithErrorContext = { errorContext in guard let errorContext = errorContext else { return true } if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { return false } return true } request.completed = { (boxedResponse, timestamp, error) -> () in if let error = error { subscriber.putError(error) } else { if let result = (boxedResponse as! BoxedMessage).body as? T { subscriber.putNext(result) subscriber.putCompletion() } else { subscriber.putError(MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR")) } } } if let tag = tag { request.shouldDependOnRequest = { other in if let other = other, let metadata = other.metadata as? WrappedRequestMetadata, let otherTag = metadata.tag { return tag.shouldDependOn(other: otherTag) } return false } } let internalId: Any! = request.internalId requestService.add(request) return ActionDisposable { [weak requestService] in requestService?.removeRequest(byInternalId: internalId) } } } } public func retryRequest(signal: Signal) -> Signal { return signal |> retry(0.2, maxDelay: 5.0, onQueue: Queue.concurrentDefaultQueue()) } class Keychain: NSObject, MTKeychain { let get: (String) -> Data? let set: (String, Data) -> Void let remove: (String) -> Void init(get: @escaping (String) -> Data?, set: @escaping (String, Data) -> Void, remove: @escaping (String) -> Void) { self.get = get self.set = set self.remove = remove } func setObject(_ object: Any!, forKey aKey: String!, group: String!) { let data = NSKeyedArchiver.archivedData(withRootObject: object) self.set(group + ":" + aKey, data) } func object(forKey aKey: String!, group: String!) -> Any! { if let data = self.get(group + ":" + aKey) { return NSKeyedUnarchiver.unarchiveObject(with: data as Data) } return nil } func removeObject(forKey aKey: String!, group: String!) { self.remove(group + ":" + aKey) } func dropGroup(_ group: String!) { } } #if os(iOS) func makeCloudDataContext(encryptionProvider: EncryptionProvider) -> CloudDataContext? { if #available(iOS 10.0, *) { return CloudDataContextImpl(encryptionProvider: encryptionProvider) } else { return nil } } #endif