2020-10-07 21:11:52 +01:00

1267 lines
65 KiB
Swift

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<Void>()
public var updateLoginTokenEvents: Signal<Void, NoError> {
return self.updateLoginTokenPipe.signal()
}
public var masterDatacenterId: Int32 {
return Int32(self.network.mtProto.datacenterId)
}
public let shouldBeServiceTaskMaster = Promise<AccountServiceTaskMasterMode>()
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<UnauthorizedAccount, NoError> {
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<UnauthorizedAccount, NoError> 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<ValueBoxKey>, encryptionParameters: ValueBoxEncryptionParameters) -> Signal<AccountPreferenceEntriesResult, NoError> {
let path = "\(rootPath)/\(accountRecordIdPathName(id))"
let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970))
return postbox
|> mapToSignal { value -> Signal<AccountPreferenceEntriesResult, NoError> 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)
}
}
}
}
public enum AccountNoticeEntriesResult {
case progress(Float)
case result(String, [ValueBoxKey: NoticeEntry])
}
public func accountNoticeEntries(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters) -> Signal<AccountNoticeEntriesResult, NoError> {
let path = "\(rootPath)/\(accountRecordIdPathName(id))"
let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970))
return postbox
|> mapToSignal { value -> Signal<AccountNoticeEntriesResult, NoError> 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())
}
}
}
}
public enum LegacyAccessChallengeDataResult {
case progress(Float)
case result(PostboxAccessChallengeData)
}
public func accountLegacyAccessChallengeData(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters) -> Signal<LegacyAccessChallengeDataResult, NoError> {
let path = "\(rootPath)/\(accountRecordIdPathName(id))"
let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970))
return postbox
|> mapToSignal { value -> Signal<LegacyAccessChallengeDataResult, NoError> 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())
}
}
}
}
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<AccountResult, NoError> {
let path = "\(rootPath)/\(accountRecordIdPathName(id))"
let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970))
return postbox
|> mapToSignal { result -> Signal<AccountResult, NoError> in
switch result {
case let .upgrading(progress):
return .single(.upgrading(progress))
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<AccountResult, NoError> 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<AccountResult, NoError> 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<TwoStepAuthData, MTRpcError> {
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<UInt8>) -> 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[..<subIndex])
hex = String(hex[subIndex...])
var ch: UInt32 = 0
if !Scanner(string: c).scanHexInt32(&ch) {
return Data()
}
var char = UInt8(ch)
data.append(&char, count: 1)
}
return data
}
func sha1Digest(_ data : Data) -> 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<Int8>) -> Void in
arc4random_buf(bytes, 32)
}
nextSalt1.append(randomSalt1)
let nextSalt2 = salt2
var g = Data(count: 4)
g.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> 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<UInt8>) -> Void in
b.withUnsafeBytes { (bBytes: UnsafePointer<UInt8>) -> 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<UInt8>) -> Void in
let _ = SecRandomCopyBytes(nil, aLength, bytes)
}
var g = Data(count: 4)
g.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> 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<Int8>) -> 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<Int8>) -> 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<Api.auth.Authorization, MTRpcError> {
return twoStepAuthData(account.network)
|> mapToSignal { authData -> Signal<Api.auth.Authorization, MTRpcError> 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<Int>, MediaBoxFetchPriority)], NoError>, MediaResourceFetchParameters?) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>?
public let fetchResourceMediaReferenceHash: (MediaResource) -> Signal<Data?, NoError>
public let prepareSecretThumbnailData: (MediaResourceData) -> (PixelDimensions, Data)?
public init(updatePeerChatInputState: @escaping (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState?, fetchResource: @escaping (Account, MediaResource, Signal<[(Range<Int>, MediaBoxFetchPriority)], NoError>, MediaResourceFetchParameters?) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>?, fetchResourceMediaReferenceHash: @escaping (MediaResource) -> Signal<Data?, NoError>, 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<MasterNotificationKey, NoError> {
return masterNotificationsKey(masterNotificationKeyValue: account.masterNotificationKey, postbox: account.postbox, ignoreDisabled: ignoreDisabled)
}
private func masterNotificationsKey(masterNotificationKeyValue: Atomic<MasterNotificationKey?>, postbox: Postbox, ignoreDisabled: Bool) -> Signal<MasterNotificationKey, NoError> {
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<Int8>) -> 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<Int8>) -> 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<Data?, NoError> {
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<AccountBackupData?, NoError> {
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<AccountServiceTaskMasterMode>()
public let shouldKeepOnlinePresence = Promise<Bool>()
public let autolockReportDeadline = Promise<Int32?>()
public let shouldExplicitelyKeepWorkerConnections = Promise<Bool>(false)
public let shouldKeepBackgroundDownloadConnections = Promise<Bool>(false)
private let networkStateValue = Promise<AccountNetworkState>(.waitingForNetwork)
public var networkState: Signal<AccountNetworkState, NoError> {
return self.networkStateValue.get()
}
private let networkTypeValue = Promise<NetworkType>()
public var networkType: Signal<NetworkType, NoError> {
return self.networkTypeValue.get()
}
private let atomicCurrentNetworkType = Atomic<NetworkType>(value: .none)
public var immediateNetworkType: NetworkType {
return self.atomicCurrentNetworkType.with { $0 }
}
private var networkTypeDisposable: Disposable?
private let _loggedOut = ValuePromise<Bool>(false, ignoreRepeated: true)
public var loggedOut: Signal<Bool, NoError> {
return self._loggedOut.get()
}
private let _importantTasksRunning = ValuePromise<AccountRunningImportantTasks>([], ignoreRepeated: true)
public var importantTasksRunning: Signal<AccountRunningImportantTasks, NoError> {
return self._importantTasksRunning.get()
}
fileprivate let masterNotificationKey = Atomic<MasterNotificationKey?>(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<Void, NoError> 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(postbox: self.postbox).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<AccountRunningImportantTasks, NoError>] = [
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<Never, NoError> 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<Never, NoError> 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<NetworkUsageStats, NoError> {
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<CachedMediaResourceRepresentationResult, NoError>
public typealias TransformOutgoingMessageMedia = (_ postbox: Postbox, _ network: Network, _ media: AnyMediaReference, _ userInteractive: Bool) -> Signal<AnyMediaReference?, NoError>
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<MediaResourceDataFetchResult, MediaResourceDataFetchError> 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
}