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

View File

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

View File

@ -44,32 +44,66 @@ 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) let messageIds = modifier.applyOutgoingReadMaxIndex(index)
if index.id.peerId.namespace == Namespaces.Peer.SecretChat { if index.id.peerId.namespace == Namespaces.Peer.SecretChat {
for id in messageIds { for id in messageIds {
if let message = modifier.getMessage(id), !message.flags.contains(.Incoming) { applySecretOutgoingMessageReadActions(modifier: modifier, id: id, beginCountdownAt: timestamp)
for attribute in message.attributes { }
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { }
if (attribute.countdownBeginTime == nil || attribute.countdownBeginTime == 0) && !message.containsSecretMedia { }
modifier.updateMessage(message.id, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo? func maybeReadSecretOutgoingMessage(modifier: Modifier, index: MessageIndex) {
if let forwardInfo = currentMessage.forwardInfo { guard index.id.peerId.namespace == Namespaces.Peer.SecretChat else {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature) 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 {
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)
}
let updatedAttributes = currentMessage.attributes.map({ currentAttribute -> MessageAttribute in
if let currentAttribute = currentAttribute as? AutoremoveTimeoutMessageAttribute {
return AutoremoveTimeoutMessageAttribute(timeout: currentAttribute.timeout, countdownBeginTime: timestamp)
} else {
return currentAttribute
} }
let updatedAttributes = currentMessage.attributes.map({ currentAttribute -> MessageAttribute in
if let currentAttribute = currentAttribute as? AutoremoveTimeoutMessageAttribute {
return AutoremoveTimeoutMessageAttribute(timeout: currentAttribute.timeout, countdownBeginTime: timestamp)
} else {
return currentAttribute
}
})
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media))
}) })
modifier.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id) return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media))
} })
break modifier.addTimestampBasedMessageAttribute(tag: 0, timestamp: timestamp + attribute.timeout, messageId: id)
} }
break
} }
} }
} }

View File

