Merge branch 'group_calls' of https://github.com/peter-iakovlev/TelegramCoreDev into group_calls

This commit is contained in:
Mikhail Filimonov 2018-03-01 12:34:09 +04:00
commit c353091c51
32 changed files with 3259 additions and 609 deletions

View File

@ -32,8 +32,6 @@
C25638021E79E7FC00311607 /* TwoStepVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */; };
C26A37EF1E5E0C41006977AC /* ChannelParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */; };
C27982511E72C97800262BFD /* MacosLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27982501E72C97800262BFD /* MacosLegacy.swift */; };
C27982531E73077800262BFD /* SecretChatStateBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27982521E73077800262BFD /* SecretChatStateBridge.swift */; };
C27982541E73078400262BFD /* SecretChatStateBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27982521E73077800262BFD /* SecretChatStateBridge.swift */; };
C28725421EF967E700613564 /* NotificationInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */; };
C28725431EF967E700613564 /* NotificationInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */; };
C29340F31F5080FA0074991E /* UpdateGroupSpecificStickerset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29340F21F5080FA0074991E /* UpdateGroupSpecificStickerset.swift */; };
@ -103,6 +101,11 @@
D0177B7B1DF8A16C00A5083A /* SecretChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */; };
D018D3371E648ACF00C5E089 /* CreateChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* CreateChannel.swift */; };
D018D3381E648ACF00C5E089 /* CreateChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* CreateChannel.swift */; };
D018EE002044939F00CBB130 /* SecretApiLayer73.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */; };
D018EE0220458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */; };
D018EE0320458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */; };
D018EE052045E95000CBB130 /* CkeckPeerChatServiceActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE042045E95000CBB130 /* CkeckPeerChatServiceActions.swift */; };
D018EE062045E95000CBB130 /* CkeckPeerChatServiceActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE042045E95000CBB130 /* CkeckPeerChatServiceActions.swift */; };
D019B1CC1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */; };
D019B1CD1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */; };
D01A21A61F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01A21A51F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift */; };
@ -556,6 +559,8 @@
D0E652201E3A364A004EEA91 /* UpdateAccountPeerName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */; };
D0E817492010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */; };
D0E8174A2010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */; };
D0E8B8B32044706300605593 /* ForwardGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8B8B22044706300605593 /* ForwardGame.swift */; };
D0E8B8B42044706300605593 /* ForwardGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8B8B22044706300605593 /* ForwardGame.swift */; };
D0F02CE51E9926C40065DEE2 /* ManagedConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */; };
D0F02CE61E9926C50065DEE2 /* ManagedConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */; };
D0F3A89F1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */; };
@ -588,6 +593,8 @@
D0F8C39E20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */; };
D0F8C3A02017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */; };
D0F8C3A12017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */; };
D0FA08BB2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */; };
D0FA08BC2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */; };
D0FA0ABD1E76C908005BB9B7 /* TwoStepVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */; };
D0FA35051EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; };
D0FA35061EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; };
@ -644,7 +651,6 @@
C23BC3861E9BE3CA00D79F92 /* ImportContact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportContact.swift; sourceTree = "<group>"; };
C251D7421E65E50500283EDE /* StickerSetInstallation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerSetInstallation.swift; sourceTree = "<group>"; };
C27982501E72C97800262BFD /* MacosLegacy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacosLegacy.swift; sourceTree = "<group>"; };
C27982521E73077800262BFD /* SecretChatStateBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatStateBridge.swift; sourceTree = "<group>"; };
C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationInfoMessageAttribute.swift; sourceTree = "<group>"; };
C29340F21F5080FA0074991E /* UpdateGroupSpecificStickerset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateGroupSpecificStickerset.swift; sourceTree = "<group>"; };
C2E064671ECEEF0A00387BB8 /* TelegramMediaInvoice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaInvoice.swift; sourceTree = "<group>"; };
@ -674,6 +680,9 @@
D017495F1E118FC30057C89A /* AccountIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountIntermediateState.swift; sourceTree = "<group>"; };
D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatState.swift; sourceTree = "<group>"; };
D018D3361E648ACF00C5E089 /* CreateChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateChannel.swift; sourceTree = "<group>"; };
D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretApiLayer73.swift; sourceTree = "<group>"; };
D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretChatLayerNegotiation.swift; sourceTree = "<group>"; };
D018EE042045E95000CBB130 /* CkeckPeerChatServiceActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CkeckPeerChatServiceActions.swift; sourceTree = "<group>"; };
D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatRekeySession.swift; sourceTree = "<group>"; };
D01A21A51F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeSavedStickersOperation.swift; sourceTree = "<group>"; };
D01A21A81F38CDDC00DDA104 /* ManagedSynchronizeSavedStickersOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeSavedStickersOperations.swift; sourceTree = "<group>"; };
@ -938,6 +947,7 @@
D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingChatContextResultMessageAttribute.swift; sourceTree = "<group>"; };
D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateAccountPeerName.swift; sourceTree = "<group>"; };
D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAdminEventLogContext.swift; sourceTree = "<group>"; };
D0E8B8B22044706300605593 /* ForwardGame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardGame.swift; sourceTree = "<group>"; };
D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedConfigurationUpdates.swift; sourceTree = "<group>"; };
D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeChatInputStateOperation.swift; sourceTree = "<group>"; };
D0F3A8A11E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeChatInputStateOperations.swift; sourceTree = "<group>"; };
@ -949,6 +959,7 @@
D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMarkupMessageAttribute.swift; sourceTree = "<group>"; };
D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupFeedPeers.swift; sourceTree = "<group>"; };
D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalTelegramCoreConfiguration.swift; sourceTree = "<group>"; };
D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentPrivacySettings.swift; sourceTree = "<group>"; };
D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoStepVerification.swift; sourceTree = "<group>"; };
D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheStorageSettings.swift; sourceTree = "<group>"; };
D0FA35071EA632E400E56FFA /* CollectCacheUsageStats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectCacheUsageStats.swift; sourceTree = "<group>"; };
@ -1020,6 +1031,7 @@
D01C7ED51EF5E468008305F1 /* ProxySettings.swift */,
D0B167221F9F972E00976B40 /* LoggingSettings.swift */,
D0AF32301FACEDEC0097362B /* CoreSettings.swift */,
D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */,
);
name = Settings;
sourceTree = "<group>";
@ -1300,6 +1312,7 @@
children = (
D0FA8BB51E223C16001E855B /* SecretApiLayer8.swift */,
D0448C981E268F9A005A61A7 /* SecretApiLayer46.swift */,
D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */,
D03B0D541D631A6900955575 /* Api.swift */,
D03B0D551D631A6900955575 /* Buffer.swift */,
D03B0D561D631A6900955575 /* Download.swift */,
@ -1364,6 +1377,7 @@
D0642EF81F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift */,
D0DB7F021F43030C00591D48 /* InstallInteractiveReadMessagesAction.swift */,
D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */,
D0E8B8B22044706300605593 /* ForwardGame.swift */,
);
name = Messages;
sourceTree = "<group>";
@ -1595,6 +1609,7 @@
D02395D51F8D09A50070F5C2 /* ChannelHistoryAvailabilitySettings.swift */,
D0C44B601FC616E200227BE0 /* SearchGroupMembers.swift */,
D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */,
D018EE042045E95000CBB130 /* CkeckPeerChatServiceActions.swift */,
);
name = Peers;
sourceTree = "<group>";
@ -1615,7 +1630,7 @@
D0FA8BA91E1FB76E001E855B /* ManagedSecretChatOutgoingOperations.swift */,
D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */,
D00C7CEA1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift */,
C27982521E73077800262BFD /* SecretChatStateBridge.swift */,
D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */,
);
name = "Secret Chats";
sourceTree = "<group>";
@ -1827,11 +1842,13 @@
D03B0CBD1D62234300955575 /* Regex.swift in Sources */,
D00BDA191EE593D600C64C5E /* TelegramChannelAdminRights.swift in Sources */,
D0B843B91DA7FF30005F29E1 /* NBMetadataCoreTest.m in Sources */,
D018EE002044939F00CBB130 /* SecretApiLayer73.swift in Sources */,
D09A2FE61D7CD4940018FB72 /* TelegramChannel.swift in Sources */,
D03B0D0E1D62255C00955575 /* UpdateGroup.swift in Sources */,
D053B4181F18DE4F00E2D58A /* AuthorSignatureMessageAttribute.swift in Sources */,
D0F3A89F1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */,
D01AC9231DD5E9A200E8160F /* ApplyUpdateMessage.swift in Sources */,
D0E8B8B32044706300605593 /* ForwardGame.swift in Sources */,
D01A21AC1F38D10E00DDA104 /* SavedStickerItem.swift in Sources */,
D0642EF91F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift in Sources */,
D03B0CF71D62250800955575 /* TelegramMediaImage.swift in Sources */,
@ -2042,6 +2059,7 @@
D05A32E41E6F0B2E002760B4 /* RecentAccountSessions.swift in Sources */,
D01A21A61F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift in Sources */,
D03E5E0C1E55E02D0029569A /* LoggedOutAccountAttribute.swift in Sources */,
D018EE052045E95000CBB130 /* CkeckPeerChatServiceActions.swift in Sources */,
D0F3A8A51E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */,
D03B0CD71D62245300955575 /* TelegramGroup.swift in Sources */,
D0B8438C1DA7CF50005F29E1 /* BotInfo.swift in Sources */,
@ -2053,18 +2071,19 @@
D0F3A8A81E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */,
D03B0CE21D62249B00955575 /* InlineBotMessageAttribute.swift in Sources */,
D0AB0B9A1D666520002C78E7 /* ManagedSynchronizePeerReadStates.swift in Sources */,
C27982531E73077800262BFD /* SecretChatStateBridge.swift in Sources */,
D03B0D5B1D631A6900955575 /* Buffer.swift in Sources */,
D0B843891DA7AB96005F29E1 /* ExportedInvitation.swift in Sources */,
D03B0E441D631E6600955575 /* NetworkLogging.m in Sources */,
D0528E651E65C82400E2FEF5 /* UpdateContactName.swift in Sources */,
D03121021DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift in Sources */,
D018EE0220458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */,
D0C48F391E8138DF0075317D /* ArchivedStickerPacksInfo.swift in Sources */,
C239BE971E62EE1E00C2C453 /* LoadMessagesIfNecessary.swift in Sources */,
D03B0CC11D62235000955575 /* StringFormat.swift in Sources */,
D0B85AC51F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */,
D0B843C31DA7FF30005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */,
C2366C861E4F403C0097CCFF /* AddressNames.swift in Sources */,
D0FA08BB2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */,
D0F8C39D20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */,
D0B843C11DA7FF30005F29E1 /* NBPhoneMetaData.m in Sources */,
D0528E601E65B94E00E2FEF5 /* SingleMessageView.swift in Sources */,
@ -2106,7 +2125,6 @@
files = (
C29340F41F5081280074991E /* UpdateGroupSpecificStickerset.swift in Sources */,
C25638021E79E7FC00311607 /* TwoStepVerification.swift in Sources */,
C27982541E73078400262BFD /* SecretChatStateBridge.swift in Sources */,
D00DBBD81E64E41100DB5485 /* CreateSecretChat.swift in Sources */,
C2FD33EC1E696C79008D13D4 /* GroupsInCommon.swift in Sources */,
C239BE9D1E630CB300C2C453 /* UpdatePinnedMessage.swift in Sources */,
@ -2144,6 +2162,7 @@
D0B418B81D7E05A6004562A4 /* ContactManagement.swift in Sources */,
D0E23DE01E8082A400B9B6D2 /* ArchivedStickerPacks.swift in Sources */,
D050F2521E4A59C200988324 /* JoinLink.swift in Sources */,
D018EE0320458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */,
D0F7B1E91E045C87007EB8A5 /* PeerCommands.swift in Sources */,
D00D97C81E32901700E5C2B6 /* PeerInputActivity.swift in Sources */,
D0754D2B1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */,
@ -2215,6 +2234,7 @@
D0FA35091EA632E400E56FFA /* CollectCacheUsageStats.swift in Sources */,
D001F3F31E128A1C007A8C60 /* UpdateMessageService.swift in Sources */,
D0C50E351E93A86600F62E39 /* CallSessionManager.swift in Sources */,
D018EE062045E95000CBB130 /* CkeckPeerChatServiceActions.swift in Sources */,
D0B8442D1DAB91E0005F29E1 /* NBMetadataCoreTest.m in Sources */,
D0C27B431F4B58C000A4E170 /* PeerSpecificStickerPack.swift in Sources */,
D0B844131DAB91CD005F29E1 /* StringFormat.swift in Sources */,
@ -2283,6 +2303,7 @@
C210DD631FBDB90800F673D8 /* SourceReferenceMessageAttribute.swift in Sources */,
D0B8440F1DAB91CD005F29E1 /* Either.swift in Sources */,
D0DC35511DE36908000195EB /* RequestChatContextResults.swift in Sources */,
D0FA08BC2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */,
D08CAA8D1ED81EDF0000FDA8 /* Localizations.swift in Sources */,
D0F7B1EC1E045C87007EB8A5 /* SearchPeers.swift in Sources */,
D001F3EF1E128A1C007A8C60 /* AccountIntermediateState.swift in Sources */,
@ -2292,6 +2313,7 @@
D03C536E1DAD5CA9004C17B3 /* PhoneNumber.swift in Sources */,
D0BC387C1E40D2880044D6FE /* TogglePeerChatPinned.swift in Sources */,
D0528E6B1E65DD2100E2FEF5 /* WebpagePreview.swift in Sources */,
D0E8B8B42044706300605593 /* ForwardGame.swift in Sources */,
D0B844111DAB91CD005F29E1 /* Regex.swift in Sources */,
D0B844321DAB91E0005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */,
D0BEAF5E1E54941B00BD963D /* Authorization.swift in Sources */,

View File

@ -253,6 +253,7 @@ private var declaredEncodables: Void = {
declareEncodable(LoggingSettings.self, f: { LoggingSettings(decoder: $0) })
declareEncodable(CachedLocalizationInfos.self, f: { CachedLocalizationInfos(decoder: $0) })
declareEncodable(SynchronizeGroupedPeersOperation.self, f: { SynchronizeGroupedPeersOperation(decoder: $0) })
declareEncodable(ContentPrivacySettings.self, f: { ContentPrivacySettings(decoder: $0) })
return
}()

View File

@ -701,7 +701,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
case let .webPageEmpty(id):
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
default:
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
updatedState.updateMedia(webpage.webpageId, media: webpage)
}
}
@ -838,7 +838,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
case let .webPageEmpty(id):
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
default:
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
updatedState.updateMedia(webpage.webpageId, media: webpage)
}
}
@ -1539,7 +1539,7 @@ private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableSt
case let .webPageEmpty(id):
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
default:
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
updatedState.updateMedia(webpage.webpageId, media: webpage)
}
}
@ -1975,7 +1975,7 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif
peerIdsWithAddedSecretMessages.insert(peerId)
}
case let .ReadSecretOutbox(peerId, maxTimestamp, actionTimestamp):
applyOutgoingReadMaxIndex(modifier: modifier, index: MessageIndex.upperBound(peerId: peerId, timestamp: maxTimestamp, namespace: Namespaces.Message.Local), beginAt: actionTimestamp)
applyOutgoingReadMaxIndex(modifier: modifier, index: MessageIndex.upperBound(peerId: peerId, timestamp: maxTimestamp, namespace: Namespaces.Message.Local), beginCountdownAt: actionTimestamp)
case let .AddPeerInputActivity(chatPeerId, peerId, activity):
if let peerId = peerId {
if updatedTypingActivities[chatPeerId] == nil {

View File

@ -44,11 +44,46 @@ public func applyMaxReadIndexInteractively(postbox: Postbox, network: Network, s
}
}
func applyOutgoingReadMaxIndex(modifier: Modifier, index: MessageIndex, beginAt timestamp: Int32) {
func applyOutgoingReadMaxIndex(modifier: Modifier, index: MessageIndex, beginCountdownAt timestamp: Int32) {
let messageIds = modifier.applyOutgoingReadMaxIndex(index)
if index.id.peerId.namespace == Namespaces.Peer.SecretChat {
for id in messageIds {
applySecretOutgoingMessageReadActions(modifier: modifier, id: id, beginCountdownAt: timestamp)
}
}
}
func maybeReadSecretOutgoingMessage(modifier: Modifier, index: MessageIndex) {
guard index.id.peerId.namespace == Namespaces.Peer.SecretChat else {
assertionFailure()
return
}
guard index.id.namespace == Namespaces.Message.Local else {
assertionFailure()
return
}
guard let combinedState = modifier.getCombinedPeerReadState(index.id.peerId) else {
return
}
if combinedState.isOutgoingMessageIndexRead(index) {
applySecretOutgoingMessageReadActions(modifier: modifier, id: index.id, beginCountdownAt: index.timestamp)
}
}
func applySecretOutgoingMessageReadActions(modifier: Modifier, id: MessageId, beginCountdownAt timestamp: Int32) {
guard id.peerId.namespace == Namespaces.Peer.SecretChat else {
assertionFailure()
return
}
guard id.namespace == Namespaces.Message.Local else {
assertionFailure()
return
}
if let message = modifier.getMessage(id), !message.flags.contains(.Incoming) {
if message.flags.intersection([.Unsent, .Sending, .Failed]).isEmpty {
for attribute in message.attributes {
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
if (attribute.countdownBeginTime == nil || attribute.countdownBeginTime == 0) && !message.containsSecretMedia {
@ -74,4 +109,3 @@ func applyOutgoingReadMaxIndex(modifier: Modifier, index: MessageIndex, beginAt
}
}
}
}

View File

@ -55,6 +55,10 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
var sentStickers: [TelegramMediaFile] = []
var sentGifs: [TelegramMediaFile] = []
if let updatedTimestamp = updatedTimestamp {
modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([message.id]), timestamp: updatedTimestamp)
}
modifier.updateMessage(message.id, update: { currentMessage in
let updatedId: MessageId
if let messageId = messageId {
@ -140,9 +144,6 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media))
})
if let updatedTimestamp = updatedTimestamp {
modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, timestamp: updatedTimestamp)
}
for file in sentStickers {
modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20)
}
@ -198,6 +199,10 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
var updatedGroupingKey: Int64?
if let latestIndex = mapping.last?.1 {
modifier.offsetPendingMessagesTimestamps(lowerBound: latestIndex.id, excludeIds: Set(mapping.map { $0.0.id }), timestamp: latestIndex.timestamp)
}
for (message, _, updatedMessage) in mapping {
modifier.updateMessage(message.id, update: { currentMessage in
let updatedId: MessageId
@ -259,10 +264,6 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
modifier.updateMessageGroupingKeysAtomically(mapping.map { $0.1.id }, groupingKey: updatedGroupingKey)
}
if let latestIndex = mapping.last?.1 {
modifier.offsetPendingMessagesTimestamps(lowerBound: latestIndex.id, timestamp: latestIndex.timestamp)
}
for file in sentStickers {
modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20)
}

View File

@ -138,7 +138,7 @@ public struct BotPaymentForm {
public let canSaveCredentials: Bool
public let passwordMissing: Bool
public let invoice: BotPaymentInvoice
public let providerId: Int32
public let providerId: PeerId
public let url: String
public let nativeProvider: BotPaymentNativeProvider?
public let savedInfo: BotPaymentRequestedInfo?
@ -229,7 +229,7 @@ public func fetchBotPaymentForm(postbox: Postbox, network: Network, messageId: M
parsedSavedCredentials = .card(id: id, title: title)
}
}
return BotPaymentForm(canSaveCredentials: (flags & (1 << 2)) != 0, passwordMissing: (flags & (1 << 3)) != 0, invoice: parsedInvoice, providerId: providerId, url: url, nativeProvider: parsedNativeProvider, savedInfo: parsedSavedInfo, savedCredentials: parsedSavedCredentials)
return BotPaymentForm(canSaveCredentials: (flags & (1 << 2)) != 0, passwordMissing: (flags & (1 << 3)) != 0, invoice: parsedInvoice, providerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: providerId), url: url, nativeProvider: parsedNativeProvider, savedInfo: parsedSavedInfo, savedCredentials: parsedSavedCredentials)
}
} |> mapError { _ -> BotPaymentFormRequestError in return .generic }
}

