From 0f42fde3f5818cfa53a2d99d77869f662f9de55d Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 7 Sep 2018 22:49:24 +0300 Subject: [PATCH] no message --- TelegramCore/AccessSecureId.swift | 2 +- TelegramCore/Account.swift | 13 + TelegramCore/AccountIntermediateState.swift | 20 +- .../AccountStateManagementUtils.swift | 27 +- TelegramCore/AccountStateManager.swift | 38 +- TelegramCore/AccountStateReset.swift | 460 +++++++++--------- TelegramCore/CallSessionManager.swift | 238 ++++----- TelegramCore/ChannelBlacklist.swift | 237 ++++----- TelegramCore/ContactManagement.swift | 31 +- TelegramCore/CreateSecretChat.swift | 4 + TelegramCore/Holes.swift | 276 +++++++---- .../ManagedSecretChatOutgoingOperations.swift | 100 ++-- ...OutgoingMessageWithChatContextResult.swift | 16 +- TelegramCore/RemovePeerMember.swift | 48 +- TelegramCore/SecretChatEncryptionConfig.swift | 31 +- TelegramCore/SecretChatRekeySession.swift | 4 +- TelegramCore/UpdateSecretChat.swift | 7 + 17 files changed, 887 insertions(+), 665 deletions(-) diff --git a/TelegramCore/AccessSecureId.swift b/TelegramCore/AccessSecureId.swift index 054a3f2de7..63eb5c1274 100644 --- a/TelegramCore/AccessSecureId.swift +++ b/TelegramCore/AccessSecureId.swift @@ -149,7 +149,7 @@ private func generateSecureSecret(network: Network, password: String) -> Signal< } } -public struct SecureIdAccessContext { +public struct SecureIdAccessContext: Equatable { let secret: Data let id: Int64 } diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 5d93848314..1597db18b2 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -614,10 +614,18 @@ func passwordKDF(password: String, derivation: TwoStepPasswordDerivation, srpSes }) } + if !MTCheckIsSafeB(srpSessionData.B, p) { + return nil + } + let B = paddedToLength(what: srpSessionData.B, to: p) let A = paddedToLength(what: MTExp(g, a, p)!, to: p) let u = sha256Digest(A + B) + if MTIsZero(u) { + return nil + } + let pbkdfInnerData = sha256Digest(salt2 + sha256Digest(salt1 + passwordData + salt1) + salt2) guard let pbkdfResult = MTPBKDF2(pbkdfInnerData, salt1, iterations) else { @@ -631,6 +639,11 @@ func passwordKDF(password: String, derivation: TwoStepPasswordDerivation, srpSes let k = sha256Digest(p + paddedToLength(what: g, to: p)) let s1 = MTModSub(B, MTModMul(k, gx, p)!, p)! + + if !MTCheckIsSafeGAOrB(s1, p) { + return nil + } + let s2 = MTAdd(a, MTMul(u, x)!)! let S = MTExp(s1, s2, p)! let K = sha256Digest(paddedToLength(what: S, to: p)) diff --git a/TelegramCore/AccountIntermediateState.swift b/TelegramCore/AccountIntermediateState.swift index 3aa19330c4..f1d20e3af9 100644 --- a/TelegramCore/AccountIntermediateState.swift +++ b/TelegramCore/AccountIntermediateState.swift @@ -12,7 +12,6 @@ import Foundation final class AccountInitialState { let state: AuthorizedAccountState.State let peerIds: Set - let messageIds: Set let chatStates: [PeerId: PeerChatState] let peerNotificationSettings: [PeerId: PeerNotificationSettings] let peerIdsWithNewMessages: Set @@ -20,10 +19,9 @@ final class AccountInitialState { let cloudReadStates: [PeerId: PeerReadState] let channelsToPollExplicitely: Set - init(state: AuthorizedAccountState.State, peerIds: Set, messageIds: Set, peerIdsWithNewMessages: Set, chatStates: [PeerId: PeerChatState], peerNotificationSettings: [PeerId: PeerNotificationSettings], locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]], cloudReadStates: [PeerId: PeerReadState], channelsToPollExplicitely: Set) { + init(state: AuthorizedAccountState.State, peerIds: Set, peerIdsWithNewMessages: Set, chatStates: [PeerId: PeerChatState], peerNotificationSettings: [PeerId: PeerNotificationSettings], locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]], cloudReadStates: [PeerId: PeerReadState], channelsToPollExplicitely: Set) { self.state = state self.peerIds = peerIds - self.messageIds = messageIds self.chatStates = chatStates self.peerIdsWithNewMessages = peerIdsWithNewMessages self.peerNotificationSettings = peerNotificationSettings @@ -102,6 +100,7 @@ struct AccountMutableState { var peers: [PeerId: Peer] var chatStates: [PeerId: PeerChatState] var peerNotificationSettings: [PeerId: PeerNotificationSettings] + var referencedMessageIds: Set var storedMessages: Set var readInboxMaxIds: [PeerId: MessageId] var namespacesWithHolesFromPreviousState: [PeerId: Set] @@ -113,10 +112,11 @@ struct AccountMutableState { var preCachedResources: [(MediaResource, Data)] = [] - init(initialState: AccountInitialState, initialPeers: [PeerId: Peer], initialStoredMessages: Set, initialReadInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set]) { + init(initialState: AccountInitialState, initialPeers: [PeerId: Peer], initialReferencedMessageIds: Set, initialStoredMessages: Set, initialReadInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set]) { self.initialState = initialState self.state = initialState.state self.peers = initialPeers + self.referencedMessageIds = initialReferencedMessageIds self.storedMessages = initialStoredMessages self.readInboxMaxIds = initialReadInboxMaxIds self.chatStates = initialState.chatStates @@ -126,12 +126,13 @@ struct AccountMutableState { self.namespacesWithHolesFromPreviousState = [:] } - init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], chatStates: [PeerId: PeerChatState], peerNotificationSettings: [PeerId: PeerNotificationSettings], storedMessages: Set, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set], namespacesWithHolesFromPreviousState: [PeerId: Set], displayAlerts: [String], branchOperationIndex: Int) { + init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], chatStates: [PeerId: PeerChatState], peerNotificationSettings: [PeerId: PeerNotificationSettings], referencedMessageIds: Set, storedMessages: Set, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set], namespacesWithHolesFromPreviousState: [PeerId: Set], displayAlerts: [String], branchOperationIndex: Int) { self.initialState = initialState self.operations = operations self.state = state self.peers = peers self.chatStates = chatStates + self.referencedMessageIds = referencedMessageIds self.storedMessages = storedMessages self.peerNotificationSettings = peerNotificationSettings self.readInboxMaxIds = readInboxMaxIds @@ -142,10 +143,11 @@ struct AccountMutableState { } func branch() -> AccountMutableState { - return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, chatStates: self.chatStates, peerNotificationSettings: self.peerNotificationSettings, storedMessages: self.storedMessages, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, displayAlerts: self.displayAlerts, branchOperationIndex: self.operations.count) + return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, chatStates: self.chatStates, peerNotificationSettings: self.peerNotificationSettings, referencedMessageIds: self.referencedMessageIds, storedMessages: self.storedMessages, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, displayAlerts: self.displayAlerts, branchOperationIndex: self.operations.count) } mutating func merge(_ other: AccountMutableState) { + self.referencedMessageIds.formUnion(other.referencedMessageIds) for i in other.branchOperationIndex ..< other.operations.count { self.addOperation(other.operations[i]) } @@ -340,6 +342,12 @@ struct AccountMutableState { if case let .Id(id) = message.id { self.storedMessages.insert(id) } + inner: for attribute in message.attributes { + if let attribute = attribute as? ReplyMessageAttribute { + self.referencedMessageIds.insert(attribute.messageId) + break inner + } + } } case let .UpdateState(state): self.state = state diff --git a/TelegramCore/AccountStateManagementUtils.swift b/TelegramCore/AccountStateManagementUtils.swift index 126383b73d..592f67c390 100644 --- a/TelegramCore/AccountStateManagementUtils.swift +++ b/TelegramCore/AccountStateManagementUtils.swift @@ -378,7 +378,7 @@ private func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set Signal { @@ -1230,21 +1230,21 @@ private func finalStateWithUpdatesAndServerTime(account: Account, state: Account } } return resolveAssociatedMessages(account: account, state: finalState) + |> mapToSignal { resultingState -> Signal in + return resolveMissingPeerNotificationSettings(account: account, state: resultingState) |> mapToSignal { resultingState -> Signal in - return resolveMissingPeerNotificationSettings(account: account, state: resultingState) - |> mapToSignal { resultingState -> Signal in - return resolveMissingPeerCloudReadStates(account: account, state: resultingState) - |> map { resultingState -> AccountFinalState in - return AccountFinalState(state: resultingState, shouldPoll: shouldPoll || hadError, incomplete: missingUpdates) - } - } + return resolveMissingPeerCloudReadStates(account: account, state: resultingState) + |> map { resultingState -> AccountFinalState in + return AccountFinalState(state: resultingState, shouldPoll: shouldPoll || hadError, incomplete: missingUpdates) + } } + } } } private func resolveAssociatedMessages(account: Account, state: AccountMutableState) -> Signal { - let missingMessageIds = state.initialState.messageIds.subtracting(state.storedMessages) + let missingMessageIds = state.referencedMessageIds.subtracting(state.storedMessages) if missingMessageIds.isEmpty { return .single(state) } else { @@ -1402,7 +1402,7 @@ func keepPollingChannel(account: Account, peerId: PeerId, stateManager: AccountS if let notificationSettings = transaction.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings { peerNotificationSettings[peerId] = notificationSettings } - let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), messageIds: Set(), peerIdsWithNewMessages: Set(), chatStates: chatStates, peerNotificationSettings: peerNotificationSettings, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:]) + let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsWithNewMessages: Set(), chatStates: chatStates, peerNotificationSettings: peerNotificationSettings, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:]) return pollChannel(account, peer: peer, state: initialState) |> mapToSignal { (finalState, _, timeout) -> Signal in return resolveAssociatedMessages(account: account, state: finalState) @@ -1441,7 +1441,7 @@ private func resetChannels(_ account: Account, peers: [Peer], state: AccountMuta return .single(nil) } } - |> map { result -> AccountMutableState in + |> mapToSignal { result -> Signal in var updatedState = state var dialogsChats: [Api.Chat] = [] @@ -1565,7 +1565,10 @@ private func resetChannels(_ account: Account, peers: [Peer], state: AccountMuta // TODO: delete messages later than top - return updatedState + return resolveAssociatedMessages(account: account, state: updatedState) + |> mapToSignal { resultingState -> Signal in + return .single(resultingState) + } } } diff --git a/TelegramCore/AccountStateManager.swift b/TelegramCore/AccountStateManager.swift index ffa64b63c6..2a19084999 100644 --- a/TelegramCore/AccountStateManager.swift +++ b/TelegramCore/AccountStateManager.swift @@ -89,8 +89,8 @@ public final class AccountStateManager { return self.isUpdatingValue.get() } - private let notificationMessagesPipe = ValuePipe<[(Message, PeerGroupId?)]>() - public var notificationMessages: Signal<[(Message, PeerGroupId?)], NoError> { + private let notificationMessagesPipe = ValuePipe<[([Message], PeerGroupId?)]>() + public var notificationMessages: Signal<[([Message], PeerGroupId?)], NoError> { return self.notificationMessagesPipe.signal() } @@ -532,15 +532,15 @@ public final class AccountStateManager { let _ = self.delayNotificatonsUntil.swap(events.delayNotificatonsUntil) } - let signal = self.account.postbox.transaction { transaction -> [(Message, PeerGroupId?)] in - var messages: [(Message, PeerGroupId?)] = [] + let signal = self.account.postbox.transaction { transaction -> [([Message], PeerGroupId?)] in + var messageList: [([Message], PeerGroupId?)] = [] for id in events.addedIncomingMessageIds { - let (message, notify, _, _) = messageForNotification(transaction: transaction, id: id, alwaysReturnMessage: false) - if let message = message, notify { - messages.append((message, transaction.getPeerGroupId(message.id.peerId))) + let (messages, notify, _, _) = messagesForNotification(transaction: transaction, id: id, alwaysReturnMessage: false) + if !messages.isEmpty && notify { + messageList.append((messages, transaction.getPeerGroupId(messages[0].id.peerId))) } } - return messages + return messageList } let _ = (signal @@ -789,10 +789,10 @@ public final class AccountStateManager { } } -public func messageForNotification(transaction: Transaction, id: MessageId, alwaysReturnMessage: Bool) -> (message: Message?, notify: Bool, sound: PeerMessageSound, displayContents: Bool) { +public func messagesForNotification(transaction: Transaction, id: MessageId, alwaysReturnMessage: Bool) -> (messages: [Message], notify: Bool, sound: PeerMessageSound, displayContents: Bool) { guard let message = transaction.getMessage(id) else { Logger.shared.log("AccountStateManager", "notification message doesn't exist") - return (nil, false, .bundledModern(id: 0), false) + return ([], false, .bundledModern(id: 0), false) } var notify = true @@ -864,7 +864,7 @@ public func messageForNotification(transaction: Transaction, id: MessageId, alwa if let channel = message.peers[message.id.peerId] as? TelegramChannel { switch channel.participationStatus { case .kicked, .left: - return (nil, false, sound, false) + return ([], false, sound, false) case .member: break } @@ -883,9 +883,21 @@ public func messageForNotification(transaction: Transaction, id: MessageId, alwa Logger.shared.log("AccountStateManager", "read state for \(id.peerId) is undefined") } + var resultMessages: [Message] = [message] + + var messageGroup: [Message]? + if message.forwardInfo != nil { + messageGroup = transaction.getMessageForwardedGroup(message.id) + } else if message.groupingKey != nil { + messageGroup = transaction.getMessageGroup(message.id) + } + if let messageGroup = messageGroup { + resultMessages.append(contentsOf: messageGroup.filter({ $0.id != message.id })) + } + if notify { - return (message, isUnread, sound, displayContents) + return (resultMessages, isUnread, sound, displayContents) } else { - return (alwaysReturnMessage ? message : nil, false, sound, displayContents) + return (alwaysReturnMessage ? resultMessages : [], false, sound, displayContents) } } diff --git a/TelegramCore/AccountStateReset.swift b/TelegramCore/AccountStateReset.swift index e8d629d168..1bb7f94f49 100644 --- a/TelegramCore/AccountStateReset.swift +++ b/TelegramCore/AccountStateReset.swift @@ -17,246 +17,246 @@ func accountStateReset(postbox: Postbox, network: Network) -> Signal retryRequest return combineLatest(network.request(Api.functions.messages.getDialogs(flags: 0, /*feed*//*feedId: nil,*/ offsetDate: 0, offsetId: 0, offsetPeer: .inputPeerEmpty, limit: 100, hash: 0)) - |> retryRequest, pinnedChats, state) - |> mapToSignal { result, pinnedChats, state -> Signal in - var dialogsDialogs: [Api.Dialog] = [] - var dialogsMessages: [Api.Message] = [] - var dialogsChats: [Api.Chat] = [] - var dialogsUsers: [Api.User] = [] - - var holeExists = false - - switch result { - case let .dialogs(dialogs, messages, chats, users): - dialogsDialogs = dialogs - dialogsMessages = messages - dialogsChats = chats - dialogsUsers = users - case let .dialogsSlice(_, dialogs, messages, chats, users): - dialogsDialogs = dialogs - dialogsMessages = messages - dialogsChats = chats - dialogsUsers = users - holeExists = true - case .dialogsNotModified: - dialogsDialogs = [] - dialogsMessages = [] - dialogsChats = [] - dialogsUsers = [] - } - - let replacePinnedItemIds: [PinnedItemId] - switch pinnedChats { - case let .peerDialogs(apiDialogs, apiMessages, apiChats, apiUsers, _): - dialogsDialogs.append(contentsOf: apiDialogs) - dialogsMessages.append(contentsOf: apiMessages) - dialogsChats.append(contentsOf: apiChats) - dialogsUsers.append(contentsOf: apiUsers) - - var itemIds: [PinnedItemId] = [] - - loop: for dialog in apiDialogs { - switch dialog { - case let .dialog(_, peer, _, _, _, _, _, _, _, _): - itemIds.append(.peer(peer.peerId)) - /*feed*/ - /*case let .dialogFeed(_, _, _, feedId, _, _, _, _): - itemIds.append(.group(PeerGroupId(rawValue: feedId))) - continue loop*/ - } - } + |> retryRequest, pinnedChats, state) + |> mapToSignal { result, pinnedChats, state -> Signal in + var dialogsDialogs: [Api.Dialog] = [] + var dialogsMessages: [Api.Message] = [] + var dialogsChats: [Api.Chat] = [] + var dialogsUsers: [Api.User] = [] + + var holeExists = false + + switch result { + case let .dialogs(dialogs, messages, chats, users): + dialogsDialogs = dialogs + dialogsMessages = messages + dialogsChats = chats + dialogsUsers = users + case let .dialogsSlice(_, dialogs, messages, chats, users): + dialogsDialogs = dialogs + dialogsMessages = messages + dialogsChats = chats + dialogsUsers = users + holeExists = true + case .dialogsNotModified: + dialogsDialogs = [] + dialogsMessages = [] + dialogsChats = [] + dialogsUsers = [] + } + + let replacePinnedItemIds: [PinnedItemId] + switch pinnedChats { + case let .peerDialogs(apiDialogs, apiMessages, apiChats, apiUsers, _): + dialogsDialogs.append(contentsOf: apiDialogs) + dialogsMessages.append(contentsOf: apiMessages) + dialogsChats.append(contentsOf: apiChats) + dialogsUsers.append(contentsOf: apiUsers) - replacePinnedItemIds = itemIds + var itemIds: [PinnedItemId] = [] + + loop: for dialog in apiDialogs { + switch dialog { + case let .dialog(_, peer, _, _, _, _, _, _, _, _): + itemIds.append(.peer(peer.peerId)) + /*feed*/ + /*case let .dialogFeed(_, _, _, feedId, _, _, _, _): + itemIds.append(.group(PeerGroupId(rawValue: feedId))) + continue loop*/ + } } - var replacementHole: ChatListHole? - var storeMessages: [StoreMessage] = [] - var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:] - var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] - var chatStates: [PeerId: PeerChatState] = [:] - var notificationSettings: [PeerId: PeerNotificationSettings] = [:] + replacePinnedItemIds = itemIds + } + + var replacementHole: ChatListHole? + var storeMessages: [StoreMessage] = [] + var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:] + var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] + var chatStates: [PeerId: PeerChatState] = [:] + var notificationSettings: [PeerId: PeerNotificationSettings] = [:] + + var topMesageIds: [PeerId: MessageId] = [:] + + loop: for dialog in dialogsDialogs { + let apiPeer: Api.Peer + let apiReadInboxMaxId: Int32 + let apiReadOutboxMaxId: Int32 + let apiTopMessage: Int32 + let apiUnreadCount: Int32 + let apiMarkedUnread: Bool + let apiUnreadMentionsCount: Int32 + var apiChannelPts: Int32? + let apiNotificationSettings: Api.PeerNotifySettings + switch dialog { + case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, peerNotificationSettings, pts, _): + apiPeer = peer + apiTopMessage = topMessage + apiReadInboxMaxId = readInboxMaxId + apiReadOutboxMaxId = readOutboxMaxId + apiUnreadCount = unreadCount + apiMarkedUnread = (flags & (1 << 3)) != 0 + apiUnreadMentionsCount = unreadMentionsCount + apiNotificationSettings = peerNotificationSettings + apiChannelPts = pts + /*feed*/ + /*case .dialogFeed: + //assertionFailure() + continue loop*/ + } - var topMesageIds: [PeerId: MessageId] = [:] + let peerId: PeerId + switch apiPeer { + case let .peerUser(userId): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) + case let .peerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) + case let .peerChannel(channelId): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) + } - loop: for dialog in dialogsDialogs { - let apiPeer: Api.Peer - let apiReadInboxMaxId: Int32 - let apiReadOutboxMaxId: Int32 - let apiTopMessage: Int32 - let apiUnreadCount: Int32 - let apiMarkedUnread: Bool - let apiUnreadMentionsCount: Int32 - var apiChannelPts: Int32? - let apiNotificationSettings: Api.PeerNotifySettings + if readStates[peerId] == nil { + readStates[peerId] = [:] + } + readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount, markedUnread: apiMarkedUnread) + + if apiTopMessage != 0 { + mentionTagSummaries[peerId] = MessageHistoryTagNamespaceSummary(version: 1, count: apiUnreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: apiTopMessage)) + topMesageIds[peerId] = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: apiTopMessage) + } + + if let apiChannelPts = apiChannelPts { + chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: apiChannelPts) + } else if peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudUser { + switch state { + case let .state(pts, _, _, _, _): + chatStates[peerId] = RegularChatState(invalidatedPts: pts) + } + } + + notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) + } + + for message in dialogsMessages { + if let storeMessage = StoreMessage(apiMessage: message) { + storeMessages.append(storeMessage) + } + } + + if holeExists { + for dialog in dialogsDialogs { switch dialog { - case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, peerNotificationSettings, pts, _): - apiPeer = peer - apiTopMessage = topMessage - apiReadInboxMaxId = readInboxMaxId - apiReadOutboxMaxId = readOutboxMaxId - apiUnreadCount = unreadCount - apiMarkedUnread = (flags & (1 << 3)) != 0 - apiUnreadMentionsCount = unreadMentionsCount - apiNotificationSettings = peerNotificationSettings - apiChannelPts = pts + case let .dialog(flags, peer, topMessage, _, _, _, _, _, _, _): + let isPinned = (flags & (1 << 2)) != 0 + + if !isPinned { + var timestamp: Int32? + for message in storeMessages { + if case let .Id(id) = message.id, id.id == topMessage { + timestamp = message.timestamp + } + } + + if let timestamp = timestamp { + let index = MessageIndex(id: MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: topMessage - 1), timestamp: timestamp) + if (replacementHole == nil || replacementHole!.index > index) { + replacementHole = ChatListHole(index: index) + } + } + } /*feed*/ /*case .dialogFeed: //assertionFailure() - continue loop*/ - } - - let peerId: PeerId - switch apiPeer { - case let .peerUser(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - case let .peerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId) - case let .peerChannel(channelId): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId) - } - - if readStates[peerId] == nil { - readStates[peerId] = [:] - } - readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount, markedUnread: apiMarkedUnread) - - if apiTopMessage != 0 { - mentionTagSummaries[peerId] = MessageHistoryTagNamespaceSummary(version: 1, count: apiUnreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: apiTopMessage)) - topMesageIds[peerId] = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: apiTopMessage) - } - - if let apiChannelPts = apiChannelPts { - chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: apiChannelPts) - } else if peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudUser { - switch state { - case let .state(pts, _, _, _, _): - chatStates[peerId] = RegularChatState(invalidatedPts: pts) - } - } - - notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings) - } - - for message in dialogsMessages { - if let storeMessage = StoreMessage(apiMessage: message) { - storeMessages.append(storeMessage) - } - } - - if holeExists { - for dialog in dialogsDialogs { - switch dialog { - case let .dialog(flags, peer, topMessage, _, _, _, _, _, _, _): - let isPinned = (flags & (1 << 2)) != 0 - - if !isPinned { - var timestamp: Int32? - for message in storeMessages { - if case let .Id(id) = message.id, id.id == topMessage { - timestamp = message.timestamp - } - } - - if let timestamp = timestamp { - let index = MessageIndex(id: MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: topMessage - 1), timestamp: timestamp) - if (replacementHole == nil || replacementHole!.index > index) { - replacementHole = ChatListHole(index: index) - } - } - } - /*feed*/ - /*case .dialogFeed: - //assertionFailure() - break*/ - } - } - } - - var peers: [Peer] = [] - var peerPresences: [PeerId: PeerPresence] = [:] - for chat in dialogsChats { - if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { - peers.append(groupOrChannel) - } - } - for user in dialogsUsers { - let telegramUser = TelegramUser(user: user) - peers.append(telegramUser) - if let presence = TelegramUserPresence(apiUser: user) { - peerPresences[telegramUser.id] = presence - } - } - - return postbox.transaction { transaction -> Void in - let previousPeerIds = transaction.resetChatList(keepPeerNamespaces: Set([Namespaces.Peer.SecretChat]), replacementHole: replacementHole) - - updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in - return updated - }) - transaction.updatePeerPresences(peerPresences) - - transaction.updateCurrentPeerNotificationSettings(notificationSettings) - - var allPeersWithMessages = Set() - for message in storeMessages { - allPeersWithMessages.insert(message.id.peerId) - } - - for (_, messageId) in topMesageIds { - if messageId.id > 1 { - var skipHole = false - if let localTopId = transaction.getTopPeerMessageIndex(peerId: messageId.peerId, namespace: messageId.namespace)?.id { - if localTopId >= messageId { - skipHole = true - } - } - if !skipHole { - transaction.addHole(MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: messageId.id - 1)) - } - } - } - - let _ = transaction.addMessages(storeMessages, location: .UpperHistoryBlock) - - transaction.resetIncomingReadStates(readStates) - - for (peerId, chatState) in chatStates { - if let chatState = chatState as? ChannelState { - if let current = transaction.getPeerChatState(peerId) as? ChannelState { - transaction.setPeerChatState(peerId, state: current.withUpdatedPts(chatState.pts)) - } else { - transaction.setPeerChatState(peerId, state: chatState) - } - } else { - transaction.setPeerChatState(peerId, state: chatState) - } - } - - transaction.setPinnedItemIds(replacePinnedItemIds) - - for (peerId, summary) in mentionTagSummaries { - transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: summary.count, maxId: summary.range.maxId) - } - - let namespacesWithHoles: [PeerId.Namespace: [MessageId.Namespace]] = [ - Namespaces.Peer.CloudUser: [Namespaces.Message.Cloud], - Namespaces.Peer.CloudGroup: [Namespaces.Message.Cloud], - Namespaces.Peer.CloudChannel: [Namespaces.Message.Cloud] - ] - for peerId in previousPeerIds { - if !allPeersWithMessages.contains(peerId), let namespaces = namespacesWithHoles[peerId.namespace] { - for namespace in namespaces { - transaction.addHole(MessageId(peerId: peerId, namespace: namespace, id: Int32.max - 1)) - } - } - } - - if let currentState = transaction.getState() as? AuthorizedAccountState, let embeddedState = currentState.state { - switch state { - case let .state(pts, _, _, seq, _): - transaction.setState(currentState.changedState(AuthorizedAccountState.State(pts: pts, qts: embeddedState.qts, date: embeddedState.date, seq: seq))) - } + break*/ } } } + + var peers: [Peer] = [] + var peerPresences: [PeerId: PeerPresence] = [:] + for chat in dialogsChats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers.append(groupOrChannel) + } + } + for user in dialogsUsers { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } + } + + return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages in + let previousPeerIds = transaction.resetChatList(keepPeerNamespaces: Set([Namespaces.Peer.SecretChat]), replacementHole: replacementHole) + + updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in + return updated + }) + transaction.updatePeerPresences(peerPresences) + + transaction.updateCurrentPeerNotificationSettings(notificationSettings) + + var allPeersWithMessages = Set() + for message in storeMessages { + allPeersWithMessages.insert(message.id.peerId) + } + + for (_, messageId) in topMesageIds { + if messageId.id > 1 { + var skipHole = false + if let localTopId = transaction.getTopPeerMessageIndex(peerId: messageId.peerId, namespace: messageId.namespace)?.id { + if localTopId >= messageId { + skipHole = true + } + } + if !skipHole { + transaction.addHole(MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: messageId.id - 1)) + } + } + } + + let _ = transaction.addMessages(storeMessages, location: .UpperHistoryBlock) + + transaction.resetIncomingReadStates(readStates) + + for (peerId, chatState) in chatStates { + if let chatState = chatState as? ChannelState { + if let current = transaction.getPeerChatState(peerId) as? ChannelState { + transaction.setPeerChatState(peerId, state: current.withUpdatedPts(chatState.pts)) + } else { + transaction.setPeerChatState(peerId, state: chatState) + } + } else { + transaction.setPeerChatState(peerId, state: chatState) + } + } + + transaction.setPinnedItemIds(replacePinnedItemIds) + + for (peerId, summary) in mentionTagSummaries { + transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: summary.count, maxId: summary.range.maxId) + } + + let namespacesWithHoles: [PeerId.Namespace: [MessageId.Namespace]] = [ + Namespaces.Peer.CloudUser: [Namespaces.Message.Cloud], + Namespaces.Peer.CloudGroup: [Namespaces.Message.Cloud], + Namespaces.Peer.CloudChannel: [Namespaces.Message.Cloud] + ] + for peerId in previousPeerIds { + if !allPeersWithMessages.contains(peerId), let namespaces = namespacesWithHoles[peerId.namespace] { + for namespace in namespaces { + transaction.addHole(MessageId(peerId: peerId, namespace: namespace, id: Int32.max - 1)) + } + } + } + + if let currentState = transaction.getState() as? AuthorizedAccountState, let embeddedState = currentState.state { + switch state { + case let .state(pts, _, _, seq, _): + transaction.setState(currentState.changedState(AuthorizedAccountState.State(pts: pts, qts: embeddedState.qts, date: embeddedState.date, seq: seq))) + } + } + }) + } } diff --git a/TelegramCore/CallSessionManager.swift b/TelegramCore/CallSessionManager.swift index 4a29355463..b1460c0655 100644 --- a/TelegramCore/CallSessionManager.swift +++ b/TelegramCore/CallSessionManager.swift @@ -455,38 +455,42 @@ private final class CallSessionManagerContext { if let internalId = self.contextIdByStableId[id] { if let context = self.contexts[internalId] { switch context.state { - case let .requested(_, accessHash, a, gA, config, _): - var key = MTExp(gB.makeData(), a, config.p.makeData())! - - if key.count > 256 { - key.count = 256 - } else { - while key.count < 256 { - key.insert(0, at: 0) + case let .requested(_, accessHash, a, gA, config, _): + let p = config.p.makeData() + if !MTCheckIsSafeGAOrB(gA, p) { + self.drop(internalId: internalId, reason: .disconnect) } - } - - let keyHash = MTSha1(key)! - - var keyId: Int64 = 0 - keyHash.withUnsafeBytes { (bytes: UnsafePointer) -> Void in - memcpy(&keyId, bytes.advanced(by: keyHash.count - 8), 8) - } - - let keyVisualHash = MTSha256(key + gA)! - - context.state = .confirming(id: id, accessHash: accessHash, key: key, keyId: keyId, keyVisualHash: keyVisualHash, disposable: (confirmCallSession(network: self.network, stableId: id, accessHash: accessHash, gA: gA, keyFingerprint: keyId) |> deliverOnMainQueue).start(next: { [weak self] updatedCall in - if let strongSelf = self, let context = strongSelf.contexts[internalId], case .confirming = context.state { - if let updatedCall = updatedCall { - strongSelf.updateSession(updatedCall) - } else { - strongSelf.drop(internalId: internalId, reason: .disconnect) + var key = MTExp(gB.makeData(), a, p)! + + if key.count > 256 { + key.count = 256 + } else { + while key.count < 256 { + key.insert(0, at: 0) } } - })) - self.contextUpdated(internalId: internalId) - default: - self.drop(internalId: internalId, reason: .disconnect) + + let keyHash = MTSha1(key)! + + var keyId: Int64 = 0 + keyHash.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + memcpy(&keyId, bytes.advanced(by: keyHash.count - 8), 8) + } + + let keyVisualHash = MTSha256(key + gA)! + + context.state = .confirming(id: id, accessHash: accessHash, key: key, keyId: keyId, keyVisualHash: keyVisualHash, disposable: (confirmCallSession(network: self.network, stableId: id, accessHash: accessHash, gA: gA, keyFingerprint: keyId) |> deliverOnMainQueue).start(next: { [weak self] updatedCall in + if let strongSelf = self, let context = strongSelf.contexts[internalId], case .confirming = context.state { + if let updatedCall = updatedCall { + strongSelf.updateSession(updatedCall) + } else { + strongSelf.drop(internalId: internalId, reason: .disconnect) + } + } + })) + self.contextUpdated(internalId: internalId) + default: + self.drop(internalId: internalId, reason: .disconnect) } } else { assertionFailure() @@ -773,54 +777,58 @@ private enum AcceptCallResult { private func acceptCallSession(postbox: Postbox, network: Network, stableId: CallSessionStableId, accessHash: Int64, b: Data) -> Signal { return validatedEncryptionConfig(postbox: postbox, network: network) - |> mapToSignal { config in - var gValue: Int32 = config.g.byteSwapped - let g = Data(bytes: &gValue, count: 4) - let p = config.p.makeData() - - let bData = b - - let gb = MTExp(g, bData, p)! - - return network.request(Api.functions.phone.acceptCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash), gB: Buffer(data: gb), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: kCallMinLayer, maxLayer: kCallMaxLayer))) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { call -> Signal in - if let call = call { - return postbox.transaction { transaction -> AcceptCallResult in - switch call { - case let .phoneCall(phoneCall, users): - var parsedUsers: [Peer] = [] - for user in users { - parsedUsers.append(TelegramUser(user: user)) - } - updatePeers(transaction: transaction, peers: parsedUsers, update: { _, updated in - return updated - }) - - switch phoneCall { - case .phoneCallEmpty, .phoneCallRequested, .phoneCallAccepted, .phoneCallDiscarded: - return .failed - case .phoneCallWaiting: - return .success(.waiting(config: config)) - case let .phoneCall(id, _, _, _, _, gAOrB, keyFingerprint, callProtocol, connection, alternativeConnections, startDate): - if id == stableId { - switch callProtocol{ - case let .phoneCallProtocol(_, _, maxLayer): - return .success(.call(config: config, gA: gAOrB.makeData(), timestamp: startDate, connections: parseConnectionSet(primary: connection, alternative: alternativeConnections), maxLayer: maxLayer)) - } - } else { - return .failed - } + |> mapToSignal { config -> Signal in + var gValue: Int32 = config.g.byteSwapped + let g = Data(bytes: &gValue, count: 4) + let p = config.p.makeData() + + let bData = b + + let gb = MTExp(g, bData, p)! + + if !MTCheckIsSafeGAOrB(gb, p) { + return .single(.failed) + } + + return network.request(Api.functions.phone.acceptCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash), gB: Buffer(data: gb), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: kCallMinLayer, maxLayer: kCallMaxLayer))) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { call -> Signal in + if let call = call { + return postbox.transaction { transaction -> AcceptCallResult in + switch call { + case let .phoneCall(phoneCall, users): + var parsedUsers: [Peer] = [] + for user in users { + parsedUsers.append(TelegramUser(user: user)) + } + updatePeers(transaction: transaction, peers: parsedUsers, update: { _, updated in + return updated + }) + + switch phoneCall { + case .phoneCallEmpty, .phoneCallRequested, .phoneCallAccepted, .phoneCallDiscarded: + return .failed + case .phoneCallWaiting: + return .success(.waiting(config: config)) + case let .phoneCall(id, _, _, _, _, gAOrB, keyFingerprint, callProtocol, connection, alternativeConnections, startDate): + if id == stableId { + switch callProtocol{ + case let .phoneCallProtocol(_, _, maxLayer): + return .success(.call(config: config, gA: gAOrB.makeData(), timestamp: startDate, connections: parseConnectionSet(primary: connection, alternative: alternativeConnections), maxLayer: maxLayer)) } + } else { + return .failed } } - } else { - return .single(.failed) } + } + } else { + return .single(.failed) } + } } } @@ -831,49 +839,53 @@ private enum RequestCallSessionResult { private func requestCallSession(postbox: Postbox, network: Network, peerId: PeerId, a: Data) -> Signal { return validatedEncryptionConfig(postbox: postbox, network: network) - |> mapToSignal { config -> Signal in - return postbox.transaction { transaction -> Signal in - if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) { - var gValue: Int32 = config.g.byteSwapped - let g = Data(bytes: &gValue, count: 4) - let p = config.p.makeData() - - let ga = MTExp(g, a, p)! - - let gAHash = MTSha256(ga)! - - return network.request(Api.functions.phone.requestCall(userId: inputUser, randomId: Int32(bitPattern: arc4random()), gAHash: Buffer(data: gAHash), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: kCallMinLayer, maxLayer: kCallMaxLayer))) - |> map { result -> RequestCallSessionResult in - switch result { - case let .phoneCall(phoneCall, _): - switch phoneCall { - case let .phoneCallRequested(id, accessHash, _, _, _, _, _): - return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: nil) - case let .phoneCallWaiting(_, id, accessHash, _, _, _, _, receiveDate): - return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: receiveDate) - default: - return .failed(.generic) - } - } - } - |> `catch` { error -> Signal in - switch error.errorDescription { - case "PARTICIPANT_VERSION_OUTDATED": - return .single(.failed(.notSupportedByPeer)) - case "USER_PRIVACY_RESTRICTED": - return .single(.failed(.privacyRestricted)) - default: - if error.errorCode == 406 { - return .single(.failed(.serverProvided(error.errorDescription))) - } else { - return .single(.failed(.generic)) - } - } - } - } else { + |> mapToSignal { config -> Signal in + return postbox.transaction { transaction -> Signal in + if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) { + var gValue: Int32 = config.g.byteSwapped + let g = Data(bytes: &gValue, count: 4) + let p = config.p.makeData() + + let ga = MTExp(g, a, p)! + if !MTCheckIsSafeGAOrB(ga, p) { return .single(.failed(.generic)) } - } |> switchToLatest + + let gAHash = MTSha256(ga)! + + return network.request(Api.functions.phone.requestCall(userId: inputUser, randomId: Int32(bitPattern: arc4random()), gAHash: Buffer(data: gAHash), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: kCallMinLayer, maxLayer: kCallMaxLayer))) + |> map { result -> RequestCallSessionResult in + switch result { + case let .phoneCall(phoneCall, _): + switch phoneCall { + case let .phoneCallRequested(id, accessHash, _, _, _, _, _): + return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: nil) + case let .phoneCallWaiting(_, id, accessHash, _, _, _, _, receiveDate): + return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: receiveDate) + default: + return .failed(.generic) + } + } + } + |> `catch` { error -> Signal in + switch error.errorDescription { + case "PARTICIPANT_VERSION_OUTDATED": + return .single(.failed(.notSupportedByPeer)) + case "USER_PRIVACY_RESTRICTED": + return .single(.failed(.privacyRestricted)) + default: + if error.errorCode == 406 { + return .single(.failed(.serverProvided(error.errorDescription))) + } else { + return .single(.failed(.generic)) + } + } + } + } else { + return .single(.failed(.generic)) + } + } + |> switchToLatest } } diff --git a/TelegramCore/ChannelBlacklist.swift b/TelegramCore/ChannelBlacklist.swift index 59d708fb96..763571ecb0 100644 --- a/TelegramCore/ChannelBlacklist.swift +++ b/TelegramCore/ChannelBlacklist.swift @@ -134,126 +134,129 @@ public func channelBlacklistParticipants(account: Account, peerId: PeerId) -> Si public func updateChannelMemberBannedRights(account: Account, peerId: PeerId, memberId: PeerId, rights: TelegramChannelBannedRights?) -> Signal<(ChannelParticipant?, RenderedChannelParticipant), NoError> { return fetchChannelParticipant(account: account, peerId: peerId, participantId: memberId) - |> mapToSignal { currentParticipant -> Signal<(ChannelParticipant?, RenderedChannelParticipant), NoError> in - return account.postbox.transaction { transaction -> Signal<(ChannelParticipant?, RenderedChannelParticipant), NoError> in - if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer), let _ = transaction.getPeer(account.peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) { - let updatedParticipant: ChannelParticipant - if let currentParticipant = currentParticipant, case let .member(_, invitedAt, _, currentBanInfo) = currentParticipant { - let banInfo: ChannelParticipantBannedInfo? - if let rights = rights, !rights.flags.isEmpty { - banInfo = ChannelParticipantBannedInfo(rights: rights, restrictedBy: currentBanInfo?.restrictedBy ?? account.peerId, isMember: currentBanInfo?.isMember ?? true) - } else { - banInfo = nil - } - updatedParticipant = ChannelParticipant.member(id: memberId, invitedAt: invitedAt, adminInfo: nil, banInfo: banInfo) + |> mapToSignal { currentParticipant -> Signal<(ChannelParticipant?, RenderedChannelParticipant), NoError> in + return account.postbox.transaction { transaction -> Signal<(ChannelParticipant?, RenderedChannelParticipant), NoError> in + if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer), let _ = transaction.getPeer(account.peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) { + let updatedParticipant: ChannelParticipant + if let currentParticipant = currentParticipant, case let .member(_, invitedAt, _, currentBanInfo) = currentParticipant { + let banInfo: ChannelParticipantBannedInfo? + if let rights = rights, !rights.flags.isEmpty { + banInfo = ChannelParticipantBannedInfo(rights: rights, restrictedBy: currentBanInfo?.restrictedBy ?? account.peerId, isMember: currentBanInfo?.isMember ?? true) } else { - let banInfo: ChannelParticipantBannedInfo? - if let rights = rights, !rights.flags.isEmpty { - banInfo = ChannelParticipantBannedInfo(rights: rights, restrictedBy: account.peerId, isMember: false) - } else { - banInfo = nil - } - updatedParticipant = ChannelParticipant.member(id: memberId, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: nil, banInfo: banInfo) + banInfo = nil } - - let effectiveRights: TelegramChannelBannedRights = rights ?? TelegramChannelBannedRights(flags: [], untilDate: 0) - - return account.network.request(Api.functions.channels.editBanned(channel: inputChannel, userId: inputUser, bannedRights: effectiveRights.apiBannedRights)) - |> retryRequest - |> mapToSignal { result -> Signal<(ChannelParticipant?, RenderedChannelParticipant), NoError> in - return account.postbox.transaction { transaction -> (ChannelParticipant?, RenderedChannelParticipant) in - transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in - if let cachedData = cachedData as? CachedChannelData { - var updatedData = cachedData - var wasKicked = false - var wasBanned = false - var wasMember = false - var wasAdmin = false - if let currentParticipant = currentParticipant { - switch currentParticipant { - case .creator: - break - case let .member(_, _, adminInfo, banInfo): - if let adminInfo = adminInfo { - wasAdmin = true - } - if let banInfo = banInfo { - if banInfo.rights.flags.contains(.banReadMessages) { - wasKicked = true - } else if !banInfo.rights.flags.isEmpty { - wasBanned = true - } - } - wasMember = true - } - } - - - var isKicked = false - var isBanned = false - if effectiveRights.flags.contains(.banReadMessages) { - isKicked = true - } else if !effectiveRights.flags.isEmpty { - isBanned = true - } - - let isMember = !wasKicked && !effectiveRights.flags.contains(.banReadMessages) - - if isKicked != wasKicked { - if let kickedCount = updatedData.participantsSummary.kickedCount { - updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedKickedCount(max(0, kickedCount + (isKicked ? 1 : -1)))) - } - } - - if isBanned != wasBanned { - if let bannedCount = updatedData.participantsSummary.bannedCount { - updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedBannedCount(max(0, bannedCount + (isBanned ? 1 : -1)))) - } - } - - if wasAdmin { - if let adminCount = updatedData.participantsSummary.adminCount { - updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedAdminCount(max(0, adminCount - 1))) - } - } - - if isMember != wasMember { - if let memberCount = updatedData.participantsSummary.memberCount { - updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedMemberCount(max(0, memberCount + (isMember ? 1 : -1)))) - } - - if !isMember, let topParticipants = updatedData.topParticipants { - var updatedParticipants = topParticipants.participants - if let index = updatedParticipants.index(where: { $0.peerId == memberId }) { - updatedParticipants.remove(at: index) - - updatedData = updatedData.withUpdatedTopParticipants(CachedChannelParticipants(participants: updatedParticipants)) - } - } - } - - return updatedData - } else { - return cachedData - } - }) - var peers: [PeerId: Peer] = [:] - var presences: [PeerId: PeerPresence] = [:] - peers[memberPeer.id] = memberPeer - if let presence = transaction.getPeerPresence(peerId: memberPeer.id) { - presences[memberPeer.id] = presence - } - if case let .member(_, _, _, maybeBanInfo) = updatedParticipant, let banInfo = maybeBanInfo { - if let peer = transaction.getPeer(banInfo.restrictedBy) { - peers[peer.id] = peer - } - } - return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: memberPeer, peers: peers, presences: presences)) - } - } + updatedParticipant = ChannelParticipant.member(id: memberId, invitedAt: invitedAt, adminInfo: nil, banInfo: banInfo) } else { - return .complete() + let banInfo: ChannelParticipantBannedInfo? + if let rights = rights, !rights.flags.isEmpty { + banInfo = ChannelParticipantBannedInfo(rights: rights, restrictedBy: account.peerId, isMember: false) + } else { + banInfo = nil + } + updatedParticipant = ChannelParticipant.member(id: memberId, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: nil, banInfo: banInfo) } - } |> switchToLatest + + let effectiveRights: TelegramChannelBannedRights = rights ?? TelegramChannelBannedRights(flags: [], untilDate: 0) + + return account.network.request(Api.functions.channels.editBanned(channel: inputChannel, userId: inputUser, bannedRights: effectiveRights.apiBannedRights)) + |> retryRequest + |> mapToSignal { result -> Signal<(ChannelParticipant?, RenderedChannelParticipant), NoError> in + account.stateManager.addUpdates(result) + + return account.postbox.transaction { transaction -> (ChannelParticipant?, RenderedChannelParticipant) in + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in + if let cachedData = cachedData as? CachedChannelData { + var updatedData = cachedData + var wasKicked = false + var wasBanned = false + var wasMember = false + var wasAdmin = false + if let currentParticipant = currentParticipant { + switch currentParticipant { + case .creator: + break + case let .member(_, _, adminInfo, banInfo): + if let adminInfo = adminInfo { + wasAdmin = true + } + if let banInfo = banInfo { + if banInfo.rights.flags.contains(.banReadMessages) { + wasKicked = true + } else if !banInfo.rights.flags.isEmpty { + wasBanned = true + } + } + wasMember = true + } + } + + + var isKicked = false + var isBanned = false + if effectiveRights.flags.contains(.banReadMessages) { + isKicked = true + } else if !effectiveRights.flags.isEmpty { + isBanned = true + } + + let isMember = !wasKicked && !effectiveRights.flags.contains(.banReadMessages) + + if isKicked != wasKicked { + if let kickedCount = updatedData.participantsSummary.kickedCount { + updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedKickedCount(max(0, kickedCount + (isKicked ? 1 : -1)))) + } + } + + if isBanned != wasBanned { + if let bannedCount = updatedData.participantsSummary.bannedCount { + updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedBannedCount(max(0, bannedCount + (isBanned ? 1 : -1)))) + } + } + + if wasAdmin { + if let adminCount = updatedData.participantsSummary.adminCount { + updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedAdminCount(max(0, adminCount - 1))) + } + } + + if isMember != wasMember { + if let memberCount = updatedData.participantsSummary.memberCount { + updatedData = updatedData.withUpdatedParticipantsSummary(updatedData.participantsSummary.withUpdatedMemberCount(max(0, memberCount + (isMember ? 1 : -1)))) + } + + if !isMember, let topParticipants = updatedData.topParticipants { + var updatedParticipants = topParticipants.participants + if let index = updatedParticipants.index(where: { $0.peerId == memberId }) { + updatedParticipants.remove(at: index) + + updatedData = updatedData.withUpdatedTopParticipants(CachedChannelParticipants(participants: updatedParticipants)) + } + } + } + + return updatedData + } else { + return cachedData + } + }) + var peers: [PeerId: Peer] = [:] + var presences: [PeerId: PeerPresence] = [:] + peers[memberPeer.id] = memberPeer + if let presence = transaction.getPeerPresence(peerId: memberPeer.id) { + presences[memberPeer.id] = presence + } + if case let .member(_, _, _, maybeBanInfo) = updatedParticipant, let banInfo = maybeBanInfo { + if let peer = transaction.getPeer(banInfo.restrictedBy) { + peers[peer.id] = peer + } + } + return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: memberPeer, peers: peers, presences: presences)) + } + } + } else { + return .complete() + } + } + |> switchToLatest } } diff --git a/TelegramCore/ContactManagement.swift b/TelegramCore/ContactManagement.swift index ad62924241..e509c5920b 100644 --- a/TelegramCore/ContactManagement.swift +++ b/TelegramCore/ContactManagement.swift @@ -70,12 +70,35 @@ func syncContactsOnce(network: Network, postbox: Postbox) -> Signal mapToSignal { peersAndPresences -> Signal in if let (peers, peerPresences, totalCount) = peersAndPresences { - return postbox.transaction { transaction in - updatePeers(transaction: transaction, peers: peers, update: { return $1 }) - transaction.updatePeerPresences(peerPresences) - transaction.replaceContactPeerIds(Set(peers.map { $0.id })) + return postbox.transaction { transaction -> Signal in + let previousIds = transaction.getContactPeerIds() + let wasEmpty = previousIds.isEmpty + transaction.replaceRemoteContactCount(totalCount) + + transaction.updatePeerPresences(peerPresences) + + if wasEmpty { + var insertSignal: Signal = .complete() + for s in stride(from: 0, to: peers.count, by: 100) { + let partPeers = Array(peers[s ..< min(s + 100, peers.count)]) + let partSignal = postbox.transaction { transaction -> Void in + updatePeers(transaction: transaction, peers: partPeers, update: { return $1 }) + var updatedIds = transaction.getContactPeerIds() + updatedIds.formUnion(partPeers.map { $0.id }) + transaction.replaceContactPeerIds(updatedIds) + } + |> delay(0.1, queue: Queue.concurrentDefaultQueue()) + insertSignal = insertSignal |> then(partSignal) + } + + return insertSignal + } else { + transaction.replaceContactPeerIds(Set(peers.map { $0.id })) + return .complete() + } } + |> switchToLatest |> ignoreValues } else { return .complete() diff --git a/TelegramCore/CreateSecretChat.swift b/TelegramCore/CreateSecretChat.swift index 2ed3fadf91..65f1a462a8 100644 --- a/TelegramCore/CreateSecretChat.swift +++ b/TelegramCore/CreateSecretChat.swift @@ -30,6 +30,10 @@ public func createSecretChat(account: Account, peerId: PeerId) -> Signal mapError { _ -> CreateSecretChatError in return .generic diff --git a/TelegramCore/Holes.swift b/TelegramCore/Holes.swift index 12a4763ff7..03d8393839 100644 --- a/TelegramCore/Holes.swift +++ b/TelegramCore/Holes.swift @@ -39,6 +39,90 @@ enum FetchMessageHistoryHoleSource { } } +func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> Void) -> Signal { + return postbox.transaction { transaction -> Signal in + var storedIds = Set() + var referencedIds = Set() + for message in storeMessages { + guard case let .Id(id) = message.id else { + continue + } + storedIds.insert(id) + for attribute in message.attributes { + referencedIds.formUnion(attribute.associatedMessageIds) + } + } + referencedIds.subtract(storedIds) + referencedIds = transaction.filterStoredMessageIds(referencedIds) + + if referencedIds.isEmpty { + f(transaction, [], []) + return .complete() + } else { + var signals: [Signal<([Api.Message], [Api.Chat], [Api.User]), NoError>] = [] + for (peerId, messageIds) in messagesIdsGroupedByPeerId(referencedIds) { + if let peer = transaction.getPeer(peerId) { + var signal: Signal? + if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup { + signal = source.request(Api.functions.messages.getMessages(id: messageIds.map({ Api.InputMessage.inputMessageID(id: $0.id) }))) + } else if peerId.namespace == Namespaces.Peer.CloudChannel { + if let inputChannel = apiInputChannel(peer) { + signal = source.request(Api.functions.channels.getMessages(channel: inputChannel, id: messageIds.map({ Api.InputMessage.inputMessageID(id: $0.id) }))) + } + } + if let signal = signal { + signals.append(signal + |> map { result in + switch result { + case let .messages(messages, chats, users): + return (messages, chats, users) + case let .messagesSlice(_, messages, chats, users): + return (messages, chats, users) + case let .channelMessages(_, _, _, messages, chats, users): + return (messages, chats, users) + case .messagesNotModified: + return ([], [], []) + } + } + |> `catch` { _ in + return Signal<([Api.Message], [Api.Chat], [Api.User]), NoError>.single(([], [], [])) + }) + } + } + } + + let fetchMessages = combineLatest(signals) + + return fetchMessages + |> mapToSignal { results -> Signal in + var additionalPeers: [Peer] = [] + var additionalMessages: [StoreMessage] = [] + for (messages, chats, users) in results { + if !messages.isEmpty { + for message in messages { + if let message = StoreMessage(apiMessage: message) { + additionalMessages.append(message) + } + } + } + for chat in chats { + if let peer = parseTelegramGroupOrChannel(chat: chat) { + additionalPeers.append(peer) + } + } + for user in users { + additionalPeers.append(TelegramUser(user: user)) + } + } + return postbox.transaction { transaction -> Void in + f(transaction, additionalPeers, additionalMessages) + } + } + } + } + |> switchToLatest +} + func fetchMessageHistoryHole(source: FetchMessageHistoryHoleSource, postbox: Postbox, hole: MessageHistoryHole, direction: MessageHistoryViewRelativeHoleDirection, tagMask: MessageTags?, limit: Int = 100) -> Signal { assert(tagMask == nil || tagMask!.rawValue != 0) return postbox.loadedPeerWithId(hole.maxIndex.id.peerId) @@ -164,104 +248,106 @@ func fetchMessageHistoryHole(source: FetchMessageHistoryHoleSource, postbox: Pos } return combineLatest(request |> retryRequest, maxIndexRequest |> retryRequest) - |> mapToSignal { result, maxIndexResult in - let messages: [Api.Message] - let chats: [Api.Chat] - let users: [Api.User] - var channelPts: Int32? - switch result { - case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers): - messages = apiMessages - chats = apiChats - users = apiUsers - case let .messagesSlice(_, messages: apiMessages, chats: apiChats, users: apiUsers): - messages = apiMessages - chats = apiChats - users = apiUsers - case let .channelMessages(_, pts, _, apiMessages, apiChats, apiUsers): - messages = apiMessages - chats = apiChats - users = apiUsers - channelPts = pts + |> mapToSignal { result, maxIndexResult in + let messages: [Api.Message] + let chats: [Api.Chat] + let users: [Api.User] + var channelPts: Int32? + switch result { + case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let .messagesSlice(_, messages: apiMessages, chats: apiChats, users: apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let .channelMessages(_, pts, _, apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + channelPts = pts + case .messagesNotModified: + messages = [] + chats = [] + users = [] + } + var updatedMaxIndex: MessageIndex? + if let maxIndexResult = maxIndexResult { + let maxIndexMessages: [Api.Message] + switch maxIndexResult { + case let .messages(apiMessages, _, _): + maxIndexMessages = apiMessages + case let .messagesSlice(_, apiMessages, _, _): + maxIndexMessages = apiMessages + case let .channelMessages(_, _, _, apiMessages, _, _): + maxIndexMessages = apiMessages case .messagesNotModified: - messages = [] - chats = [] - users = [] + maxIndexMessages = [] } - var updatedMaxIndex: MessageIndex? - if let maxIndexResult = maxIndexResult { - let maxIndexMessages: [Api.Message] - switch maxIndexResult { - case let .messages(apiMessages, _, _): - maxIndexMessages = apiMessages - case let .messagesSlice(_, apiMessages, _, _): - maxIndexMessages = apiMessages - case let .channelMessages(_, _, _, apiMessages, _, _): - maxIndexMessages = apiMessages - case .messagesNotModified: - maxIndexMessages = [] + if !maxIndexMessages.isEmpty { + assert(maxIndexMessages.count == 1) + if let storeMessage = StoreMessage(apiMessage: maxIndexMessages[0]), case let .Id(id) = storeMessage.id { + updatedMaxIndex = MessageIndex(id: id, timestamp: storeMessage.timestamp) } - if !maxIndexMessages.isEmpty { - assert(maxIndexMessages.count == 1) - if let storeMessage = StoreMessage(apiMessage: maxIndexMessages[0]), case let .Id(id) = storeMessage.id { - updatedMaxIndex = MessageIndex(id: id, timestamp: storeMessage.timestamp) - } - } - } - return postbox.transaction { transaction in - var storeMessages: [StoreMessage] = [] - - for message in messages { - if let storeMessage = StoreMessage(apiMessage: message) { - if let channelPts = channelPts { - var attributes = storeMessage.attributes - attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts)) - storeMessages.append(storeMessage.withUpdatedAttributes(attributes)) - } else { - storeMessages.append(storeMessage) - } - } - } - - let fillDirection: HoleFillDirection - switch direction { - case .UpperToLower: - fillDirection = .UpperToLower(updatedMinIndex: nil, clippingMaxIndex: nil) - case .LowerToUpper: - fillDirection = .LowerToUpper(updatedMaxIndex: updatedMaxIndex, clippingMinIndex: nil) - case let .AroundId(id): - fillDirection = .AroundId(id, lowerComplete: false, upperComplete: false) - case let .AroundIndex(index): - fillDirection = .AroundId(index.id, lowerComplete: false, upperComplete: false) - } - - transaction.fillMultipleHoles(hole, fillType: HoleFill(complete: messages.count == 0 || implicitelyFillHole, direction: fillDirection), tagMask: tagMask, messages: storeMessages) - - var peers: [Peer] = [] - var peerPresences: [PeerId: PeerPresence] = [:] - for chat in chats { - if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { - peers.append(groupOrChannel) - } - } - for user in users { - let telegramUser = TelegramUser(user: user) - peers.append(telegramUser) - if let presence = TelegramUserPresence(apiUser: user) { - peerPresences[telegramUser.id] = presence - } - } - - updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in - return updated - }) - transaction.updatePeerPresences(peerPresences) - - print("fetchMessageHistoryHole for \(peer.displayTitle) done") - - return } } + + var storeMessages: [StoreMessage] = [] + + for message in messages { + if let storeMessage = StoreMessage(apiMessage: message) { + if let channelPts = channelPts { + var attributes = storeMessage.attributes + attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts)) + storeMessages.append(storeMessage.withUpdatedAttributes(attributes)) + } else { + storeMessages.append(storeMessage) + } + } + } + + return withResolvedAssociatedMessages(postbox: postbox, source: source, storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages in + let fillDirection: HoleFillDirection + switch direction { + case .UpperToLower: + fillDirection = .UpperToLower(updatedMinIndex: nil, clippingMaxIndex: nil) + case .LowerToUpper: + fillDirection = .LowerToUpper(updatedMaxIndex: updatedMaxIndex, clippingMinIndex: nil) + case let .AroundId(id): + fillDirection = .AroundId(id, lowerComplete: false, upperComplete: false) + case let .AroundIndex(index): + fillDirection = .AroundId(index.id, lowerComplete: false, upperComplete: false) + } + + transaction.fillMultipleHoles(hole, fillType: HoleFill(complete: messages.count == 0 || implicitelyFillHole, direction: fillDirection), tagMask: tagMask, messages: storeMessages) + let _ = transaction.addMessages(additionalMessages, location: .Random) + + var peers: [Peer] = additionalPeers + var peerPresences: [PeerId: PeerPresence] = [:] + for chat in chats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers.append(groupOrChannel) + } + } + for user in users { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } + } + + updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in + return updated + }) + transaction.updatePeerPresences(peerPresences) + + print("fetchMessageHistoryHole for \(peer.displayTitle) done") + + return + }) + } } else { return .complete() } @@ -468,7 +554,7 @@ func fetchChatListHole(postbox: Postbox, network: Network, groupId: PeerGroupId? } return fetchChatList(postbox: postbox, network: network, location: location, upperBound: hole.index) |> mapToSignal { fetchedChats -> Signal in - return postbox.transaction { transaction in + return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages in for peer in fetchedChats.peers { updatePeers(transaction: transaction, peers: [peer], update: { _, updated -> Peer in return updated @@ -506,7 +592,7 @@ func fetchChatListHole(postbox: Postbox, network: Network, groupId: PeerGroupId? for (peerId, summary) in fetchedChats.mentionTagSummaries { transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: summary.count, maxId: summary.range.maxId) } - } + }) } } diff --git a/TelegramCore/ManagedSecretChatOutgoingOperations.swift b/TelegramCore/ManagedSecretChatOutgoingOperations.swift index 13b9dc9995..31e0eb5918 100644 --- a/TelegramCore/ManagedSecretChatOutgoingOperations.swift +++ b/TelegramCore/ManagedSecretChatOutgoingOperations.swift @@ -124,47 +124,47 @@ func managedSecretChatOutgoingOperations(postbox: Postbox, network: Network) -> for (entry, disposable) in beginOperations { let signal = takenImmutableOperation(postbox: postbox, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex) - |> mapToSignal { entry -> Signal in - if let entry = entry { - if let operation = entry.contents as? SecretChatOutgoingOperation { - switch operation.contents { - case let .initialHandshakeAccept(gA, accessHash, b): - return initialHandshakeAccept(postbox: postbox, network: network, peerId: entry.peerId, accessHash: accessHash, gA: gA, b: b, tagLocalIndex: entry.tagLocalIndex) - case let .sendMessage(layer, id, file): - return sendMessage(postbox: postbox, network: network, messageId: id, file: file, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered, layer: layer) - case let .reportLayerSupport(layer, actionGloballyUniqueId, layerSupport): - return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .reportLayerSupport(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, layerSupport: layerSupport), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .deleteMessages(layer, actionGloballyUniqueId, globallyUniqueIds): - return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .deleteMessages(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .clearHistory(layer, actionGloballyUniqueId): - return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .clearHistory(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .pfsRequestKey(layer, actionGloballyUniqueId, rekeySessionId, a): - return pfsRequestKey(postbox: postbox, network: network, peerId: entry.peerId, layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId: rekeySessionId, a: a, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .pfsCommitKey(layer, actionGloballyUniqueId, rekeySessionId, keyFingerprint): - return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .pfsCommitKey(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId: rekeySessionId, keyFingerprint: keyFingerprint), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .pfsAcceptKey(layer, actionGloballyUniqueId, rekeySessionId, gA, b): - return pfsAcceptKey(postbox: postbox, network: network, peerId: entry.peerId, layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId: rekeySessionId, gA: gA, b: b, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .pfsAbortSession(layer, actionGloballyUniqueId, rekeySessionId): - return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .pfsAbortSession(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId: rekeySessionId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .noop(layer, actionGloballyUniqueId): - return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .noop(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .readMessagesContent(layer, actionGloballyUniqueId, globallyUniqueIds): - return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .readMessageContents(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, messageId): - return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, timeout: timeout, messageId: messageId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo): - return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .resendOperations(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, fromSeqNo: fromSeqNo, toSeqNo: toSeqNo), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds, messageId): - return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .screenshotMessages(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds, messageId: messageId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) - case let .terminate(reportSpam): - return requestTerminateSecretChat(postbox: postbox, network: network, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex, reportSpam: reportSpam) - } - } else { - assertionFailure() + |> mapToSignal { entry -> Signal in + if let entry = entry { + if let operation = entry.contents as? SecretChatOutgoingOperation { + switch operation.contents { + case let .initialHandshakeAccept(gA, accessHash, b): + return initialHandshakeAccept(postbox: postbox, network: network, peerId: entry.peerId, accessHash: accessHash, gA: gA, b: b, tagLocalIndex: entry.tagLocalIndex) + case let .sendMessage(layer, id, file): + return sendMessage(postbox: postbox, network: network, messageId: id, file: file, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered, layer: layer) + case let .reportLayerSupport(layer, actionGloballyUniqueId, layerSupport): + return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .reportLayerSupport(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, layerSupport: layerSupport), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .deleteMessages(layer, actionGloballyUniqueId, globallyUniqueIds): + return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .deleteMessages(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .clearHistory(layer, actionGloballyUniqueId): + return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .clearHistory(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .pfsRequestKey(layer, actionGloballyUniqueId, rekeySessionId, a): + return pfsRequestKey(postbox: postbox, network: network, peerId: entry.peerId, layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId: rekeySessionId, a: a, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .pfsCommitKey(layer, actionGloballyUniqueId, rekeySessionId, keyFingerprint): + return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .pfsCommitKey(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId: rekeySessionId, keyFingerprint: keyFingerprint), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .pfsAcceptKey(layer, actionGloballyUniqueId, rekeySessionId, gA, b): + return pfsAcceptKey(postbox: postbox, network: network, peerId: entry.peerId, layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId: rekeySessionId, gA: gA, b: b, tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .pfsAbortSession(layer, actionGloballyUniqueId, rekeySessionId): + return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .pfsAbortSession(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, rekeySessionId: rekeySessionId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .noop(layer, actionGloballyUniqueId): + return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .noop(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .readMessagesContent(layer, actionGloballyUniqueId, globallyUniqueIds): + return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .readMessageContents(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, messageId): + return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, timeout: timeout, messageId: messageId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo): + return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .resendOperations(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, fromSeqNo: fromSeqNo, toSeqNo: toSeqNo), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds, messageId): + return sendServiceActionMessage(postbox: postbox, network: network, peerId: entry.peerId, action: .screenshotMessages(layer: layer, actionGloballyUniqueId: actionGloballyUniqueId, globallyUniqueIds: globallyUniqueIds, messageId: messageId), tagLocalIndex: entry.tagLocalIndex, wasDelivered: operation.delivered) + case let .terminate(reportSpam): + return requestTerminateSecretChat(postbox: postbox, network: network, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex, reportSpam: reportSpam) } + } else { + assertionFailure() } - return .complete() } + return .complete() + } disposable.set(signal.start()) } }) @@ -185,9 +185,29 @@ func managedSecretChatOutgoingOperations(postbox: Postbox, network: Network) -> private func initialHandshakeAccept(postbox: Postbox, network: Network, peerId: PeerId, accessHash: Int64, gA: MemoryBuffer, b: MemoryBuffer, tagLocalIndex: Int32) -> Signal { return validatedEncryptionConfig(postbox: postbox, network: network) |> mapToSignal { config -> Signal in + let p = config.p.makeData() + + if !MTCheckIsSafeGAOrB(gA.makeData(), p) { + return postbox.transaction { transaction -> Void in + let removed = transaction.operationLogRemoveEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: tagLocalIndex) + assert(removed) + if let state = transaction.getPeerChatState(peerId) as? SecretChatState { + var updatedState = state + updatedState = updatedState.withUpdatedEmbeddedState(.terminated) + transaction.setPeerChatState(peerId, state: updatedState) + if let peer = transaction.getPeer(peerId) as? TelegramSecretChat { + updatePeers(transaction: transaction, peers: [peer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in + return updated + }) + } + } else { + assertionFailure() + } + } + } + var gValue: Int32 = config.g.byteSwapped let g = Data(bytes: &gValue, count: 4) - let p = config.p.makeData() let bData = b.makeData() @@ -258,7 +278,7 @@ private func initialHandshakeAccept(postbox: Postbox, network: Network, peerId: } private func pfsRequestKey(postbox: Postbox, network: Network, peerId: PeerId, layer: SecretChatSequenceBasedLayer, actionGloballyUniqueId: Int64, rekeySessionId: Int64, a: MemoryBuffer, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal { -return validatedEncryptionConfig(postbox: postbox, network: network) + return validatedEncryptionConfig(postbox: postbox, network: network) |> mapToSignal { config -> Signal in var gValue: Int32 = config.g.byteSwapped let g = Data(bytes: &gValue, count: 4) diff --git a/TelegramCore/OutgoingMessageWithChatContextResult.swift b/TelegramCore/OutgoingMessageWithChatContextResult.swift index 50f9b5f077..039320b151 100644 --- a/TelegramCore/OutgoingMessageWithChatContextResult.swift +++ b/TelegramCore/OutgoingMessageWithChatContextResult.swift @@ -28,7 +28,21 @@ public func outgoingMessageWithChatContextResult(to peerId: PeerId, results: Cha switch result { case let .internalReference(_, id, type, title, description, image, file, message): if type == "game" { - return .message(text: "", attributes: attributes, mediaReference: .standalone(media: TelegramMediaGame(gameId: 0, accessHash: 0, name: "", title: title ?? "", description: description ?? "", image: image, file: file)), replyToMessageId: nil, localGroupingKey: nil) + if peerId.namespace == Namespaces.Peer.SecretChat { + let filteredAttributes = attributes.filter { attribute in + if let _ = attribute as? ReplyMarkupMessageAttribute { + return false + } + return true + } + if let media: Media = file ?? image { + return .message(text: caption, attributes: filteredAttributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil) + } else { + return .message(text: caption, attributes: filteredAttributes, mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil) + } + } else { + return .message(text: "", attributes: attributes, mediaReference: .standalone(media: TelegramMediaGame(gameId: 0, accessHash: 0, name: "", title: title ?? "", description: description ?? "", image: image, file: file)), replyToMessageId: nil, localGroupingKey: nil) + } } else if let file = file, type == "gif" { return .message(text: caption, attributes: attributes, mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil) } else if let image = image { diff --git a/TelegramCore/RemovePeerMember.swift b/TelegramCore/RemovePeerMember.swift index e59b866ff1..003563ecea 100644 --- a/TelegramCore/RemovePeerMember.swift +++ b/TelegramCore/RemovePeerMember.swift @@ -22,32 +22,32 @@ public func removePeerMember(account: Account, peerId: PeerId, memberId: PeerId) if let peer = transaction.getPeer(peerId), let memberPeer = transaction.getPeer(memberId), let inputUser = apiInputUser(memberPeer) { if let group = peer as? TelegramGroup { return account.network.request(Api.functions.messages.deleteChatUser(chatId: group.id.id, userId: inputUser)) - |> mapError { error -> Void in - return Void() - } - |> `catch` { _ -> Signal in - return .complete() - } - |> mapToSignal { result -> Signal in - account.stateManager.addUpdates(result) - - return account.postbox.transaction { transaction -> Void in - transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in - if let cachedData = cachedData as? CachedGroupData, let participants = cachedData.participants { - var updatedParticipants = participants.participants - for i in 0 ..< participants.participants.count { - if participants.participants[i].peerId == memberId { - updatedParticipants.remove(at: i) - break - } + |> mapError { error -> Void in + return Void() + } + |> `catch` { _ -> Signal in + return .complete() + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + + return account.postbox.transaction { transaction -> Void in + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in + if let cachedData = cachedData as? CachedGroupData, let participants = cachedData.participants { + var updatedParticipants = participants.participants + for i in 0 ..< participants.participants.count { + if participants.participants[i].peerId == memberId { + updatedParticipants.remove(at: i) + break } - - return cachedData.withUpdatedParticipants(CachedGroupParticipants(participants: updatedParticipants, version: participants.version)) - } else { - return cachedData } - }) - } + + return cachedData.withUpdatedParticipants(CachedGroupParticipants(participants: updatedParticipants, version: participants.version)) + } else { + return cachedData + } + }) + } } } else { return .complete() diff --git a/TelegramCore/SecretChatEncryptionConfig.swift b/TelegramCore/SecretChatEncryptionConfig.swift index ca751986e3..ed4506e69c 100644 --- a/TelegramCore/SecretChatEncryptionConfig.swift +++ b/TelegramCore/SecretChatEncryptionConfig.swift @@ -35,13 +35,28 @@ public final class SecretChatEncryptionConfig: PostboxCoding { func validatedEncryptionConfig(postbox: Postbox, network: Network) -> Signal { return network.request(Api.functions.messages.getDhConfig(version: 0, randomLength: 0)) - |> retryRequest - |> map { result -> SecretChatEncryptionConfig in - switch result { - case let .dhConfig(g, p, version, _): - return SecretChatEncryptionConfig(g: g, p: MemoryBuffer(p), version: version) - case .dhConfigNotModified(_): - preconditionFailure() - } + |> retryRequest + |> mapToSignal { result -> Signal in + switch result { + case let .dhConfig(g, p, version, _): + if !MTCheckIsSafeG(UInt32(g)) { + Logger.shared.log("SecretChatEncryptionConfig", "Invalid g") + return .complete() + } + + if !MTCheckMod(p.makeData(), UInt32(g), network.context.keychain) { + Logger.shared.log("SecretChatEncryptionConfig", "Invalid p or g") + return .complete() + } + + if !MTCheckIsSafePrime(p.makeData(), network.context.keychain) { + Logger.shared.log("SecretChatEncryptionConfig", "Invalid p") + return .never() + } + return .single(SecretChatEncryptionConfig(g: g, p: MemoryBuffer(p), version: version)) + case .dhConfigNotModified(_): + assertionFailure() + return .never() } + } } diff --git a/TelegramCore/SecretChatRekeySession.swift b/TelegramCore/SecretChatRekeySession.swift index e5f751615c..68253cc9c5 100644 --- a/TelegramCore/SecretChatRekeySession.swift +++ b/TelegramCore/SecretChatRekeySession.swift @@ -45,10 +45,12 @@ func secretChatAdvanceRekeySessionIfNeeded(transaction: Transaction, peerId: Pee switch rekeySession.data { case let .requested(a, config): var gValue: Int32 = config.g.byteSwapped - let g = Data(bytes: &gValue, count: 4) let p = config.p.makeData() let aData = a.makeData() + if !MTCheckIsSafeGAOrB(gB.makeData(), p) { + return state.withUpdatedEmbeddedState(.terminated) + } var key = MTExp(gB.makeData(), aData, p)! diff --git a/TelegramCore/UpdateSecretChat.swift b/TelegramCore/UpdateSecretChat.swift index 1df89c2d31..8221031e9f 100644 --- a/TelegramCore/UpdateSecretChat.swift +++ b/TelegramCore/UpdateSecretChat.swift @@ -26,6 +26,13 @@ func updateSecretChat(accountPeerId: PeerId, transaction: Transaction, chat: Api let pData = p.makeData() let aData = a.makeData() + if !MTCheckIsSafeGAOrB(gAOrB.makeData(), pData) { + var updatedState = currentState + updatedState = updatedState.withUpdatedEmbeddedState(.terminated) + transaction.setPeerChatState(chat.peerId, state: updatedState) + return + } + var key = MTExp(gAOrB.makeData(), aData, pData)! if key.count > 256 {