@ -55,6 +55,10 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
var sentStickers: [TelegramMediaFile] = [] var sentStickers: [TelegramMediaFile] = []
var sentGifs: [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 modifier.updateMessage(message.id, update: { currentMessage in
let updatedId: MessageId let updatedId: MessageId
if let messageId = 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)) 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 { for file in sentStickers {
modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20) 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? 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 { for (message, _, updatedMessage) in mapping {
modifier.updateMessage(message.id, update: { currentMessage in modifier.updateMessage(message.id, update: { currentMessage in
let updatedId: MessageId let updatedId: MessageId
@ -259,10 +264,6 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
modifier.updateMessageGroupingKeysAtomically(mapping.map { $0.1.id }, groupingKey: updatedGroupingKey) 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 { for file in sentStickers {
modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20) 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 canSaveCredentials: Bool
public let passwordMissing: Bool public let passwordMissing: Bool
public let invoice: BotPaymentInvoice public let invoice: BotPaymentInvoice
public let providerId: Int32 public let providerId: PeerId
public let url: String public let url: String
public let nativeProvider: BotPaymentNativeProvider? public let nativeProvider: BotPaymentNativeProvider?
public let savedInfo: BotPaymentRequestedInfo? public let savedInfo: BotPaymentRequestedInfo?
@ -229,7 +229,7 @@ public func fetchBotPaymentForm(postbox: Postbox, network: Network, messageId: M
parsedSavedCredentials = .card(id: id, title: title) 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 } } |> mapError { _ -> BotPaymentFormRequestError in return .generic }
} }

View File

@ -9,12 +9,47 @@ import Foundation
import SwiftSignalKit import SwiftSignalKit
#endif #endif
public enum CallSessionError { public enum CallSessionError: Equatable {
case generic case generic
case privacyRestricted case privacyRestricted
case notSupportedByPeer case notSupportedByPeer
case serverProvided(String) case serverProvided(String)
case disconnected 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 { public enum CallSessionEndedType {
@ -23,14 +58,31 @@ public enum CallSessionEndedType {
case missed case missed
} }
public enum CallSessionTerminationReason { public enum CallSessionTerminationReason: Equatable {
case ended(CallSessionEndedType) case ended(CallSessionEndedType)
case error(CallSessionError) 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 { public struct ReportCallRating {
public let id:Int64 public let id: Int64
public let accessHash:Int64 public let accessHash: Int64
} }
enum CallSessionInternalState { enum CallSessionInternalState {
@ -127,6 +179,8 @@ private final class CallSessionContext {
var state: CallSessionInternalState var state: CallSessionInternalState
let subscribers = Bag<(CallSession) -> Void>() let subscribers = Bag<(CallSession) -> Void>()
let acknowledgeIncomingCallDisposable = MetaDisposable()
var isEmpty: Bool { var isEmpty: Bool {
if case .terminated = self.state { if case .terminated = self.state {
return self.subscribers.isEmpty return self.subscribers.isEmpty
@ -140,6 +194,10 @@ private final class CallSessionContext {
self.isOutgoing = isOutgoing self.isOutgoing = isOutgoing
self.state = state self.state = state
} }
deinit {
self.acknowledgeIncomingCallDisposable.dispose()
}
} }
private final class CallSessionManagerContext { private final class CallSessionManagerContext {
@ -252,7 +310,9 @@ private final class CallSessionManagerContext {
if randomStatus == 0 { if randomStatus == 0 {
let internalId = CallSessionInternalId() 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.contextIdByStableId[stableId] = internalId
self.contextUpdated(internalId: internalId) self.contextUpdated(internalId: internalId)
self.ringingStatesUpdated() 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: case .basicLayer:
layer = .layer8 layer = .layer8
case let .sequenceBasedLayer(sequenceState): case let .sequenceBasedLayer(sequenceState):
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer) layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
} }
if let layer = layer { if let layer = layer {
var globallyUniqueIds: [Int64] = [] var globallyUniqueIds: [Int64] = []
@ -84,7 +84,7 @@ public func clearHistoryInteractively(postbox: Postbox, peerId: PeerId) -> Signa
case .basicLayer: case .basicLayer:
layer = .layer8 layer = .layer8
case let .sequenceBasedLayer(sequenceState): case let .sequenceBasedLayer(sequenceState):
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer) layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
} }
if let layer = layer { 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 #endif
public class InlineBotMessageAttribute: MessageAttribute { public class InlineBotMessageAttribute: MessageAttribute {
public let peerId: PeerId public let peerId: PeerId?
public let title: String?
public var associatedPeerIds: [PeerId] { 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.peerId = peerId
self.title = title
} }
required public init(decoder: PostboxDecoder) { 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) { 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: case .basicLayer:
layer = .layer8 layer = .layer8
case let .sequenceBasedLayer(sequenceState): case let .sequenceBasedLayer(sequenceState):
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer) layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
} }
if let layer = layer { if let layer = layer {
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: layer, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState) 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 { private enum BoxedDecryptedMessage {
case layer8(SecretApi8.DecryptedMessage) case layer8(SecretApi8.DecryptedMessage)
case layer46(SecretApi46.DecryptedMessage) case layer46(SecretApi46.DecryptedMessage)
case layer73(SecretApi73.DecryptedMessage)
func serialize(_ buffer: Buffer, role: SecretChatRole, sequenceInfo: SecretChatOperationSequenceInfo?) { func serialize(_ buffer: Buffer, role: SecretChatRole, sequenceInfo: SecretChatOperationSequenceInfo?) {
switch self { switch self {
@ -350,6 +351,26 @@ private enum BoxedDecryptedMessage {
assertionFailure() 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) let _ = message.serialize(buffer, true)
} }
} }
@ -450,11 +471,96 @@ private func decryptedAttributes46(_ attributes: [TelegramMediaFileAttribute]) -
return result return result
} }
private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, uploadedFile: SecretChatOutgoingFile?, layer: SecretChatLayer) -> BoxedDecryptedMessage { private func decryptedAttributes73(_ attributes: [TelegramMediaFileAttribute]) -> [SecretApi73.DocumentAttribute] {
var media: Media? = message.media.first 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 messageAutoremoveTimeout: Int32 = 0
var replyGlobalId:Int64? = nil var replyGlobalId: Int64? = nil
var flags:Int32 = 0 var flags: Int32 = 0
for attribute in message.attributes { for attribute in message.attributes {
if let attribute = attribute as? ReplyMessageAttribute { if let attribute = attribute as? ReplyMessageAttribute {
if let message = message.associatedMessages[attribute.messageId] { if let message = message.associatedMessages[attribute.messageId] {
@ -465,30 +571,83 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
} }
} }
var viaBotName: String?
var entities: [MessageTextEntity]?
for attribute in message.attributes { for attribute in message.attributes {
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
messageAutoremoveTimeout = attribute.timeout 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 media = media {
if let image = media as? TelegramMediaImage, let uploadedFile = uploadedFile, let largestRepresentation = largestImageRepresentation(image.representations) { 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 { switch layer {
case .layer8: case .layer8:
let randomBytesData = malloc(15)! let randomBytesData = malloc(15)!
arc4random_buf(randomBytesData, 15) arc4random_buf(randomBytesData, 15)
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true) 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)) return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: decryptedMedia))
case .layer46: 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) 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 { } 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 { switch layer {
case .layer8: case .layer8:
if let uploadedFile = uploadedFile { if let uploadedFile = uploadedFile {
@ -496,7 +655,7 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
arc4random_buf(randomBytesData, 15) arc4random_buf(randomBytesData, 15)
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true) 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)) return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: decryptedMedia))
} }
@ -517,18 +676,99 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
if let voiceDuration = voiceDuration { 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)) 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 { } 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 { } else {
if let resource = file.resource as? CloudDocumentMediaResource, let size = file.size { 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 decryptedMedia = decryptedMedia {
if let _ = viaBotName {
flags |= (1 << 11)
}
flags |= (1 << 9) 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)) return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: .decryptedMessageMediaEmpty))
case .layer46: 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))) return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
case .layer46: case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds))) 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): case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds):
switch layer { switch layer {
@ -568,6 +822,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds))) return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
case .layer46: case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds))) 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): case let .clearHistory(layer, actionGloballyUniqueId):
switch layer { switch layer {
@ -578,11 +834,15 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionFlushHistory)) return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionFlushHistory))
case .layer46: case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionFlushHistory)) return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionFlushHistory))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionFlushHistory))
} }
case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo): case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo):
switch layer { switch layer {
case .layer46: case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionResend(startSeqNo: fromSeqNo, endSeqNo: toSeqNo))) 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): case let .reportLayerSupport(layer, actionGloballyUniqueId, layerSupport):
switch layer { switch layer {
@ -594,31 +854,43 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionNotifyLayer(layer: layerSupport))) return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
case .layer46: case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNotifyLayer(layer: layerSupport))) 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): case let .pfsRequestKey(layer, actionGloballyUniqueId, rekeySessionId, gA):
switch layer { switch layer {
case .layer46: case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionRequestKey(exchangeId: rekeySessionId, gA: Buffer(buffer: gA)))) 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): case let .pfsAcceptKey(layer, actionGloballyUniqueId, rekeySessionId, gB, keyFingerprint):
switch layer { switch layer {
case .layer46: case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAcceptKey(exchangeId: rekeySessionId, gB: Buffer(buffer: gB), keyFingerprint: keyFingerprint))) 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): case let .pfsAbortSession(layer, actionGloballyUniqueId, rekeySessionId):
switch layer { switch layer {
case .layer46: case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAbortKey(exchangeId: rekeySessionId))) 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): case let .pfsCommitKey(layer, actionGloballyUniqueId, rekeySessionId, keyFingerprint):
switch layer { switch layer {
case .layer46: case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionCommitKey(exchangeId: rekeySessionId, keyFingerprint: keyFingerprint))) 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): case let .noop(layer, actionGloballyUniqueId):
switch layer { switch layer {
case .layer46: case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNoop)) return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNoop))
case .layer73:
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNoop))
} }
case let .readMessageContents(layer, actionGloballyUniqueId, globallyUniqueIds): case let .readMessageContents(layer, actionGloballyUniqueId, globallyUniqueIds):
switch layer { switch layer {
@ -630,6 +902,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds))) return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
case .layer46: case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds))) 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, _): case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, _):
switch layer { switch layer {
@ -641,6 +915,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout))) return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
case .layer46: case .layer46:
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout))) 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: case .basicLayer:
layer = .layer8 layer = .layer8
case let .sequenceBasedLayer(sequenceState): case let .sequenceBasedLayer(sequenceState):
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer) layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
} }
} }
if let layer = layer { if let layer = layer {
@ -699,50 +975,108 @@ 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> { 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<Void, NoError> in return postbox.modify { modifier -> Signal<[MediaId: Data], 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) {
if let message = modifier.getMessage(messageId), let globallyUniqueId = message.globallyUniqueId { return messageWithThumbnailData(mediaBox: postbox.mediaBox, message: message)
let decryptedMessage = boxedDecryptedMessage(message: message, globallyUniqueId: globallyUniqueId, uploadedFile: file, layer: layer) } else {
return sendBoxedDecryptedMessage(postbox: postbox, network: network, peer: peer, state: state, operationIndex: tagLocalIndex, decryptedMessage: decryptedMessage, globallyUniqueId: globallyUniqueId, file: file, asService: wasDelivered, wasDelivered: wasDelivered) return .single([:])
|> mapToSignal { result in }
return postbox.modify { modifier -> Void in }
if result == nil { |> switchToLatest
replaceOutgoingOperationWithEmptyMessage(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, globallyUniqueId: globallyUniqueId) |> mapToSignal { thumbnailData -> Signal<Void, NoError> in
} else { return postbox.modify { modifier -> Signal<Void, NoError> in
markOutgoingOperationAsCompleted(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil) 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 {
modifier.updateMessage(message.id, update: { currentMessage in let decryptedMessage = boxedDecryptedMessage(modifier: modifier, message: message, globallyUniqueId: globallyUniqueId, uploadedFile: file, thumbnailData: thumbnailData, layer: layer)
var flags = StoreMessageFlags(currentMessage.flags) 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
if result == nil {
replaceOutgoingOperationWithEmptyMessage(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, globallyUniqueId: globallyUniqueId)
} else {
markOutgoingOperationAsCompleted(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil)
}
var timestamp = message.timestamp var timestamp = message.timestamp
if let result = result { if let result = result {
switch result { switch result {
case let .sentEncryptedMessage(date): case let .sentEncryptedMessage(date):
timestamp = date timestamp = date
case let .sentEncryptedFile(date, file): case let .sentEncryptedFile(date, _):
timestamp = date timestamp = date
} }
flags.remove(.Unsent)
flags.remove(.Sending)
} else {
flags = [.Failed]
} }
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo { modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([messageId]), timestamp: timestamp)
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
} modifier.updateMessage(message.id, update: { currentMessage in
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)) var flags = StoreMessageFlags(currentMessage.flags)
}) if let _ = result {
} flags.remove(.Unsent)
flags.remove(.Sending)
} else {
flags = [.Failed]
}
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))
})
maybeReadSecretOutgoingMessage(modifier: modifier, index: MessageIndex(id: message.id, timestamp: timestamp))
}
}
} else {
replaceOutgoingOperationWithEmptyMessage(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, globallyUniqueId: arc4random64())
modifier.deleteMessages([messageId])
//assertionFailure()
return .complete()
} }
} else { } else {
assertionFailure() return .complete()
return .never()
} }
} else { } |> switchToLatest
return .complete() }
}
} |> switchToLatest
} }
private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId: PeerId, action: SecretMessageAction, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal<Void, NoError> { private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId: PeerId, action: SecretMessageAction, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal<Void, NoError> {
@ -758,6 +1092,7 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId
markOutgoingOperationAsCompleted(modifier: modifier, peerId: peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil) markOutgoingOperationAsCompleted(modifier: modifier, peerId: peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil)
} }
if let messageId = action.messageId { if let messageId = action.messageId {
var resultTimestamp: Int32?
modifier.updateMessage(messageId, update: { currentMessage in modifier.updateMessage(messageId, update: { currentMessage in
var flags = StoreMessageFlags(currentMessage.flags) var flags = StoreMessageFlags(currentMessage.flags)
var timestamp = currentMessage.timestamp var timestamp = currentMessage.timestamp
@ -773,12 +1108,17 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId
} else { } else {
flags = [.Failed] flags = [.Failed]
} }
resultTimestamp = timestamp
var storeForwardInfo: StoreMessageForwardInfo? var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo { if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature) 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)) 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> { 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() let payload = Buffer()
var sequenceInfo: SecretChatOperationSequenceInfo? 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 { switch state.embeddedState {
case .terminated, .handshake: case .terminated, .handshake:
break break
case .basicLayer: case .basicLayer:
maybeKey = state.keychain.indefinitelyValidKey() if let key = state.keychain.indefinitelyValidKey() {
maybeParameters = SecretChatEncryptionParameters(key: key, mode: mode)
}
case let .sequenceBasedLayer(sequenceState): case let .sequenceBasedLayer(sequenceState):
let topReceivedOperationIndex: Int32 let topReceivedOperationIndex: Int32
if let topProcessedCanonicalIncomingOperationIndex = sequenceState.topProcessedCanonicalIncomingOperationIndex { if let topProcessedCanonicalIncomingOperationIndex = sequenceState.topProcessedCanonicalIncomingOperationIndex {
@ -805,18 +1156,20 @@ private func sendBoxedDecryptedMessage(postbox: Postbox, network: Network, peer:
topReceivedOperationIndex = -1 topReceivedOperationIndex = -1
} }
let canonicalOperationIndex = sequenceState.canonicalOutgoingOperationIndex(operationIndex) let canonicalOperationIndex = sequenceState.canonicalOutgoingOperationIndex(operationIndex)
maybeKey = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: canonicalOperationIndex) if let key = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: canonicalOperationIndex) {
Logger.shared.log("SecretChat", "sending message with index \(canonicalOperationIndex) key \(String(describing: maybeKey?.fingerprint))") 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) sequenceInfo = SecretChatOperationSequenceInfo(topReceivedOperationIndex: topReceivedOperationIndex, operationIndex: canonicalOperationIndex)
} }
guard let key = maybeKey else { guard let parameters = maybeParameters else {
Logger.shared.log("SecretChat", "no valid key found") Logger.shared.log("SecretChat", "no valid key found")
return .single(nil) return .single(nil)
} }
decryptedMessage.serialize(payload, role: state.role, sequenceInfo: sequenceInfo) 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 sendMessage: Signal<Api.messages.SentEncryptedMessage, MTRpcError>
let inputPeer = Api.InputEncryptedChat.inputEncryptedChat(chatId: peer.id.id, accessHash: peer.accessHash) 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: case .basicLayer:
layer = .layer8 layer = .layer8
case let .sequenceBasedLayer(sequenceState): 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 CloudSecretFile: Int32 = 10
public static let CloudGame: Int32 = 11 public static let CloudGame: Int32 = 11
public static let CloudInvoice: Int32 = 12 public static let CloudInvoice: Int32 = 12
public static let LocalWebpage: Int32 = 13
} }
public struct Peer { public struct Peer {
@ -132,6 +133,7 @@ private enum PreferencesKeyValues: Int32 {
case proxySettings = 5 case proxySettings = 5
case loggingSettings = 6 case loggingSettings = 6
case coreSettings = 7 case coreSettings = 7
case contentPrivacySettings = 8
} }
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
@ -188,4 +190,10 @@ public struct PreferencesKeys {
key.setInt32(0, value: PreferencesKeyValues.coreSettings.rawValue) key.setInt32(0, value: PreferencesKeyValues.coreSettings.rawValue)
return key 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)) 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? { public func outgoingMessageWithChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult) -> EnqueueMessage? {
var attributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = []
attributes.append(OutgoingChatContextResultMessageAttribute(queryId: results.queryId, id: result.id)) 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 { switch result.message {
case let .auto(caption, entities, replyMarkup): case let .auto(caption, entities, replyMarkup):

View File

@ -509,9 +509,11 @@ public final class PendingMessageManager {
messages.sort { MessageIndex($0.0) < MessageIndex($1.0) } messages.sort { MessageIndex($0.0) < MessageIndex($1.0) }
if peerId.namespace == Namespaces.Peer.SecretChat { 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) { } else if let peer = modifier.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
var isForward = false var isForward = false
var replyMessageId: Int32? var replyMessageId: Int32?
@ -637,68 +639,41 @@ public final class PendingMessageManager {
} |> switchToLatest } |> switchToLatest
} }
private func sendMessageContent(network: Network, postbox: Postbox, stateManager: AccountStateManager, messageId: MessageId, content: PendingMessageUploadedContent) -> Signal<Void, NoError> { private static func sendSecretMessageContent(modifier: Modifier, message: Message, content: PendingMessageUploadedContent) {
return postbox.modify { [weak self] modifier -> Signal<Void, NoError> in var secretFile: SecretChatOutgoingFile?
guard let message = modifier.getMessage(messageId) else { switch content {
return .complete() case let .secretMedia(file, size, key):
if let fileReference = SecretChatOutgoingFileReference(file) {
secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key)
}
default:
break
}
var layer: SecretChatLayer?
let state = modifier.getPeerChatState(message.id.peerId) as? SecretChatState
if let state = state {
switch state.embeddedState {
case .terminated, .handshake:
break
case .basicLayer:
layer = .layer8
case let .sequenceBasedLayer(sequenceState):
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
} }
}
if messageId.peerId.namespace == Namespaces.Peer.SecretChat {
var secretFile: SecretChatOutgoingFile? if let state = state, let layer = layer {
switch content { var sentAsAction = false
case let .secretMedia(file, size, key): for media in message.media {
if let fileReference = SecretChatOutgoingFileReference(file) { if let media = media as? TelegramMediaAction {
secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key) if case let .messageAutoremoveTimeoutUpdated(value) = media.action {
} sentAsAction = true
default: let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: message.id.peerId, operation: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: message.globallyUniqueId!, timeout: value, messageId: message.id), state: state)
break
}
var layer: SecretChatLayer?
let state = modifier.getPeerChatState(messageId.peerId) as? SecretChatState
if let state = state {
switch state.embeddedState {
case .terminated, .handshake:
break
case .basicLayer:
layer = .layer8
case let .sequenceBasedLayer(sequenceState):
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
}
}
if let state = state, let layer = layer {
var sentAsAction = false
for media in message.media {
if let media = media as? TelegramMediaAction {
if case let .messageAutoremoveTimeoutUpdated(value) = media.action {
sentAsAction = true
let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: message.id.peerId, operation: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: message.globallyUniqueId!, timeout: value, messageId: message.id), state: state)
if updatedState != state {
modifier.setPeerChatState(message.id.peerId, state: updatedState)
}
modifier.updateMessage(message.id, update: { currentMessage in
var flags = StoreMessageFlags(message.flags)
if !flags.contains(.Failed) {
flags.insert(.Sending)
}
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: currentMessage.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))
})
}
break
}
}
if !sentAsAction {
let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: messageId.peerId, operation: .sendMessage(layer: layer, id: messageId, file: secretFile), state: state)
if updatedState != 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) var flags = StoreMessageFlags(message.flags)
if !flags.contains(.Failed) { if !flags.contains(.Failed) {
flags.insert(.Sending) flags.insert(.Sending)
@ -710,15 +685,46 @@ public final class PendingMessageManager {
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.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)) return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.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))
}) })
} }
} else { break
modifier.updateMessage(messageId, 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)
}
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))
})
} }
}
if !sentAsAction {
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(message.id.peerId, state: updatedState)
}
modifier.updateMessage(message.id, update: { currentMessage in
var flags = StoreMessageFlags(message.flags)
if !flags.contains(.Failed) {
flags.insert(.Sending)
}
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: currentMessage.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))
})
}
} else {
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)
}
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() return .complete()
} else if let peer = modifier.getPeer(messageId.peerId), let inputPeer = apiInputPeer(peer) { } else if let peer = modifier.getPeer(messageId.peerId), let inputPeer = apiInputPeer(peer) {
var uniqueId: Int64 = 0 var uniqueId: Int64 = 0

View File

@ -40,7 +40,9 @@ func messageContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods
var autoremoveAttribute: AutoremoveTimeoutMessageAttribute? var autoremoveAttribute: AutoremoveTimeoutMessageAttribute?
for attribute in attributes { for attribute in attributes {
if let attribute = attribute as? OutgoingChatContextResultMessageAttribute { if let attribute = attribute as? OutgoingChatContextResultMessageAttribute {
contextResult = attribute if peerId.namespace != Namespaces.Peer.SecretChat {
contextResult = attribute
}
} else if let attribute = attribute as? AutoremoveTimeoutMessageAttribute { } else if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
autoremoveAttribute = attribute autoremoveAttribute = attribute
} }
@ -105,7 +107,11 @@ private enum PredownloadedResource {
case none 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 return Signal<Signal<PredownloadedResource, PendingMessageUploadError>, PendingMessageUploadError> { subscriber in
let data = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)).start(next: { data in let data = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)).start(next: { data in
if data.complete { if data.complete {
@ -154,7 +160,11 @@ private func maybePredownloadedImageResource(postbox: Postbox, resource: MediaRe
} |> switchToLatest } |> 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) return auxiliaryMethods.fetchResourceMediaReferenceHash(resource)
|> mapToSignal { hash -> Signal<PredownloadedResource, NoError> in |> mapToSignal { hash -> Signal<PredownloadedResource, NoError> in
if let hash = hash { 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> { private func uploadedMediaImageContent(network: Network, postbox: Postbox, peerId: PeerId, image: TelegramMediaImage, text: String, autoremoveAttribute: AutoremoveTimeoutMessageAttribute?) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
if let largestRepresentation = largestImageRepresentation(image.representations) { 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 return predownloadedResource
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in |> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
var referenceKey: CachedSentMediaReferenceKey? var referenceKey: CachedSentMediaReferenceKey?
@ -324,9 +334,14 @@ private enum UploadedMediaTransform {
case done(Media?) case done(Media?)
} }
private enum UploadedMediaThumbnailResult {
case file(Api.InputFile)
case none
}
private enum UploadedMediaThumbnail { private enum UploadedMediaThumbnail {
case pending case pending
case done(Api.InputFile?) case done(UploadedMediaThumbnailResult)
} }
private func uploadedThumbnail(network: Network, postbox: Postbox, image: TelegramMediaImageRepresentation) -> Signal<Api.InputFile?, PendingMessageUploadError> { 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> { 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? var referenceKey: CachedSentMediaReferenceKey?
switch result { switch result {
case let .media(media): case let .media(media):
@ -433,27 +448,40 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
return .single(.pending) return .single(.pending)
case let .done(media): case let .done(media):
if let media = media as? TelegramMediaFile, let smallestThumbnail = smallestImageRepresentation(media.previewRepresentations) { if let media = media as? TelegramMediaFile, let smallestThumbnail = smallestImageRepresentation(media.previewRepresentations) {
return uploadedThumbnail(network: network, postbox: postbox, image: smallestThumbnail) if peerId.namespace == Namespaces.Peer.SecretChat {
|> mapError { _ -> PendingMessageUploadError in return .generic } return .single(.done(.none))
|> map { result in } else {
return .done(result) return uploadedThumbnail(network: network, postbox: postbox, image: smallestThumbnail)
} |> mapError { _ -> PendingMessageUploadError in return .generic }
|> map { result in
if let result = result {
return .done(.file(result))
} else {
return .done(.none)
}
}
}
} else { } else {
return .single(.done(nil)) return .single(.done(.none))
} }
} }
}) })
return combineLatest(upload, thumbnail) return combineLatest(upload, thumbnail)
|> mapToSignal { content, media -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in |> mapToSignal { content, thumbnailResult -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
switch content { switch content {
case let .progress(progress): case let .progress(progress):
return .single(.progress(progress)) return .single(.progress(progress))
case let .inputFile(inputFile): case let .inputFile(inputFile):
if case let .done(thumbnail) = media { if case let .done(thumbnail) = thumbnailResult {
var flags: Int32 = 0 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 flags |= 1 << 2
} }
@ -465,10 +493,8 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
} }
} }
//flags |= 1 << 3
if ttlSeconds != nil { 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 return postbox.modify { modifier -> Api.InputPeer? in
@ -477,7 +503,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|> mapError { _ -> PendingMessageUploadError in return .generic } |> mapError { _ -> PendingMessageUploadError in return .generic }
|> mapToSignal { inputPeer -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in |> mapToSignal { inputPeer -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
if let inputPeer = inputPeer { 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 } |> mapError { _ -> PendingMessageUploadError in return .generic }
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in |> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
switch result { switch result {
@ -498,7 +524,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
return .complete() return .complete()
} }
case let .inputSecretFile(file, size, key): case let .inputSecretFile(file, size, key):
if case .done = media { if case .done = thumbnailResult {
return .single(.content(.secretMedia(file, size, key))) return .single(.content(.secretMedia(file, size, key)))
} else { } else {
return .complete() 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 { if let parsedObject = SecretApi46.parse(Buffer(bufferNoCopy: operation.contents)), let apiMessage = parsedObject as? SecretApi46.DecryptedMessage {
return SecretChatServiceAction(apiMessage) 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 return nil
} }
@ -134,6 +138,18 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier:
} else { } else {
throw MessageParsingError.contentParsingError 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 { switch updatedState.embeddedState {
@ -195,10 +211,14 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier:
case .handshake: case .handshake:
throw MessageParsingError.invalidChatState throw MessageParsingError.invalidChatState
case .basicLayer: case .basicLayer:
if layerSupport >= 46 { if layerSupport >= 73 {
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) 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 = 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 { } else {
throw MessageParsingError.contentParsingError throw MessageParsingError.contentParsingError
} }
@ -206,7 +226,8 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier:
if sequenceState.layerNegotiationState.remotelyRequestedLayer != layerSupport { if sequenceState.layerNegotiationState.remotelyRequestedLayer != layerSupport {
let updatedNegotiationState = sequenceState.layerNegotiationState.withUpdatedRemotelyRequestedLayer(layerSupport) let updatedNegotiationState = sequenceState.layerNegotiationState.withUpdatedRemotelyRequestedLayer(layerSupport)
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedLayerNegotiationState(updatedNegotiationState))) 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): 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 { extension StoreMessage {
convenience init?(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi8.DecryptedMessage, file: SecretChatFileReference?) { convenience init?(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi8.DecryptedMessage, file: SecretChatFileReference?) {
switch apiMessage { switch apiMessage {
@ -431,16 +488,83 @@ extension TelegramMediaFileAttribute {
self = .Sticker(displayText: alt, packReference: packReference, maskData: nil) self = .Sticker(displayText: alt, packReference: packReference, maskData: nil)
case let .documentAttributeVideo(duration, w, h): case let .documentAttributeVideo(duration, w, h):
self = .Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: []) 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)])? { private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi46.DecryptedMessage, file: SecretChatFileReference?, messageIdForGloballyUniqueMessageId: (Int64) -> MessageId?) -> (StoreMessage, [(MediaResource, Data)])? {
switch apiMessage { 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 text = message
var parsedMedia: [Media] = [] var parsedMedia: [Media] = []
var attributes: [MessageAttribute] = [] 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(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 { if let media = media {
switch media { switch media {
case let .decryptedMessageMediaPhoto(thumb, thumbW, thumbH, w, h, size, key, iv, caption): 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 text = caption
} }
if let file = file { 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] = [] var previewRepresentations: [TelegramMediaImageRepresentation] = []
if thumb.size != 0 { if thumb.size != 0 {
let resource = LocalFileMediaResource(fileId: arc4random64()) let resource = LocalFileMediaResource(fileId: arc4random64())
@ -507,16 +637,47 @@ 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) 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) 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] = [] var parsedAttributes: [TelegramMediaFileAttribute] = []
for attribute in attributes { for attribute in attributes {
if let parsedAttribute = TelegramMediaFileAttribute(attribute) { if let parsedAttribute = TelegramMediaFileAttribute(attribute) {
parsedAttributes.append(parsedAttribute) 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) 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) parsedMedia.append(fileMedia)
default: 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 break
} }
} }
@ -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,114 +19,117 @@ func processSecretChatIncomingEncryptedOperations(modifier: Modifier, peerId: Pe
modifier.operationLogEnumerateEntries(peerId: peerId, tag: OperationLogTags.SecretIncomingEncrypted, { entry in modifier.operationLogEnumerateEntries(peerId: peerId, tag: OperationLogTags.SecretIncomingEncrypted, { entry in
if let operation = entry.contents as? SecretChatIncomingEncryptedOperation { if let operation = entry.contents as? SecretChatIncomingEncryptedOperation {
if let key = updatedState.keychain.key(fingerprint: operation.keyFingerprint) { 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 let decryptedContents = decryptedContents { if decryptedContents == nil {
withExtendedLifetime(decryptedContents, { decryptedContents = withDecryptedMessageContents(parameters: SecretChatEncryptionParameters(key: key, mode: .v1), data: operation.contents)
let buffer = BufferReader(Buffer(bufferNoCopy: decryptedContents)) }
if let decryptedContents = decryptedContents {
withExtendedLifetime(decryptedContents, {
let buffer = BufferReader(Buffer(bufferNoCopy: decryptedContents))
do {
guard let topLevelSignature = buffer.readInt32() else {
throw MessagePreParsingError.malformedData
}
let parsedLayer: Int32
let sequenceInfo: SecretChatOperationSequenceInfo?
do { if topLevelSignature == 0x1be31789 {
guard let topLevelSignature = buffer.readInt32() else { guard let _ = parseBytes(buffer) else {
throw MessagePreParsingError.malformedData
}
let parsedLayer: Int32
let sequenceInfo: SecretChatOperationSequenceInfo?
if topLevelSignature == 0x1be31789 {
guard let _ = parseBytes(buffer) else {
throw MessagePreParsingError.malformedData
}
guard let layerValue = buffer.readInt32() else {
throw MessagePreParsingError.malformedData
}
guard let seqInValue = buffer.readInt32() else {
throw MessagePreParsingError.malformedData
}
guard let seqOutValue = buffer.readInt32() else {
throw MessagePreParsingError.malformedData
}
switch updatedState.role {
case .creator:
if seqOutValue < 0 || (seqInValue >= 0 && (seqInValue & 1) == 0) || (seqOutValue & 1) != 0 {
throw MessagePreParsingError.protocolViolation
}
case .participant:
if seqOutValue < 0 || (seqInValue >= 0 && (seqInValue & 1) != 0) || (seqOutValue & 1) == 0 {
throw MessagePreParsingError.protocolViolation
}
}
sequenceInfo = SecretChatOperationSequenceInfo(topReceivedOperationIndex: seqInValue / 2, operationIndex: seqOutValue / 2)
if layerValue == 17 {
parsedLayer = 46
} else {
parsedLayer = layerValue
}
} else {
parsedLayer = 8
sequenceInfo = nil
buffer.reset()
}
guard let messageContents = buffer.readBuffer(decryptedContents.length - Int(buffer.offset)) else {
throw MessagePreParsingError.malformedData throw MessagePreParsingError.malformedData
} }
let entryTagLocalIndex: StorePeerOperationLogEntryTagLocalIndex guard let layerValue = buffer.readInt32() else {
throw MessagePreParsingError.malformedData
}
switch updatedState.embeddedState { guard let seqInValue = buffer.readInt32() else {
case .terminated: throw MessagePreParsingError.malformedData
throw MessagePreParsingError.invalidChatState }
case .handshake:
throw MessagePreParsingError.invalidChatState guard let seqOutValue = buffer.readInt32() else {
case .basicLayer: throw MessagePreParsingError.malformedData
if parsedLayer >= 46 { }
guard let sequenceInfo = sequenceInfo else {
throw MessagePreParsingError.protocolViolation switch updatedState.role {
} case .creator:
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) if seqOutValue < 0 || (seqInValue >= 0 && (seqInValue & 1) == 0) || (seqOutValue & 1) != 0 {
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceBasedLayerState)) throw MessagePreParsingError.protocolViolation
modifier.setPeerChatState(peerId, state: updatedState) }
entryTagLocalIndex = .manual(sequenceBasedLayerState.baseIncomingOperationIndex + sequenceInfo.operationIndex) case .participant:
} else { if seqOutValue < 0 || (seqInValue >= 0 && (seqInValue & 1) != 0) || (seqOutValue & 1) == 0 {
if parsedLayer != 8 && parsedLayer != 17 {
throw MessagePreParsingError.protocolViolation
}
entryTagLocalIndex = .automatic
}
case let .sequenceBasedLayer(sequenceState):
if parsedLayer < 46 {
throw MessagePreParsingError.protocolViolation throw MessagePreParsingError.protocolViolation
} }
entryTagLocalIndex = .manual(sequenceState.baseIncomingOperationIndex + sequenceInfo!.operationIndex)
} }
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretIncomingDecrypted, tagLocalIndex: entryTagLocalIndex, tagMergedIndex: .none, contents: SecretChatIncomingDecryptedOperation(timestamp: operation.timestamp, layer: parsedLayer, sequenceInfo: sequenceInfo, contents: MemoryBuffer(messageContents), file: operation.mediaFileReference)) sequenceInfo = SecretChatOperationSequenceInfo(topReceivedOperationIndex: seqInValue / 2, operationIndex: seqOutValue / 2)
addedDecryptedOperations = true
} catch let error { if layerValue == 17 {
if let error = error as? MessagePreParsingError { parsedLayer = 46
switch error { } else {
case .invalidChatState: parsedLayer = layerValue
break
case .malformedData, .protocolViolation:
break
}
} }
Logger.shared.log("SecretChat", "peerId \(peerId) malformed data after decryption") } else {
parsedLayer = 8
sequenceInfo = nil
buffer.reset()
} }
removeTagLocalIndices.append(entry.tagLocalIndex) guard let messageContents = buffer.readBuffer(decryptedContents.length - Int(buffer.offset)) else {
}) throw MessagePreParsingError.malformedData
} else { }
Logger.shared.log("SecretChat", "peerId \(peerId) couldn't decrypt message content")
let entryTagLocalIndex: StorePeerOperationLogEntryTagLocalIndex
switch updatedState.embeddedState {
case .terminated:
throw MessagePreParsingError.invalidChatState
case .handshake:
throw MessagePreParsingError.invalidChatState
case .basicLayer:
if parsedLayer >= 46 {
guard let sequenceInfo = sequenceInfo else {
throw MessagePreParsingError.protocolViolation
}
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)
} else {
if parsedLayer != 8 && parsedLayer != 17 {
throw MessagePreParsingError.protocolViolation
}
entryTagLocalIndex = .automatic
}
case let .sequenceBasedLayer(sequenceState):
if parsedLayer < 46 {
throw MessagePreParsingError.protocolViolation
}
entryTagLocalIndex = .manual(sequenceState.baseIncomingOperationIndex + sequenceInfo!.operationIndex)
}
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretIncomingDecrypted, tagLocalIndex: entryTagLocalIndex, tagMergedIndex: .none, contents: SecretChatIncomingDecryptedOperation(timestamp: operation.timestamp, layer: parsedLayer, sequenceInfo: sequenceInfo, contents: MemoryBuffer(messageContents), file: operation.mediaFileReference))
addedDecryptedOperations = true
} catch let error {
if let error = error as? MessagePreParsingError {
switch error {
case .invalidChatState:
break
case .malformedData, .protocolViolation:
break
}
}
Logger.shared.log("SecretChat", "peerId \(peerId) malformed data after decryption")
}
removeTagLocalIndices.append(entry.tagLocalIndex) removeTagLocalIndices.append(entry.tagLocalIndex)
} })
}) } else {
Logger.shared.log("SecretChat", "peerId \(peerId) couldn't decrypt message content")
removeTagLocalIndices.append(entry.tagLocalIndex)
}
} else { } else {
Logger.shared.log("SecretChat", "peerId \(peerId) key \(operation.keyFingerprint) doesn't exist") 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,121 +7,224 @@ import Foundation
import MtProtoKitDynamic import MtProtoKitDynamic
#endif #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) {
let x: Int = 0 switch mode {
case .v1:
var sha1AData = Data() let x: Int = 0
sha1AData.count = 16 + 32
sha1AData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in var sha1AData = Data()
memcpy(bytes, msgKey, 16) sha1AData.count = 16 + 32
memcpy(bytes.advanced(by: 16), key.key.memory.advanced(by: x), 32) sha1AData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
memcpy(bytes, msgKey, 16)
memcpy(bytes.advanced(by: 16), key.key.memory.advanced(by: x), 32)
}
let sha1A = MTSha1(sha1AData)!
var sha1BData = Data()
sha1BData.count = 16 + 16 + 16
sha1BData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
memcpy(bytes, key.key.memory.advanced(by: 32 + x), 16)
memcpy(bytes.advanced(by: 16), msgKey, 16)
memcpy(bytes.advanced(by: 16 + 16), key.key.memory.advanced(by: 48 + x), 16)
}
let sha1B = MTSha1(sha1BData)!
var sha1CData = Data()
sha1CData.count = 32 + 16
sha1CData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
memcpy(bytes, key.key.memory.advanced(by: 64 + x), 32)
memcpy(bytes.advanced(by: 32), msgKey, 16)
}
let sha1C = MTSha1(sha1CData)!
var sha1DData = Data()
sha1DData.count = 16 + 32
sha1DData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
memcpy(bytes, msgKey, 16)
memcpy(bytes.advanced(by: 16), key.key.memory.advanced(by: 96 + x), 32)
}
let sha1D = MTSha1(sha1DData)!
var aesKey = Data()
aesKey.count = 8 + 12 + 12
aesKey.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
sha1A.withUnsafeBytes { (sha1A: UnsafePointer<UInt8>) -> Void in
memcpy(bytes, sha1A, 8)
}
sha1B.withUnsafeBytes { (sha1B: UnsafePointer<UInt8>) -> Void in
memcpy(bytes.advanced(by: 8), sha1B.advanced(by: 8), 12)
}
sha1C.withUnsafeBytes { (sha1C: UnsafePointer<UInt8>) -> Void in
memcpy(bytes.advanced(by: 8 + 12), sha1C.advanced(by: 4), 12)
}
}
var aesIv = Data()
aesIv.count = 12 + 8 + 4 + 8
aesIv.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
sha1A.withUnsafeBytes { (sha1A: UnsafePointer<UInt8>) -> Void in
memcpy(bytes, sha1A.advanced(by: 8), 12)
}
sha1B.withUnsafeBytes { (sha1B: UnsafePointer<UInt8>) -> Void in
memcpy(bytes.advanced(by: 12), sha1B, 8)
}
sha1C.withUnsafeBytes { (sha1C: UnsafePointer<UInt8>) -> Void in
memcpy(bytes.advanced(by: 12 + 8), sha1C.advanced(by: 16), 4)
}
sha1D.withUnsafeBytes { (sha1D: UnsafePointer<UInt8>) -> Void in
memcpy(bytes.advanced(by: 12 + 8 + 4), sha1D, 8)
}
}
return (aesKey, aesIv)
case let .v2(role):
var xValue: Int
switch role {
case .creator:
xValue = 0
case .participant:
xValue = 8
}
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)
} }
let sha1A = MTSha1(sha1AData)!
var sha1BData = Data()
sha1BData.count = 16 + 16 + 16
sha1BData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
memcpy(bytes, key.key.memory.advanced(by: 32 + x), 16)
memcpy(bytes.advanced(by: 16), msgKey, 16)
memcpy(bytes.advanced(by: 16 + 16), key.key.memory.advanced(by: 48 + x), 16)
}
let sha1B = MTSha1(sha1BData)!
var sha1CData = Data()
sha1CData.count = 32 + 16
sha1CData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
memcpy(bytes, key.key.memory.advanced(by: 64 + x), 32)
memcpy(bytes.advanced(by: 32), msgKey, 16)
}
let sha1C = MTSha1(sha1CData)!
var sha1DData = Data()
sha1DData.count = 16 + 32
sha1DData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
memcpy(bytes, msgKey, 16)
memcpy(bytes.advanced(by: 16), key.key.memory.advanced(by: 96 + x), 32)
}
let sha1D = MTSha1(sha1DData)!
var aesKey = Data()
aesKey.count = 8 + 12 + 12
aesKey.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
sha1A.withUnsafeBytes { (sha1A: UnsafePointer<UInt8>) -> Void in
memcpy(bytes, sha1A, 8)
}
sha1B.withUnsafeBytes { (sha1B: UnsafePointer<UInt8>) -> Void in
memcpy(bytes.advanced(by: 8), sha1B.advanced(by: 8), 12)
}
sha1C.withUnsafeBytes { (sha1C: UnsafePointer<UInt8>) -> Void in
memcpy(bytes.advanced(by: 8 + 12), sha1C.advanced(by: 4), 12)
}
}
var aesIv = Data()
aesIv.count = 12 + 8 + 4 + 8
aesIv.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
sha1A.withUnsafeBytes { (sha1A: UnsafePointer<UInt8>) -> Void in
memcpy(bytes, sha1A.advanced(by: 8), 12)
}
sha1B.withUnsafeBytes { (sha1B: UnsafePointer<UInt8>) -> Void in
memcpy(bytes.advanced(by: 12), sha1B, 8)
}
sha1C.withUnsafeBytes { (sha1C: UnsafePointer<UInt8>) -> Void in
memcpy(bytes.advanced(by: 12 + 8), sha1C.advanced(by: 16), 4)
}
sha1D.withUnsafeBytes { (sha1D: UnsafePointer<UInt8>) -> Void in
memcpy(bytes.advanced(by: 12 + 8 + 4), sha1D, 8)
}
}
return (aesKey, aesIv)
} }
func withDecryptedMessageContents(key: SecretChatKey, data: MemoryBuffer, _ f: (MemoryBuffer?) -> Void) { func withDecryptedMessageContents(parameters: SecretChatEncryptionParameters, data: MemoryBuffer) -> MemoryBuffer? {
assert(key.key.length == 256) assert(parameters.key.key.length == 256)
if data.length < 4 + 16 + 16 { if data.length < 4 + 16 + 16 {
f(nil) return nil
return
} }
let msgKey = data.memory.advanced(by: 8) let msgKey = data.memory.advanced(by: 8)
let (aesKey, aesIv) = messageKey(key: key, msgKey: msgKey) switch parameters.mode {
case .v1:
let decryptedData = MTAesDecrypt(Data(bytes: data.memory.advanced(by: 8 + 16), count: data.length - (8 + 16)), aesKey, aesIv)! let (aesKey, aesIv) = messageKey(key: parameters.key, msgKey: msgKey, mode: parameters.mode)
if decryptedData.count < 4 * 3 { let decryptedData = MTAesDecrypt(Data(bytes: data.memory.advanced(by: 8 + 16), count: data.length - (8 + 16)), aesKey, aesIv)!
f(nil)
return if decryptedData.count < 4 * 3 {
} return nil
}
var payloadLength: Int32 = 0
decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in var payloadLength: Int32 = 0
memcpy(&payloadLength, bytes, 4) decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
} memcpy(&payloadLength, bytes, 4)
}
let paddingLength = decryptedData.count - (Int(payloadLength) + 4)
if Int(payloadLength) > decryptedData.count - 4 || paddingLength > 16 { let paddingLength = decryptedData.count - (Int(payloadLength) + 4)
f(nil) if Int(payloadLength) > decryptedData.count - 4 || paddingLength > 16 {
return return nil
} }
let calculatedMsgKeyData = MTSubdataSha1(decryptedData, 0, UInt(payloadLength) + 4)! let calculatedMsgKeyData = MTSubdataSha1(decryptedData, 0, UInt(payloadLength) + 4)!
let msgKeyMatches = calculatedMsgKeyData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Bool in let msgKeyMatches = calculatedMsgKeyData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Bool in
return memcmp(bytes.advanced(by: calculatedMsgKeyData.count - 16), msgKey, 16) == 0 return memcmp(bytes.advanced(by: calculatedMsgKeyData.count - 16), msgKey, 16) == 0
} }
if !msgKeyMatches { if !msgKeyMatches {
f(nil) return nil
return }
}
let result = decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in return Data(bytes: bytes.advanced(by: 4), count: Int(payloadLength))
f(MemoryBuffer(memory: UnsafeMutablePointer(mutating: bytes.advanced(by: 4)), capacity: Int(payloadLength), length: Int(payloadLength), freeWhenDone: false)) }
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
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 payloadLength: Int32 = Int32(data.length)
var payloadData = Data() var payloadData = Data()
withUnsafeBytes(of: &payloadLength, { bytes -> Void in withUnsafeBytes(of: &payloadLength, { bytes -> Void in
@ -129,33 +232,94 @@ func encryptedMessageContents(key: SecretChatKey, data: MemoryBuffer) -> Data {
}) })
payloadData.append(data.memory.assumingMemoryBound(to: UInt8.self), count: data.length) payloadData.append(data.memory.assumingMemoryBound(to: UInt8.self), count: data.length)
var msgKey = MTSha1(payloadData)! switch parameters.mode {
msgKey.replaceSubrange(0 ..< (msgKey.count - 16), with: Data()) case .v1:
var msgKey = MTSha1(payloadData)!
var randomBuf = malloc(16)! msgKey.replaceSubrange(0 ..< (msgKey.count - 16), with: Data())
defer {
free(randomBuf) var randomBuf = malloc(16)!
defer {
free(randomBuf)
}
let randomBytes = randomBuf.assumingMemoryBound(to: UInt8.self)
arc4random_buf(randomBuf, 16)
var randomIndex = 0
while payloadData.count % 16 != 0 {
payloadData.append(randomBytes.advanced(by: randomIndex), count: 1)
randomIndex += 1
}
let (aesKey, aesIv) = msgKey.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> (Data, Data) in
return messageKey(key: parameters.key, msgKey: bytes, mode: parameters.mode)
}
let encryptedData = MTAesEncrypt(payloadData, 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)
})
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)
})
encryptedPayload.append(msgKey)
encryptedPayload.append(encryptedData)
return encryptedPayload
} }
let randomBytes = randomBuf.assumingMemoryBound(to: UInt8.self)
arc4random_buf(randomBuf, 16)
var randomIndex = 0
while payloadData.count % 16 != 0 {
payloadData.append(randomBytes.advanced(by: randomIndex), count: 1)
randomIndex += 1
}
let (aesKey, aesIv) = msgKey.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> (Data, Data) in
return messageKey(key: key, msgKey: bytes)
}
let encryptedData = MTAesEncrypt(payloadData, aesKey, aesIv)!
var encryptedPayload = Data()
var keyFingerprint: Int64 = 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
} }

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 { enum SecretChatSequenceBasedLayer: Int32 {
case layer46 = 46 case layer46 = 46
case layer73 = 73
var secretChatLayer: SecretChatLayer {
switch self {
case .layer46:
return .layer46
case .layer73:
return .layer73
}
}
} }
private enum SecretChatOutgoingOperationValue: Int32 { 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 _ = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
let a = MemoryBuffer(memory: aBytes, capacity: 256, length: 256, freeWhenDone: true) 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)))) return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(SecretChatRekeySessionState(id: sessionId, data: .requesting))))
} }
default: default:
@ -69,7 +69,7 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
assert(remoteKeyFingerprint == keyFingerprint) 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 keyValidityOperationIndex = modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing)
let keyValidityOperationCanonicalIndex = sequenceState.canonicalOutgoingOperationIndex(keyValidityOperationIndex) 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) 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 return updatedState
} else { } else {
@ -107,7 +107,7 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
switch rekeySession.data { switch rekeySession.data {
case .requesting, .requested: case .requesting, .requested:
if rekeySessionId < rekeySession.id { 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 { } else {
acceptSession = false acceptSession = false
} }
@ -117,14 +117,13 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
} }
if acceptSession { if acceptSession {
let sessionId = arc4random64()
let bBytes = malloc(256)! let bBytes = malloc(256)!
let _ = SecRandomCopyBytes(nil, 256, bBytes.assumingMemoryBound(to: UInt8.self)) let _ = SecRandomCopyBytes(nil, 256, bBytes.assumingMemoryBound(to: UInt8.self))
let b = MemoryBuffer(memory: bBytes, capacity: 256, length: 256, freeWhenDone: true) let b = MemoryBuffer(memory: bBytes, capacity: 256, length: 256, freeWhenDone: true)
let rekeySession = SecretChatRekeySessionState(id: rekeySessionId, data: .accepting) 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))) return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(rekeySession)))
} }
} }