View File

@ -9,12 +9,47 @@ import Foundation
import SwiftSignalKit
#endif
public enum CallSessionError {
public enum CallSessionError: Equatable {
case generic
case privacyRestricted
case notSupportedByPeer
case serverProvided(String)
case disconnected
public static func ==(lhs: CallSessionError, rhs: CallSessionError) -> Bool {
switch lhs {
case .generic:
if case .generic = rhs {
return true
} else {
return false
}
case .privacyRestricted:
if case .privacyRestricted = rhs {
return true
} else {
return false
}
case .notSupportedByPeer:
if case .notSupportedByPeer = rhs {
return true
} else {
return false
}
case let .serverProvided(text):
if case .serverProvided(text) = rhs {
return true
} else {
return false
}
case .disconnected:
if case .disconnected = rhs {
return true
} else {
return false
}
}
}
}
public enum CallSessionEndedType {
@ -23,9 +58,26 @@ public enum CallSessionEndedType {
case missed
}
public enum CallSessionTerminationReason {
public enum CallSessionTerminationReason: Equatable {
case ended(CallSessionEndedType)
case error(CallSessionError)
public static func ==(lhs: CallSessionTerminationReason, rhs: CallSessionTerminationReason) -> Bool {
switch lhs {
case let .ended(type):
if case .ended(type) = rhs {
return true
} else {
return false
}
case let .error(error):
if case .error(error) = rhs {
return true
} else {
return false
}
}
}
}
public struct ReportCallRating {
@ -127,6 +179,8 @@ private final class CallSessionContext {
var state: CallSessionInternalState
let subscribers = Bag<(CallSession) -> Void>()
let acknowledgeIncomingCallDisposable = MetaDisposable()
var isEmpty: Bool {
if case .terminated = self.state {
return self.subscribers.isEmpty
@ -140,6 +194,10 @@ private final class CallSessionContext {
self.isOutgoing = isOutgoing
self.state = state
}
deinit {
self.acknowledgeIncomingCallDisposable.dispose()
}
}
private final class CallSessionManagerContext {
@ -252,7 +310,9 @@ private final class CallSessionManagerContext {
if randomStatus == 0 {
let internalId = CallSessionInternalId()
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: false, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b))
let context = CallSessionContext(peerId: peerId, isOutgoing: false, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b))
self.contexts[internalId] = context
context.acknowledgeIncomingCallDisposable.set(self.network.request(Api.functions.phone.receivedCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash))).start())
self.contextIdByStableId[stableId] = internalId
self.contextUpdated(internalId: internalId)
self.ringingStatesUpdated()

View File

@ -0,0 +1,21 @@
import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
#else
import Postbox
import SwiftSignalKit
#endif
public func checkPeerChatServiceActions(postbox: Postbox, peerId: PeerId) -> Signal<Void, NoError> {
return postbox.modify { modifier -> Void in
if peerId.namespace == Namespaces.Peer.SecretChat {
if let state = modifier.getPeerChatState(peerId) as? SecretChatState {
let updatedState = secretChatCheckLayerNegotiationIfNeeded(modifier: modifier, peerId: peerId, state: state)
if state != updatedState {
modifier.setPeerChatState(peerId, state: updatedState)
}
}
}
}
}

View File

@ -0,0 +1,66 @@
import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
import MtProtoKitMac
#else
import Postbox
import SwiftSignalKit
import MtProtoKitDynamic
#endif
public final class ContentPrivacySettings: PreferencesEntry, Equatable {
public let enableSecretChatWebpagePreviews: Bool?
public static var defaultSettings = ContentPrivacySettings(enableSecretChatWebpagePreviews: nil)
public init(enableSecretChatWebpagePreviews: Bool?) {
self.enableSecretChatWebpagePreviews = enableSecretChatWebpagePreviews
}
public init(decoder: PostboxDecoder) {
self.enableSecretChatWebpagePreviews = decoder.decodeOptionalInt32ForKey("enableSecretChatWebpagePreviews").flatMap { $0 != 0 }
}
public func encode(_ encoder: PostboxEncoder) {
if let enableSecretChatWebpagePreviews = self.enableSecretChatWebpagePreviews {
encoder.encodeInt32(enableSecretChatWebpagePreviews ? 1 : 0, forKey: "enableSecretChatWebpagePreviews")
} else {
encoder.encodeNil(forKey: "enableSecretChatWebpagePreviews")
}
}
public func withUpdatedEnableSecretChatWebpagePreviews(_ enableSecretChatWebpagePreviews: Bool) -> ContentPrivacySettings {
return ContentPrivacySettings(enableSecretChatWebpagePreviews: enableSecretChatWebpagePreviews)
}
public func isEqual(to: PreferencesEntry) -> Bool {
guard let to = to as? ContentPrivacySettings else {
return false
}
return self == to
}
public static func ==(lhs: ContentPrivacySettings, rhs: ContentPrivacySettings) -> Bool {
if lhs.enableSecretChatWebpagePreviews != rhs.enableSecretChatWebpagePreviews {
return false
}
return true
}
}
public func updateContentPrivacySettings(postbox: Postbox, _ f: @escaping (ContentPrivacySettings) -> ContentPrivacySettings) -> Signal<Void, NoError> {
return postbox.modify { modifier -> Void in
var updated: ContentPrivacySettings?
modifier.updatePreferencesEntry(key: PreferencesKeys.contentPrivacySettings, { current in
if let current = current as? ContentPrivacySettings {
updated = f(current)
return updated
} else {
updated = f(ContentPrivacySettings.defaultSettings)
return updated
}
})
}
}

View File

@ -36,7 +36,7 @@ public func deleteMessagesInteractively(postbox: Postbox, messageIds: [MessageId
case .basicLayer:
layer = .layer8
case let .sequenceBasedLayer(sequenceState):
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
}
if let layer = layer {
var globallyUniqueIds: [Int64] = []
@ -84,7 +84,7 @@ public func clearHistoryInteractively(postbox: Postbox, peerId: PeerId) -> Signa
case .basicLayer:
layer = .layer8
case let .sequenceBasedLayer(sequenceState):
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
}
if let layer = layer {

View File

@ -0,0 +1,27 @@
import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
#else
import Postbox
import SwiftSignalKit
#endif
public func forwardGameWithScore(account: Account, messageId: MessageId, to peerId: PeerId) -> Signal<Void, NoError> {
return account.postbox.modify { modifier -> Signal<Void, NoError> in
if let message = modifier.getMessage(messageId), let fromPeer = modifier.getPeer(messageId.peerId), let fromInputPeer = apiInputPeer(fromPeer), let toPeer = modifier.getPeer(peerId), let toInputPeer = apiInputPeer(toPeer) {
return account.network.request(Api.functions.messages.forwardMessages(flags: 1 << 8, fromPeer: fromInputPeer, id: [messageId.id], randomId: [arc4random64()], toPeer: toInputPeer))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { updates -> Signal<Void, NoError> in
if let updates = updates {
account.stateManager.addUpdates(updates)
}
return .complete()
}
}
return .complete()
} |> switchToLatest
}

View File

@ -6,21 +6,41 @@ import Foundation
#endif
public class InlineBotMessageAttribute: MessageAttribute {
public let peerId: PeerId
public let peerId: PeerId?
public let title: String?
public var associatedPeerIds: [PeerId] {
return [self.peerId]
if let peerId = self.peerId {
return [peerId]
} else {
return []
}
}
init(peerId: PeerId) {
init(peerId: PeerId?, title: String?) {
self.peerId = peerId
self.title = title
}
required public init(decoder: PostboxDecoder) {
self.peerId = PeerId(decoder.decodeInt64ForKey("i", orElse: 0))
if let peerId = decoder.decodeOptionalInt64ForKey("i") {
self.peerId = PeerId(peerId)
} else {
self.peerId = nil
}
self.title = decoder.decodeOptionalStringForKey("t")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt64(self.peerId.toInt64(), forKey: "i")
if let peerId = self.peerId {
encoder.encodeInt64(peerId.toInt64(), forKey: "i")
} else {
encoder.encodeNil(forKey: "i")
}
if let title = self.title {
encoder.encodeString(title, forKey: "t")
} else {
encoder.encodeNil(forKey: "t")
}
}
}

View File

@ -234,7 +234,7 @@ private func initialHandshakeAccept(postbox: Postbox, network: Network, peerId:
case .basicLayer:
layer = .layer8
case let .sequenceBasedLayer(sequenceState):
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
}
if let layer = layer {
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: layer, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
@ -326,6 +326,7 @@ private func pfsAcceptKey(postbox: Postbox, network: Network, peerId: PeerId, la
private enum BoxedDecryptedMessage {
case layer8(SecretApi8.DecryptedMessage)
case layer46(SecretApi46.DecryptedMessage)
case layer73(SecretApi73.DecryptedMessage)
func serialize(_ buffer: Buffer, role: SecretChatRole, sequenceInfo: SecretChatOperationSequenceInfo?) {
switch self {
@ -350,6 +351,26 @@ private enum BoxedDecryptedMessage {
assertionFailure()
}
let _ = message.serialize(buffer, true)
case let .layer73(message):
buffer.appendInt32(0x1be31789)
let randomBytes = malloc(15)!
arc4random_buf(randomBytes, 15)
serializeBytes(Buffer(memory: randomBytes, size: 15, capacity: 15, freeWhenDone: false), buffer: buffer, boxed: false)
free(randomBytes)
buffer.appendInt32(73)
if let sequenceInfo = sequenceInfo {
let inSeqNo = sequenceInfo.topReceivedOperationIndex * 2 + (role == .creator ? 0 : 1)
let outSeqNo = sequenceInfo.operationIndex * 2 + (role == .creator ? 1 : 0)
buffer.appendInt32(inSeqNo)
buffer.appendInt32(outSeqNo)
} else {
buffer.appendInt32(0)
buffer.appendInt32(0)
assertionFailure()
}
let _ = message.serialize(buffer, true)
}
}
@ -450,8 +471,93 @@ private func decryptedAttributes46(_ attributes: [TelegramMediaFileAttribute]) -
return result
}
private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, uploadedFile: SecretChatOutgoingFile?, layer: SecretChatLayer) -> BoxedDecryptedMessage {
var media: Media? = message.media.first
private func decryptedAttributes73(_ attributes: [TelegramMediaFileAttribute]) -> [SecretApi73.DocumentAttribute] {
var result: [SecretApi73.DocumentAttribute] = []
for attribute in attributes {
switch attribute {
case let .FileName(fileName):
result.append(.documentAttributeFilename(fileName: fileName))
case .Animated:
result.append(.documentAttributeAnimated)
case let .Sticker(displayText, packReference, _):
var stickerSet: SecretApi73.InputStickerSet = .inputStickerSetEmpty
if let packReference = packReference, case let .name(name) = packReference {
stickerSet = .inputStickerSetShortName(shortName: name)
}
result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet))
case let .ImageSize(size):
result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height)))
case let .Video(duration, size, videoFlags):
var flags: Int32 = 0
if videoFlags.contains(.instantRoundVideo) {
flags |= 1 << 0
}
result.append(.documentAttributeVideo(flags: flags, duration: Int32(duration), w: Int32(size.width), h: Int32(size.height)))
case let .Audio(isVoice, duration, title, performer, waveform):
var flags: Int32 = 0
if isVoice {
flags |= (1 << 10)
}
if let _ = title {
flags |= Int32(1 << 0)
}
if let _ = performer {
flags |= Int32(1 << 1)
}
var waveformBuffer: Buffer?
if let waveform = waveform {
flags |= Int32(1 << 2)
waveformBuffer = Buffer(data: waveform.makeData())
}
result.append(.documentAttributeAudio(flags: flags, duration: Int32(duration), title: title, performer: performer, waveform: waveformBuffer))
case .HasLinkedStickers:
break
}
}
return result
}
private func decryptedEntities73(_ entities: [MessageTextEntity]?) -> [SecretApi73.MessageEntity]? {
guard let entities = entities else {
return nil
}
var result: [SecretApi73.MessageEntity] = []
for entity in entities {
switch entity.type {
case .Unknown:
break
case .Mention:
result.append(.messageEntityMention(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
case .Hashtag:
result.append(.messageEntityHashtag(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
case .BotCommand:
result.append(.messageEntityBotCommand(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
case .Url:
result.append(.messageEntityUrl(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
case .Email:
result.append(.messageEntityEmail(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
case .Bold:
result.append(.messageEntityBold(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
case .Italic:
result.append(.messageEntityItalic(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
case .Code:
result.append(.messageEntityCode(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
case .Pre:
result.append(.messageEntityPre(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count), language: ""))
case let .TextUrl(url):
result.append(.messageEntityTextUrl(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count), url: url))
case .TextMention:
break
case .PhoneNumber:
break
}
}
return result
}
private func boxedDecryptedMessage(modifier: Modifier, message: Message, globallyUniqueId: Int64, uploadedFile: SecretChatOutgoingFile?, thumbnailData: [MediaId: Data], layer: SecretChatLayer) -> BoxedDecryptedMessage {
let media: Media? = message.media.first
var messageAutoremoveTimeout: Int32 = 0
var replyGlobalId: Int64? = nil
var flags: Int32 = 0
@ -465,30 +571,83 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
}
}
var viaBotName: String?
var entities: [MessageTextEntity]?
for attribute in message.attributes {
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
messageAutoremoveTimeout = attribute.timeout
break
} else if let attribute = attribute as? InlineBotMessageAttribute {
if let title = attribute.title {
viaBotName = title
} else if let peerId = attribute.peerId, let peer = modifier.getPeer(peerId), let addressName = peer.addressName {
viaBotName = addressName
}
} else if let attribute = attribute as? TextEntitiesMessageAttribute {
entities = attribute.entities
}
}
if let media = media {
if let image = media as? TelegramMediaImage, let uploadedFile = uploadedFile, let largestRepresentation = largestImageRepresentation(image.representations) {
let thumbW: Int32
let thumbH: Int32
let thumb: Buffer
if let smallestRepresentation = smallestImageRepresentation(image.representations), let data = thumbnailData[image.imageId] {
thumbW = Int32(smallestRepresentation.dimensions.width)
thumbH = Int32(smallestRepresentation.dimensions.height)
thumb = Buffer(data: data)
} else {
thumbW = 90
thumbH = 90
thumb = Buffer()
}
switch layer {
case .layer8:
let randomBytesData = malloc(15)!
arc4random_buf(randomBytesData, 15)
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: Buffer(), thumbW: 90, thumbH: 90, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: thumb, thumbW: thumbW, thumbH: thumbH, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: decryptedMedia))
case .layer46:
let decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: Buffer(), thumbW: 90, thumbH: 90, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), caption: "")
if let _ = viaBotName {
flags |= (1 << 11)
}
let decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: thumb, thumbW: thumbW, thumbH: thumbH, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), caption: "")
flags |= (1 << 9)
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: nil, replyToRandomId: replyGlobalId))
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
case .layer73:
if let _ = viaBotName {
flags |= (1 << 11)
}
let decryptedEntites = entities.flatMap(decryptedEntities73)
if let _ = decryptedEntites {
flags |= (1 << 7)
}
let decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: thumb, thumbW: thumbW, thumbH: thumbH, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), caption: "")
flags |= (1 << 9)
if message.groupingKey != nil {
flags |= (1 << 17)
}
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
}
} else if let file = media as? TelegramMediaFile {
let thumbW: Int32
let thumbH: Int32
let thumb: Buffer
if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let data = thumbnailData[file.fileId] {
thumbW = Int32(smallestRepresentation.dimensions.width)
thumbH = Int32(smallestRepresentation.dimensions.height)
thumb = Buffer(data: data)
} else {
thumbW = 0
thumbH = 0
thumb = Buffer()
}
switch layer {
case .layer8:
if let uploadedFile = uploadedFile {
@ -496,7 +655,7 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
arc4random_buf(randomBytesData, 15)
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: Buffer(), thumbW: 0, thumbH: 0, fileName: file.fileName ?? "file", mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: thumb, thumbW: thumbW, thumbH: thumbH, fileName: file.fileName ?? "file", mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: decryptedMedia))
}
@ -517,17 +676,98 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
if let voiceDuration = voiceDuration {
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: voiceDuration, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
} else {
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: Buffer(), thumbW: 0, thumbH: 0, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), attributes: decryptedAttributes46(file.attributes), caption: "")
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: thumb, thumbW: thumbW, thumbH: thumbH, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), attributes: decryptedAttributes46(file.attributes), caption: "")
}
} else {
if let resource = file.resource as? CloudDocumentMediaResource, let size = file.size {
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: resource.fileId, accessHash: resource.accessHash, date: 0, mimeType: file.mimeType, size: Int32(size), thumb: SecretApi46.PhotoSize.photoSizeEmpty(type: "s"), dcId: Int32(resource.datacenterId), attributes: decryptedAttributes46(file.attributes))
let thumb: SecretApi46.PhotoSize
if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let thumbResource = smallestRepresentation.resource as? CloudFileMediaResource {
thumb = .photoSize(type: "s", location: .fileLocation(dcId: Int32(thumbResource.datacenterId), volumeId: thumbResource.volumeId, localId: thumbResource.localId, secret: thumbResource.secret), w: Int32(smallestRepresentation.dimensions.width), h: Int32(smallestRepresentation.dimensions.height), size: thumbResource.size.flatMap(Int32.init) ?? 0)
} else {
thumb = SecretApi46.PhotoSize.photoSizeEmpty(type: "s")
}
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: resource.fileId, accessHash: resource.accessHash, date: 0, mimeType: file.mimeType, size: Int32(size), thumb: thumb, dcId: Int32(resource.datacenterId), attributes: decryptedAttributes46(file.attributes))
}
}
if let decryptedMedia = decryptedMedia {
if let _ = viaBotName {
flags |= (1 << 11)
}
flags |= (1 << 9)
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: nil, replyToRandomId: replyGlobalId))
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
}
case .layer73:
var decryptedMedia: SecretApi73.DecryptedMessageMedia?
if let uploadedFile = uploadedFile {
var voiceDuration: Int32?
for attribute in file.attributes {
if case let .Audio(isVoice, duration, _, _, _) = attribute {
if isVoice {
voiceDuration = Int32(duration)
}
break
}
}
if let voiceDuration = voiceDuration {
decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: voiceDuration, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
} else {
decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: thumb, thumbW: thumbW, thumbH: thumbH, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), attributes: decryptedAttributes73(file.attributes), caption: "")
}
} else {
if let resource = file.resource as? CloudDocumentMediaResource, let size = file.size {
let thumb: SecretApi73.PhotoSize
if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let thumbResource = smallestRepresentation.resource as? CloudFileMediaResource {
thumb = .photoSize(type: "s", location: .fileLocation(dcId: Int32(thumbResource.datacenterId), volumeId: thumbResource.volumeId, localId: thumbResource.localId, secret: thumbResource.secret), w: Int32(smallestRepresentation.dimensions.width), h: Int32(smallestRepresentation.dimensions.height), size: thumbResource.size.flatMap(Int32.init) ?? 0)
} else {
thumb = SecretApi73.PhotoSize.photoSizeEmpty(type: "s")
}
decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: resource.fileId, accessHash: resource.accessHash, date: 0, mimeType: file.mimeType, size: Int32(size), thumb: thumb, dcId: Int32(resource.datacenterId), attributes: decryptedAttributes73(file.attributes))
}
}
if let decryptedMedia = decryptedMedia {
if let _ = viaBotName {
flags |= (1 << 11)
}
let decryptedEntites = entities.flatMap(decryptedEntities73)
if let _ = decryptedEntites {
flags |= (1 << 7)
}
flags |= (1 << 9)
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
}
}
} else if let webpage = media as? TelegramMediaWebpage {
var url: String?
if case let .Loaded(content) = webpage.content {
url = content.url
}
if let url = url, !url.isEmpty {
switch layer {
case .layer8:
break
case .layer46:
if let _ = viaBotName {
flags |= (1 << 11)
}
let decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaWebPage(url: url)
flags |= (1 << 9)
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
case .layer73:
if let _ = viaBotName {
flags |= (1 << 11)
}
let decryptedEntites = entities.flatMap(decryptedEntities73)
if let _ = decryptedEntites {
flags |= (1 << 7)
}
let decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaWebPage(url: url)
flags |= (1 << 9)
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
}
}
}
@ -541,7 +781,19 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: .decryptedMessageMediaEmpty))
case .layer46:
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: .decryptedMessageMediaEmpty, entities: nil, viaBotName: nil, replyToRandomId: replyGlobalId))
if let _ = viaBotName {
flags |= (1 << 11)
}
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: .decryptedMessageMediaEmpty, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
case .layer73:
if let _ = viaBotName {
flags |= (1 << 11)
}
let decryptedEntites = entities.flatMap(decryptedEntities73)
if let _ = decryptedEntites {
flags |= (1 << 7)
}
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: .decryptedMessageMediaEmpty, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
}
}
@ -557,6 +809,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
}
case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds):
switch layer {
@ -568,6 +822,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
}
case let .clearHistory(layer, actionGloballyUniqueId):
switch layer {
@ -578,11 +834,15 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionFlushHistory))
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionFlushHistory))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionFlushHistory))
}
case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo):
switch layer {
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionResend(startSeqNo: fromSeqNo, endSeqNo: toSeqNo)))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionResend(startSeqNo: fromSeqNo, endSeqNo: toSeqNo)))
}
case let .reportLayerSupport(layer, actionGloballyUniqueId, layerSupport):
switch layer {
@ -594,31 +854,43 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
}
case let .pfsRequestKey(layer, actionGloballyUniqueId, rekeySessionId, gA):
switch layer {
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionRequestKey(exchangeId: rekeySessionId, gA: Buffer(buffer: gA))))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionRequestKey(exchangeId: rekeySessionId, gA: Buffer(buffer: gA))))
}
case let .pfsAcceptKey(layer, actionGloballyUniqueId, rekeySessionId, gB, keyFingerprint):
switch layer {
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAcceptKey(exchangeId: rekeySessionId, gB: Buffer(buffer: gB), keyFingerprint: keyFingerprint)))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAcceptKey(exchangeId: rekeySessionId, gB: Buffer(buffer: gB), keyFingerprint: keyFingerprint)))
}
case let .pfsAbortSession(layer, actionGloballyUniqueId, rekeySessionId):
switch layer {
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAbortKey(exchangeId: rekeySessionId)))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAbortKey(exchangeId: rekeySessionId)))
}
case let .pfsCommitKey(layer, actionGloballyUniqueId, rekeySessionId, keyFingerprint):
switch layer {
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionCommitKey(exchangeId: rekeySessionId, keyFingerprint: keyFingerprint)))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionCommitKey(exchangeId: rekeySessionId, keyFingerprint: keyFingerprint)))
}
case let .noop(layer, actionGloballyUniqueId):
switch layer {
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNoop))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNoop))
}
case let .readMessageContents(layer, actionGloballyUniqueId, globallyUniqueIds):
switch layer {
@ -630,6 +902,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
}
case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, _):
switch layer {
@ -641,6 +915,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
}
}
}
@ -681,7 +957,7 @@ private func replaceOutgoingOperationWithEmptyMessage(modifier: Modifier, peerId
case .basicLayer:
layer = .layer8
case let .sequenceBasedLayer(sequenceState):
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
}
}
if let layer = layer {
@ -699,11 +975,57 @@ private func replaceOutgoingOperationWithEmptyMessage(modifier: Modifier, peerId
}
}
private func resourceThumbnailData(mediaBox: MediaBox, resource: MediaResource, mediaId: MediaId) -> Signal<(MediaId, Data)?, NoError> {
return mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|> take(1)
|> map { data -> (MediaId, Data)? in
if data.complete, data.size < 1024 * 16, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
return (mediaId, content)
} else {
return nil
}
}
}
private func messageWithThumbnailData(mediaBox: MediaBox, message: Message) -> Signal<[MediaId: Data], NoError> {
var signals: [Signal<(MediaId, Data)?, NoError>] = []
for media in message.media {
if let image = media as? TelegramMediaImage {
if let smallestRepresentation = smallestImageRepresentation(image.representations) {
signals.append(resourceThumbnailData(mediaBox: mediaBox, resource: smallestRepresentation.resource, mediaId: image.imageId))
}
} else if let file = media as? TelegramMediaFile {
if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations) {
signals.append(resourceThumbnailData(mediaBox: mediaBox, resource: smallestRepresentation.resource, mediaId: file.fileId))
}
}
}
return combineLatest(signals)
|> map { values in
var result: [MediaId: Data] = [:]
for value in values {
if let value = value {
result[value.0] = value.1
}
}
return result
}
}
private func sendMessage(postbox: Postbox, network: Network, messageId: MessageId, file: SecretChatOutgoingFile?, tagLocalIndex: Int32, wasDelivered: Bool, layer: SecretChatLayer) -> Signal<Void, NoError> {
return postbox.modify { modifier -> Signal<[MediaId: Data], NoError> in
if let message = modifier.getMessage(messageId) {
return messageWithThumbnailData(mediaBox: postbox.mediaBox, message: message)
} else {
return .single([:])
}
}
|> switchToLatest
|> mapToSignal { thumbnailData -> Signal<Void, NoError> in
return postbox.modify { modifier -> Signal<Void, NoError> in
if let state = modifier.getPeerChatState(messageId.peerId) as? SecretChatState, let peer = modifier.getPeer(messageId.peerId) as? TelegramSecretChat {
if let message = modifier.getMessage(messageId), let globallyUniqueId = message.globallyUniqueId {
let decryptedMessage = boxedDecryptedMessage(message: message, globallyUniqueId: globallyUniqueId, uploadedFile: file, layer: layer)
let decryptedMessage = boxedDecryptedMessage(modifier: modifier, message: message, globallyUniqueId: globallyUniqueId, uploadedFile: file, thumbnailData: thumbnailData, layer: layer)
return sendBoxedDecryptedMessage(postbox: postbox, network: network, peer: peer, state: state, operationIndex: tagLocalIndex, decryptedMessage: decryptedMessage, globallyUniqueId: globallyUniqueId, file: file, asService: wasDelivered, wasDelivered: wasDelivered)
|> mapToSignal { result in
return postbox.modify { modifier -> Void in
@ -712,16 +1034,22 @@ private func sendMessage(postbox: Postbox, network: Network, messageId: MessageI
} else {
markOutgoingOperationAsCompleted(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil)
}
modifier.updateMessage(message.id, update: { currentMessage in
var flags = StoreMessageFlags(currentMessage.flags)
var timestamp = message.timestamp
if let result = result {
switch result {
case let .sentEncryptedMessage(date):
timestamp = date
case let .sentEncryptedFile(date, file):
case let .sentEncryptedFile(date, _):
timestamp = date
}
}
modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([messageId]), timestamp: timestamp)
modifier.updateMessage(message.id, update: { currentMessage in
var flags = StoreMessageFlags(currentMessage.flags)
if let _ = result {
flags.remove(.Unsent)
flags.remove(.Sending)
} else {
@ -731,19 +1059,25 @@ private func sendMessage(postbox: Postbox, network: Network, messageId: MessageI
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
})
maybeReadSecretOutgoingMessage(modifier: modifier, index: MessageIndex(id: message.id, timestamp: timestamp))
}
}
} else {
assertionFailure()
return .never()
replaceOutgoingOperationWithEmptyMessage(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, globallyUniqueId: arc4random64())
modifier.deleteMessages([messageId])
//assertionFailure()
return .complete()
}
} else {
return .complete()
}
} |> switchToLatest
}
}
private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId: PeerId, action: SecretMessageAction, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal<Void, NoError> {
return postbox.modify { modifier -> Signal<Void, NoError> in
@ -758,6 +1092,7 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId
markOutgoingOperationAsCompleted(modifier: modifier, peerId: peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil)
}
if let messageId = action.messageId {
var resultTimestamp: Int32?
modifier.updateMessage(messageId, update: { currentMessage in
var flags = StoreMessageFlags(currentMessage.flags)
var timestamp = currentMessage.timestamp
@ -773,12 +1108,17 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId
} else {
flags = [.Failed]
}
resultTimestamp = timestamp
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
})
if let resultTimestamp = resultTimestamp {
maybeReadSecretOutgoingMessage(modifier: modifier, index: MessageIndex(id: messageId, timestamp: resultTimestamp))
}
}
}
}
@ -791,12 +1131,23 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId
private func sendBoxedDecryptedMessage(postbox: Postbox, network: Network, peer: TelegramSecretChat, state: SecretChatState, operationIndex: Int32, decryptedMessage: BoxedDecryptedMessage, globallyUniqueId: Int64, file: SecretChatOutgoingFile?, asService: Bool, wasDelivered: Bool) -> Signal<Api.messages.SentEncryptedMessage?, NoError> {
let payload = Buffer()
var sequenceInfo: SecretChatOperationSequenceInfo?
var maybeKey: SecretChatKey?
var maybeParameters: SecretChatEncryptionParameters?
let mode: SecretChatEncryptionMode
switch decryptedMessage {
case .layer8, .layer46:
mode = .v1
default:
mode = .v2(role: state.role)
}
switch state.embeddedState {
case .terminated, .handshake:
break
case .basicLayer:
maybeKey = state.keychain.indefinitelyValidKey()
if let key = state.keychain.indefinitelyValidKey() {
maybeParameters = SecretChatEncryptionParameters(key: key, mode: mode)
}
case let .sequenceBasedLayer(sequenceState):
let topReceivedOperationIndex: Int32
if let topProcessedCanonicalIncomingOperationIndex = sequenceState.topProcessedCanonicalIncomingOperationIndex {
@ -805,18 +1156,20 @@ private func sendBoxedDecryptedMessage(postbox: Postbox, network: Network, peer:
topReceivedOperationIndex = -1
}
let canonicalOperationIndex = sequenceState.canonicalOutgoingOperationIndex(operationIndex)
maybeKey = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: canonicalOperationIndex)
Logger.shared.log("SecretChat", "sending message with index \(canonicalOperationIndex) key \(String(describing: maybeKey?.fingerprint))")
if let key = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: canonicalOperationIndex) {
maybeParameters = SecretChatEncryptionParameters(key: key, mode: mode)
}
Logger.shared.log("SecretChat", "sending message with index \(canonicalOperationIndex) key \(String(describing: maybeParameters?.key.fingerprint))")
sequenceInfo = SecretChatOperationSequenceInfo(topReceivedOperationIndex: topReceivedOperationIndex, operationIndex: canonicalOperationIndex)
}
guard let key = maybeKey else {
guard let parameters = maybeParameters else {
Logger.shared.log("SecretChat", "no valid key found")
return .single(nil)
}
decryptedMessage.serialize(payload, role: state.role, sequenceInfo: sequenceInfo)
let encryptedPayload = encryptedMessageContents(key: key, data: MemoryBuffer(payload))
let encryptedPayload = encryptedMessageContents(parameters: parameters, data: MemoryBuffer(payload))
let sendMessage: Signal<Api.messages.SentEncryptedMessage, MTRpcError>
let inputPeer = Api.InputEncryptedChat.inputEncryptedChat(chatId: peer.id.id, accessHash: peer.accessHash)

View File

@ -46,7 +46,7 @@ public func markMessageContentAsConsumedInteractively(postbox: Postbox, messageI
case .basicLayer:
layer = .layer8
case let .sequenceBasedLayer(sequenceState):
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
}
}

View File

@ -25,6 +25,7 @@ public struct Namespaces {
public static let CloudSecretFile: Int32 = 10
public static let CloudGame: Int32 = 11
public static let CloudInvoice: Int32 = 12
public static let LocalWebpage: Int32 = 13
}
public struct Peer {
@ -132,6 +133,7 @@ private enum PreferencesKeyValues: Int32 {
case proxySettings = 5
case loggingSettings = 6
case coreSettings = 7
case contentPrivacySettings = 8
}
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
@ -188,4 +190,10 @@ public struct PreferencesKeys {
key.setInt32(0, value: PreferencesKeyValues.coreSettings.rawValue)
return key
}()
public static let contentPrivacySettings: ValueBoxKey = {
let key = ValueBoxKey(length: 4)
key.setInt32(0, value: PreferencesKeyValues.contentPrivacySettings.rawValue)
return key
}()
}

View File

@ -12,114 +12,10 @@ private func aspectFitSize(_ size: CGSize, to: CGSize) -> CGSize {
return CGSize(width: floor(size.width * scale), height: floor(size.height * scale))
}
/*
if ([result isKindOfClass:[TGBotContextMediaResult class]]) {
TGBotContextMediaResult *concreteResult = (TGBotContextMediaResult *)result;
if ([concreteResult.type isEqualToString:@"game"]) {
TGGameMediaAttachment *gameMedia = [[TGGameMediaAttachment alloc] initWithGameId:0 accessHash:0 shortName:nil title:concreteResult.title gameDescription:concreteResult.resultDescription photo:concreteResult.photo document:concreteResult.document];
[strongSelf->_companion controllerWantsToSendGame:gameMedia asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
} else if (concreteResult.document != nil) {
TGDocumentAttributeVideo *video = nil;
bool isAnimated = false;
for (id attribute in concreteResult.document.attributes) {
if ([attribute isKindOfClass:[TGDocumentAttributeVideo class]]) {
video = attribute;
} else if ([attribute isKindOfClass:[TGDocumentAttributeAnimated class]]) {
isAnimated = true;
}
}
if (video != nil && !isAnimated) {
TGVideoMediaAttachment *videoMedia = [[TGVideoMediaAttachment alloc] init];
videoMedia = [[TGVideoMediaAttachment alloc] init];
videoMedia.videoId = concreteResult.document.documentId;
videoMedia.accessHash = concreteResult.document.accessHash;
videoMedia.duration = video.duration;
videoMedia.dimensions = video.size;
videoMedia.thumbnailInfo = concreteResult.document.thumbnailInfo;
TGVideoInfo *videoInfo = [[TGVideoInfo alloc] init];
[videoInfo addVideoWithQuality:1 url:[[NSString alloc] initWithFormat:@"video:%lld:%lld:%d:%d", videoMedia.videoId, videoMedia.accessHash, concreteResult.document.datacenterId, concreteResult.document.size] size:concreteResult.document.size];
videoMedia.videoInfo = videoInfo;
[strongSelf->_companion controllerWantsToSendRemoteVideoWithMedia:videoMedia asReplyToMessageId:[strongSelf currentReplyMessageId] text:concreteMessage.caption botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
} else {
[strongSelf->_companion controllerWantsToSendRemoteDocument:concreteResult.document asReplyToMessageId:[strongSelf currentReplyMessageId] text:concreteMessage.caption botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
}
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
} else if (concreteResult.photo != nil) {
[strongSelf->_companion controllerWantsToSendRemoteImage:concreteResult.photo text:concreteMessage.caption asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
}
} else if ([result isKindOfClass:[TGBotContextExternalResult class]]) {
TGBotContextExternalResult *concreteResult = (TGBotContextExternalResult *)result;
if ([concreteResult.type isEqualToString:@"gif"]) {
TGExternalGifSearchResult *externalGifSearchResult = [[TGExternalGifSearchResult alloc] initWithUrl:concreteResult.url originalUrl:concreteResult.originalUrl thumbnailUrl:concreteResult.thumbUrl size:concreteResult.size];
id description = [strongSelf->_companion documentDescriptionFromExternalGifSearchResult:externalGifSearchResult text:concreteMessage.caption botContextResult:botContextResult];
if (description != nil) {
[strongSelf->_companion controllerWantsToSendImagesWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId] botReplyMarkup:concreteMessage.replyMarkup];
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
[TGRecentContextBotsSignal addRecentBot:results.userId];
}
} else if ([concreteResult.type isEqualToString:@"photo"]) {
TGExternalImageSearchResult *externalImageSearchResult = [[TGExternalImageSearchResult alloc] initWithUrl:concreteResult.url originalUrl:concreteResult.originalUrl thumbnailUrl:concreteResult.thumbUrl title:concreteResult.title size:concreteResult.size];
id description = [strongSelf->_companion imageDescriptionFromExternalImageSearchResult:externalImageSearchResult text:concreteMessage.caption botContextResult:botContextResult];
if (description != nil) {
[strongSelf->_companion controllerWantsToSendImagesWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId] botReplyMarkup:concreteMessage.replyMarkup];
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
[TGRecentContextBotsSignal addRecentBot:results.userId];
}
} else if ([concreteResult.type isEqualToString:@"audio"] || [concreteResult.type isEqualToString:@"voice"] || [concreteResult.type isEqualToString:@"file"]) {
id description = [strongSelf->_companion documentDescriptionFromBotContextResult:concreteResult text:concreteMessage.caption botContextResult:botContextResult];
if (description != nil) {
[strongSelf->_companion controllerWantsToSendImagesWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId] botReplyMarkup:concreteMessage.replyMarkup];
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
[TGRecentContextBotsSignal addRecentBot:results.userId];
}
} else {
if (![_companion allowMessageForwarding] && !TGAppDelegateInstance.allowSecretWebpages) {
for (id result in [TGMessage textCheckingResultsForText:concreteMessage.caption highlightMentionsAndTags:false highlightCommands:false entities:nil]) {
if ([result isKindOfClass:[NSTextCheckingResult class]] && ((NSTextCheckingResult *)result).resultType == NSTextCheckingTypeLink) {
[_companion maybeAskForSecretWebpages];
return;
}
}
}
[strongSelf->_companion controllerWantsToSendTextMessage:concreteMessage.caption entities:@[] asReplyToMessageId:[strongSelf currentReplyMessageId] withAttachedMessages:[strongSelf currentForwardMessages] disableLinkPreviews:false botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
}
}
} else if ([result.sendMessage isKindOfClass:[TGBotContextResultSendMessageText class]]) {
TGBotContextResultSendMessageText *concreteMessage = (TGBotContextResultSendMessageText *)result.sendMessage;
if (![_companion allowMessageForwarding] && !TGAppDelegateInstance.allowSecretWebpages) {
for (id result in [TGMessage textCheckingResultsForText:concreteMessage.message highlightMentionsAndTags:false highlightCommands:false entities:nil]) {
if ([result isKindOfClass:[NSTextCheckingResult class]] && ((NSTextCheckingResult *)result).resultType == NSTextCheckingTypeLink) {
[_companion maybeAskForSecretWebpages];
return;
}
}
}
[strongSelf->_companion controllerWantsToSendTextMessage:concreteMessage.message entities:concreteMessage.entities asReplyToMessageId:[strongSelf currentReplyMessageId] withAttachedMessages:[strongSelf currentForwardMessages] disableLinkPreviews:false botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
} else if ([result.sendMessage isKindOfClass:[TGBotContextResultSendMessageGeo class]]) {
TGBotContextResultSendMessageGeo *concreteMessage = (TGBotContextResultSendMessageGeo *)result.sendMessage;
[strongSelf->_companion controllerWantsToSendMapWithLatitude:concreteMessage.location.latitude longitude:concreteMessage.location.longitude venue:concreteMessage.location.venue asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
} else if ([result.sendMessage isKindOfClass:[TGBotContextResultSendMessageContact class]]) {
TGBotContextResultSendMessageContact *concreteMessage = (TGBotContextResultSendMessageContact *)result.sendMessage;
TGUser *contactUser = [[TGUser alloc] init];
contactUser.firstName = concreteMessage.contact.firstName;
contactUser.lastName = concreteMessage.contact.lastName;
contactUser.phoneNumber = concreteMessage.contact.phoneNumber;
[strongSelf->_companion controllerWantsToSendContact:contactUser asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
}
}*/
public func outgoingMessageWithChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult) -> EnqueueMessage? {
var attributes: [MessageAttribute] = []
attributes.append(OutgoingChatContextResultMessageAttribute(queryId: results.queryId, id: result.id))
attributes.append(InlineBotMessageAttribute(peerId: results.botId))
attributes.append(InlineBotMessageAttribute(peerId: results.botId, title: nil))
switch result.message {
case let .auto(caption, entities, replyMarkup):

View File

@ -509,9 +509,11 @@ public final class PendingMessageManager {
messages.sort { MessageIndex($0.0) < MessageIndex($1.0) }
if peerId.namespace == Namespaces.Peer.SecretChat {
//assertionFailure()
for (message, content) in messages {
PendingMessageManager.sendSecretMessageContent(modifier: modifier, message: message, content: content)
}
return failMessages(postbox: postbox, ids: group.map { $0.0 })
return .complete()
} else if let peer = modifier.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
var isForward = false
var replyMessageId: Int32?
@ -637,13 +639,7 @@ public final class PendingMessageManager {
} |> switchToLatest
}
private func sendMessageContent(network: Network, postbox: Postbox, stateManager: AccountStateManager, messageId: MessageId, content: PendingMessageUploadedContent) -> Signal<Void, NoError> {
return postbox.modify { [weak self] modifier -> Signal<Void, NoError> in
guard let message = modifier.getMessage(messageId) else {
return .complete()
}
if messageId.peerId.namespace == Namespaces.Peer.SecretChat {
private static func sendSecretMessageContent(modifier: Modifier, message: Message, content: PendingMessageUploadedContent) {
var secretFile: SecretChatOutgoingFile?
switch content {
case let .secretMedia(file, size, key):
@ -655,7 +651,7 @@ public final class PendingMessageManager {
}
var layer: SecretChatLayer?
let state = modifier.getPeerChatState(messageId.peerId) as? SecretChatState
let state = modifier.getPeerChatState(message.id.peerId) as? SecretChatState
if let state = state {
switch state.embeddedState {
case .terminated, .handshake:
@ -663,7 +659,7 @@ public final class PendingMessageManager {
case .basicLayer:
layer = .layer8
case let .sequenceBasedLayer(sequenceState):
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
}
}
@ -694,11 +690,11 @@ public final class PendingMessageManager {
}
if !sentAsAction {
let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: messageId.peerId, operation: .sendMessage(layer: layer, id: messageId, file: secretFile), state: state)
let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: message.id.peerId, operation: .sendMessage(layer: layer, id: message.id, file: secretFile), state: state)
if updatedState != state {
modifier.setPeerChatState(messageId.peerId, state: updatedState)
modifier.setPeerChatState(message.id.peerId, state: updatedState)
}
modifier.updateMessage(messageId, update: { currentMessage in
modifier.updateMessage(message.id, update: { currentMessage in
var flags = StoreMessageFlags(message.flags)
if !flags.contains(.Failed) {
flags.insert(.Sending)
@ -711,7 +707,7 @@ public final class PendingMessageManager {
})
}
} else {
modifier.updateMessage(messageId, update: { currentMessage in
modifier.updateMessage(message.id, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
@ -719,6 +715,16 @@ public final class PendingMessageManager {
return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
})
}
}
private func sendMessageContent(network: Network, postbox: Postbox, stateManager: AccountStateManager, messageId: MessageId, content: PendingMessageUploadedContent) -> Signal<Void, NoError> {
return postbox.modify { [weak self] modifier -> Signal<Void, NoError> in
guard let message = modifier.getMessage(messageId) else {
return .complete()
}
if messageId.peerId.namespace == Namespaces.Peer.SecretChat {
PendingMessageManager.sendSecretMessageContent(modifier: modifier, message: message, content: content)
return .complete()
} else if let peer = modifier.getPeer(messageId.peerId), let inputPeer = apiInputPeer(peer) {
var uniqueId: Int64 = 0

View File

@ -40,7 +40,9 @@ func messageContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods
var autoremoveAttribute: AutoremoveTimeoutMessageAttribute?
for attribute in attributes {
if let attribute = attribute as? OutgoingChatContextResultMessageAttribute {
if peerId.namespace != Namespaces.Peer.SecretChat {
contextResult = attribute
}
} else if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
autoremoveAttribute = attribute
}
@ -105,7 +107,11 @@ private enum PredownloadedResource {
case none
}
private func maybePredownloadedImageResource(postbox: Postbox, resource: MediaResource) -> Signal<PredownloadedResource, PendingMessageUploadError> {
private func maybePredownloadedImageResource(postbox: Postbox, peerId: PeerId, resource: MediaResource) -> Signal<PredownloadedResource, PendingMessageUploadError> {
if peerId.namespace == Namespaces.Peer.SecretChat {
return .single(.none)
}
return Signal<Signal<PredownloadedResource, PendingMessageUploadError>, PendingMessageUploadError> { subscriber in
let data = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)).start(next: { data in
if data.complete {
@ -154,7 +160,11 @@ private func maybePredownloadedImageResource(postbox: Postbox, resource: MediaRe
} |> switchToLatest
}
private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, resource: MediaResource) -> Signal<PredownloadedResource, PendingMessageUploadError> {
private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, peerId: PeerId, resource: MediaResource) -> Signal<PredownloadedResource, PendingMessageUploadError> {
if peerId.namespace == Namespaces.Peer.SecretChat {
return .single(.none)
}
return auxiliaryMethods.fetchResourceMediaReferenceHash(resource)
|> mapToSignal { hash -> Signal<PredownloadedResource, NoError> in
if let hash = hash {
@ -185,7 +195,7 @@ private func maybeCacheUploadedResource(postbox: Postbox, key: CachedSentMediaRe
private func uploadedMediaImageContent(network: Network, postbox: Postbox, peerId: PeerId, image: TelegramMediaImage, text: String, autoremoveAttribute: AutoremoveTimeoutMessageAttribute?) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
if let largestRepresentation = largestImageRepresentation(image.representations) {
let predownloadedResource: Signal<PredownloadedResource, PendingMessageUploadError> = maybePredownloadedImageResource(postbox: postbox, resource: largestRepresentation.resource)
let predownloadedResource: Signal<PredownloadedResource, PendingMessageUploadError> = maybePredownloadedImageResource(postbox: postbox, peerId: peerId, resource: largestRepresentation.resource)
return predownloadedResource
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
var referenceKey: CachedSentMediaReferenceKey?
@ -324,9 +334,14 @@ private enum UploadedMediaTransform {
case done(Media?)
}
private enum UploadedMediaThumbnailResult {
case file(Api.InputFile)
case none
}
private enum UploadedMediaThumbnail {
case pending
case done(Api.InputFile?)
case done(UploadedMediaThumbnailResult)
}
private func uploadedThumbnail(network: Network, postbox: Postbox, image: TelegramMediaImageRepresentation) -> Signal<Api.InputFile?, PendingMessageUploadError> {
@ -359,7 +374,7 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA
}
private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], file: TelegramMediaFile) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, resource: file.resource) |> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource) |> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
var referenceKey: CachedSentMediaReferenceKey?
switch result {
case let .media(media):
@ -433,27 +448,40 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
return .single(.pending)
case let .done(media):
if let media = media as? TelegramMediaFile, let smallestThumbnail = smallestImageRepresentation(media.previewRepresentations) {
if peerId.namespace == Namespaces.Peer.SecretChat {
return .single(.done(.none))
} else {
return uploadedThumbnail(network: network, postbox: postbox, image: smallestThumbnail)
|> mapError { _ -> PendingMessageUploadError in return .generic }
|> map { result in
return .done(result)
if let result = result {
return .done(.file(result))
} else {
return .done(.none)
}
}
}
} else {
return .single(.done(nil))
return .single(.done(.none))
}
}
})
return combineLatest(upload, thumbnail)
|> mapToSignal { content, media -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|> mapToSignal { content, thumbnailResult -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
switch content {
case let .progress(progress):
return .single(.progress(progress))
case let .inputFile(inputFile):
if case let .done(thumbnail) = media {
if case let .done(thumbnail) = thumbnailResult {
var flags: Int32 = 0
if let _ = thumbnail {
var thumbnailFile: Api.InputFile?
if case let .file(file) = thumbnail {
thumbnailFile = file
}
if let thumbnailFile = thumbnailFile {
flags |= 1 << 2
}
@ -465,10 +493,8 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
}
}
//flags |= 1 << 3
if ttlSeconds != nil {
return .single(.content(.media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnail, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds), text)))
return .single(.content(.media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds), text)))
}
return postbox.modify { modifier -> Api.InputPeer? in
@ -477,7 +503,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|> mapError { _ -> PendingMessageUploadError in return .generic }
|> mapToSignal { inputPeer -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
if let inputPeer = inputPeer {
return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: .inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnail, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds)))
return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: .inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds)))
|> mapError { _ -> PendingMessageUploadError in return .generic }
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
switch result {
@ -498,7 +524,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
return .complete()
}
case let .inputSecretFile(file, size, key):
if case .done = media {
if case .done = thumbnailResult {
return .single(.content(.secretMedia(file, size, key)))
} else {
return .complete()

View File

@ -45,6 +45,10 @@ private func parsedServiceAction(_ operation: SecretChatIncomingDecryptedOperati
if let parsedObject = SecretApi46.parse(Buffer(bufferNoCopy: operation.contents)), let apiMessage = parsedObject as? SecretApi46.DecryptedMessage {
return SecretChatServiceAction(apiMessage)
}
case .layer73:
if let parsedObject = SecretApi73.parse(Buffer(bufferNoCopy: operation.contents)), let apiMessage = parsedObject as? SecretApi73.DecryptedMessage {
return SecretChatServiceAction(apiMessage)
}
}
return nil
}
@ -134,6 +138,18 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier:
} else {
throw MessageParsingError.contentParsingError
}
case .layer73:
if let parsedObject = SecretApi73.parse(Buffer(bufferNoCopy: operation.contents)), let apiMessage = parsedObject as? SecretApi73.DecryptedMessage {
if let (parsedMessage, parsedResources) = parseMessage(peerId: peerId, authorId: updatedPeer.regularPeerId, tagLocalIndex: entry.tagLocalIndex, timestamp: operation.timestamp, apiMessage: apiMessage, file: operation.file, messageIdForGloballyUniqueMessageId: { id in
return modifier.messageIdForGloballyUniqueMessageId(peerId: peerId, id: id)
}) {
message = parsedMessage
resources = parsedResources
}
serviceAction = SecretChatServiceAction(apiMessage)
} else {
throw MessageParsingError.contentParsingError
}
}
switch updatedState.embeddedState {
@ -195,10 +211,14 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier:
case .handshake:
throw MessageParsingError.invalidChatState
case .basicLayer:
if layerSupport >= 46 {
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: 46, locallyRequestedLayer: 46, remotelyRequestedLayer: layerSupport), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
if layerSupport >= 73 {
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: .layer73, locallyRequestedLayer: 73, remotelyRequestedLayer: layerSupport), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceBasedLayerState))
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer46, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer73, actionGloballyUniqueId: arc4random64(), layerSupport: 73), state: updatedState)
} else if layerSupport >= 46 {
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: .layer46, locallyRequestedLayer: 46, remotelyRequestedLayer: layerSupport), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceBasedLayerState))
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer46, actionGloballyUniqueId: arc4random64(), layerSupport: 73), state: updatedState)
} else {
throw MessageParsingError.contentParsingError
}
@ -206,7 +226,8 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier:
if sequenceState.layerNegotiationState.remotelyRequestedLayer != layerSupport {
let updatedNegotiationState = sequenceState.layerNegotiationState.withUpdatedRemotelyRequestedLayer(layerSupport)
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedLayerNegotiationState(updatedNegotiationState)))
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer46, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
updatedState = secretChatCheckLayerNegotiationIfNeeded(modifier: modifier, peerId: peerId, state: updatedState)
}
}
case let .setMessageAutoremoveTimeout(timeout):
@ -378,6 +399,42 @@ extension SecretChatServiceAction {
}
}
extension SecretChatServiceAction {
init?(_ apiMessage: SecretApi73.DecryptedMessage) {
switch apiMessage {
case .decryptedMessage:
return nil
case let .decryptedMessageService(_, action):
switch action {
case let .decryptedMessageActionDeleteMessages(randomIds):
self = .deleteMessages(globallyUniqueIds: randomIds)
case .decryptedMessageActionFlushHistory:
self = .clearHistory
case let .decryptedMessageActionNotifyLayer(layer):
self = .reportLayerSupport(layer)
case let .decryptedMessageActionReadMessages(randomIds):
self = .markMessagesContentAsConsumed(globallyUniqueIds: randomIds)
case .decryptedMessageActionScreenshotMessages:
return nil
case let .decryptedMessageActionSetMessageTTL(ttlSeconds):
self = .setMessageAutoremoveTimeout(ttlSeconds)
case let .decryptedMessageActionResend(startSeqNo, endSeqNo):
self = .resendOperations(fromSeq: startSeqNo, toSeq: endSeqNo)
case let .decryptedMessageActionRequestKey(exchangeId, gA):
self = .rekeyAction(.pfsRequestKey(rekeySessionId: exchangeId, gA: MemoryBuffer(gA)))
case let .decryptedMessageActionAcceptKey(exchangeId, gB, keyFingerprint):
self = .rekeyAction(.pfsAcceptKey(rekeySessionId: exchangeId, gB: MemoryBuffer(gB), keyFingerprint: keyFingerprint))
case let .decryptedMessageActionCommitKey(exchangeId, keyFingerprint):
self = .rekeyAction(.pfsCommitKey(rekeySessionId: exchangeId, keyFingerprint: keyFingerprint))
case let .decryptedMessageActionAbortKey(exchangeId):
self = .rekeyAction(.pfsAbortSession(rekeySessionId: exchangeId))
case .decryptedMessageActionNoop:
return nil
}
}
}
}
extension StoreMessage {
convenience init?(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi8.DecryptedMessage, file: SecretChatFileReference?) {
switch apiMessage {
@ -431,16 +488,83 @@ extension TelegramMediaFileAttribute {
self = .Sticker(displayText: alt, packReference: packReference, maskData: nil)
case let .documentAttributeVideo(duration, w, h):
self = .Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: [])
default:
return nil
}
}
}
extension TelegramMediaFileAttribute {
init?(_ apiAttribute: SecretApi73.DocumentAttribute) {
switch apiAttribute {
case .documentAttributeAnimated:
self = .Animated
case let .documentAttributeAudio(flags, duration, title, performer, waveform):
let isVoice = (flags & (1 << 10)) != 0
var waveformBuffer: MemoryBuffer?
if let waveform = waveform {
let memory = malloc(waveform.size)!
memcpy(memory, waveform.data, waveform.size)
waveformBuffer = MemoryBuffer(memory: memory, capacity: waveform.size, length: waveform.size, freeWhenDone: true)
}
self = .Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: performer, waveform: waveformBuffer)
case let .documentAttributeFilename(fileName):
self = .FileName(fileName: fileName)
case let .documentAttributeImageSize(w, h):
self = .ImageSize(size: CGSize(width: CGFloat(w), height: CGFloat(h)))
case let .documentAttributeSticker(alt, stickerset):
let packReference: StickerPackReference?
switch stickerset {
case .inputStickerSetEmpty:
packReference = nil
case let .inputStickerSetShortName(shortName):
packReference = .name(shortName)
}
self = .Sticker(displayText: alt, packReference: packReference, maskData: nil)
case let .documentAttributeVideo(flags, duration, w, h):
var videoFlags: TelegramMediaVideoFlags = []
if (flags & (1 << 0)) != 0 {
videoFlags.insert(.instantRoundVideo)
}
self = .Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: videoFlags)
}
}
}
private func parseEntities(_ entities: [SecretApi46.MessageEntity]?) -> TextEntitiesMessageAttribute {
var result: [MessageTextEntity] = []
if let entities = entities {
for entity in entities {
switch entity {
case let .messageEntityMention(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Mention))
case let .messageEntityHashtag(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Hashtag))
case let .messageEntityBotCommand(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BotCommand))
case let .messageEntityUrl(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Url))
case let .messageEntityEmail(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Email))
case let .messageEntityBold(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Bold))
case let .messageEntityItalic(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Italic))
case let .messageEntityCode(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Code))
case let .messageEntityPre(offset, length, _):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Pre))
case let .messageEntityTextUrl(offset, length, url):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .TextUrl(url: url)))
case .messageEntityUnknown:
break
}
}
}
return TextEntitiesMessageAttribute(entities: result)
}
private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi46.DecryptedMessage, file: SecretChatFileReference?, messageIdForGloballyUniqueMessageId: (Int64) -> MessageId?) -> (StoreMessage, [(MediaResource, Data)])? {
switch apiMessage {
case let .decryptedMessage(flags, randomId, ttl, message, media, entities, viaBotName, replyToRandomId):
case let .decryptedMessage(_, randomId, ttl, message, media, entities, viaBotName, replyToRandomId):
var text = message
var parsedMedia: [Media] = []
var attributes: [MessageAttribute] = []
@ -450,6 +574,12 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttl, countdownBeginTime: nil))
}
attributes.append(parseEntities(entities))
if let viaBotName = viaBotName, !viaBotName.isEmpty {
attributes.append(InlineBotMessageAttribute(peerId: nil, title: viaBotName))
}
if let media = media {
switch media {
case let .decryptedMessageMediaPhoto(thumb, thumbW, thumbH, w, h, size, key, iv, caption):
@ -497,7 +627,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
text = caption
}
if let file = file {
var parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: []), .FileName(fileName: "video.mov")]
let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: []), .FileName(fileName: "video.mov")]
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
@ -507,18 +637,49 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
parsedMedia.append(fileMedia)
}
case let .decryptedMessageMediaExternalDocument(id, accessHash, date, mimeType, size, thumb, dcId, attributes):
case let .decryptedMessageMediaExternalDocument(id, accessHash, _, mimeType, size, thumb, dcId, attributes):
var parsedAttributes: [TelegramMediaFileAttribute] = []
for attribute in attributes {
if let parsedAttribute = TelegramMediaFileAttribute(attribute) {
parsedAttributes.append(parsedAttribute)
}
}
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
parsedMedia.append(fileMedia)
var previewRepresentations: [TelegramMediaImageRepresentation] = []
switch thumb {
case let .photoSize(_, location, w, h, size):
switch location {
case let .fileLocation(dcId, volumeId, localId, secret):
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size))))
case .fileLocationUnavailable:
break
}
case let .photoCachedSize(_, location, w, h, bytes):
if bytes.size > 0 {
switch location {
case let .fileLocation(dcId, volumeId, localId, secret):
let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size)
resources.append((resource, bytes.makeData()))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource))
case .fileLocationUnavailable:
break
}
}
default:
break
}
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
parsedMedia.append(fileMedia)
case let .decryptedMessageMediaWebPage(url):
parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url)))
case let .decryptedMessageMediaGeoPoint(lat, long):
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil))
case let .decryptedMessageMediaContact(phoneNumber, firstName, lastName, userId):
parsedMedia.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)))
case let .decryptedMessageMediaVenue(lat, long, title, address, provider, venueId):
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: nil), liveBroadcastingTimeout: nil))
case .decryptedMessageMediaEmpty:
break
}
}
if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) {
attributes.append(ReplyMessageAttribute(messageId: replyMessageId))
@ -564,3 +725,214 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
}
}
}
private func parseEntities(_ entities: [SecretApi73.MessageEntity]?) -> TextEntitiesMessageAttribute {
var result: [MessageTextEntity] = []
if let entities = entities {
for entity in entities {
switch entity {
case let .messageEntityMention(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Mention))
case let .messageEntityHashtag(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Hashtag))
case let .messageEntityBotCommand(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BotCommand))
case let .messageEntityUrl(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Url))
case let .messageEntityEmail(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Email))
case let .messageEntityBold(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Bold))
case let .messageEntityItalic(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Italic))
case let .messageEntityCode(offset, length):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Code))
case let .messageEntityPre(offset, length, _):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Pre))
case let .messageEntityTextUrl(offset, length, url):
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .TextUrl(url: url)))
case .messageEntityUnknown:
break
}
}
}
return TextEntitiesMessageAttribute(entities: result)
}
private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi73.DecryptedMessage, file: SecretChatFileReference?, messageIdForGloballyUniqueMessageId: (Int64) -> MessageId?) -> (StoreMessage, [(MediaResource, Data)])? {
switch apiMessage {
case let .decryptedMessage(_, randomId, ttl, message, media, entities, viaBotName, replyToRandomId, groupedId):
var text = message
var parsedMedia: [Media] = []
var attributes: [MessageAttribute] = []
var resources: [(MediaResource, Data)] = []
if ttl > 0 {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttl, countdownBeginTime: nil))
}
attributes.append(parseEntities(entities))
if let viaBotName = viaBotName, !viaBotName.isEmpty {
attributes.append(InlineBotMessageAttribute(peerId: nil, title: viaBotName))
}
if let media = media {
switch media {
case let .decryptedMessageMediaPhoto(thumb, thumbW, thumbH, w, h, size, key, iv, caption):
if !caption.isEmpty {
text = caption
}
if let file = file {
var representations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource))
resources.append((resource, thumb.makeData()))
}
representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size)))
let image = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudSecretImage, id: file.id), representations: representations, reference: nil)
parsedMedia.append(image)
}
case let .decryptedMessageMediaAudio(duration, mimeType, size, key, iv):
if let file = file {
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: [TelegramMediaFileAttribute.Audio(isVoice: true, duration: Int(duration), title: nil, performer: nil, waveform: nil)])
parsedMedia.append(fileMedia)
}
case let .decryptedMessageMediaDocument(thumb, thumbW, thumbH, mimeType, size, key, iv, attributes, caption):
if !caption.isEmpty {
text = caption
}
if let file = file {
var parsedAttributes: [TelegramMediaFileAttribute] = []
for attribute in attributes {
if let parsedAttribute = TelegramMediaFileAttribute(attribute) {
parsedAttributes.append(parsedAttribute)
}
}
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource))
resources.append((resource, thumb.makeData()))
}
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
parsedMedia.append(fileMedia)
}
case let .decryptedMessageMediaVideo(thumb, thumbW, thumbH, duration, mimeType, w, h, size, key, iv, caption):
if !caption.isEmpty {
text = caption
}
if let file = file {
let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: []), .FileName(fileName: "video.mov")]
var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64())
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource))
resources.append((resource, thumb.makeData()))
}
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
parsedMedia.append(fileMedia)
}
case let .decryptedMessageMediaExternalDocument(id, accessHash, date, mimeType, size, thumb, dcId, attributes):
var parsedAttributes: [TelegramMediaFileAttribute] = []
for attribute in attributes {
if let parsedAttribute = TelegramMediaFileAttribute(attribute) {
parsedAttributes.append(parsedAttribute)
}
}
var previewRepresentations: [TelegramMediaImageRepresentation] = []
switch thumb {
case let .photoSize(_, location, w, h, size):
switch location {
case let .fileLocation(dcId, volumeId, localId, secret):
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size))))
case .fileLocationUnavailable:
break
}
case let .photoCachedSize(_, location, w, h, bytes):
if bytes.size > 0 {
switch location {
case let .fileLocation(dcId, volumeId, localId, secret):
let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size)
resources.append((resource, bytes.makeData()))
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource))
case .fileLocationUnavailable:
break
}
}
default:
break
}
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
parsedMedia.append(fileMedia)
case let .decryptedMessageMediaWebPage(url):
parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url)))
case let .decryptedMessageMediaGeoPoint(lat, long):
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil))
case let .decryptedMessageMediaContact(phoneNumber, firstName, lastName, userId):
parsedMedia.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)))
case let .decryptedMessageMediaVenue(lat, long, title, address, provider, venueId):
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: nil), liveBroadcastingTimeout: nil))
case .decryptedMessageMediaEmpty:
break
}
}
var groupingKey: Int64?
if let groupedId = groupedId {
inner: for media in parsedMedia {
if let _ = media as? TelegramMediaImage {
groupingKey = groupedId
break inner
} else if let _ = media as? TelegramMediaFile {
groupingKey = groupedId
break inner
}
}
}
if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) {
attributes.append(ReplyMessageAttribute(messageId: replyMessageId))
}
var entitiesAttribute: TextEntitiesMessageAttribute?
for attribute in attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
entitiesAttribute = attribute
break
}
}
let (tags, globalTags) = tagsForStoreMessage(incoming: true, attributes: attributes, media: parsedMedia, textEntities: entitiesAttribute?.entities)
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources)
case let .decryptedMessageService(randomId, action):
switch action {
case .decryptedMessageActionDeleteMessages:
return nil
case .decryptedMessageActionFlushHistory:
return nil
case .decryptedMessageActionNotifyLayer:
return nil
case .decryptedMessageActionReadMessages:
return nil
case .decryptedMessageActionScreenshotMessages:
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), [])
case let .decryptedMessageActionSetMessageTTL(ttlSeconds):
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), [])
case .decryptedMessageActionResend:
return nil
case .decryptedMessageActionRequestKey:
return nil
case .decryptedMessageActionAcceptKey:
return nil
case .decryptedMessageActionCommitKey:
return nil
case .decryptedMessageActionAbortKey:
return nil
case .decryptedMessageActionNoop:
return nil
}
}
}

