import Foundation import Postbox import SwiftSignalKit import MtProtoKit import TelegramApi import CryptoUtils import SyncCore import EncryptionProvider private let accountRecordToActiveKeychainId = Atomic<[AccountRecordId: Int]>(value: [:]) private func makeExclusiveKeychain(id: AccountRecordId, postbox: Postbox) -> Keychain { var keychainId = 0 let _ = accountRecordToActiveKeychainId.modify { dict in var dict = dict if let value = dict[id] { dict[id] = value + 1 keychainId = value + 1 } else { keychainId = 0 dict[id] = 0 } return dict } return Keychain(get: { key in let enabled = accountRecordToActiveKeychainId.with { dict -> Bool in return dict[id] == keychainId } if enabled { return postbox.keychainEntryForKey(key) } else { Logger.shared.log("Keychain", "couldn't get \(key) — not current") return nil } }, set: { (key, data) in let enabled = accountRecordToActiveKeychainId.with { dict -> Bool in return dict[id] == keychainId } if enabled { postbox.setKeychainEntryForKey(key, value: data) } else { Logger.shared.log("Keychain", "couldn't set \(key) — not current") } }, remove: { key in let enabled = accountRecordToActiveKeychainId.with { dict -> Bool in return dict[id] == keychainId } if enabled { postbox.removeKeychainEntryForKey(key) } else { Logger.shared.log("Keychain", "couldn't remove \(key) — not current") } }) } public class UnauthorizedAccount { public let networkArguments: NetworkInitializationArguments public let id: AccountRecordId public let rootPath: String public let basePath: String public let testingEnvironment: Bool public let postbox: Postbox public let network: Network private let stateManager: UnauthorizedAccountStateManager private let updateLoginTokenPipe = ValuePipe() public var updateLoginTokenEvents: Signal { return self.updateLoginTokenPipe.signal() } public var masterDatacenterId: Int32 { return Int32(self.network.mtProto.datacenterId) } public let shouldBeServiceTaskMaster = Promise() init(networkArguments: NetworkInitializationArguments, id: AccountRecordId, rootPath: String, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, shouldKeepAutoConnection: Bool = true) { self.networkArguments = networkArguments self.id = id self.rootPath = rootPath self.basePath = basePath self.testingEnvironment = testingEnvironment self.postbox = postbox self.network = network let updateLoginTokenPipe = self.updateLoginTokenPipe self.stateManager = UnauthorizedAccountStateManager(network: network, updateLoginToken: { updateLoginTokenPipe.putNext(Void()) }) network.shouldKeepConnection.set(self.shouldBeServiceTaskMaster.get() |> map { mode -> Bool in switch mode { case .now, .always: return true case .never: return false } }) network.context.performBatchUpdates({ var datacenterIds: [Int] = [1, 2] if !testingEnvironment { datacenterIds.append(contentsOf: [4]) } for id in datacenterIds { if network.context.authInfoForDatacenter(withId: id, selector: .persistent) == nil { network.context.authInfoForDatacenter(withIdRequired: id, isCdn: false, selector: .ephemeralMain) } } network.context.beginExplicitBackupAddressDiscovery() }) self.stateManager.reset() } public func changedMasterDatacenterId(accountManager: AccountManager, masterDatacenterId: Int32) -> Signal { if masterDatacenterId == Int32(self.network.mtProto.datacenterId) { return .single(self) } else { let keychain = makeExclusiveKeychain(id: self.id, postbox: self.postbox) return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in return (transaction.getSharedData(SharedDataKeys.localizationSettings) as? LocalizationSettings, transaction.getSharedData(SharedDataKeys.proxySettings) as? ProxySettings) } |> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?), NoError> in return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?) in return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings) as? NetworkSettings) } } |> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal in return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) |> map { network in let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network) updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get()) return updated } } } } } func accountNetworkUsageInfoPath(basePath: String) -> String { return basePath + "/network-usage" } public func accountRecordIdPathName(_ id: AccountRecordId) -> String { return "account-\(UInt64(bitPattern: id.int64))" } public enum AccountResult { case upgrading(Float) case unauthorized(UnauthorizedAccount) case authorized(Account) } public enum AccountPreferenceEntriesResult { case progress(Float) case result(String, [ValueBoxKey: PreferencesEntry]) } public func accountPreferenceEntries(rootPath: String, id: AccountRecordId, keys: Set, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true, isReadOnly: true) return postbox |> mapToSignal { value -> Signal in switch value { case let .upgrading(progress): return .single(.progress(progress)) case let .postbox(postbox): return postbox.transaction { transaction -> AccountPreferenceEntriesResult in var result: [ValueBoxKey: PreferencesEntry] = [:] for key in keys { if let value = transaction.getPreferencesEntry(key: key) { result[key] = value } } return .result(path, result) } case .error: return .single(.progress(0.0)) } } } public enum AccountNoticeEntriesResult { case progress(Float) case result(String, [ValueBoxKey: NoticeEntry]) } public func accountNoticeEntries(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true, isReadOnly: true) return postbox |> mapToSignal { value -> Signal in switch value { case let .upgrading(progress): return .single(.progress(progress)) case let .postbox(postbox): return postbox.transaction { transaction -> AccountNoticeEntriesResult in return .result(path, transaction.getAllNoticeEntries()) } case .error: return .single(.progress(0.0)) } } } public enum LegacyAccessChallengeDataResult { case progress(Float) case result(PostboxAccessChallengeData) } public func accountLegacyAccessChallengeData(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true, isReadOnly: true) return postbox |> mapToSignal { value -> Signal in switch value { case let .upgrading(progress): return .single(.progress(progress)) case let .postbox(postbox): return postbox.transaction { transaction -> LegacyAccessChallengeDataResult in return .result(transaction.legacyGetAccessChallengeData()) } case .error: return .single(.progress(0.0)) } } } public func accountWithId(accountManager: AccountManager, networkArguments: NetworkInitializationArguments, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, supplementary: Bool, rootPath: String, beginWithTestingEnvironment: Bool, backupData: AccountBackupData?, auxiliaryMethods: AccountAuxiliaryMethods, shouldKeepAutoConnection: Bool = true) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: false, isReadOnly: false) return postbox |> mapToSignal { result -> Signal in switch result { case let .upgrading(progress): return .single(.upgrading(progress)) case .error: return .single(.upgrading(0.0)) case let .postbox(postbox): return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in return (transaction.getSharedData(SharedDataKeys.localizationSettings) as? LocalizationSettings, transaction.getSharedData(SharedDataKeys.proxySettings) as? ProxySettings) } |> mapToSignal { localizationSettings, proxySettings -> Signal in return postbox.transaction { transaction -> (PostboxCoding?, LocalizationSettings?, ProxySettings?, NetworkSettings?) in var state = transaction.getState() if state == nil, let backupData = backupData { let backupState = AuthorizedAccountState(isTestingEnvironment: beginWithTestingEnvironment, masterDatacenterId: backupData.masterDatacenterId, peerId: PeerId(backupData.peerId), state: nil) state = backupState let dict = NSMutableDictionary() dict.setObject(MTDatacenterAuthInfo(authKey: backupData.masterDatacenterKey, authKeyId: backupData.masterDatacenterKeyId, saltSet: [], authKeyAttributes: [:]), forKey: backupData.masterDatacenterId as NSNumber) let data = NSKeyedArchiver.archivedData(withRootObject: dict) transaction.setState(backupState) transaction.setKeychainEntry(data, forKey: "persistent:datacenterAuthInfoById") } return (state, localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings) as? NetworkSettings) } |> mapToSignal { (accountState, localizationSettings, proxySettings, networkSettings) -> Signal in let keychain = makeExclusiveKeychain(id: id, postbox: postbox) if let accountState = accountState { switch accountState { case let unauthorizedState as UnauthorizedAccountState: return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) |> map { network -> AccountResult in return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } case let authorizedState as AuthorizedAccountState: return postbox.transaction { transaction -> String? in return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone } |> mapToSignal { phoneNumber in return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber) |> map { network -> AccountResult in return .authorized(Account(accountManager: accountManager, id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods, supplementary: supplementary)) } } case _: assertionFailure("Unexpected accountState \(accountState)") } } return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) |> map { network -> AccountResult in return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } } } } } } public enum TwoStepPasswordDerivation { case unknown case sha256_sha256_PBKDF2_HMAC_sha512_sha256_srp(salt1: Data, salt2: Data, iterations: Int32, g: Int32, p: Data) fileprivate init(_ apiAlgo: Api.PasswordKdfAlgo) { switch apiAlgo { case .passwordKdfAlgoUnknown: self = .unknown case let .passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(salt1, salt2, g, p): self = .sha256_sha256_PBKDF2_HMAC_sha512_sha256_srp(salt1: salt1.makeData(), salt2: salt2.makeData(), iterations: 100000, g: g, p: p.makeData()) } } var apiAlgo: Api.PasswordKdfAlgo { switch self { case .unknown: return .passwordKdfAlgoUnknown case let .sha256_sha256_PBKDF2_HMAC_sha512_sha256_srp(salt1, salt2, iterations, g, p): precondition(iterations == 100000) return .passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(salt1: Buffer(data: salt1), salt2: Buffer(data: salt2), g: g, p: Buffer(data: p)) } } } public enum TwoStepSecurePasswordDerivation { case unknown case sha512(salt: Data) case PBKDF2_HMAC_sha512(salt: Data, iterations: Int32) init(_ apiAlgo: Api.SecurePasswordKdfAlgo) { switch apiAlgo { case .securePasswordKdfAlgoUnknown: self = .unknown case let .securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt): self = .PBKDF2_HMAC_sha512(salt: salt.makeData(), iterations: 100000) case let .securePasswordKdfAlgoSHA512(salt): self = .sha512(salt: salt.makeData()) } } var apiAlgo: Api.SecurePasswordKdfAlgo { switch self { case .unknown: return .securePasswordKdfAlgoUnknown case let .PBKDF2_HMAC_sha512(salt, iterations): precondition(iterations == 100000) return .securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt: Buffer(data: salt)) case let .sha512(salt): return .securePasswordKdfAlgoSHA512(salt: Buffer(data: salt)) } } } public struct TwoStepSRPSessionData { public let id: Int64 public let B: Data } public struct TwoStepAuthData { public let nextPasswordDerivation: TwoStepPasswordDerivation public let currentPasswordDerivation: TwoStepPasswordDerivation? public let srpSessionData: TwoStepSRPSessionData? public let hasRecovery: Bool public let hasSecretValues: Bool public let currentHint: String? public let unconfirmedEmailPattern: String? public let secretRandom: Data public let nextSecurePasswordDerivation: TwoStepSecurePasswordDerivation } public func twoStepAuthData(_ network: Network) -> Signal { return network.request(Api.functions.account.getPassword()) |> map { config -> TwoStepAuthData in switch config { case let .password(flags, currentAlgo, srpB, srpId, hint, emailUnconfirmedPattern, newAlgo, newSecureAlgo, secureRandom): let hasRecovery = (flags & (1 << 0)) != 0 let hasSecureValues = (flags & (1 << 1)) != 0 let currentDerivation = currentAlgo.flatMap(TwoStepPasswordDerivation.init) let nextDerivation = TwoStepPasswordDerivation(newAlgo) let nextSecureDerivation = TwoStepSecurePasswordDerivation(newSecureAlgo) switch nextSecureDerivation { case .unknown: break case .PBKDF2_HMAC_sha512: break case .sha512: preconditionFailure() } var srpSessionData: TwoStepSRPSessionData? if let srpB = srpB, let srpId = srpId { srpSessionData = TwoStepSRPSessionData(id: srpId, B: srpB.makeData()) } return TwoStepAuthData(nextPasswordDerivation: nextDerivation, currentPasswordDerivation: currentDerivation, srpSessionData: srpSessionData, hasRecovery: hasRecovery, hasSecretValues: hasSecureValues, currentHint: hint, unconfirmedEmailPattern: emailUnconfirmedPattern, secretRandom: secureRandom.makeData(), nextSecurePasswordDerivation: nextSecureDerivation) } } } public func hexString(_ data: Data) -> String { let hexString = NSMutableString() data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in for i in 0 ..< data.count { hexString.appendFormat("%02x", UInt(bytes.advanced(by: i).pointee)) } } return hexString as String } public func dataWithHexString(_ string: String) -> Data { var hex = string if hex.count % 2 != 0 { return Data() } var data = Data() while hex.count > 0 { let subIndex = hex.index(hex.startIndex, offsetBy: 2) let c = String(hex[.. Data { return data.withUnsafeBytes { bytes -> Data in return CryptoSHA1(bytes, Int32(data.count)) } } func sha256Digest(_ data : Data) -> Data { return data.withUnsafeBytes { bytes -> Data in return CryptoSHA256(bytes, Int32(data.count)) } } func sha512Digest(_ data : Data) -> Data { return data.withUnsafeBytes { bytes -> Data in return CryptoSHA512(bytes, Int32(data.count)) } } func passwordUpdateKDF(encryptionProvider: EncryptionProvider, password: String, derivation: TwoStepPasswordDerivation) -> (Data, TwoStepPasswordDerivation)? { guard let passwordData = password.data(using: .utf8, allowLossyConversion: true) else { return nil } switch derivation { case .unknown: return nil case let .sha256_sha256_PBKDF2_HMAC_sha512_sha256_srp(salt1, salt2, iterations, gValue, p): var nextSalt1 = salt1 var randomSalt1 = Data() randomSalt1.count = 32 randomSalt1.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in arc4random_buf(bytes, 32) } nextSalt1.append(randomSalt1) let nextSalt2 = salt2 var g = Data(count: 4) g.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in var gValue = gValue withUnsafeBytes(of: &gValue, { (sourceBuffer: UnsafeRawBufferPointer) -> Void in let sourceBytes = sourceBuffer.bindMemory(to: Int8.self).baseAddress! for i in 0 ..< 4 { bytes.advanced(by: i).pointee = sourceBytes.advanced(by: 4 - i - 1).pointee } }) } let pbkdfInnerData = sha256Digest(nextSalt2 + sha256Digest(nextSalt1 + passwordData + nextSalt1) + nextSalt2) guard let pbkdfResult = MTPBKDF2(pbkdfInnerData, nextSalt1, iterations) else { return nil } let x = sha256Digest(nextSalt2 + pbkdfResult + nextSalt2) let gx = MTExp(encryptionProvider, g, x, p)! return (gx, .sha256_sha256_PBKDF2_HMAC_sha512_sha256_srp(salt1: nextSalt1, salt2: nextSalt2, iterations: iterations, g: gValue, p: p)) } } struct PasswordKDFResult { let id: Int64 let A: Data let M1: Data } private func paddedToLength(what: Data, to: Data) -> Data { if what.count < to.count { var what = what for _ in 0 ..< to.count - what.count { what.insert(0, at: 0) } return what } else { return what } } private func paddedXor(_ a: Data, _ b: Data) -> Data { let count = max(a.count, b.count) var a = a var b = b while a.count < count { a.insert(0, at: 0) } while b.count < count { b.insert(0, at: 0) } a.withUnsafeMutableBytes { (aBytes: UnsafeMutablePointer) -> Void in b.withUnsafeBytes { (bBytes: UnsafePointer) -> Void in for i in 0 ..< count { aBytes.advanced(by: i).pointee = aBytes.advanced(by: i).pointee ^ bBytes.advanced(by: i).pointee } } } return a } func passwordKDF(encryptionProvider: EncryptionProvider, password: String, derivation: TwoStepPasswordDerivation, srpSessionData: TwoStepSRPSessionData) -> PasswordKDFResult? { guard let passwordData = password.data(using: .utf8, allowLossyConversion: true) else { return nil } switch derivation { case .unknown: return nil case let .sha256_sha256_PBKDF2_HMAC_sha512_sha256_srp(salt1, salt2, iterations, gValue, p): var a = Data(count: p.count) let aLength = a.count a.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in let _ = SecRandomCopyBytes(nil, aLength, bytes) } var g = Data(count: 4) g.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in var gValue = gValue withUnsafeBytes(of: &gValue, { (sourceBuffer: UnsafeRawBufferPointer) -> Void in let sourceBytes = sourceBuffer.bindMemory(to: Int8.self).baseAddress! for i in 0 ..< 4 { bytes.advanced(by: i).pointee = sourceBytes.advanced(by: 4 - i - 1).pointee } }) } if !MTCheckIsSafeB(encryptionProvider, srpSessionData.B, p) { return nil } let B = paddedToLength(what: srpSessionData.B, to: p) let A = paddedToLength(what: MTExp(encryptionProvider, g, a, p)!, to: p) let u = sha256Digest(A + B) if MTIsZero(encryptionProvider, u) { return nil } let pbkdfInnerData = sha256Digest(salt2 + sha256Digest(salt1 + passwordData + salt1) + salt2) guard let pbkdfResult = MTPBKDF2(pbkdfInnerData, salt1, iterations) else { return nil } let x = sha256Digest(salt2 + pbkdfResult + salt2) let gx = MTExp(encryptionProvider, g, x, p)! let k = sha256Digest(p + paddedToLength(what: g, to: p)) let s1 = MTModSub(encryptionProvider, B, MTModMul(encryptionProvider, k, gx, p)!, p)! if !MTCheckIsSafeGAOrB(encryptionProvider, s1, p) { return nil } let s2 = MTAdd(encryptionProvider, a, MTMul(encryptionProvider, u, x)!)! let S = MTExp(encryptionProvider, s1, s2, p)! let K = sha256Digest(paddedToLength(what: S, to: p)) let m1 = paddedXor(sha256Digest(p), sha256Digest(paddedToLength(what: g, to: p))) let m2 = sha256Digest(salt1) let m3 = sha256Digest(salt2) let M = sha256Digest(m1 + m2 + m3 + A + B + K) return PasswordKDFResult(id: srpSessionData.id, A: A, M1: M) } } func securePasswordUpdateKDF(password: String, derivation: TwoStepSecurePasswordDerivation) -> (Data, TwoStepSecurePasswordDerivation)? { guard let passwordData = password.data(using: .utf8, allowLossyConversion: true) else { return nil } switch derivation { case .unknown: return nil case let .sha512(salt): var nextSalt = salt var randomSalt = Data() randomSalt.count = 32 randomSalt.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in arc4random_buf(bytes, 32) } nextSalt.append(randomSalt) var data = Data() data.append(nextSalt) data.append(passwordData) data.append(nextSalt) return (sha512Digest(data), .sha512(salt: nextSalt)) case let .PBKDF2_HMAC_sha512(salt, iterations): var nextSalt = salt var randomSalt = Data() randomSalt.count = 32 randomSalt.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in arc4random_buf(bytes, 32) } nextSalt.append(randomSalt) guard let passwordHash = MTPBKDF2(passwordData, nextSalt, iterations) else { return nil } return (passwordHash, .PBKDF2_HMAC_sha512(salt: nextSalt, iterations: iterations)) } } func securePasswordKDF(password: String, derivation: TwoStepSecurePasswordDerivation) -> Data? { guard let passwordData = password.data(using: .utf8, allowLossyConversion: true) else { return nil } switch derivation { case .unknown: return nil case let .sha512(salt): var data = Data() data.append(salt) data.append(passwordData) data.append(salt) return sha512Digest(data) case let .PBKDF2_HMAC_sha512(salt, iterations): guard let passwordHash = MTPBKDF2(passwordData, salt, iterations) else { return nil } return passwordHash } } func verifyPassword(_ account: UnauthorizedAccount, password: String) -> Signal { return twoStepAuthData(account.network) |> mapToSignal { authData -> Signal in guard let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData else { return .fail(MTRpcError(errorCode: 400, errorDescription: "INTERNAL_NO_PASSWORD")) } let kdfResult = passwordKDF(encryptionProvider: account.network.encryptionProvider, password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) if let kdfResult = kdfResult { return account.network.request(Api.functions.auth.checkPassword(password: .inputCheckPasswordSRP(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1))), automaticFloodWait: false) } else { return .fail(MTRpcError(errorCode: 400, errorDescription: "KDF_ERROR")) } } } public enum AccountServiceTaskMasterMode { case now case always case never } public struct AccountNetworkProxyState: Equatable { public let address: String public let hasConnectionIssues: Bool } public enum AccountNetworkState: Equatable { case waitingForNetwork case connecting(proxy: AccountNetworkProxyState?) case updating(proxy: AccountNetworkProxyState?) case online(proxy: AccountNetworkProxyState?) } public final class AccountAuxiliaryMethods { public let updatePeerChatInputState: (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState? public let fetchResource: (Account, MediaResource, Signal<[(Range, MediaBoxFetchPriority)], NoError>, MediaResourceFetchParameters?) -> Signal? public let fetchResourceMediaReferenceHash: (MediaResource) -> Signal public let prepareSecretThumbnailData: (MediaResourceData) -> (PixelDimensions, Data)? public init(updatePeerChatInputState: @escaping (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState?, fetchResource: @escaping (Account, MediaResource, Signal<[(Range, MediaBoxFetchPriority)], NoError>, MediaResourceFetchParameters?) -> Signal?, fetchResourceMediaReferenceHash: @escaping (MediaResource) -> Signal, prepareSecretThumbnailData: @escaping (MediaResourceData) -> (PixelDimensions, Data)?) { self.updatePeerChatInputState = updatePeerChatInputState self.fetchResource = fetchResource self.fetchResourceMediaReferenceHash = fetchResourceMediaReferenceHash self.prepareSecretThumbnailData = prepareSecretThumbnailData } } public struct AccountRunningImportantTasks: OptionSet { public var rawValue: Int32 public init(rawValue: Int32) { self.rawValue = rawValue } public static let other = AccountRunningImportantTasks(rawValue: 1 << 0) public static let pendingMessages = AccountRunningImportantTasks(rawValue: 1 << 1) } public struct MasterNotificationKey: Codable { public let id: Data public let data: Data } public func masterNotificationsKey(account: Account, ignoreDisabled: Bool) -> Signal { return masterNotificationsKey(masterNotificationKeyValue: account.masterNotificationKey, postbox: account.postbox, ignoreDisabled: ignoreDisabled) } private func masterNotificationsKey(masterNotificationKeyValue: Atomic, postbox: Postbox, ignoreDisabled: Bool) -> Signal { if let key = masterNotificationKeyValue.with({ $0 }) { return .single(key) } return postbox.transaction(ignoreDisabled: ignoreDisabled, { transaction -> MasterNotificationKey in if let value = transaction.keychainEntryForKey("master-notification-secret"), !value.isEmpty { let authKeyHash = sha1Digest(value) let authKeyId = authKeyHash.subdata(in: authKeyHash.count - 8 ..< authKeyHash.count) let keyData = MasterNotificationKey(id: authKeyId, data: value) let _ = masterNotificationKeyValue.swap(keyData) return keyData } else { var secretData = Data(count: 256) let secretDataCount = secretData.count if !secretData.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer) -> Bool in let copyResult = SecRandomCopyBytes(nil, secretDataCount, bytes) return copyResult == errSecSuccess }) { assertionFailure() } transaction.setKeychainEntry(secretData, forKey: "master-notification-secret") let authKeyHash = sha1Digest(secretData) let authKeyId = authKeyHash.subdata(in: authKeyHash.count - 8 ..< authKeyHash.count) let keyData = MasterNotificationKey(id: authKeyId, data: secretData) let _ = masterNotificationKeyValue.swap(keyData) return keyData } }) } public func decryptedNotificationPayload(key: MasterNotificationKey, data: Data) -> Data? { if data.count < 8 { return nil } if data.subdata(in: 0 ..< 8) != key.id { return nil } let x = 8 let msgKey = data.subdata(in: 8 ..< (8 + 16)) let rawData = data.subdata(in: (8 + 16) ..< data.count) let sha256_a = sha256Digest(msgKey + key.data.subdata(in: x ..< (x + 36))) let sha256_b = sha256Digest(key.data.subdata(in: (40 + x) ..< (40 + x + 36)) + msgKey) let aesKey = sha256_a.subdata(in: 0 ..< 8) + sha256_b.subdata(in: 8 ..< (8 + 16)) + sha256_a.subdata(in: 24 ..< (24 + 8)) let aesIv = sha256_b.subdata(in: 0 ..< 8) + sha256_a.subdata(in: 8 ..< (8 + 16)) + sha256_b.subdata(in: 24 ..< (24 + 8)) guard let data = MTAesDecrypt(rawData, aesKey, aesIv), data.count > 4 else { return nil } var dataLength: Int32 = 0 data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in memcpy(&dataLength, bytes, 4) } if dataLength < 0 || dataLength > data.count - 4 { return nil } let checkMsgKeyLarge = sha256Digest(key.data.subdata(in: (88 + x) ..< (88 + x + 32)) + data) let checkMsgKey = checkMsgKeyLarge.subdata(in: 8 ..< (8 + 16)) if checkMsgKey != msgKey { return nil } return data.subdata(in: 4 ..< (4 + Int(dataLength))) } public func decryptedNotificationPayload(account: Account, data: Data) -> Signal { return masterNotificationsKey(masterNotificationKeyValue: account.masterNotificationKey, postbox: account.postbox, ignoreDisabled: true) |> map { secret -> Data? in return decryptedNotificationPayload(key: secret, data: data) } } public func accountBackupData(postbox: Postbox) -> Signal { return postbox.transaction { transaction -> AccountBackupData? in guard let state = transaction.getState() as? AuthorizedAccountState else { return nil } guard let authInfoData = transaction.keychainEntryForKey("persistent:datacenterAuthInfoById") else { return nil } guard let authInfo = NSKeyedUnarchiver.unarchiveObject(with: authInfoData) as? NSDictionary else { return nil } guard let datacenterAuthInfo = authInfo.object(forKey: state.masterDatacenterId as NSNumber) as? MTDatacenterAuthInfo else { return nil } guard let authKey = datacenterAuthInfo.authKey else { return nil } return AccountBackupData(masterDatacenterId: state.masterDatacenterId, peerId: state.peerId.toInt64(), masterDatacenterKey: authKey, masterDatacenterKeyId: datacenterAuthInfo.authKeyId) } } public class Account { public let id: AccountRecordId public let basePath: String public let testingEnvironment: Bool public let supplementary: Bool public let postbox: Postbox public let network: Network public let networkArguments: NetworkInitializationArguments public let peerId: PeerId public let auxiliaryMethods: AccountAuxiliaryMethods private let serviceQueue = Queue() public private(set) var stateManager: AccountStateManager! private(set) var contactSyncManager: ContactSyncManager! public private(set) var callSessionManager: CallSessionManager! public private(set) var viewTracker: AccountViewTracker! public private(set) var pendingMessageManager: PendingMessageManager! public private(set) var pendingUpdateMessageManager: PendingUpdateMessageManager! public private(set) var messageMediaPreuploadManager: MessageMediaPreuploadManager! private(set) var mediaReferenceRevalidationContext: MediaReferenceRevalidationContext! private var peerInputActivityManager: PeerInputActivityManager! private var localInputActivityManager: PeerInputActivityManager! private var accountPresenceManager: AccountPresenceManager! private var notificationAutolockReportManager: NotificationAutolockReportManager! fileprivate let managedContactsDisposable = MetaDisposable() fileprivate let managedStickerPacksDisposable = MetaDisposable() private let becomeMasterDisposable = MetaDisposable() private let managedServiceViewsDisposable = MetaDisposable() private let managedOperationsDisposable = DisposableSet() private var storageSettingsDisposable: Disposable? public let importableContacts = Promise<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData]>() public let shouldBeServiceTaskMaster = Promise() public let shouldKeepOnlinePresence = Promise() public let autolockReportDeadline = Promise() public let shouldExplicitelyKeepWorkerConnections = Promise(false) public let shouldKeepBackgroundDownloadConnections = Promise(false) private let networkStateValue = Promise(.waitingForNetwork) public var networkState: Signal { return self.networkStateValue.get() } private let networkTypeValue = Promise() public var networkType: Signal { return self.networkTypeValue.get() } private let atomicCurrentNetworkType = Atomic(value: .none) public var immediateNetworkType: NetworkType { return self.atomicCurrentNetworkType.with { $0 } } private var networkTypeDisposable: Disposable? private let _loggedOut = ValuePromise(false, ignoreRepeated: true) public var loggedOut: Signal { return self._loggedOut.get() } private let _importantTasksRunning = ValuePromise([], ignoreRepeated: true) public var importantTasksRunning: Signal { return self._importantTasksRunning.get() } fileprivate let masterNotificationKey = Atomic(value: nil) var transformOutgoingMessageMedia: TransformOutgoingMessageMedia? private var lastSmallLogPostTimestamp: Double? private let smallLogPostDisposable = MetaDisposable() public init(accountManager: AccountManager, id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, networkArguments: NetworkInitializationArguments, peerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods, supplementary: Bool) { self.id = id self.basePath = basePath self.testingEnvironment = testingEnvironment self.postbox = postbox self.network = network self.networkArguments = networkArguments self.peerId = peerId self.auxiliaryMethods = auxiliaryMethods self.supplementary = supplementary self.peerInputActivityManager = PeerInputActivityManager() self.callSessionManager = CallSessionManager(postbox: postbox, network: network, maxLayer: networkArguments.voipMaxLayer, versions: networkArguments.voipVersions, addUpdates: { [weak self] updates in self?.stateManager?.addUpdates(updates) }) self.stateManager = AccountStateManager(accountPeerId: self.peerId, accountManager: accountManager, postbox: self.postbox, network: self.network, callSessionManager: self.callSessionManager, addIsContactUpdates: { [weak self] updates in self?.contactSyncManager?.addIsContactUpdates(updates) }, shouldKeepOnlinePresence: self.shouldKeepOnlinePresence.get(), peerInputActivityManager: self.peerInputActivityManager, auxiliaryMethods: auxiliaryMethods) self.contactSyncManager = ContactSyncManager(postbox: postbox, network: network, accountPeerId: peerId, stateManager: self.stateManager) self.localInputActivityManager = PeerInputActivityManager() self.accountPresenceManager = AccountPresenceManager(shouldKeepOnlinePresence: self.shouldKeepOnlinePresence.get(), network: network) let _ = (postbox.transaction { transaction -> Void in transaction.updatePeerPresencesInternal(presences: [peerId: TelegramUserPresence(status: .present(until: Int32.max - 1), lastActivity: 0)], merge: { _, updated in return updated }) transaction.setNeedsPeerGroupMessageStatsSynchronization(groupId: Namespaces.PeerGroup.archive, namespace: Namespaces.Message.Cloud) }).start() self.notificationAutolockReportManager = NotificationAutolockReportManager(deadline: self.autolockReportDeadline.get(), network: network) self.autolockReportDeadline.set( networkArguments.autolockDeadine |> distinctUntilChanged ) self.viewTracker = AccountViewTracker(account: self) self.messageMediaPreuploadManager = MessageMediaPreuploadManager() self.mediaReferenceRevalidationContext = MediaReferenceRevalidationContext() self.pendingMessageManager = PendingMessageManager(network: network, postbox: postbox, accountPeerId: peerId, auxiliaryMethods: auxiliaryMethods, stateManager: self.stateManager, localInputActivityManager: self.localInputActivityManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.mediaReferenceRevalidationContext) self.pendingUpdateMessageManager = PendingUpdateMessageManager(postbox: postbox, network: network, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext) self.network.loggedOut = { [weak self] in Logger.shared.log("Account", "network logged out") if let strongSelf = self { strongSelf._loggedOut.set(true) strongSelf.callSessionManager.dropAll() } } self.network.didReceiveSoftAuthResetError = { [weak self] in self?.postSmallLogIfNeeded() } let networkStateQueue = Queue() let networkStateSignal = combineLatest(queue: networkStateQueue, self.stateManager.isUpdating, network.connectionStatus/*, delayNetworkStatus*/) |> map { isUpdating, connectionStatus/*, delayNetworkStatus*/ -> AccountNetworkState in /*if delayNetworkStatus { return .online(proxy: nil) }*/ switch connectionStatus { case .waitingForNetwork: return .waitingForNetwork case let .connecting(proxyAddress, proxyHasConnectionIssues): var proxyState: AccountNetworkProxyState? if let proxyAddress = proxyAddress { proxyState = AccountNetworkProxyState(address: proxyAddress, hasConnectionIssues: proxyHasConnectionIssues) } return .connecting(proxy: proxyState) case let .updating(proxyAddress): var proxyState: AccountNetworkProxyState? if let proxyAddress = proxyAddress { proxyState = AccountNetworkProxyState(address: proxyAddress, hasConnectionIssues: false) } return .updating(proxy: proxyState) case let .online(proxyAddress): var proxyState: AccountNetworkProxyState? if let proxyAddress = proxyAddress { proxyState = AccountNetworkProxyState(address: proxyAddress, hasConnectionIssues: false) } if isUpdating { return .updating(proxy: proxyState) } else { return .online(proxy: proxyState) } } } self.networkStateValue.set(networkStateSignal |> distinctUntilChanged) self.networkTypeValue.set(currentNetworkType()) let atomicCurrentNetworkType = self.atomicCurrentNetworkType self.networkTypeDisposable = self.networkTypeValue.get().start(next: { value in let _ = atomicCurrentNetworkType.swap(value) }) let serviceTasksMasterBecomeMaster = self.shouldBeServiceTaskMaster.get() |> distinctUntilChanged |> deliverOn(self.serviceQueue) self.becomeMasterDisposable.set(serviceTasksMasterBecomeMaster.start(next: { [weak self] value in if let strongSelf = self, (value == .now || value == .always) { strongSelf.postbox.becomeMasterClient() } })) let shouldBeMaster = combineLatest(self.shouldBeServiceTaskMaster.get(), postbox.isMasterClient()) |> map { [weak self] shouldBeMaster, isMaster -> Bool in if shouldBeMaster == .always && !isMaster { self?.postbox.becomeMasterClient() } return (shouldBeMaster == .now || shouldBeMaster == .always) && isMaster } |> distinctUntilChanged self.network.shouldKeepConnection.set(shouldBeMaster) self.network.shouldExplicitelyKeepWorkerConnections.set(self.shouldExplicitelyKeepWorkerConnections.get()) self.network.shouldKeepBackgroundDownloadConnections.set(self.shouldKeepBackgroundDownloadConnections.get()) let serviceTasksMaster = shouldBeMaster |> deliverOn(self.serviceQueue) |> mapToSignal { [weak self] value -> Signal in if let strongSelf = self, value { Logger.shared.log("Account", "Became master") return managedServiceViews(accountPeerId: peerId, network: strongSelf.network, postbox: strongSelf.postbox, stateManager: strongSelf.stateManager, pendingMessageManager: strongSelf.pendingMessageManager) } else { Logger.shared.log("Account", "Resigned master") return .never() } } self.managedServiceViewsDisposable.set(serviceTasksMaster.start()) let pendingMessageManager = self.pendingMessageManager Logger.shared.log("Account", "Begin watching unsent message ids") self.managedOperationsDisposable.add(postbox.unsentMessageIdsView().start(next: { [weak pendingMessageManager] view in pendingMessageManager?.updatePendingMessageIds(view.ids) })) self.managedOperationsDisposable.add(managedSecretChatOutgoingOperations(auxiliaryMethods: auxiliaryMethods, postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedCloudChatRemoveMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedAutoremoveMessageOperations(network: self.network, postbox: self.postbox, isRemove: true).start()) self.managedOperationsDisposable.add(managedAutoremoveMessageOperations(network: self.network, postbox: self.postbox, isRemove: false).start()) self.managedOperationsDisposable.add(managedGlobalNotificationSettings(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedSynchronizePinnedChatsOperations(postbox: self.postbox, network: self.network, accountPeerId: self.peerId, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedSynchronizeGroupedPeersOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager, namespace: .stickers).start()) self.managedOperationsDisposable.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager, namespace: .masks).start()) self.managedOperationsDisposable.add(managedSynchronizeMarkFeaturedStickerPacksAsSeenOperations(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedSynchronizeRecentlyUsedMediaOperations(postbox: self.postbox, network: self.network, category: .stickers, revalidationContext: self.mediaReferenceRevalidationContext).start()) self.managedOperationsDisposable.add(managedSynchronizeSavedGifsOperations(postbox: self.postbox, network: self.network, revalidationContext: self.mediaReferenceRevalidationContext).start()) self.managedOperationsDisposable.add(managedSynchronizeSavedStickersOperations(postbox: self.postbox, network: self.network, revalidationContext: self.mediaReferenceRevalidationContext).start()) self.managedOperationsDisposable.add(managedRecentlyUsedInlineBots(postbox: self.postbox, network: self.network, accountPeerId: peerId).start()) self.managedOperationsDisposable.add(managedLocalTypingActivities(activities: self.localInputActivityManager.allActivities(), postbox: self.postbox, network: self.network, accountPeerId: self.peerId).start()) self.managedOperationsDisposable.add(managedSynchronizeConsumeMessageContentOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedConsumePersonalMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network, accountPeerId: self.peerId).start()) let importantBackgroundOperations: [Signal] = [ managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] }, self.pendingMessageManager.hasPendingMessages |> map { !$0.isEmpty ? AccountRunningImportantTasks.pendingMessages : [] }, self.pendingUpdateMessageManager.updatingMessageMedia |> map { !$0.isEmpty ? AccountRunningImportantTasks.pendingMessages : [] }, self.accountPresenceManager.isPerformingUpdate() |> map { $0 ? AccountRunningImportantTasks.other : [] }, self.notificationAutolockReportManager.isPerformingUpdate() |> map { $0 ? AccountRunningImportantTasks.other : [] } ] let importantBackgroundOperationsRunning = combineLatest(queue: Queue(), importantBackgroundOperations) |> map { values -> AccountRunningImportantTasks in var result: AccountRunningImportantTasks = [] for value in values { result.formUnion(value) } return result } self.managedOperationsDisposable.add(importantBackgroundOperationsRunning.start(next: { [weak self] value in if let strongSelf = self { strongSelf._importantTasksRunning.set(value) } })) self.managedOperationsDisposable.add((accountManager.sharedData(keys: [SharedDataKeys.proxySettings]) |> map { sharedData -> ProxyServerSettings? in if let settings = sharedData.entries[SharedDataKeys.proxySettings] as? ProxySettings { return settings.effectiveActiveServer } else { return nil } } |> distinctUntilChanged).start(next: { activeServer in let updated = activeServer.flatMap { activeServer -> MTSocksProxySettings? in return activeServer.mtProxySettings } network.context.updateApiEnvironment { environment in let current = environment?.socksProxySettings let updateNetwork: Bool if let current = current, let updated = updated { updateNetwork = !current.isEqual(updated) } else { updateNetwork = (current != nil) != (updated != nil) } if updateNetwork { network.dropConnectionStatus() return environment?.withUpdatedSocksProxySettings(updated) } else { return nil } } })) self.managedOperationsDisposable.add(managedConfigurationUpdates(accountManager: accountManager, postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedVoipConfigurationUpdates(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedAppConfigurationUpdates(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedAutodownloadSettingsUpdates(accountManager: accountManager, network: self.network).start()) self.managedOperationsDisposable.add(managedTermsOfServiceUpdates(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedAppUpdateInfo(network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedAppChangelog(postbox: self.postbox, network: self.network, stateManager: self.stateManager, appVersion: self.networkArguments.appVersion).start()) self.managedOperationsDisposable.add(managedPromoInfoUpdates(postbox: self.postbox, network: self.network, viewTracker: self.viewTracker).start()) self.managedOperationsDisposable.add(managedLocalizationUpdatesOperations(accountManager: accountManager, postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedPendingPeerNotificationSettings(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedSynchronizeAppLogEventsOperations(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedNotificationSettingsBehaviors(postbox: self.postbox).start()) self.managedOperationsDisposable.add(managedThemesUpdates(accountManager: accountManager, postbox: self.postbox, network: self.network).start()) if !self.supplementary { self.managedOperationsDisposable.add(managedAnimatedEmojiUpdates(postbox: self.postbox, network: self.network).start()) } self.managedOperationsDisposable.add(managedGreetingStickers(postbox: self.postbox, network: self.network).start()) let mediaBox = postbox.mediaBox self.storageSettingsDisposable = accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings]).start(next: { [weak mediaBox] sharedData in guard let mediaBox = mediaBox else { return } let settings: CacheStorageSettings = sharedData.entries[SharedDataKeys.cacheStorageSettings] as? CacheStorageSettings ?? CacheStorageSettings.defaultSettings mediaBox.setMaxStoreTimes(general: settings.defaultCacheStorageTimeout, shortLived: 60 * 60, gigabytesLimit: settings.defaultCacheStorageLimitGigabytes) }) let _ = masterNotificationsKey(masterNotificationKeyValue: self.masterNotificationKey, postbox: self.postbox, ignoreDisabled: false).start(next: { key in let encoder = JSONEncoder() if let data = try? encoder.encode(key) { let _ = try? data.write(to: URL(fileURLWithPath: "\(basePath)/notificationsKey")) } }) } deinit { self.managedContactsDisposable.dispose() self.managedStickerPacksDisposable.dispose() self.managedServiceViewsDisposable.dispose() self.managedOperationsDisposable.dispose() self.storageSettingsDisposable?.dispose() self.smallLogPostDisposable.dispose() self.networkTypeDisposable?.dispose() } private func postSmallLogIfNeeded() { let timestamp = CFAbsoluteTimeGetCurrent() if self.lastSmallLogPostTimestamp == nil || self.lastSmallLogPostTimestamp! < timestamp - 30.0 { self.lastSmallLogPostTimestamp = timestamp let network = self.network self.smallLogPostDisposable.set((Logger.shared.collectShortLog() |> mapToSignal { events -> Signal in if events.isEmpty { return .complete() } else { return network.request(Api.functions.help.saveAppLog(events: events.map { event -> Api.InputAppEvent in return .inputAppEvent(time: event.0, type: "", peer: 0, data: .jsonString(value: event.1)) })) |> ignoreValues |> `catch` { _ -> Signal in return .complete() } } }).start()) } } public func resetStateManagement() { self.stateManager.reset() self.restartContactManagement() self.managedStickerPacksDisposable.set(manageStickerPacks(network: self.network, postbox: self.postbox).start()) if !self.supplementary { self.viewTracker.chatHistoryPreloadManager.start() } } public func resetCachedData() { self.viewTracker.reset() } public func restartContactManagement() { self.contactSyncManager.beginSync(importableContacts: self.importableContacts.get()) } public func addAdditionalPreloadHistoryPeerId(peerId: PeerId) -> Disposable { return self.viewTracker.chatHistoryPreloadManager.addAdditionalPeerId(peerId: peerId) } public func peerInputActivities(peerId: PeerActivitySpace) -> Signal<[(PeerId, PeerInputActivity)], NoError> { return self.peerInputActivityManager.activities(peerId: peerId) |> map { activities in return activities.map({ ($0.0, $0.1.activity) }) } } public func allPeerInputActivities() -> Signal<[PeerActivitySpace: [PeerId: PeerInputActivity]], NoError> { return self.peerInputActivityManager.allActivities() |> map { activities in var result: [PeerActivitySpace: [PeerId: PeerInputActivity]] = [:] for (chatPeerId, chatActivities) in activities { result[chatPeerId] = chatActivities.mapValues({ $0.activity }) } return result } } public func updateLocalInputActivity(peerId: PeerActivitySpace, activity: PeerInputActivity, isPresent: Bool) { self.localInputActivityManager.transaction { manager in if isPresent { manager.addActivity(chatPeerId: peerId, peerId: self.peerId, activity: activity) } else { manager.removeActivity(chatPeerId: peerId, peerId: self.peerId, activity: activity) } } } public func acquireLocalInputActivity(peerId: PeerActivitySpace, activity: PeerInputActivity) -> Disposable { return self.localInputActivityManager.acquireActivity(chatPeerId: peerId, peerId: self.peerId, activity: activity) } public func addUpdates(serializedData: Data) -> Void { if let object = Api.parse(Buffer(data: serializedData)) { //self.stateManager.addUpdates() } } } public func accountNetworkUsageStats(account: Account, reset: ResetNetworkUsageStats) -> Signal { return networkUsageStats(basePath: account.basePath, reset: reset) } public func updateAccountNetworkUsageStats(account: Account, category: MediaResourceStatsCategory, delta: NetworkUsageStatsConnectionsEntry) { updateNetworkUsageStats(basePath: account.basePath, category: category, delta: delta) } public typealias FetchCachedResourceRepresentation = (_ account: Account, _ resource: MediaResource, _ representation: CachedMediaResourceRepresentation) -> Signal public typealias TransformOutgoingMessageMedia = (_ postbox: Postbox, _ network: Network, _ media: AnyMediaReference, _ userInteractive: Bool) -> Signal public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: FetchCachedResourceRepresentation? = nil, transformOutgoingMessageMedia: TransformOutgoingMessageMedia? = nil, preFetchedResourcePath: @escaping (MediaResource) -> String? = { _ in return nil }) { account.postbox.mediaBox.preFetchedResourcePath = preFetchedResourcePath account.postbox.mediaBox.fetchResource = { [weak account] resource, intervals, parameters -> Signal in if let strongAccount = account { if let result = fetchResource(account: strongAccount, resource: resource, intervals: intervals, parameters: parameters) { return result } else if let result = strongAccount.auxiliaryMethods.fetchResource(strongAccount, resource, intervals, parameters) { return result } else { return .never() } } else { return .never() } } account.postbox.mediaBox.fetchCachedResourceRepresentation = { [weak account] resource, representation in if let strongAccount = account, let fetchCachedResourceRepresentation = fetchCachedResourceRepresentation { return fetchCachedResourceRepresentation(strongAccount, resource, representation) } else { return .never() } } account.transformOutgoingMessageMedia = transformOutgoingMessageMedia account.pendingMessageManager.transformOutgoingMessageMedia = transformOutgoingMessageMedia account.pendingUpdateMessageManager.transformOutgoingMessageMedia = transformOutgoingMessageMedia }