View File

@ -13,6 +13,7 @@ public enum SecretChatRole: Int32 {
enum SecretChatLayer: Int32 { enum SecretChatLayer: Int32 {
case layer8 = 8 case layer8 = 8
case layer46 = 46 case layer46 = 46
case layer73 = 73
} }
public struct SecretChatKeySha1Fingerprint: PostboxCoding, Equatable { public struct SecretChatKeySha1Fingerprint: PostboxCoding, Equatable {
@ -240,24 +241,24 @@ enum SecretChatHandshakeState: PostboxCoding, Equatable {
} }
struct SecretChatLayerNegotiationState: PostboxCoding, Equatable { struct SecretChatLayerNegotiationState: PostboxCoding, Equatable {
let activeLayer: Int32 let activeLayer: SecretChatSequenceBasedLayer
let locallyRequestedLayer: Int32? let locallyRequestedLayer: Int32?
let remotelyRequestedLayer: Int32? let remotelyRequestedLayer: Int32?
init(activeLayer: Int32, locallyRequestedLayer: Int32?, remotelyRequestedLayer: Int32?) { init(activeLayer: SecretChatSequenceBasedLayer, locallyRequestedLayer: Int32?, remotelyRequestedLayer: Int32?) {
self.activeLayer = activeLayer self.activeLayer = activeLayer
self.locallyRequestedLayer = locallyRequestedLayer self.locallyRequestedLayer = locallyRequestedLayer
self.remotelyRequestedLayer = remotelyRequestedLayer self.remotelyRequestedLayer = remotelyRequestedLayer
} }
init(decoder: PostboxDecoder) { 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.locallyRequestedLayer = decoder.decodeOptionalInt32ForKey("lr")
self.remotelyRequestedLayer = decoder.decodeOptionalInt32ForKey("rr") self.remotelyRequestedLayer = decoder.decodeOptionalInt32ForKey("rr")
} }
func encode(_ encoder: PostboxEncoder) { func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.activeLayer, forKey: "a") encoder.encodeInt32(self.activeLayer.rawValue, forKey: "a")
if let locallyRequestedLayer = self.locallyRequestedLayer { if let locallyRequestedLayer = self.locallyRequestedLayer {
encoder.encodeInt32(locallyRequestedLayer, forKey: "lr") encoder.encodeInt32(locallyRequestedLayer, forKey: "lr")
} else { } else {
@ -283,6 +284,14 @@ struct SecretChatLayerNegotiationState: PostboxCoding, Equatable {
return true 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 { func withUpdatedRemotelyRequestedLayer(_ remotelyRequestedLayer: Int32?) -> SecretChatLayerNegotiationState {
return SecretChatLayerNegotiationState(activeLayer: self.activeLayer, locallyRequestedLayer: self.locallyRequestedLayer, remotelyRequestedLayer: remotelyRequestedLayer) 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 #else
redact = true redact = true
#endif #endif
return recursiveDescription(redact: redact, of: self.body) if let body = self.body as? RedactingCustomStringConvertible {
/*if let body = self.body as? RedactingCustomStringConvertible { return body.redactingDescription(redact)
return body.redactingDescription(false)
#else
return body.redactingDescription(true)
#endif
} else { } 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) return (TelegramMediaExpiredContent(data: .file), nil)
} }
case let .messageMediaWebPage(webpage): case let .messageMediaWebPage(webpage):
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage) { if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) {
return (mediaWebpage, nil) return (mediaWebpage, nil)
} }
case .messageMediaUnsupported: case .messageMediaUnsupported:
@ -475,7 +475,7 @@ extension StoreMessage {
} }
if let viaBotId = viaBotId { 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 { if let replyToMsgId = replyToMsgId {

View File

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

View File

@ -44,19 +44,9 @@ 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 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))))
updatedState = secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(modifier: modifier, peerId: currentPeer.id, state: updatedState)
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)
}
modifier.setPeerChatState(currentPeer.id, state: updatedState) modifier.setPeerChatState(currentPeer.id, state: updatedState)
updatePeers(modifier: modifier, peers: [currentPeer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in updatePeers(modifier: modifier, peers: [currentPeer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in
return updated return updated

View File

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