View File

@ -19,7 +19,10 @@ func processSecretChatIncomingEncryptedOperations(modifier: Modifier, peerId: Pe
modifier.operationLogEnumerateEntries(peerId: peerId, tag: OperationLogTags.SecretIncomingEncrypted, { entry in
if let operation = entry.contents as? SecretChatIncomingEncryptedOperation {
if let key = updatedState.keychain.key(fingerprint: operation.keyFingerprint) {
withDecryptedMessageContents(key: key, data: operation.contents, { decryptedContents in
var decryptedContents = withDecryptedMessageContents(parameters: SecretChatEncryptionParameters(key: key, mode: .v2(role: updatedState.role)), data: operation.contents)
if decryptedContents == nil {
decryptedContents = withDecryptedMessageContents(parameters: SecretChatEncryptionParameters(key: key, mode: .v1), data: operation.contents)
}
if let decryptedContents = decryptedContents {
withExtendedLifetime(decryptedContents, {
let buffer = BufferReader(Buffer(bufferNoCopy: decryptedContents))
@ -88,7 +91,8 @@ func processSecretChatIncomingEncryptedOperations(modifier: Modifier, peerId: Pe
guard let sequenceInfo = sequenceInfo else {
throw MessagePreParsingError.protocolViolation
}
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: parsedLayer, locallyRequestedLayer: nil, remotelyRequestedLayer: nil), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: secretChatCommonSupportedLayer(remoteLayer: parsedLayer), locallyRequestedLayer: nil, remotelyRequestedLayer: nil), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceBasedLayerState))
modifier.setPeerChatState(peerId, state: updatedState)
entryTagLocalIndex = .manual(sequenceBasedLayerState.baseIncomingOperationIndex + sequenceInfo.operationIndex)
@ -126,7 +130,6 @@ func processSecretChatIncomingEncryptedOperations(modifier: Modifier, peerId: Pe
Logger.shared.log("SecretChat", "peerId \(peerId) couldn't decrypt message content")
removeTagLocalIndices.append(entry.tagLocalIndex)
}
})
} else {
Logger.shared.log("SecretChat", "peerId \(peerId) key \(operation.keyFingerprint) doesn't exist")
}

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,9 @@ import Foundation
import MtProtoKitDynamic
#endif
private func messageKey(key: SecretChatKey, msgKey: UnsafeRawPointer) -> (aesKey: Data, aesIv: Data) {
private func messageKey(key: SecretChatKey, msgKey: UnsafeRawPointer, mode: SecretChatEncryptionMode) -> (aesKey: Data, aesIv: Data) {
switch mode {
case .v1:
let x: Int = 0
var sha1AData = Data()
@ -74,25 +76,58 @@ private func messageKey(key: SecretChatKey, msgKey: UnsafeRawPointer) -> (aesKey
}
}
return (aesKey, aesIv)
case let .v2(role):
var xValue: Int
switch role {
case .creator:
xValue = 0
case .participant:
xValue = 8
}
func withDecryptedMessageContents(key: SecretChatKey, data: MemoryBuffer, _ f: (MemoryBuffer?) -> Void) {
assert(key.key.length == 256)
var sha256_a_data = Data()
sha256_a_data.append(msgKey.assumingMemoryBound(to: UInt8.self), count: 16)
sha256_a_data.append(key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: xValue), count: 36)
let sha256_a = MTSha256(sha256_a_data)!
var sha256_b_data = Data()
sha256_b_data.append(key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: 40 + xValue), count: 36)
sha256_b_data.append(msgKey.assumingMemoryBound(to: UInt8.self), count: 16)
let sha256_b = MTSha256(sha256_b_data)!
var aesKey = Data()
aesKey.append(sha256_a.subdata(in: 0 ..< (0 + 8)))
aesKey.append(sha256_b.subdata(in: 8 ..< (8 + 16)))
aesKey.append(sha256_a.subdata(in: 24 ..< (24 + 8)))
var aesIv = Data()
aesIv.append(sha256_b.subdata(in: 0 ..< (0 + 8)))
aesIv.append(sha256_a.subdata(in: 8 ..< (8 + 16)))
aesIv.append(sha256_b.subdata(in: 24 ..< (24 + 8)))
return (aesKey, aesIv)
}
}
func withDecryptedMessageContents(parameters: SecretChatEncryptionParameters, data: MemoryBuffer) -> MemoryBuffer? {
assert(parameters.key.key.length == 256)
if data.length < 4 + 16 + 16 {
f(nil)
return
return nil
}
let msgKey = data.memory.advanced(by: 8)
let (aesKey, aesIv) = messageKey(key: key, msgKey: msgKey)
switch parameters.mode {
case .v1:
let (aesKey, aesIv) = messageKey(key: parameters.key, msgKey: msgKey, mode: parameters.mode)
let decryptedData = MTAesDecrypt(Data(bytes: data.memory.advanced(by: 8 + 16), count: data.length - (8 + 16)), aesKey, aesIv)!
if decryptedData.count < 4 * 3 {
f(nil)
return
return nil
}
var payloadLength: Int32 = 0
@ -102,8 +137,7 @@ func withDecryptedMessageContents(key: SecretChatKey, data: MemoryBuffer, _ f: (
let paddingLength = decryptedData.count - (Int(payloadLength) + 4)
if Int(payloadLength) > decryptedData.count - 4 || paddingLength > 16 {
f(nil)
return
return nil
}
let calculatedMsgKeyData = MTSubdataSha1(decryptedData, 0, UInt(payloadLength) + 4)!
@ -112,16 +146,85 @@ func withDecryptedMessageContents(key: SecretChatKey, data: MemoryBuffer, _ f: (
}
if !msgKeyMatches {
f(nil)
return
return nil
}
let result = decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
return Data(bytes: bytes.advanced(by: 4), count: Int(payloadLength))
}
return MemoryBuffer(data: result)
case let .v2(role):
let senderRole: SecretChatRole
switch role {
case .creator:
senderRole = .participant
case .participant:
senderRole = .creator
}
let (aesKey, aesIv) = messageKey(key: parameters.key, msgKey: msgKey, mode: .v2(role: senderRole))
let decryptedData = MTAesDecrypt(Data(bytes: data.memory.advanced(by: 8 + 16), count: data.length - (8 + 16)), aesKey, aesIv)!
if decryptedData.count < 4 * 3 {
return nil
}
var payloadLength: Int32 = 0
decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
f(MemoryBuffer(memory: UnsafeMutablePointer(mutating: bytes.advanced(by: 4)), capacity: Int(payloadLength), length: Int(payloadLength), freeWhenDone: false))
memcpy(&payloadLength, bytes, 4)
}
let paddingLength = decryptedData.count - (Int(payloadLength) + 4)
let xValue: Int
switch role {
case .creator:
xValue = 8
case .participant:
xValue = 0
}
var keyLargeData = Data()
keyLargeData.append(parameters.key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: 88 + xValue), count: 32)
keyLargeData.append(decryptedData)
let keyLarge = MTSha256(keyLargeData)!
let localMessageKey = keyLarge.subdata(in: 8 ..< (8 + 16))
let msgKeyData = Data(bytes: msgKey.assumingMemoryBound(to: UInt8.self), count: 16)
if Int(payloadLength) <= 0 || Int(payloadLength) > decryptedData.count - 4 || paddingLength < 12 || paddingLength > 1024 {
if localMessageKey != msgKeyData {
Logger.shared.log("SecretChatEncryption", "message key doesn't match (length check)")
}
return nil
}
if localMessageKey != msgKeyData {
Logger.shared.log("SecretChatEncryption", "message key doesn't match")
return nil
}
let result = decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
return Data(bytes: bytes.advanced(by: 4), count: Int(payloadLength))
}
return MemoryBuffer(data: result)
}
}
func encryptedMessageContents(key: SecretChatKey, data: MemoryBuffer) -> Data {
enum SecretChatEncryptionMode {
case v1
case v2(role: SecretChatRole)
}
struct SecretChatEncryptionParameters {
let key: SecretChatKey
let mode: SecretChatEncryptionMode
}
func encryptedMessageContents(parameters: SecretChatEncryptionParameters, data: MemoryBuffer) -> Data {
var payloadLength: Int32 = Int32(data.length)
var payloadData = Data()
withUnsafeBytes(of: &payloadLength, { bytes -> Void in
@ -129,6 +232,8 @@ func encryptedMessageContents(key: SecretChatKey, data: MemoryBuffer) -> Data {
})
payloadData.append(data.memory.assumingMemoryBound(to: UInt8.self), count: data.length)
switch parameters.mode {
case .v1:
var msgKey = MTSha1(payloadData)!
msgKey.replaceSubrange(0 ..< (msgKey.count - 16), with: Data())
@ -146,12 +251,70 @@ func encryptedMessageContents(key: SecretChatKey, data: MemoryBuffer) -> Data {
}
let (aesKey, aesIv) = msgKey.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> (Data, Data) in
return messageKey(key: key, msgKey: bytes)
return messageKey(key: parameters.key, msgKey: bytes, mode: parameters.mode)
}
let encryptedData = MTAesEncrypt(payloadData, aesKey, aesIv)!
var encryptedPayload = Data()
var keyFingerprint: Int64 = key.fingerprint
var keyFingerprint: Int64 = parameters.key.fingerprint
withUnsafeBytes(of: &keyFingerprint, { bytes -> Void in
encryptedPayload.append(bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), count: 8)
})
encryptedPayload.append(msgKey)
encryptedPayload.append(encryptedData)
return encryptedPayload
case let .v2(role):
var randomBytes = Data(count: 128)
randomBytes.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Void in
arc4random_buf(bytes, randomBytes.count)
}
var decryptedData = payloadData
var take = 0
while take < 12 {
decryptedData.append(randomBytes.subdata(in: take ..< (take + 1)))
take += 1
}
while decryptedData.count % 16 != 0 {
decryptedData.append(randomBytes.subdata(in: take ..< (take + 1)))
take += 1
}
var remainingCount = Int(arc4random_uniform(UInt32(72 + 1 - take)))
while remainingCount % 16 != 0 {
remainingCount -= 1
}
for _ in 0 ..< remainingCount {
decryptedData.append(randomBytes.subdata(in: take ..< (take + 1)))
take += 1
}
var xValue: Int
switch role {
case .creator:
xValue = 0
case .participant:
xValue = 8
}
var keyData = Data()
keyData.append(parameters.key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: 88 + xValue), count: 32)
keyData.append(decryptedData)
let keyLarge = MTSha256(keyData)!
let msgKey = keyLarge.subdata(in: 8 ..< (8 + 16))
let (aesKey, aesIv) = msgKey.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> (Data, Data) in
return messageKey(key: parameters.key, msgKey: bytes, mode: parameters.mode)
}
let encryptedData = MTAesEncrypt(decryptedData, aesKey, aesIv)!
var encryptedPayload = Data()
var keyFingerprint: Int64 = parameters.key.fingerprint
withUnsafeBytes(of: &keyFingerprint, { bytes -> Void in
encryptedPayload.append(bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), count: 8)
})
@ -159,3 +322,4 @@ func encryptedMessageContents(key: SecretChatKey, data: MemoryBuffer) -> Data {
encryptedPayload.append(encryptedData)
return encryptedPayload
}
}

View File

@ -0,0 +1,63 @@
import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
#else
import Postbox
import SwiftSignalKit
#endif
private let topSupportedLayer: SecretChatSequenceBasedLayer = .layer73
func secretChatCommonSupportedLayer(remoteLayer: Int32) -> SecretChatSequenceBasedLayer {
switch remoteLayer {
case 46:
return .layer46
case 73:
return .layer73
default:
return topSupportedLayer
}
}
func secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(modifier: Modifier, peerId: PeerId, state: SecretChatState) -> SecretChatState {
switch state.embeddedState {
case .basicLayer:
var updatedState = state
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer8, actionGloballyUniqueId: arc4random64(), layerSupport: topSupportedLayer.rawValue), state: updatedState)
return updatedState
case let .sequenceBasedLayer(sequenceState):
var updatedState = state
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: sequenceState.layerNegotiationState.activeLayer.secretChatLayer, actionGloballyUniqueId: arc4random64(), layerSupport: topSupportedLayer.rawValue), state: updatedState)
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedLayerNegotiationState(sequenceState.layerNegotiationState.withUpdatedLocallyRequestedLayer(topSupportedLayer.rawValue))))
return updatedState
default:
return state
}
}
func secretChatCheckLayerNegotiationIfNeeded(modifier: Modifier, peerId: PeerId, state: SecretChatState) -> SecretChatState {
switch state.embeddedState {
case let .sequenceBasedLayer(sequenceState):
if sequenceState.layerNegotiationState.activeLayer != topSupportedLayer {
var updatedState = state
if let remotelyRequestedLayer = sequenceState.layerNegotiationState.remotelyRequestedLayer {
let updatedSequenceState = sequenceState.withUpdatedLayerNegotiationState(sequenceState.layerNegotiationState.withUpdatedActiveLayer(secretChatCommonSupportedLayer(remoteLayer: remotelyRequestedLayer)))
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(updatedSequenceState))
}
if (sequenceState.layerNegotiationState.locallyRequestedLayer ?? 0) < topSupportedLayer.rawValue {
updatedState = secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(modifier: modifier, peerId: peerId, state: updatedState)
}
return updatedState
} else {
return state
}
case .basicLayer:
return state
default:
return state
}
}

View File

@ -78,6 +78,16 @@ struct SecretChatOutgoingFile: PostboxCoding {
enum SecretChatSequenceBasedLayer: Int32 {
case layer46 = 46
case layer73 = 73
var secretChatLayer: SecretChatLayer {
switch self {
case .layer46:
return .layer46
case .layer73:
return .layer73
}
}
}
private enum SecretChatOutgoingOperationValue: Int32 {

View File

@ -23,7 +23,7 @@ func secretChatInitiateRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId,
let _ = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
let a = MemoryBuffer(memory: aBytes, capacity: 256, length: 256, freeWhenDone: true)
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsRequestKey(layer: .layer46, actionGloballyUniqueId: arc4random64(), rekeySessionId: sessionId, a: a), mutable: true, delivered: false))
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsRequestKey(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: sessionId, a: a), mutable: true, delivered: false))
return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(SecretChatRekeySessionState(id: sessionId, data: .requesting))))
}
default:
@ -69,7 +69,7 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
assert(remoteKeyFingerprint == keyFingerprint)
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsCommitKey(layer: .layer46, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, keyFingerprint: keyFingerprint), mutable: true, delivered: false))
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsCommitKey(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, keyFingerprint: keyFingerprint), mutable: true, delivered: false))
let keyValidityOperationIndex = modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing)
let keyValidityOperationCanonicalIndex = sequenceState.canonicalOutgoingOperationIndex(keyValidityOperationIndex)
@ -92,7 +92,7 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
return SecretChatKey(fingerprint: keyFingerprint, key: key, validity: .sequenceBasedIndexRange(fromCanonicalIndex: keyValidityOperationCanonicalIndex), useCount: 0)
}))
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .noop(layer: .layer46, actionGloballyUniqueId: arc4random64()), mutable: true, delivered: false))
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .noop(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64()), mutable: true, delivered: false))
return updatedState
} else {
@ -107,7 +107,7 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
switch rekeySession.data {
case .requesting, .requested:
if rekeySessionId < rekeySession.id {
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAbortSession(layer: .layer46, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id), mutable: true, delivered: false))
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAbortSession(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id), mutable: true, delivered: false))
} else {
acceptSession = false
}
@ -117,14 +117,13 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
}
if acceptSession {
let sessionId = arc4random64()
let bBytes = malloc(256)!
let _ = SecRandomCopyBytes(nil, 256, bBytes.assumingMemoryBound(to: UInt8.self))
let b = MemoryBuffer(memory: bBytes, capacity: 256, length: 256, freeWhenDone: true)
let rekeySession = SecretChatRekeySessionState(id: rekeySessionId, data: .accepting)
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAcceptKey(layer: .layer46, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, gA: gA, b: b), mutable: true, delivered: false))
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAcceptKey(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, gA: gA, b: b), mutable: true, delivered: false))
return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(rekeySession)))
}
}

View File

@ -13,6 +13,7 @@ public enum SecretChatRole: Int32 {
enum SecretChatLayer: Int32 {
case layer8 = 8
case layer46 = 46
case layer73 = 73
}
public struct SecretChatKeySha1Fingerprint: PostboxCoding, Equatable {
@ -240,24 +241,24 @@ enum SecretChatHandshakeState: PostboxCoding, Equatable {
}
struct SecretChatLayerNegotiationState: PostboxCoding, Equatable {
let activeLayer: Int32
let activeLayer: SecretChatSequenceBasedLayer
let locallyRequestedLayer: Int32?
let remotelyRequestedLayer: Int32?
init(activeLayer: Int32, locallyRequestedLayer: Int32?, remotelyRequestedLayer: Int32?) {
init(activeLayer: SecretChatSequenceBasedLayer, locallyRequestedLayer: Int32?, remotelyRequestedLayer: Int32?) {
self.activeLayer = activeLayer
self.locallyRequestedLayer = locallyRequestedLayer
self.remotelyRequestedLayer = remotelyRequestedLayer
}
init(decoder: PostboxDecoder) {
self.activeLayer = decoder.decodeInt32ForKey("a", orElse: 0)
self.activeLayer = SecretChatSequenceBasedLayer(rawValue: decoder.decodeInt32ForKey("a", orElse: 0)) ?? .layer46
self.locallyRequestedLayer = decoder.decodeOptionalInt32ForKey("lr")
self.remotelyRequestedLayer = decoder.decodeOptionalInt32ForKey("rr")
}
func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.activeLayer, forKey: "a")
encoder.encodeInt32(self.activeLayer.rawValue, forKey: "a")
if let locallyRequestedLayer = self.locallyRequestedLayer {
encoder.encodeInt32(locallyRequestedLayer, forKey: "lr")
} else {
@ -283,6 +284,14 @@ struct SecretChatLayerNegotiationState: PostboxCoding, Equatable {
return true
}
func withUpdatedActiveLayer(_ activeLayer: SecretChatSequenceBasedLayer) -> SecretChatLayerNegotiationState {
return SecretChatLayerNegotiationState(activeLayer: activeLayer, locallyRequestedLayer: self.locallyRequestedLayer, remotelyRequestedLayer: self.remotelyRequestedLayer)
}
func withUpdatedLocallyRequestedLayer(_ locallyRequestedLayer: Int32?) -> SecretChatLayerNegotiationState {
return SecretChatLayerNegotiationState(activeLayer: self.activeLayer, locallyRequestedLayer: locallyRequestedLayer, remotelyRequestedLayer: self.remotelyRequestedLayer)
}
func withUpdatedRemotelyRequestedLayer(_ remotelyRequestedLayer: Int32?) -> SecretChatLayerNegotiationState {
return SecretChatLayerNegotiationState(activeLayer: self.activeLayer, locallyRequestedLayer: self.locallyRequestedLayer, remotelyRequestedLayer: remotelyRequestedLayer)
}

View File

@ -1,29 +0,0 @@
import Foundation
#if os(macOS)
import PostboxMac
#else
import Postbox
#endif
public enum SecretChatBridgeRole: Int32 {
case creator
case participant
}
public struct SecretChatStateBridge {
public let role:SecretChatBridgeRole
public init(role: SecretChatBridgeRole) {
self.role = role
}
public var state: PeerChatState {
return SecretChatState(role: SecretChatRole(rawValue: role.rawValue)!, embeddedState: .terminated, keychain: SecretChatKeychain(keys: []), keyFingerprint: nil, messageAutoremoveTimeout: nil)
}
}
public func terminateLegacySecretChat(modifier: Modifier, peerId: PeerId, state: SecretChatStateBridge) -> PeerChatState {
return addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: SecretChatOutgoingOperationContents.terminate(reportSpam: false), state: state.state as! SecretChatState).withUpdatedEmbeddedState(.terminated)
}

View File

@ -90,16 +90,11 @@ public class BoxedMessage: NSObject {
#else
redact = true
#endif
return recursiveDescription(redact: redact, of: self.body)
/*if let body = self.body as? RedactingCustomStringConvertible {
return body.redactingDescription(false)
#else
return body.redactingDescription(true)
#endif
if let body = self.body as? RedactingCustomStringConvertible {
return body.redactingDescription(redact)
} else {
return "\(self.body)"
}*/
return recursiveDescription(redact: redact, of: self.body)
}
}
}
}

View File

@ -299,7 +299,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
return (TelegramMediaExpiredContent(data: .file), nil)
}
case let .messageMediaWebPage(webpage):
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage) {
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) {
return (mediaWebpage, nil)
}
case .messageMediaUnsupported:
@ -475,7 +475,7 @@ extension StoreMessage {
}
if let viaBotId = viaBotId {
attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId)))
attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId), title: nil))
}
if let replyToMsgId = replyToMsgId {

View File

@ -191,7 +191,7 @@ public func ==(lhs: TelegramMediaWebpageLoadedContent, rhs: TelegramMediaWebpage
}
public enum TelegramMediaWebpageContent {
case Pending(Int32)
case Pending(Int32, String?)
case Loaded(TelegramMediaWebpageLoadedContent)
}
@ -213,7 +213,7 @@ public final class TelegramMediaWebpage: Media, Equatable {
self.webpageId = MediaId(decoder.decodeBytesForKeyNoCopy("i")!)
if decoder.decodeInt32ForKey("ct", orElse: 0) == 0 {
self.content = .Pending(decoder.decodeInt32ForKey("pendingDate", orElse: 0))
self.content = .Pending(decoder.decodeInt32ForKey("pendingDate", orElse: 0), decoder.decodeOptionalStringForKey("pendingUrl"))
} else {
self.content = .Loaded(TelegramMediaWebpageLoadedContent(decoder: decoder))
}
@ -225,9 +225,14 @@ public final class TelegramMediaWebpage: Media, Equatable {
encoder.encodeBytes(buffer, forKey: "i")
switch self.content {
case let .Pending(date):
case let .Pending(date, url):
encoder.encodeInt32(0, forKey: "ct")
encoder.encodeInt32(date, forKey: "pendingDate")
if let url = url {
encoder.encodeString(url, forKey: "pendingUrl")
} else {
encoder.encodeNil(forKey: "pendingUrl")
}
case let .Loaded(loadedContent):
encoder.encodeInt32(1, forKey: "ct")
loadedContent.encode(encoder)
@ -251,10 +256,14 @@ public final class TelegramMediaWebpage: Media, Equatable {
}
switch lhs.content {
case let .Pending(lhsDate):
case let .Pending(lhsDate, lhsUrl):
switch rhs.content {
case let .Pending(rhsDate) where lhsDate == rhsDate:
case let .Pending(rhsDate, rhsUrl):
if lhsDate == rhsDate, lhsUrl == rhsUrl {
return true
} else {
return false
}
default:
return false
}
@ -269,12 +278,12 @@ public final class TelegramMediaWebpage: Media, Equatable {
}
}
func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage) -> TelegramMediaWebpage? {
func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) -> TelegramMediaWebpage? {
switch webpage {
case .webPageNotModified:
return nil
case let .webPagePending(id, date):
return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date))
return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date, url))
case let .webPage(_, id, url, displayUrl, hash, type, siteName, title, description, photo, embedUrl, embedType, embedWidth, embedHeight, duration, author, document, cachedPage):
var embedSize: CGSize?
if let embedWidth = embedWidth, let embedHeight = embedHeight {

View File

@ -45,18 +45,8 @@ func updateSecretChat(accountPeerId: PeerId, modifier: Modifier, chat: Api.Encry
var updatedState = currentState.withUpdatedKeychain(SecretChatKeychain(keys: [SecretChatKey(fingerprint: keyFingerprint, key: MemoryBuffer(data: key), validity: .indefinite, useCount: 0)])).withUpdatedEmbeddedState(.basicLayer).withUpdatedKeyFingerprint(SecretChatKeyFingerprint(sha1: SecretChatKeySha1Fingerprint(digest: sha1Digest(key)), sha256: SecretChatKeySha256Fingerprint(digest: sha256Digest(key))))
var layer: SecretChatLayer?
switch updatedState.embeddedState {
case .terminated, .handshake:
break
case .basicLayer:
layer = .layer8
case let .sequenceBasedLayer(sequenceState):
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
}
if let layer = layer {
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: currentPeer.id, operation: .reportLayerSupport(layer: layer, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
}
updatedState = secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(modifier: modifier, peerId: currentPeer.id, state: updatedState)
modifier.setPeerChatState(currentPeer.id, state: updatedState)
updatePeers(modifier: modifier, peers: [currentPeer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in
return updated

View File

@ -21,7 +21,7 @@ public func webpagePreview(account: Account, url: String, webpageId: MediaId? =
|> mapToSignal { result -> Signal<TelegramMediaWebpage?, NoError> in
switch result {
case let .messageMediaWebPage(webpage):
if let media = telegramMediaWebpageFromApiWebpage(webpage) {
if let media = telegramMediaWebpageFromApiWebpage(webpage, url: url) {
if case .Loaded = media.content {
return .single(media)
} else {
@ -45,9 +45,9 @@ public func actualizedWebpage(postbox: Postbox, network: Network, webpage: Teleg
return .single(.webPageNotModified)
}
|> mapToSignal { result -> Signal<TelegramMediaWebpage, NoError> in
if let updatedWebpage = telegramMediaWebpageFromApiWebpage(result), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId {
if let updatedWebpage = telegramMediaWebpageFromApiWebpage(result, url: nil), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId {
return postbox.modify { modifier -> TelegramMediaWebpage in
modifier.updateMedia(updatedWebpage.webpageId, update: updatedWebpage)
modifier.updateMedia(webpage.webpageId, update: updatedWebpage)
return updatedWebpage
}
} else {