From ae635dbd367f3f71feac9876529d464a997e4760 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 11 Nov 2020 11:50:01 +1100 Subject: [PATCH] Make SignalMessaging work with SignalUtilitiesKit --- Podfile | 30 +- Podfile.lock | 119 +- Pods | 2 +- SessionServiceKit.podspec | 61 - Signal.xcodeproj/project.pbxproj | 433 +- .../Models/OWSContactOffersInteraction.h | 2 +- .../Models/TSUnreadIndicatorInteraction.h | 2 +- SignalMessaging/SignalMessaging-Prefix.pch | 6 +- SignalMessaging/SignalMessaging.h | 2 +- .../ContactShareApprovalViewController.swift | 2 +- .../EditContactShareNameViewController.swift | 1 - .../ViewControllers/MediaMessageView.swift | 2 +- ...ModalActivityIndicatorViewController.swift | 2 +- .../NewNonContactConversationViewController.m | 2 +- .../SelectRecipientViewController.m | 10 +- .../SelectThreadViewController.m | 12 +- .../SharingThreadPickerViewController.m | 8 +- .../ViewControllers/ViewControllerUtils.m | 4 +- .../ViewModels/OWSQuotedReplyModel.h | 2 +- .../ViewModels/OWSQuotedReplyModel.m | 20 +- SignalMessaging/Views/ContactCellView.m | 10 +- SignalMessaging/Views/ContactTableViewCell.m | 2 +- SignalMessaging/Views/ContactsViewHelper.m | 14 +- SignalMessaging/Views/OWSFlatButton.swift | 2 +- SignalMessaging/Views/ThreadViewHelper.m | 12 +- .../appearance/OWSConversationColor.h | 2 +- .../appearance/OWSConversationColor.m | 2 +- SignalMessaging/appearance/Theme.m | 6 +- .../attachments/AttachmentSharing.m | 6 +- .../attachments/SignalAttachment.swift | 2 +- .../categories/NSAttributedString+OWS.m | 2 +- SignalMessaging/categories/UIColor+OWS.m | 2 +- SignalMessaging/categories/UIFont+OWS.m | 11 +- SignalMessaging/categories/UIView+OWS.h | 2 +- SignalMessaging/categories/UIView+OWS.m | 12 +- .../categories/UIViewController+OWS.m | 31 +- SignalMessaging/contacts/OWSContactsManager.h | 4 +- SignalMessaging/contacts/OWSContactsManager.m | 28 +- SignalMessaging/contacts/OWSSyncManager.h | 2 +- SignalMessaging/contacts/OWSSyncManager.m | 32 +- .../contacts/SystemContactsFetcher.swift | 2 +- SignalMessaging/environment/AppSetup.m | 35 +- SignalMessaging/environment/Environment.h | 2 +- SignalMessaging/environment/Environment.m | 4 +- .../environment/NoopCallMessageHandler.swift | 2 +- SignalMessaging/environment/OWSSounds.m | 8 +- .../environment/SignalKeyingStorage.m | 6 +- .../environment/VersionMigrations.m | 18 +- .../migrations/OWSDatabaseMigration.h | 2 +- .../migrations/OWSDatabaseMigration.m | 6 +- .../migrations/OWSDatabaseMigrationRunner.m | 2 +- .../OWSResaveCollectionDBMigration.m | 2 +- SignalMessaging/profiles/OWSProfileManager.h | 2 +- SignalMessaging/profiles/OWSProfileManager.m | 62 +- SignalMessaging/profiles/OWSUserProfile.h | 2 +- SignalMessaging/profiles/OWSUserProfile.m | 20 +- .../profiles/ProfileFetcherJob.swift | 2 - SignalMessaging/utils/BlockListUIUtils.m | 10 +- SignalMessaging/utils/DebugLogger.m | 6 +- .../utils/DeviceSleepManager.swift | 2 +- SignalMessaging/utils/FullTextSearcher.swift | 2 +- SignalMessaging/utils/OWSAudioPlayer.m | 1 - .../utils/OWSContactAvatarBuilder.h | 2 +- .../utils/OWSContactAvatarBuilder.m | 2 +- SignalMessaging/utils/OWSGroupAvatarBuilder.m | 4 +- SignalMessaging/utils/OWSPreferences.m | 18 +- SignalMessaging/utils/ThreadUtil.m | 32 +- SignalMessaging/utils/UIUtil.h | 4 +- SignalMessaging/utils/UIUtil.m | 2 +- SignalServiceKit/.clang-format | 15 - SignalServiceKit/.gitignore | 30 - SignalServiceKit/.travis.yml | 17 - SignalServiceKit/LICENSE | 621 -- .../Certificates/DigiCertGlobalRootG2.crt | Bin 914 -> 0 bytes .../DigiCertSHA2HighAssuranceServerCA.crt | Bin 1205 -> 0 bytes .../Resources/Certificates/GIAG2.crt | Bin 1012 -> 0 bytes .../Resources/Certificates/GSR2.crt | Bin 958 -> 0 bytes .../Resources/Certificates/GSR4.crt | Bin 485 -> 0 bytes .../Resources/Certificates/GTSR1.crt | Bin 1374 -> 0 bytes .../Resources/Certificates/GTSR2.crt | Bin 1374 -> 0 bytes .../Resources/Certificates/GTSR3.crt | Bin 528 -> 0 bytes .../Resources/Certificates/GTSR4.crt | Bin 526 -> 0 bytes .../Resources/Certificates/SFSRootCAG2.crt | Bin 1011 -> 0 bytes .../Resources/Certificates/ias-root.cer | Bin 1359 -> 0 bytes .../Resources/Certificates/textsecure.cer | Bin 1011 -> 0 bytes .../extract_analytics_event_names.py | 373 - SignalServiceKit/protobuf/Fingerprint.proto | 24 - SignalServiceKit/protobuf/Makefile | 38 - SignalServiceKit/protobuf/Provisioning.proto | 38 - SignalServiceKit/protobuf/README.md | 22 - SignalServiceKit/protobuf/SignalIOS.proto | 45 - SignalServiceKit/protobuf/SignalService.proto | 475 -- .../protobuf/WebSocketResources.proto | 49 - .../src/Account/AccountServiceClient.swift | 34 - .../src/Account/CreatePreKeysOperation.swift | 57 - .../src/Account/PreKeyRefreshOperation.swift | 105 - .../Account/RotateSignedKeyOperation.swift | 79 - .../src/Account/TSAccountManager.h | 171 - .../src/Account/TSAccountManager.m | 776 -- .../src/Account/TSPreKeyManager.h | 36 - .../src/Account/TSPreKeyManager.m | 298 - SignalServiceKit/src/Contacts/CDSQuote.h | 32 - SignalServiceKit/src/Contacts/CDSQuote.m | 191 - .../src/Contacts/CDSSigningCertificate.h | 30 - .../src/Contacts/CDSSigningCertificate.m | 395 - SignalServiceKit/src/Contacts/Contact.h | 58 - SignalServiceKit/src/Contacts/Contact.m | 433 - .../src/Contacts/ContactDiscoveryService.h | 63 - .../src/Contacts/ContactDiscoveryService.m | 775 -- .../src/Contacts/ContactsUpdater.h | 25 - .../src/Contacts/ContactsUpdater.m | 119 - .../OWSContactDiscoveryOperation.swift | 535 -- .../OWSDisappearingMessagesConfiguration.h | 34 - .../OWSDisappearingMessagesConfiguration.m | 133 - SignalServiceKit/src/Contacts/PhoneNumber.h | 50 - SignalServiceKit/src/Contacts/PhoneNumber.m | 582 -- .../src/Contacts/PhoneNumberUtil.h | 44 - .../src/Contacts/PhoneNumberUtil.m | 609 -- SignalServiceKit/src/Contacts/SignalAccount.h | 46 - SignalServiceKit/src/Contacts/SignalAccount.m | 56 - .../src/Contacts/SignalRecipient.h | 50 - .../src/Contacts/SignalRecipient.m | 283 - SignalServiceKit/src/Contacts/TSThread.h | 182 - SignalServiceKit/src/Contacts/TSThread.m | 740 -- .../src/Contacts/Threads/TSContactThread.h | 50 - .../src/Contacts/Threads/TSContactThread.m | 143 - .../src/Contacts/Threads/TSGroupThread.h | 67 - .../src/Contacts/Threads/TSGroupThread.m | 339 - .../Devices/OWSBlockedPhoneNumbersMessage.h | 19 - .../Devices/OWSBlockedPhoneNumbersMessage.m | 57 - .../src/Devices/OWSChunkedOutputStream.h | 23 - .../src/Devices/OWSChunkedOutputStream.m | 96 - .../src/Devices/OWSContactsOutputStream.h | 27 - .../src/Devices/OWSContactsOutputStream.m | 109 - SignalServiceKit/src/Devices/OWSDevice.h | 78 - SignalServiceKit/src/Devices/OWSDevice.m | 353 - .../src/Devices/OWSDeviceProvisioner.h | 36 - .../src/Devices/OWSDeviceProvisioner.m | 122 - .../src/Devices/OWSGroupsOutputStream.h | 18 - .../src/Devices/OWSGroupsOutputStream.m | 85 - .../src/Devices/OWSLinkedDeviceReadReceipt.h | 26 - .../src/Devices/OWSLinkedDeviceReadReceipt.m | 76 - .../src/Devices/OWSProvisioningCipher.h | 16 - .../src/Devices/OWSProvisioningCipher.m | 157 - .../src/Devices/OWSProvisioningMessage.h | 21 - .../src/Devices/OWSProvisioningMessage.m | 93 - .../OWSReadReceiptsForLinkedDevicesMessage.h | 20 - .../OWSReadReceiptsForLinkedDevicesMessage.m | 56 - .../src/Devices/OWSReceiptsForSenderMessage.h | 33 - .../src/Devices/OWSReceiptsForSenderMessage.m | 139 - .../src/Devices/OWSRecordTranscriptJob.h | 26 - .../src/Devices/OWSRecordTranscriptJob.m | 291 - .../Devices/OWSVerificationStateSyncMessage.h | 27 - .../Devices/OWSVerificationStateSyncMessage.m | 111 - .../Deprecated/FileServerAPI+Deprecated.swift | 148 - .../src/Loki/API/Deprecated/ProofOfWork.swift | 109 - SignalServiceKit/src/Loki/API/DotNetAPI.swift | 225 - .../src/Loki/API/FileServerAPI.swift | 75 - .../src/Loki/API/LokiMessage.swift | 75 - .../src/Loki/API/MessageWrapper.swift | 72 - .../OnionRequestAPI+Encryption.swift | 72 - .../API/Onion Requests/OnionRequestAPI.swift | 435 - .../Storage+OnionRequests.swift | 48 - .../src/Loki/API/Open Groups/PublicChat.swift | 43 - .../Loki/API/Open Groups/PublicChatAPI.swift | 527 -- .../Loki/API/Open Groups/PublicChatInfo.swift | 6 - .../API/Open Groups/PublicChatMessage.swift | 190 - .../API/Open Groups/PublicChatPoller.swift | 248 - .../API/Open Groups/Storage+PublicChats.swift | 22 - .../Open Groups/To Do/PublicChatManager.swift | 131 - SignalServiceKit/src/Loki/API/Poller.swift | 114 - .../src/Loki/API/SignalMessage.swift | 28 - SignalServiceKit/src/Loki/API/Snode.swift | 66 - SignalServiceKit/src/Loki/API/SnodeAPI.swift | 352 - .../src/Loki/API/Storage+SnodeAPI.swift | 58 - .../API/Utilities/DecryptionUtilities.swift | 18 - .../API/Utilities/EncryptionUtilities.swift | 38 - .../src/Loki/API/Utilities/HTTP.swift | 107 - .../src/Loki/Architecture Overview.md | 37 - .../src/Loki/Crypto/Mnemonic.swift | 162 - .../Deprecated/LokiDatabaseUtilities.swift | 98 - .../Deprecated/OWSPrimaryStorage+Loki.h | 50 - .../Deprecated/OWSPrimaryStorage+Loki.m | 194 - .../Deprecated/OWSPrimaryStorage+Loki.swift | 89 - .../Deprecated/Storage+Collections.swift | 21 - .../src/Loki/Database/Storage.swift | 76 - .../src/Loki/Documentation/SessionReset.md | 56 - .../src/Loki/Mnemonic/english.txt | 1 - .../src/Loki/Mnemonic/japanese.txt | 1 - .../src/Loki/Mnemonic/portuguese.txt | 1 - .../src/Loki/Mnemonic/spanish.txt | 1 - .../Closed Groups/ClosedGroupPoller.swift | 78 - .../Closed Groups/ClosedGroupRatchet.swift | 44 - .../Closed Groups/ClosedGroupSenderKey.swift | 51 - .../ClosedGroupUpdateMessage.swift | 125 - .../Closed Groups/ClosedGroupUtilities.swift | 70 - .../Closed Groups/ClosedGroupsProtocol.swift | 486 -- .../SharedSenderKeysImplementation.swift | 220 - .../Closed Groups/Storage+ClosedGroups.swift | 83 - .../src/Loki/Protocol/Mentions/Mention.swift | 15 - .../Protocol/Mentions/MentionsManager.swift | 92 - .../Protocol/Meta/SessionMetaProtocol.swift | 134 - .../LokiSessionResetImplementation.swift | 57 - .../SSKProtoPrekeyBundleMessage+Loki.swift | 23 - .../SessionManagementProtocol.swift | 223 - .../SessionRequestMessage.swift | 51 - .../Storage+SessionManagement.swift | 31 - .../Shelved/Multi Device/DeviceLink.swift | 96 - .../Multi Device/DeviceLinkIndex.swift | 43 - .../Multi Device/DeviceLinkingSession.swift | 69 - .../DeviceLinkingSessionDelegate.swift | 6 - .../Multi Device/DeviceLinkingUtilities.swift | 57 - .../Multi Device/LKDeviceLinkMessage.h | 19 - .../Multi Device/LKDeviceLinkMessage.m | 89 - .../Multi Device/LKUnlinkDeviceMessage.h | 12 - .../Multi Device/LKUnlinkDeviceMessage.m | 27 - .../Multi Device/MultiDeviceProtocol.swift | 274 - .../Sync Messages/ClosedGroupParser.swift | 30 - .../Shelved/Sync Messages/ContactParser.swift | 28 - .../Sync Messages/LKSyncOpenGroupsMessage.h | 14 - .../Sync Messages/LKSyncOpenGroupsMessage.m | 43 - .../Sync Messages/SyncMessagesProtocol.swift | 297 - .../Protocol/Utilities/GroupUtilities.swift | 25 - .../Protocol/Utilities/LKGroupUtilities.h | 24 - .../Protocol/Utilities/LKGroupUtilities.m | 76 - .../Protocol/Utilities/TTLUtilities.swift | 32 - .../LokiPushNotificationManager.swift | 153 - .../Utilities/AnyPromise+Conversion.swift | 10 - .../Loki/Utilities/Array+Description.swift | 7 - .../Loki/Utilities/BuildConfiguration.swift | 21 - .../Loki/Utilities/Data+SecureRandom.swift | 12 - .../src/Loki/Utilities/Data+Streaming.swift | 22 - .../src/Loki/Utilities/Debugging.swift | 12 - .../Utilities/Dictionary+Description.swift | 13 - .../Loki/Utilities/DisplayNameUtilities.swift | 68 - .../Utilities/DisplayNameUtilities2.swift | 28 - .../Utilities/ECKeyPair+Hexadecimal.swift | 22 - .../src/Loki/Utilities/GeneralUtilities.swift | 7 - .../src/Loki/Utilities/JSON.swift | 2 - .../src/Loki/Utilities/LKUserDefaults.swift | 73 - .../src/Loki/Utilities/NSArray+Functional.h | 8 - .../src/Loki/Utilities/NSArray+Functional.m | 32 - .../src/Loki/Utilities/NSObject+Casting.h | 6 - .../src/Loki/Utilities/NSObject+Casting.m | 10 - .../src/Loki/Utilities/NSSet+Functional.h | 8 - .../src/Loki/Utilities/NSSet+Functional.m | 32 - .../Loki/Utilities/Notification+Loki.swift | 52 - .../src/Loki/Utilities/Promise+Delaying.swift | 11 - .../src/Loki/Utilities/Promise+Hashing.swift | 13 - .../src/Loki/Utilities/Promise+Retrying.swift | 14 - .../Loki/Utilities/Promise+Threading.swift | 76 - .../src/Loki/Utilities/String+Trimming.swift | 18 - .../Attachments/OWSAttachmentDownloads.h | 50 - .../Attachments/OWSAttachmentDownloads.m | 556 -- .../Messages/Attachments/OWSMediaUtils.swift | 134 - .../Attachments/OWSThumbnailService.swift | 178 - .../src/Messages/Attachments/TSAttachment.h | 103 - .../src/Messages/Attachments/TSAttachment.m | 306 - .../Attachments/TSAttachmentPointer.h | 74 - .../Attachments/TSAttachmentPointer.m | 265 - .../Messages/Attachments/TSAttachmentStream.h | 108 - .../Messages/Attachments/TSAttachmentStream.m | 887 --- .../OWSIncomingSentMessageTranscript.h | 50 - .../OWSIncomingSentMessageTranscript.m | 108 - .../OWSOutgoingSentMessageTranscript.h | 25 - .../OWSOutgoingSentMessageTranscript.m | 118 - .../DeviceSyncing/OWSOutgoingSyncMessage.h | 35 - .../DeviceSyncing/OWSOutgoingSyncMessage.m | 125 - .../OWSSyncConfigurationMessage.h | 22 - .../OWSSyncConfigurationMessage.m | 66 - .../DeviceSyncing/OWSSyncContactsMessage.h | 28 - .../DeviceSyncing/OWSSyncContactsMessage.m | 157 - .../DeviceSyncing/OWSSyncGroupsMessage.h | 24 - .../DeviceSyncing/OWSSyncGroupsMessage.m | 104 - .../OWSSyncGroupsRequestMessage.h | 27 - .../OWSSyncGroupsRequestMessage.m | 85 - .../Interactions/OWSContact+Private.h | 60 - .../src/Messages/Interactions/OWSContact.h | 183 - .../src/Messages/Interactions/OWSContact.m | 1126 --- ...sappearingConfigurationUpdateInfoMessage.h | 28 - ...sappearingConfigurationUpdateInfoMessage.m | 94 - ...DisappearingMessagesConfigurationMessage.h | 30 - ...DisappearingMessagesConfigurationMessage.m | 72 - .../Interactions/OWSDynamicOutgoingMessage.h | 36 - .../Interactions/OWSDynamicOutgoingMessage.m | 64 - .../Interactions/OWSEndSessionMessage.h | 30 - .../Interactions/OWSEndSessionMessage.m | 74 - .../Interactions/OWSLinkPreview.swift | 907 --- .../OWSVerificationStateChangeMessage.h | 26 - .../OWSVerificationStateChangeMessage.m | 34 - .../Messages/Interactions/TSErrorMessage.h | 76 - .../Messages/Interactions/TSErrorMessage.m | 228 - .../TSErrorMessage_privateConstructor.h | 19 - .../Messages/Interactions/TSIncomingMessage.h | 90 - .../Messages/Interactions/TSIncomingMessage.m | 180 - .../src/Messages/Interactions/TSInfoMessage.h | 69 - .../src/Messages/Interactions/TSInfoMessage.m | 194 - .../src/Messages/Interactions/TSInteraction.h | 88 - .../src/Messages/Interactions/TSInteraction.m | 292 - .../src/Messages/Interactions/TSMessage.h | 84 - .../src/Messages/Interactions/TSMessage.m | 466 -- .../Messages/Interactions/TSOutgoingMessage.h | 265 - .../Messages/Interactions/TSOutgoingMessage.m | 1172 --- .../Messages/Interactions/TSQuotedMessage.h | 108 - .../Messages/Interactions/TSQuotedMessage.m | 378 - .../TSInvalidIdentityKeyErrorMessage.h | 19 - .../TSInvalidIdentityKeyErrorMessage.m | 30 - ...SInvalidIdentityKeyReceivingErrorMessage.h | 22 - ...SInvalidIdentityKeyReceivingErrorMessage.m | 154 - .../TSInvalidIdentityKeySendingErrorMessage.h | 24 - .../TSInvalidIdentityKeySendingErrorMessage.m | 61 - .../Messages/OWSAddToContactsOfferMessage.h | 21 - .../Messages/OWSAddToContactsOfferMessage.m | 56 - .../OWSAddToProfileWhitelistOfferMessage.h | 19 - .../OWSAddToProfileWhitelistOfferMessage.m | 40 - .../src/Messages/OWSBatchMessageProcessor.h | 38 - .../src/Messages/OWSBatchMessageProcessor.m | 545 -- .../src/Messages/OWSBlockingManager.h | 49 - .../src/Messages/OWSBlockingManager.m | 442 - .../Messages/OWSDisappearingMessagesFinder.h | 45 - .../Messages/OWSDisappearingMessagesFinder.m | 269 - .../src/Messages/OWSDisappearingMessagesJob.h | 54 - .../src/Messages/OWSDisappearingMessagesJob.m | 422 - .../OWSFailedAttachmentDownloadsJob.h | 29 - .../OWSFailedAttachmentDownloadsJob.m | 142 - .../src/Messages/OWSFailedMessagesJob.h | 29 - .../src/Messages/OWSFailedMessagesJob.m | 149 - .../src/Messages/OWSIdentityManager.h | 92 - .../src/Messages/OWSIdentityManager.m | 976 --- .../src/Messages/OWSIncompleteCallsJob.h | 29 - .../src/Messages/OWSIncompleteCallsJob.m | 160 - .../src/Messages/OWSMessageDecrypter.h | 47 - .../src/Messages/OWSMessageDecrypter.m | 686 -- .../src/Messages/OWSMessageHandler.h | 22 - .../src/Messages/OWSMessageHandler.m | 186 - .../src/Messages/OWSMessageManager.h | 33 - .../src/Messages/OWSMessageManager.m | 1736 ---- .../src/Messages/OWSMessageReceiver.h | 26 - .../src/Messages/OWSMessageReceiver.m | 514 -- .../src/Messages/OWSMessageSend.swift | 96 - .../src/Messages/OWSMessageSender.h | 121 - .../src/Messages/OWSMessageSender.m | 1764 ---- .../src/Messages/OWSMessageServiceParams.h | 45 - .../src/Messages/OWSMessageServiceParams.m | 49 - .../src/Messages/OWSMessageUtils.h | 23 - .../src/Messages/OWSMessageUtils.m | 124 - .../src/Messages/OWSOutgoingCallMessage.h | 48 - .../src/Messages/OWSOutgoingCallMessage.m | 196 - .../src/Messages/OWSOutgoingNullMessage.h | 31 - .../src/Messages/OWSOutgoingNullMessage.m | 107 - .../src/Messages/OWSOutgoingReceiptManager.h | 22 - .../src/Messages/OWSOutgoingReceiptManager.m | 312 - .../src/Messages/OWSProfileKeyMessage.h | 28 - .../src/Messages/OWSProfileKeyMessage.m | 78 - .../src/Messages/OWSReadReceiptManager.h | 86 - .../src/Messages/OWSReadReceiptManager.m | 552 -- .../src/Messages/OWSReadTracking.h | 36 - .../src/Messages/OWSSignalAddress.swift | 33 - .../OWSUnknownContactBlockOfferMessage.h | 17 - .../OWSUnknownContactBlockOfferMessage.m | 38 - .../src/Messages/PreKeyBundle+jsonDict.h | 15 - .../src/Messages/PreKeyBundle+jsonDict.m | 110 - SignalServiceKit/src/Messages/TSCall.h | 44 - SignalServiceKit/src/Messages/TSCall.m | 178 - SignalServiceKit/src/Messages/TSGroupModel.h | 47 - SignalServiceKit/src/Messages/TSGroupModel.m | 194 - .../src/Messages/TypingIndicatorMessage.swift | 122 - .../src/Messages/UD/OWSRequestMaker.swift | 248 - .../src/Messages/UD/OWSUDManager.swift | 518 -- .../src/Network/API/NetworkManager.swift | 53 - .../API/OWSDeviceProvisioningCodeService.h | 16 - .../API/OWSDeviceProvisioningCodeService.m | 64 - .../API/OWSDeviceProvisioningService.h | 20 - .../API/OWSDeviceProvisioningService.m | 59 - .../src/Network/API/OWSDevicesService.h | 23 - .../src/Network/API/OWSDevicesService.m | 131 - .../src/Network/API/OWSRequestBuilder.h | 15 - .../src/Network/API/OWSRequestBuilder.m | 43 - .../src/Network/API/OWSUploadOperation.h | 27 - .../src/Network/API/OWSUploadOperation.m | 191 - .../Network/API/Requests/OWSRequestFactory.h | 115 - .../Network/API/Requests/OWSRequestFactory.m | 544 -- .../src/Network/API/Requests/TSRequest.h | 36 - .../src/Network/API/Requests/TSRequest.m | 119 - .../Network/API/SignalServiceProfile.swift | 54 - .../src/Network/API/TSNetworkManager.h | 45 - .../src/Network/API/TSNetworkManager.m | 583 -- .../src/Network/ContentProxy.swift | 127 - .../src/Network/MessageSenderJobQueue.swift | 256 - .../src/Network/OWSCensorshipConfiguration.h | 33 - .../src/Network/OWSCensorshipConfiguration.m | 246 - .../src/Network/OWSCountryMetadata.h | 21 - .../src/Network/OWSCountryMetadata.m | 378 - .../src/Network/OWSSignalService.h | 35 - .../src/Network/OWSSignalService.m | 326 - .../src/Network/OutageDetection.swift | 128 - .../Network/ProxiedContentDownloader.swift | 932 --- .../src/Network/ReachabilityManager.swift | 57 - .../src/Network/SSKWebSocket.swift | 185 - .../src/Network/SignalServiceClient.swift | 94 - .../src/Network/WebSockets/OWSWebSocket.h | 53 - .../src/Network/WebSockets/OWSWebSocket.m | 1141 --- .../src/Network/WebSockets/TSSocketManager.h | 50 - .../src/Network/WebSockets/TSSocketManager.m | 78 - .../src/Protocols/ContactsManagerProtocol.h | 35 - .../src/Protocols/NotificationsProtocol.h | 32 - .../src/Protocols/OWSCallMessageHandler.h | 28 - .../src/Protocols/ProfileManagerProtocol.h | 46 - SignalServiceKit/src/Protocols/ProtoUtils.h | 27 - SignalServiceKit/src/Protocols/ProtoUtils.m | 97 - .../src/Protos/Generated/Fingerprint.pb.swift | 164 - .../Protos/Generated/FingerprintProto.swift | 235 - .../Protos/Generated/Provisioning.pb.swift | 254 - .../Protos/Generated/ProvisioningProto.swift | 310 - .../src/Protos/Generated/SSKProto.swift | 7075 ----------------- .../src/Protos/Generated/SignalIOS.pb.swift | 318 - .../src/Protos/Generated/SignalIOSProto.swift | 409 - .../Protos/Generated/SignalService.pb.swift | 5199 ------------ .../src/Protos/Generated/WebSocketProto.swift | 486 -- .../Generated/WebSocketResources.pb.swift | 378 - SignalServiceKit/src/SSKEnvironment.h | 118 - SignalServiceKit/src/SSKEnvironment.m | 244 - .../src/Security/OWSFingerprint.h | 50 - .../src/Security/OWSFingerprint.m | 334 - .../src/Security/OWSFingerprintBuilder.h | 30 - .../src/Security/OWSFingerprintBuilder.m | 65 - .../src/Security/OWSHTTPSecurityPolicy.h | 17 - .../src/Security/OWSHTTPSecurityPolicy.m | 105 - .../src/Security/OWSRecipientIdentity.h | 55 - .../src/Security/OWSRecipientIdentity.m | 184 - SignalServiceKit/src/SessionServiceKit.h | 15 - .../AxolotlStore/OWSPrimaryStorage+Calling.h | 24 - .../AxolotlStore/OWSPrimaryStorage+Calling.m | 34 - .../OWSPrimaryStorage+PreKeyStore.h | 18 - .../OWSPrimaryStorage+PreKeyStore.m | 114 - .../OWSPrimaryStorage+SessionStore.h | 27 - .../OWSPrimaryStorage+SessionStore.m | 273 - .../OWSPrimaryStorage+SignedPreKeyStore.h | 40 - .../OWSPrimaryStorage+SignedPreKeyStore.m | 225 - .../OWSPrimaryStorage+keyFromIntLong.h | 15 - .../OWSPrimaryStorage+keyFromIntLong.m | 18 - .../src/Storage/FullTextSearchFinder.swift | 273 - .../src/Storage/OWSIncomingMessageFinder.h | 28 - .../src/Storage/OWSIncomingMessageFinder.m | 150 - .../src/Storage/OWSMediaGalleryFinder.h | 52 - .../src/Storage/OWSMediaGalleryFinder.m | 218 - .../src/Storage/OWSPrimaryStorage.h | 51 - .../src/Storage/OWSPrimaryStorage.m | 461 -- .../src/Storage/OWSStorage+Subclass.h | 32 - SignalServiceKit/src/Storage/OWSStorage.h | 116 - SignalServiceKit/src/Storage/OWSStorage.m | 946 --- .../src/Storage/SSKIncrementingIdFinder.swift | 27 - SignalServiceKit/src/Storage/SSKJobRecord.h | 57 - SignalServiceKit/src/Storage/SSKJobRecord.m | 127 - .../src/Storage/SSKKeychainStorage.swift | 108 - .../src/Storage/SSKMessageSenderJobRecord.h | 29 - .../src/Storage/SSKMessageSenderJobRecord.m | 51 - .../src/Storage/TSDatabaseSecondaryIndexes.h | 22 - .../src/Storage/TSDatabaseSecondaryIndexes.m | 54 - SignalServiceKit/src/Storage/TSDatabaseView.h | 74 - SignalServiceKit/src/Storage/TSDatabaseView.m | 514 -- .../src/Storage/TSStorageHeaders.h | 14 - SignalServiceKit/src/Storage/TSStorageKeys.h | 30 - .../src/Storage/TSYapDatabaseObject.h | 166 - .../src/Storage/TSYapDatabaseObject.m | 250 - .../src/Storage/YapDatabaseConnection+OWS.h | 46 - .../src/Storage/YapDatabaseConnection+OWS.m | 194 - .../src/Storage/YapDatabaseTransaction+OWS.h | 45 - .../src/Storage/YapDatabaseTransaction+OWS.m | 171 - SignalServiceKit/src/TSConstants.h | 74 - SignalServiceKit/src/TSConstants.m | 18 - SignalServiceKit/src/TSPrefix.h | 21 - .../src/TestUtils/Factories.swift | 558 -- .../src/TestUtils/FakeContactsManager.swift | 45 - .../src/TestUtils/MockSSKEnvironment.h | 50 - .../src/TestUtils/MockSSKEnvironment.m | 135 - .../TestUtils/NoopNotificationsManager.swift | 23 - .../src/TestUtils/OWSFakeCallMessageHandler.h | 17 - .../src/TestUtils/OWSFakeCallMessageHandler.m | 42 - .../src/TestUtils/OWSFakeContactsUpdater.h | 13 - .../src/TestUtils/OWSFakeContactsUpdater.m | 21 - .../src/TestUtils/OWSFakeMessageSender.h | 25 - .../src/TestUtils/OWSFakeMessageSender.m | 78 - .../src/TestUtils/OWSFakeNetworkManager.h | 19 - .../src/TestUtils/OWSFakeNetworkManager.m | 24 - .../src/TestUtils/OWSFakeProfileManager.h | 17 - .../src/TestUtils/OWSFakeProfileManager.m | 102 - .../src/TestUtils/OWSMockSyncManager.swift | 59 - .../src/TestUtils/TestAppContext.h | 17 - .../src/TestUtils/TestAppContext.m | 155 - .../src/TestUtils/TestKeychainStorage.swift | 56 - SignalServiceKit/src/Util/AppContext.h | 128 - SignalServiceKit/src/Util/AppContext.m | 60 - SignalServiceKit/src/Util/AppReadiness.h | 36 - SignalServiceKit/src/Util/AppReadiness.m | 142 - SignalServiceKit/src/Util/AppVersion.h | 30 - SignalServiceKit/src/Util/AppVersion.m | 132 - SignalServiceKit/src/Util/ByteParser.h | 38 - SignalServiceKit/src/Util/ByteParser.m | 142 - SignalServiceKit/src/Util/DataSource.h | 65 - SignalServiceKit/src/Util/DataSource.m | 404 - SignalServiceKit/src/Util/DeviceNames.swift | 220 - SignalServiceKit/src/Util/FeatureFlags.swift | 32 - SignalServiceKit/src/Util/FunctionalUtil.h | 25 - SignalServiceKit/src/Util/FunctionalUtil.m | 97 - SignalServiceKit/src/Util/JobQueue.swift | 411 - SignalServiceKit/src/Util/LRUCache.swift | 105 - SignalServiceKit/src/Util/MIMETypeUtil.h | 66 - SignalServiceKit/src/Util/MIMETypeUtil.m | 2625 ------ .../src/Util/MessageSender+Promise.swift | 25 - SignalServiceKit/src/Util/NSArray+OWS.h | 13 - SignalServiceKit/src/Util/NSArray+OWS.m | 25 - SignalServiceKit/src/Util/NSData+Image.h | 26 - SignalServiceKit/src/Util/NSData+Image.m | 420 - .../src/Util/NSError+MessageSending.h | 15 - .../src/Util/NSError+MessageSending.m | 61 - .../src/Util/NSNotificationCenter+OWS.h | 22 - .../src/Util/NSNotificationCenter+OWS.m | 29 - .../src/Util/NSRegularExpression+SSK.swift | 55 - SignalServiceKit/src/Util/NSString+SSK.h | 15 - SignalServiceKit/src/Util/NSString+SSK.m | 25 - SignalServiceKit/src/Util/NSTimer+OWS.h | 19 - SignalServiceKit/src/Util/NSTimer+OWS.m | 69 - .../Util/NSURLSessionDataTask+StatusCode.h | 13 - .../Util/NSURLSessionDataTask+StatusCode.m | 18 - .../src/Util/NSUserDefaults+OWS.h | 17 - .../src/Util/NSUserDefaults+OWS.m | 51 - SignalServiceKit/src/Util/OWS2FAManager.h | 45 - SignalServiceKit/src/Util/OWS2FAManager.m | 270 - SignalServiceKit/src/Util/OWSAnalytics.h | 165 - SignalServiceKit/src/Util/OWSAnalytics.m | 425 - .../src/Util/OWSAnalyticsEvents.h | 237 - .../src/Util/OWSAnalyticsEvents.m | 549 -- SignalServiceKit/src/Util/OWSBackgroundTask.h | 60 - SignalServiceKit/src/Util/OWSBackgroundTask.m | 437 - SignalServiceKit/src/Util/OWSBackupFragment.h | 44 - SignalServiceKit/src/Util/OWSBackupFragment.m | 13 - SignalServiceKit/src/Util/OWSDispatch.h | 21 - SignalServiceKit/src/Util/OWSDispatch.m | 34 - SignalServiceKit/src/Util/OWSError.h | 69 - SignalServiceKit/src/Util/OWSError.m | 74 - SignalServiceKit/src/Util/OWSFileSystem.h | 57 - SignalServiceKit/src/Util/OWSFileSystem.m | 430 - SignalServiceKit/src/Util/OWSMath.h | 112 - SignalServiceKit/src/Util/OWSOperation.h | 88 - SignalServiceKit/src/Util/OWSOperation.m | 268 - SignalServiceKit/src/Util/OWSQueues.h | 26 - .../src/Util/OWSSyncManagerProtocol.h | 32 - SignalServiceKit/src/Util/ParamParser.swift | 142 - .../Util/Promise+retainUntilComplete.swift | 63 - .../src/Util/ReverseDispatchQueue.swift | 75 - SignalServiceKit/src/Util/SSKAsserts.h | 68 - .../src/Util/SSKPreferences.swift | 60 - SignalServiceKit/src/Util/String+SSK.swift | 86 - .../src/Util/SwiftSingletons.swift | 35 - .../src/Util/TypingIndicators.swift | 459 -- SignalServiceKit/src/Util/UIImage+OWS.h | 21 - SignalServiceKit/src/Util/UIImage+OWS.m | 242 - SignalServiceKit/src/Util/WeakTimer.swift | 43 - .../src/Util/YapDatabase+Promise.swift | 52 - .../tests/Account/SignedPreKeyDeletionTests.m | 133 - ...OWSDisappearingMessagesConfigurationTest.m | 63 - .../tests/Contacts/SignalRecipientTest.m | 64 - .../tests/Contacts/TSContactThreadTest.m | 42 - .../tests/Contacts/TSGroupThreadTest.m | 24 - .../tests/Contacts/TSThreadTest.m | 148 - .../tests/Devices/OWSDeviceProvisionerTest.m | 98 - .../tests/Devices/OWSProvisioningCipherTest.m | 155 - .../Messages/Interactions/TSMessageTest.m | 65 - .../Interactions/TSOutgoingMessageTest.m | 115 - .../OWSDisappearingMessageFinderTest.m | 192 - .../Messages/OWSDisappearingMessagesJobTest.m | 138 - .../Messages/OWSIncomingMessageFinderTest.m | 136 - .../tests/Messages/OWSLinkPreviewTest.swift | 583 -- .../tests/Messages/OWSMessageManagerTest.m | 185 - .../tests/Messages/OWSMessageSenderTest.m | 546 -- .../tests/Messages/OWSUDManagerTest.swift | 182 - .../Network/MessageSendJobQueueTest.swift | 238 - .../Network/MessageSenderJobRecordTest.swift | 45 - SignalServiceKit/tests/SSKBaseTestObjC.h | 23 - SignalServiceKit/tests/SSKBaseTestObjC.m | 59 - SignalServiceKit/tests/SSKBaseTestSwift.swift | 38 - SignalServiceKit/tests/SSKSwiftTests.swift | 40 - .../tests/Security/OWSFingerprintTest.m | 80 - .../Storage/TSStorageIdentityKeyStoreTests.m | 115 - .../tests/Storage/TSStoragePreKeyStoreTests.m | 63 - .../tests/Util/DeviceNamesTest.swift | 90 - .../tests/Util/JobQueueTest.swift | 173 - .../tests/Util/TSMessageStorageTests.m | 179 - SignalUtilitiesKit/Meta/SignalUtilitiesKit.h | 10 + 590 files changed, 287 insertions(+), 86438 deletions(-) delete mode 100644 SessionServiceKit.podspec delete mode 100644 SignalServiceKit/.clang-format delete mode 100644 SignalServiceKit/.gitignore delete mode 100644 SignalServiceKit/.travis.yml delete mode 100644 SignalServiceKit/LICENSE delete mode 100644 SignalServiceKit/Resources/Certificates/DigiCertGlobalRootG2.crt delete mode 100644 SignalServiceKit/Resources/Certificates/DigiCertSHA2HighAssuranceServerCA.crt delete mode 100644 SignalServiceKit/Resources/Certificates/GIAG2.crt delete mode 100644 SignalServiceKit/Resources/Certificates/GSR2.crt delete mode 100644 SignalServiceKit/Resources/Certificates/GSR4.crt delete mode 100644 SignalServiceKit/Resources/Certificates/GTSR1.crt delete mode 100644 SignalServiceKit/Resources/Certificates/GTSR2.crt delete mode 100644 SignalServiceKit/Resources/Certificates/GTSR3.crt delete mode 100644 SignalServiceKit/Resources/Certificates/GTSR4.crt delete mode 100644 SignalServiceKit/Resources/Certificates/SFSRootCAG2.crt delete mode 100644 SignalServiceKit/Resources/Certificates/ias-root.cer delete mode 100644 SignalServiceKit/Resources/Certificates/textsecure.cer delete mode 100755 SignalServiceKit/Utilities/extract_analytics_event_names.py delete mode 100644 SignalServiceKit/protobuf/Fingerprint.proto delete mode 100644 SignalServiceKit/protobuf/Makefile delete mode 100644 SignalServiceKit/protobuf/Provisioning.proto delete mode 100644 SignalServiceKit/protobuf/README.md delete mode 100644 SignalServiceKit/protobuf/SignalIOS.proto delete mode 100644 SignalServiceKit/protobuf/SignalService.proto delete mode 100644 SignalServiceKit/protobuf/WebSocketResources.proto delete mode 100644 SignalServiceKit/src/Account/AccountServiceClient.swift delete mode 100644 SignalServiceKit/src/Account/CreatePreKeysOperation.swift delete mode 100644 SignalServiceKit/src/Account/PreKeyRefreshOperation.swift delete mode 100644 SignalServiceKit/src/Account/RotateSignedKeyOperation.swift delete mode 100644 SignalServiceKit/src/Account/TSAccountManager.h delete mode 100644 SignalServiceKit/src/Account/TSAccountManager.m delete mode 100644 SignalServiceKit/src/Account/TSPreKeyManager.h delete mode 100644 SignalServiceKit/src/Account/TSPreKeyManager.m delete mode 100644 SignalServiceKit/src/Contacts/CDSQuote.h delete mode 100644 SignalServiceKit/src/Contacts/CDSQuote.m delete mode 100644 SignalServiceKit/src/Contacts/CDSSigningCertificate.h delete mode 100644 SignalServiceKit/src/Contacts/CDSSigningCertificate.m delete mode 100644 SignalServiceKit/src/Contacts/Contact.h delete mode 100644 SignalServiceKit/src/Contacts/Contact.m delete mode 100644 SignalServiceKit/src/Contacts/ContactDiscoveryService.h delete mode 100644 SignalServiceKit/src/Contacts/ContactDiscoveryService.m delete mode 100644 SignalServiceKit/src/Contacts/ContactsUpdater.h delete mode 100644 SignalServiceKit/src/Contacts/ContactsUpdater.m delete mode 100644 SignalServiceKit/src/Contacts/OWSContactDiscoveryOperation.swift delete mode 100644 SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.h delete mode 100644 SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.m delete mode 100644 SignalServiceKit/src/Contacts/PhoneNumber.h delete mode 100644 SignalServiceKit/src/Contacts/PhoneNumber.m delete mode 100644 SignalServiceKit/src/Contacts/PhoneNumberUtil.h delete mode 100644 SignalServiceKit/src/Contacts/PhoneNumberUtil.m delete mode 100644 SignalServiceKit/src/Contacts/SignalAccount.h delete mode 100644 SignalServiceKit/src/Contacts/SignalAccount.m delete mode 100644 SignalServiceKit/src/Contacts/SignalRecipient.h delete mode 100644 SignalServiceKit/src/Contacts/SignalRecipient.m delete mode 100644 SignalServiceKit/src/Contacts/TSThread.h delete mode 100644 SignalServiceKit/src/Contacts/TSThread.m delete mode 100644 SignalServiceKit/src/Contacts/Threads/TSContactThread.h delete mode 100644 SignalServiceKit/src/Contacts/Threads/TSContactThread.m delete mode 100644 SignalServiceKit/src/Contacts/Threads/TSGroupThread.h delete mode 100644 SignalServiceKit/src/Contacts/Threads/TSGroupThread.m delete mode 100644 SignalServiceKit/src/Devices/OWSBlockedPhoneNumbersMessage.h delete mode 100644 SignalServiceKit/src/Devices/OWSBlockedPhoneNumbersMessage.m delete mode 100644 SignalServiceKit/src/Devices/OWSChunkedOutputStream.h delete mode 100644 SignalServiceKit/src/Devices/OWSChunkedOutputStream.m delete mode 100644 SignalServiceKit/src/Devices/OWSContactsOutputStream.h delete mode 100644 SignalServiceKit/src/Devices/OWSContactsOutputStream.m delete mode 100644 SignalServiceKit/src/Devices/OWSDevice.h delete mode 100644 SignalServiceKit/src/Devices/OWSDevice.m delete mode 100644 SignalServiceKit/src/Devices/OWSDeviceProvisioner.h delete mode 100644 SignalServiceKit/src/Devices/OWSDeviceProvisioner.m delete mode 100644 SignalServiceKit/src/Devices/OWSGroupsOutputStream.h delete mode 100644 SignalServiceKit/src/Devices/OWSGroupsOutputStream.m delete mode 100644 SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h delete mode 100644 SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m delete mode 100644 SignalServiceKit/src/Devices/OWSProvisioningCipher.h delete mode 100644 SignalServiceKit/src/Devices/OWSProvisioningCipher.m delete mode 100644 SignalServiceKit/src/Devices/OWSProvisioningMessage.h delete mode 100644 SignalServiceKit/src/Devices/OWSProvisioningMessage.m delete mode 100644 SignalServiceKit/src/Devices/OWSReadReceiptsForLinkedDevicesMessage.h delete mode 100644 SignalServiceKit/src/Devices/OWSReadReceiptsForLinkedDevicesMessage.m delete mode 100644 SignalServiceKit/src/Devices/OWSReceiptsForSenderMessage.h delete mode 100644 SignalServiceKit/src/Devices/OWSReceiptsForSenderMessage.m delete mode 100644 SignalServiceKit/src/Devices/OWSRecordTranscriptJob.h delete mode 100644 SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m delete mode 100644 SignalServiceKit/src/Devices/OWSVerificationStateSyncMessage.h delete mode 100644 SignalServiceKit/src/Devices/OWSVerificationStateSyncMessage.m delete mode 100644 SignalServiceKit/src/Loki/API/Deprecated/FileServerAPI+Deprecated.swift delete mode 100644 SignalServiceKit/src/Loki/API/Deprecated/ProofOfWork.swift delete mode 100644 SignalServiceKit/src/Loki/API/DotNetAPI.swift delete mode 100644 SignalServiceKit/src/Loki/API/FileServerAPI.swift delete mode 100644 SignalServiceKit/src/Loki/API/LokiMessage.swift delete mode 100644 SignalServiceKit/src/Loki/API/MessageWrapper.swift delete mode 100644 SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift delete mode 100644 SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift delete mode 100644 SignalServiceKit/src/Loki/API/Onion Requests/Storage+OnionRequests.swift delete mode 100644 SignalServiceKit/src/Loki/API/Open Groups/PublicChat.swift delete mode 100644 SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift delete mode 100644 SignalServiceKit/src/Loki/API/Open Groups/PublicChatInfo.swift delete mode 100644 SignalServiceKit/src/Loki/API/Open Groups/PublicChatMessage.swift delete mode 100644 SignalServiceKit/src/Loki/API/Open Groups/PublicChatPoller.swift delete mode 100644 SignalServiceKit/src/Loki/API/Open Groups/Storage+PublicChats.swift delete mode 100644 SignalServiceKit/src/Loki/API/Open Groups/To Do/PublicChatManager.swift delete mode 100644 SignalServiceKit/src/Loki/API/Poller.swift delete mode 100644 SignalServiceKit/src/Loki/API/SignalMessage.swift delete mode 100644 SignalServiceKit/src/Loki/API/Snode.swift delete mode 100644 SignalServiceKit/src/Loki/API/SnodeAPI.swift delete mode 100644 SignalServiceKit/src/Loki/API/Storage+SnodeAPI.swift delete mode 100644 SignalServiceKit/src/Loki/API/Utilities/DecryptionUtilities.swift delete mode 100644 SignalServiceKit/src/Loki/API/Utilities/EncryptionUtilities.swift delete mode 100644 SignalServiceKit/src/Loki/API/Utilities/HTTP.swift delete mode 100644 SignalServiceKit/src/Loki/Architecture Overview.md delete mode 100644 SignalServiceKit/src/Loki/Crypto/Mnemonic.swift delete mode 100644 SignalServiceKit/src/Loki/Database/Deprecated/LokiDatabaseUtilities.swift delete mode 100644 SignalServiceKit/src/Loki/Database/Deprecated/OWSPrimaryStorage+Loki.h delete mode 100644 SignalServiceKit/src/Loki/Database/Deprecated/OWSPrimaryStorage+Loki.m delete mode 100644 SignalServiceKit/src/Loki/Database/Deprecated/OWSPrimaryStorage+Loki.swift delete mode 100644 SignalServiceKit/src/Loki/Database/Deprecated/Storage+Collections.swift delete mode 100644 SignalServiceKit/src/Loki/Database/Storage.swift delete mode 100644 SignalServiceKit/src/Loki/Documentation/SessionReset.md delete mode 100644 SignalServiceKit/src/Loki/Mnemonic/english.txt delete mode 100644 SignalServiceKit/src/Loki/Mnemonic/japanese.txt delete mode 100644 SignalServiceKit/src/Loki/Mnemonic/portuguese.txt delete mode 100644 SignalServiceKit/src/Loki/Mnemonic/spanish.txt delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupPoller.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupRatchet.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUtilities.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/Storage+ClosedGroups.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Mentions/Mention.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Mentions/MentionsManager.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Meta/SessionMetaProtocol.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Session Management/LokiSessionResetImplementation.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Session Management/SSKProtoPrekeyBundleMessage+Loki.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Session Management/SessionRequestMessage.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Session Management/Storage+SessionManagement.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLink.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkIndex.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkingSession.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkingSessionDelegate.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkingUtilities.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKDeviceLinkMessage.h delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKDeviceLinkMessage.m delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKUnlinkDeviceMessage.h delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKUnlinkDeviceMessage.m delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/MultiDeviceProtocol.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/ClosedGroupParser.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/ContactParser.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/LKSyncOpenGroupsMessage.h delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/LKSyncOpenGroupsMessage.m delete mode 100644 SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/SyncMessagesProtocol.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Utilities/GroupUtilities.swift delete mode 100644 SignalServiceKit/src/Loki/Protocol/Utilities/LKGroupUtilities.h delete mode 100644 SignalServiceKit/src/Loki/Protocol/Utilities/LKGroupUtilities.m delete mode 100644 SignalServiceKit/src/Loki/Protocol/Utilities/TTLUtilities.swift delete mode 100644 SignalServiceKit/src/Loki/Push Notifications/LokiPushNotificationManager.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/AnyPromise+Conversion.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/Array+Description.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/BuildConfiguration.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/Data+SecureRandom.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/Data+Streaming.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/Debugging.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/Dictionary+Description.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities2.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/ECKeyPair+Hexadecimal.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/GeneralUtilities.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/JSON.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/LKUserDefaults.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/NSArray+Functional.h delete mode 100644 SignalServiceKit/src/Loki/Utilities/NSArray+Functional.m delete mode 100644 SignalServiceKit/src/Loki/Utilities/NSObject+Casting.h delete mode 100644 SignalServiceKit/src/Loki/Utilities/NSObject+Casting.m delete mode 100644 SignalServiceKit/src/Loki/Utilities/NSSet+Functional.h delete mode 100644 SignalServiceKit/src/Loki/Utilities/NSSet+Functional.m delete mode 100644 SignalServiceKit/src/Loki/Utilities/Notification+Loki.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/Promise+Delaying.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/Promise+Hashing.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/Promise+Retrying.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/Promise+Threading.swift delete mode 100644 SignalServiceKit/src/Loki/Utilities/String+Trimming.swift delete mode 100644 SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.h delete mode 100644 SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m delete mode 100644 SignalServiceKit/src/Messages/Attachments/OWSMediaUtils.swift delete mode 100644 SignalServiceKit/src/Messages/Attachments/OWSThumbnailService.swift delete mode 100644 SignalServiceKit/src/Messages/Attachments/TSAttachment.h delete mode 100644 SignalServiceKit/src/Messages/Attachments/TSAttachment.m delete mode 100644 SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.h delete mode 100644 SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.m delete mode 100644 SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h delete mode 100644 SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.h delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentMessageTranscript.h delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentMessageTranscript.m delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.h delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.m delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncConfigurationMessage.h delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncConfigurationMessage.m delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.h delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.h delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsRequestMessage.h delete mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsRequestMessage.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSContact.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSContact.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSDisappearingMessagesConfigurationMessage.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSDisappearingMessagesConfigurationMessage.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSDynamicOutgoingMessage.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSDynamicOutgoingMessage.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSEndSessionMessage.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSEndSessionMessage.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSVerificationStateChangeMessage.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/OWSVerificationStateChangeMessage.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSErrorMessage.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSErrorMessage.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSErrorMessage_privateConstructor.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSIncomingMessage.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSIncomingMessage.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSInfoMessage.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSInfoMessage.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSInteraction.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSInteraction.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSMessage.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSMessage.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h delete mode 100644 SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m delete mode 100644 SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.h delete mode 100644 SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.m delete mode 100644 SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyReceivingErrorMessage.h delete mode 100644 SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyReceivingErrorMessage.m delete mode 100644 SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeySendingErrorMessage.h delete mode 100644 SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeySendingErrorMessage.m delete mode 100644 SignalServiceKit/src/Messages/OWSAddToContactsOfferMessage.h delete mode 100644 SignalServiceKit/src/Messages/OWSAddToContactsOfferMessage.m delete mode 100644 SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.h delete mode 100644 SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.m delete mode 100644 SignalServiceKit/src/Messages/OWSBatchMessageProcessor.h delete mode 100644 SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m delete mode 100644 SignalServiceKit/src/Messages/OWSBlockingManager.h delete mode 100644 SignalServiceKit/src/Messages/OWSBlockingManager.m delete mode 100644 SignalServiceKit/src/Messages/OWSDisappearingMessagesFinder.h delete mode 100644 SignalServiceKit/src/Messages/OWSDisappearingMessagesFinder.m delete mode 100644 SignalServiceKit/src/Messages/OWSDisappearingMessagesJob.h delete mode 100644 SignalServiceKit/src/Messages/OWSDisappearingMessagesJob.m delete mode 100644 SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.h delete mode 100644 SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.m delete mode 100644 SignalServiceKit/src/Messages/OWSFailedMessagesJob.h delete mode 100644 SignalServiceKit/src/Messages/OWSFailedMessagesJob.m delete mode 100644 SignalServiceKit/src/Messages/OWSIdentityManager.h delete mode 100644 SignalServiceKit/src/Messages/OWSIdentityManager.m delete mode 100644 SignalServiceKit/src/Messages/OWSIncompleteCallsJob.h delete mode 100644 SignalServiceKit/src/Messages/OWSIncompleteCallsJob.m delete mode 100644 SignalServiceKit/src/Messages/OWSMessageDecrypter.h delete mode 100644 SignalServiceKit/src/Messages/OWSMessageDecrypter.m delete mode 100644 SignalServiceKit/src/Messages/OWSMessageHandler.h delete mode 100644 SignalServiceKit/src/Messages/OWSMessageHandler.m delete mode 100644 SignalServiceKit/src/Messages/OWSMessageManager.h delete mode 100644 SignalServiceKit/src/Messages/OWSMessageManager.m delete mode 100644 SignalServiceKit/src/Messages/OWSMessageReceiver.h delete mode 100644 SignalServiceKit/src/Messages/OWSMessageReceiver.m delete mode 100644 SignalServiceKit/src/Messages/OWSMessageSend.swift delete mode 100644 SignalServiceKit/src/Messages/OWSMessageSender.h delete mode 100644 SignalServiceKit/src/Messages/OWSMessageSender.m delete mode 100644 SignalServiceKit/src/Messages/OWSMessageServiceParams.h delete mode 100644 SignalServiceKit/src/Messages/OWSMessageServiceParams.m delete mode 100644 SignalServiceKit/src/Messages/OWSMessageUtils.h delete mode 100644 SignalServiceKit/src/Messages/OWSMessageUtils.m delete mode 100644 SignalServiceKit/src/Messages/OWSOutgoingCallMessage.h delete mode 100644 SignalServiceKit/src/Messages/OWSOutgoingCallMessage.m delete mode 100644 SignalServiceKit/src/Messages/OWSOutgoingNullMessage.h delete mode 100644 SignalServiceKit/src/Messages/OWSOutgoingNullMessage.m delete mode 100644 SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.h delete mode 100644 SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.m delete mode 100644 SignalServiceKit/src/Messages/OWSProfileKeyMessage.h delete mode 100644 SignalServiceKit/src/Messages/OWSProfileKeyMessage.m delete mode 100644 SignalServiceKit/src/Messages/OWSReadReceiptManager.h delete mode 100644 SignalServiceKit/src/Messages/OWSReadReceiptManager.m delete mode 100644 SignalServiceKit/src/Messages/OWSReadTracking.h delete mode 100644 SignalServiceKit/src/Messages/OWSSignalAddress.swift delete mode 100644 SignalServiceKit/src/Messages/OWSUnknownContactBlockOfferMessage.h delete mode 100644 SignalServiceKit/src/Messages/OWSUnknownContactBlockOfferMessage.m delete mode 100644 SignalServiceKit/src/Messages/PreKeyBundle+jsonDict.h delete mode 100644 SignalServiceKit/src/Messages/PreKeyBundle+jsonDict.m delete mode 100644 SignalServiceKit/src/Messages/TSCall.h delete mode 100644 SignalServiceKit/src/Messages/TSCall.m delete mode 100644 SignalServiceKit/src/Messages/TSGroupModel.h delete mode 100644 SignalServiceKit/src/Messages/TSGroupModel.m delete mode 100644 SignalServiceKit/src/Messages/TypingIndicatorMessage.swift delete mode 100644 SignalServiceKit/src/Messages/UD/OWSRequestMaker.swift delete mode 100644 SignalServiceKit/src/Messages/UD/OWSUDManager.swift delete mode 100644 SignalServiceKit/src/Network/API/NetworkManager.swift delete mode 100644 SignalServiceKit/src/Network/API/OWSDeviceProvisioningCodeService.h delete mode 100644 SignalServiceKit/src/Network/API/OWSDeviceProvisioningCodeService.m delete mode 100644 SignalServiceKit/src/Network/API/OWSDeviceProvisioningService.h delete mode 100644 SignalServiceKit/src/Network/API/OWSDeviceProvisioningService.m delete mode 100644 SignalServiceKit/src/Network/API/OWSDevicesService.h delete mode 100644 SignalServiceKit/src/Network/API/OWSDevicesService.m delete mode 100644 SignalServiceKit/src/Network/API/OWSRequestBuilder.h delete mode 100644 SignalServiceKit/src/Network/API/OWSRequestBuilder.m delete mode 100644 SignalServiceKit/src/Network/API/OWSUploadOperation.h delete mode 100644 SignalServiceKit/src/Network/API/OWSUploadOperation.m delete mode 100644 SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h delete mode 100644 SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m delete mode 100644 SignalServiceKit/src/Network/API/Requests/TSRequest.h delete mode 100644 SignalServiceKit/src/Network/API/Requests/TSRequest.m delete mode 100644 SignalServiceKit/src/Network/API/SignalServiceProfile.swift delete mode 100644 SignalServiceKit/src/Network/API/TSNetworkManager.h delete mode 100644 SignalServiceKit/src/Network/API/TSNetworkManager.m delete mode 100644 SignalServiceKit/src/Network/ContentProxy.swift delete mode 100644 SignalServiceKit/src/Network/MessageSenderJobQueue.swift delete mode 100644 SignalServiceKit/src/Network/OWSCensorshipConfiguration.h delete mode 100644 SignalServiceKit/src/Network/OWSCensorshipConfiguration.m delete mode 100644 SignalServiceKit/src/Network/OWSCountryMetadata.h delete mode 100644 SignalServiceKit/src/Network/OWSCountryMetadata.m delete mode 100644 SignalServiceKit/src/Network/OWSSignalService.h delete mode 100644 SignalServiceKit/src/Network/OWSSignalService.m delete mode 100644 SignalServiceKit/src/Network/OutageDetection.swift delete mode 100644 SignalServiceKit/src/Network/ProxiedContentDownloader.swift delete mode 100644 SignalServiceKit/src/Network/ReachabilityManager.swift delete mode 100644 SignalServiceKit/src/Network/SSKWebSocket.swift delete mode 100644 SignalServiceKit/src/Network/SignalServiceClient.swift delete mode 100644 SignalServiceKit/src/Network/WebSockets/OWSWebSocket.h delete mode 100644 SignalServiceKit/src/Network/WebSockets/OWSWebSocket.m delete mode 100644 SignalServiceKit/src/Network/WebSockets/TSSocketManager.h delete mode 100644 SignalServiceKit/src/Network/WebSockets/TSSocketManager.m delete mode 100644 SignalServiceKit/src/Protocols/ContactsManagerProtocol.h delete mode 100644 SignalServiceKit/src/Protocols/NotificationsProtocol.h delete mode 100644 SignalServiceKit/src/Protocols/OWSCallMessageHandler.h delete mode 100644 SignalServiceKit/src/Protocols/ProfileManagerProtocol.h delete mode 100644 SignalServiceKit/src/Protocols/ProtoUtils.h delete mode 100644 SignalServiceKit/src/Protocols/ProtoUtils.m delete mode 100644 SignalServiceKit/src/Protos/Generated/Fingerprint.pb.swift delete mode 100644 SignalServiceKit/src/Protos/Generated/FingerprintProto.swift delete mode 100644 SignalServiceKit/src/Protos/Generated/Provisioning.pb.swift delete mode 100644 SignalServiceKit/src/Protos/Generated/ProvisioningProto.swift delete mode 100644 SignalServiceKit/src/Protos/Generated/SSKProto.swift delete mode 100644 SignalServiceKit/src/Protos/Generated/SignalIOS.pb.swift delete mode 100644 SignalServiceKit/src/Protos/Generated/SignalIOSProto.swift delete mode 100644 SignalServiceKit/src/Protos/Generated/SignalService.pb.swift delete mode 100644 SignalServiceKit/src/Protos/Generated/WebSocketProto.swift delete mode 100644 SignalServiceKit/src/Protos/Generated/WebSocketResources.pb.swift delete mode 100644 SignalServiceKit/src/SSKEnvironment.h delete mode 100644 SignalServiceKit/src/SSKEnvironment.m delete mode 100644 SignalServiceKit/src/Security/OWSFingerprint.h delete mode 100644 SignalServiceKit/src/Security/OWSFingerprint.m delete mode 100644 SignalServiceKit/src/Security/OWSFingerprintBuilder.h delete mode 100644 SignalServiceKit/src/Security/OWSFingerprintBuilder.m delete mode 100644 SignalServiceKit/src/Security/OWSHTTPSecurityPolicy.h delete mode 100644 SignalServiceKit/src/Security/OWSHTTPSecurityPolicy.m delete mode 100644 SignalServiceKit/src/Security/OWSRecipientIdentity.h delete mode 100644 SignalServiceKit/src/Security/OWSRecipientIdentity.m delete mode 100644 SignalServiceKit/src/SessionServiceKit.h delete mode 100644 SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+Calling.h delete mode 100644 SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+Calling.m delete mode 100644 SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.h delete mode 100644 SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.m delete mode 100644 SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SessionStore.h delete mode 100644 SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SessionStore.m delete mode 100644 SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.h delete mode 100644 SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.m delete mode 100644 SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+keyFromIntLong.h delete mode 100644 SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+keyFromIntLong.m delete mode 100644 SignalServiceKit/src/Storage/FullTextSearchFinder.swift delete mode 100644 SignalServiceKit/src/Storage/OWSIncomingMessageFinder.h delete mode 100644 SignalServiceKit/src/Storage/OWSIncomingMessageFinder.m delete mode 100644 SignalServiceKit/src/Storage/OWSMediaGalleryFinder.h delete mode 100644 SignalServiceKit/src/Storage/OWSMediaGalleryFinder.m delete mode 100644 SignalServiceKit/src/Storage/OWSPrimaryStorage.h delete mode 100644 SignalServiceKit/src/Storage/OWSPrimaryStorage.m delete mode 100644 SignalServiceKit/src/Storage/OWSStorage+Subclass.h delete mode 100644 SignalServiceKit/src/Storage/OWSStorage.h delete mode 100644 SignalServiceKit/src/Storage/OWSStorage.m delete mode 100644 SignalServiceKit/src/Storage/SSKIncrementingIdFinder.swift delete mode 100644 SignalServiceKit/src/Storage/SSKJobRecord.h delete mode 100644 SignalServiceKit/src/Storage/SSKJobRecord.m delete mode 100644 SignalServiceKit/src/Storage/SSKKeychainStorage.swift delete mode 100644 SignalServiceKit/src/Storage/SSKMessageSenderJobRecord.h delete mode 100644 SignalServiceKit/src/Storage/SSKMessageSenderJobRecord.m delete mode 100644 SignalServiceKit/src/Storage/TSDatabaseSecondaryIndexes.h delete mode 100644 SignalServiceKit/src/Storage/TSDatabaseSecondaryIndexes.m delete mode 100644 SignalServiceKit/src/Storage/TSDatabaseView.h delete mode 100644 SignalServiceKit/src/Storage/TSDatabaseView.m delete mode 100644 SignalServiceKit/src/Storage/TSStorageHeaders.h delete mode 100644 SignalServiceKit/src/Storage/TSStorageKeys.h delete mode 100644 SignalServiceKit/src/Storage/TSYapDatabaseObject.h delete mode 100644 SignalServiceKit/src/Storage/TSYapDatabaseObject.m delete mode 100644 SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.h delete mode 100644 SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.m delete mode 100644 SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.h delete mode 100644 SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.m delete mode 100644 SignalServiceKit/src/TSConstants.h delete mode 100644 SignalServiceKit/src/TSConstants.m delete mode 100644 SignalServiceKit/src/TSPrefix.h delete mode 100644 SignalServiceKit/src/TestUtils/Factories.swift delete mode 100644 SignalServiceKit/src/TestUtils/FakeContactsManager.swift delete mode 100644 SignalServiceKit/src/TestUtils/MockSSKEnvironment.h delete mode 100644 SignalServiceKit/src/TestUtils/MockSSKEnvironment.m delete mode 100644 SignalServiceKit/src/TestUtils/NoopNotificationsManager.swift delete mode 100644 SignalServiceKit/src/TestUtils/OWSFakeCallMessageHandler.h delete mode 100644 SignalServiceKit/src/TestUtils/OWSFakeCallMessageHandler.m delete mode 100644 SignalServiceKit/src/TestUtils/OWSFakeContactsUpdater.h delete mode 100644 SignalServiceKit/src/TestUtils/OWSFakeContactsUpdater.m delete mode 100644 SignalServiceKit/src/TestUtils/OWSFakeMessageSender.h delete mode 100644 SignalServiceKit/src/TestUtils/OWSFakeMessageSender.m delete mode 100644 SignalServiceKit/src/TestUtils/OWSFakeNetworkManager.h delete mode 100644 SignalServiceKit/src/TestUtils/OWSFakeNetworkManager.m delete mode 100644 SignalServiceKit/src/TestUtils/OWSFakeProfileManager.h delete mode 100644 SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m delete mode 100644 SignalServiceKit/src/TestUtils/OWSMockSyncManager.swift delete mode 100644 SignalServiceKit/src/TestUtils/TestAppContext.h delete mode 100644 SignalServiceKit/src/TestUtils/TestAppContext.m delete mode 100644 SignalServiceKit/src/TestUtils/TestKeychainStorage.swift delete mode 100755 SignalServiceKit/src/Util/AppContext.h delete mode 100755 SignalServiceKit/src/Util/AppContext.m delete mode 100755 SignalServiceKit/src/Util/AppReadiness.h delete mode 100755 SignalServiceKit/src/Util/AppReadiness.m delete mode 100755 SignalServiceKit/src/Util/AppVersion.h delete mode 100755 SignalServiceKit/src/Util/AppVersion.m delete mode 100644 SignalServiceKit/src/Util/ByteParser.h delete mode 100644 SignalServiceKit/src/Util/ByteParser.m delete mode 100755 SignalServiceKit/src/Util/DataSource.h delete mode 100755 SignalServiceKit/src/Util/DataSource.m delete mode 100644 SignalServiceKit/src/Util/DeviceNames.swift delete mode 100644 SignalServiceKit/src/Util/FeatureFlags.swift delete mode 100644 SignalServiceKit/src/Util/FunctionalUtil.h delete mode 100644 SignalServiceKit/src/Util/FunctionalUtil.m delete mode 100644 SignalServiceKit/src/Util/JobQueue.swift delete mode 100644 SignalServiceKit/src/Util/LRUCache.swift delete mode 100644 SignalServiceKit/src/Util/MIMETypeUtil.h delete mode 100644 SignalServiceKit/src/Util/MIMETypeUtil.m delete mode 100644 SignalServiceKit/src/Util/MessageSender+Promise.swift delete mode 100644 SignalServiceKit/src/Util/NSArray+OWS.h delete mode 100644 SignalServiceKit/src/Util/NSArray+OWS.m delete mode 100644 SignalServiceKit/src/Util/NSData+Image.h delete mode 100644 SignalServiceKit/src/Util/NSData+Image.m delete mode 100644 SignalServiceKit/src/Util/NSError+MessageSending.h delete mode 100644 SignalServiceKit/src/Util/NSError+MessageSending.m delete mode 100644 SignalServiceKit/src/Util/NSNotificationCenter+OWS.h delete mode 100644 SignalServiceKit/src/Util/NSNotificationCenter+OWS.m delete mode 100644 SignalServiceKit/src/Util/NSRegularExpression+SSK.swift delete mode 100644 SignalServiceKit/src/Util/NSString+SSK.h delete mode 100644 SignalServiceKit/src/Util/NSString+SSK.m delete mode 100644 SignalServiceKit/src/Util/NSTimer+OWS.h delete mode 100644 SignalServiceKit/src/Util/NSTimer+OWS.m delete mode 100644 SignalServiceKit/src/Util/NSURLSessionDataTask+StatusCode.h delete mode 100644 SignalServiceKit/src/Util/NSURLSessionDataTask+StatusCode.m delete mode 100644 SignalServiceKit/src/Util/NSUserDefaults+OWS.h delete mode 100644 SignalServiceKit/src/Util/NSUserDefaults+OWS.m delete mode 100644 SignalServiceKit/src/Util/OWS2FAManager.h delete mode 100644 SignalServiceKit/src/Util/OWS2FAManager.m delete mode 100755 SignalServiceKit/src/Util/OWSAnalytics.h delete mode 100755 SignalServiceKit/src/Util/OWSAnalytics.m delete mode 100755 SignalServiceKit/src/Util/OWSAnalyticsEvents.h delete mode 100755 SignalServiceKit/src/Util/OWSAnalyticsEvents.m delete mode 100644 SignalServiceKit/src/Util/OWSBackgroundTask.h delete mode 100644 SignalServiceKit/src/Util/OWSBackgroundTask.m delete mode 100644 SignalServiceKit/src/Util/OWSBackupFragment.h delete mode 100644 SignalServiceKit/src/Util/OWSBackupFragment.m delete mode 100644 SignalServiceKit/src/Util/OWSDispatch.h delete mode 100644 SignalServiceKit/src/Util/OWSDispatch.m delete mode 100644 SignalServiceKit/src/Util/OWSError.h delete mode 100644 SignalServiceKit/src/Util/OWSError.m delete mode 100644 SignalServiceKit/src/Util/OWSFileSystem.h delete mode 100644 SignalServiceKit/src/Util/OWSFileSystem.m delete mode 100644 SignalServiceKit/src/Util/OWSMath.h delete mode 100644 SignalServiceKit/src/Util/OWSOperation.h delete mode 100644 SignalServiceKit/src/Util/OWSOperation.m delete mode 100644 SignalServiceKit/src/Util/OWSQueues.h delete mode 100644 SignalServiceKit/src/Util/OWSSyncManagerProtocol.h delete mode 100644 SignalServiceKit/src/Util/ParamParser.swift delete mode 100644 SignalServiceKit/src/Util/Promise+retainUntilComplete.swift delete mode 100644 SignalServiceKit/src/Util/ReverseDispatchQueue.swift delete mode 100755 SignalServiceKit/src/Util/SSKAsserts.h delete mode 100644 SignalServiceKit/src/Util/SSKPreferences.swift delete mode 100644 SignalServiceKit/src/Util/String+SSK.swift delete mode 100644 SignalServiceKit/src/Util/SwiftSingletons.swift delete mode 100644 SignalServiceKit/src/Util/TypingIndicators.swift delete mode 100644 SignalServiceKit/src/Util/UIImage+OWS.h delete mode 100644 SignalServiceKit/src/Util/UIImage+OWS.m delete mode 100644 SignalServiceKit/src/Util/WeakTimer.swift delete mode 100644 SignalServiceKit/src/Util/YapDatabase+Promise.swift delete mode 100644 SignalServiceKit/tests/Account/SignedPreKeyDeletionTests.m delete mode 100644 SignalServiceKit/tests/Contacts/OWSDisappearingMessagesConfigurationTest.m delete mode 100644 SignalServiceKit/tests/Contacts/SignalRecipientTest.m delete mode 100644 SignalServiceKit/tests/Contacts/TSContactThreadTest.m delete mode 100644 SignalServiceKit/tests/Contacts/TSGroupThreadTest.m delete mode 100644 SignalServiceKit/tests/Contacts/TSThreadTest.m delete mode 100644 SignalServiceKit/tests/Devices/OWSDeviceProvisionerTest.m delete mode 100644 SignalServiceKit/tests/Devices/OWSProvisioningCipherTest.m delete mode 100644 SignalServiceKit/tests/Messages/Interactions/TSMessageTest.m delete mode 100644 SignalServiceKit/tests/Messages/Interactions/TSOutgoingMessageTest.m delete mode 100644 SignalServiceKit/tests/Messages/OWSDisappearingMessageFinderTest.m delete mode 100644 SignalServiceKit/tests/Messages/OWSDisappearingMessagesJobTest.m delete mode 100644 SignalServiceKit/tests/Messages/OWSIncomingMessageFinderTest.m delete mode 100644 SignalServiceKit/tests/Messages/OWSLinkPreviewTest.swift delete mode 100644 SignalServiceKit/tests/Messages/OWSMessageManagerTest.m delete mode 100644 SignalServiceKit/tests/Messages/OWSMessageSenderTest.m delete mode 100644 SignalServiceKit/tests/Messages/OWSUDManagerTest.swift delete mode 100644 SignalServiceKit/tests/Network/MessageSendJobQueueTest.swift delete mode 100644 SignalServiceKit/tests/Network/MessageSenderJobRecordTest.swift delete mode 100644 SignalServiceKit/tests/SSKBaseTestObjC.h delete mode 100644 SignalServiceKit/tests/SSKBaseTestObjC.m delete mode 100644 SignalServiceKit/tests/SSKBaseTestSwift.swift delete mode 100644 SignalServiceKit/tests/SSKSwiftTests.swift delete mode 100644 SignalServiceKit/tests/Security/OWSFingerprintTest.m delete mode 100644 SignalServiceKit/tests/Storage/TSStorageIdentityKeyStoreTests.m delete mode 100644 SignalServiceKit/tests/Storage/TSStoragePreKeyStoreTests.m delete mode 100644 SignalServiceKit/tests/Util/DeviceNamesTest.swift delete mode 100644 SignalServiceKit/tests/Util/JobQueueTest.swift delete mode 100644 SignalServiceKit/tests/Util/TSMessageStorageTests.m diff --git a/Podfile b/Podfile index 43126a2da..ad8376163 100644 --- a/Podfile +++ b/Podfile @@ -10,20 +10,6 @@ def shared_pods # OWS Pods ### - pod 'SessionCoreKit', git: 'https://github.com/loki-project/session-ios-core-kit.git', testspecs: ["Tests"] # Fork of SignalCoreKit - # pod 'SignalCoreKit', path: '../SignalCoreKit', testspecs: ["Tests"] - - pod 'SessionAxolotlKit', git: 'https://github.com/loki-project/session-ios-protocol-kit.git', branch: 'master', testspecs: ["Tests"] # Fork of AxolotlKit - # pod 'AxolotlKit', path: '../SignalProtocolKit', testspecs: ["Tests"] - - pod 'SessionCurve25519Kit', git: 'https://github.com/loki-project/session-ios-curve-25519-kit', testspecs: ["Tests"] # Fork of Curve25519Kit - # pod 'Curve25519Kit', path: '../Curve25519Kit', testspecs: ["Tests"] - - pod 'SessionMetadataKit', git: 'https://github.com/loki-project/session-ios-metadata-kit', testspecs: ["Tests"] # Fork of SignalMetadataKit - # pod 'SignalMetadataKit', path: '../SignalMetadataKit', testspecs: ["Tests"] - - pod 'SessionServiceKit', path: '.', testspecs: ["Tests"] - # Project does not compile with PromiseKit 6.7.1 # see: https://github.com/mxcl/PromiseKit/issues/990 pod 'PromiseKit', "6.5.3" @@ -71,10 +57,6 @@ target 'Signal' do pod 'FeedKit', '~> 8.1', :inhibit_warnings => true pod 'NVActivityIndicatorView', '~> 4.7', :inhibit_warnings => true pod 'Sodium', '~> 0.8.0', :inhibit_warnings => true - - target 'SignalTests' do - inherit! :search_paths - end end target 'SignalShareExtension' do @@ -94,8 +76,16 @@ target 'LokiPushNotificationService' do end target 'SignalMessaging' do - project 'Signal' - shared_pods + pod 'AFNetworking', inhibit_warnings: true + pod 'CocoaLumberjack', :inhibit_warnings => true + pod 'CryptoSwift', :inhibit_warnings => true + pod 'Curve25519Kit', :inhibit_warnings => true + pod 'libPhoneNumber-iOS', :inhibit_warnings => true + pod 'Mantle', git: 'https://github.com/signalapp/Mantle', branch: 'signal-master', :inhibit_warnings => true + pod 'PromiseKit', :inhibit_warnings => true + pod 'PureLayout', '~> 3.1.4', :inhibit_warnings => true + pod 'YapDatabase/SQLCipher', :git => 'https://github.com/signalapp/YapDatabase.git', branch: 'signal-release', :inhibit_warnings => true + pod 'YYImage', git: 'https://github.com/signalapp/YYImage', :inhibit_warnings => true end target 'SignalUtilitiesKit' do diff --git a/Podfile.lock b/Podfile.lock index dc9ac5f8a..9c3602e67 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -41,83 +41,6 @@ PODS: - PureLayout (3.1.6) - Reachability (3.2) - SAMKeychain (1.5.3) - - SessionAxolotlKit (1.0.7): - - CocoaLumberjack - - SessionCoreKit (~> 1.0.0) - - SessionCurve25519Kit (~> 2.1.2) - - SessionHKDFKit (~> 0.0.5) - - SwiftProtobuf (~> 1.5.0) - - SessionAxolotlKit/Tests (1.0.7): - - CocoaLumberjack - - SessionCoreKit (~> 1.0.0) - - SessionCurve25519Kit (~> 2.1.2) - - SessionHKDFKit (~> 0.0.5) - - SwiftProtobuf (~> 1.5.0) - - SessionCoreKit (1.0.0): - - CocoaLumberjack - - GRKOpenSSLFramework - - SessionCoreKit/Tests (1.0.0): - - CocoaLumberjack - - GRKOpenSSLFramework - - SessionCurve25519Kit (2.1.3): - - CocoaLumberjack - - SessionCoreKit (~> 1.0.0) - - SessionCurve25519Kit/Tests (2.1.3): - - CocoaLumberjack - - SessionCoreKit (~> 1.0.0) - - SessionHKDFKit (0.0.5): - - CocoaLumberjack - - SessionCoreKit - - SessionMetadataKit (1.0.9): - - CocoaLumberjack - - CryptoSwift (~> 1.3) - - SessionAxolotlKit (~> 1.0.7) - - SessionCoreKit (~> 1.0.0) - - SessionCurve25519Kit (~> 2.1.2) - - SessionHKDFKit (~> 0.0.5) - - SwiftProtobuf (~> 1.5.0) - - SessionMetadataKit/Tests (1.0.9): - - CocoaLumberjack - - CryptoSwift (~> 1.3) - - SessionAxolotlKit (~> 1.0.7) - - SessionCoreKit (~> 1.0.0) - - SessionCurve25519Kit (~> 2.1.2) - - SessionHKDFKit (~> 0.0.5) - - SwiftProtobuf (~> 1.5.0) - - SessionServiceKit (1.0.0): - - AFNetworking - - CocoaLumberjack - - CryptoSwift (~> 1.3) - - GRKOpenSSLFramework - - libPhoneNumber-iOS - - Mantle - - PromiseKit (~> 6.0) - - Reachability - - SAMKeychain - - SessionAxolotlKit (~> 1.0.7) - - SessionCoreKit (~> 1.0.0) - - SessionCurve25519Kit (~> 2.1.3) - - SessionMetadataKit (~> 1.0.9) - - Starscream - - SwiftProtobuf (~> 1.5.0) - - YapDatabase/SQLCipher - - SessionServiceKit/Tests (1.0.0): - - AFNetworking - - CocoaLumberjack - - CryptoSwift (~> 1.3) - - GRKOpenSSLFramework - - libPhoneNumber-iOS - - Mantle - - PromiseKit (~> 6.0) - - Reachability - - SAMKeychain - - SessionAxolotlKit (~> 1.0.7) - - SessionCoreKit (~> 1.0.0) - - SessionCurve25519Kit (~> 2.1.3) - - SessionMetadataKit (~> 1.0.9) - - Starscream - - SwiftProtobuf (~> 1.5.0) - - YapDatabase/SQLCipher - Sodium (0.8.0) - SQLCipher (4.4.0): - SQLCipher/standard (= 4.4.0) @@ -214,16 +137,6 @@ DEPENDENCIES: - PureLayout (~> 3.1.4) - Reachability - SAMKeychain - - SessionAxolotlKit (from `https://github.com/loki-project/session-ios-protocol-kit.git`, branch `master`) - - SessionAxolotlKit/Tests (from `https://github.com/loki-project/session-ios-protocol-kit.git`, branch `master`) - - SessionCoreKit (from `https://github.com/loki-project/session-ios-core-kit.git`) - - SessionCoreKit/Tests (from `https://github.com/loki-project/session-ios-core-kit.git`) - - SessionCurve25519Kit (from `https://github.com/loki-project/session-ios-curve-25519-kit`) - - SessionCurve25519Kit/Tests (from `https://github.com/loki-project/session-ios-curve-25519-kit`) - - SessionMetadataKit (from `https://github.com/loki-project/session-ios-metadata-kit`) - - SessionMetadataKit/Tests (from `https://github.com/loki-project/session-ios-metadata-kit`) - - SessionServiceKit (from `.`) - - SessionServiceKit/Tests (from `.`) - Sodium (~> 0.8.0) - SQLCipher (>= 4.0.1) - SSZipArchive @@ -248,7 +161,6 @@ SPEC REPOS: - PureLayout - Reachability - SAMKeychain - - SessionHKDFKit - Sodium - SQLCipher - SSZipArchive @@ -259,17 +171,6 @@ EXTERNAL SOURCES: Mantle: :branch: signal-master :git: https://github.com/signalapp/Mantle - SessionAxolotlKit: - :branch: master - :git: https://github.com/loki-project/session-ios-protocol-kit.git - SessionCoreKit: - :git: https://github.com/loki-project/session-ios-core-kit.git - SessionCurve25519Kit: - :git: https://github.com/loki-project/session-ios-curve-25519-kit - SessionMetadataKit: - :git: https://github.com/loki-project/session-ios-metadata-kit - SessionServiceKit: - :path: "." Starscream: :branch: signal-release :git: https://github.com/signalapp/Starscream.git @@ -283,18 +184,6 @@ CHECKOUT OPTIONS: Mantle: :commit: b72c2d1e6132501db906de2cffa8ded7803c54f4 :git: https://github.com/signalapp/Mantle - SessionAxolotlKit: - :commit: be92fccb6152ee02c8c2658cb3c2e21201f119d1 - :git: https://github.com/loki-project/session-ios-protocol-kit.git - SessionCoreKit: - :commit: 0d66c90657b62cb66ecd2767c57408a951650f23 - :git: https://github.com/loki-project/session-ios-core-kit.git - SessionCurve25519Kit: - :commit: c3bc075d1e1c8339eebe2af184869de1a007d855 - :git: https://github.com/loki-project/session-ios-curve-25519-kit - SessionMetadataKit: - :commit: df787d84bb8adb23c10df669296dee8d7988e410 - :git: https://github.com/loki-project/session-ios-metadata-kit Starscream: :commit: b09ea163c3cb305152c65b299cb024610f52e735 :git: https://github.com/signalapp/Starscream.git @@ -320,12 +209,6 @@ SPEC CHECKSUMS: PureLayout: bd3c4ec3a3819ad387c99ebb72c6b129c3ed4d2d Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c - SessionAxolotlKit: f3558573a3cb52ebb921572f2f3b683da5eddad9 - SessionCoreKit: 778a3f6e3da788b43497734166646025b6392e88 - SessionCurve25519Kit: 9bb9afe199e4bc23578a4b15932ad2c57bd047b1 - SessionHKDFKit: b0f4e669411703ab925aba07491c5611564d1419 - SessionMetadataKit: d37afdc47d20c7046faa139a92e68fa99f76c95b - SessionServiceKit: b12afb3975b33a9579802111f948838861d914bb Sodium: 63c0ca312a932e6da481689537d4b35568841bdc SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072 SSZipArchive: 62d4947b08730e4cda640473b0066d209ff033c9 @@ -335,6 +218,6 @@ SPEC CHECKSUMS: YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: d78dc9a752cd3ce8f01fa327b8518dff3f5236d5 +PODFILE CHECKSUM: 99d57680804c51117e98de7f4d4c243750ed26e1 COCOAPODS: 1.10.0.rc.1 diff --git a/Pods b/Pods index e28da414f..323754e2f 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit e28da414f77b9cba508c92e90b16b815847cde7e +Subproject commit 323754e2fbc3d87c87538814d3695755818164ac diff --git a/SessionServiceKit.podspec b/SessionServiceKit.podspec deleted file mode 100644 index 252237898..000000000 --- a/SessionServiceKit.podspec +++ /dev/null @@ -1,61 +0,0 @@ -# -# Be sure to run `pod lib lint SignalServiceKit.podspec' to ensure this is a -# valid spec before submitting. -# -# Any lines starting with a # are optional, but their use is encouraged -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html -# - -Pod::Spec.new do |s| - s.name = "SessionServiceKit" - s.version = "1.0.0" - s.summary = "A Swift/Objective-C library for communicating with the Session messaging service." - - s.description = <<-DESC -A Swift/Objective-C library for communicating with the Session messaging service. - DESC - - s.homepage = "https://github.com/loki-project/session-ios" - s.license = 'GPLv3' - s.author = { "Niels Andriesse" => "niels@loki.network" } - s.source = { :git => "https://github.com/loki-project/session-ios.git", :tag => s.version.to_s } - s.social_media_url = 'https://getsession.org/' - - s.platform = :ios, '10.0' - #s.ios.deployment_target = '9.0' - #s.osx.deployment_target = '10.9' - s.requires_arc = true - s.source_files = 'SignalServiceKit/src/**/*.{h,m,mm,swift}' - - # We want to use modules to avoid clobbering CocoaLumberjack macros defined - # by other OWS modules which *also* import CocoaLumberjack. But because we - # also use Objective-C++, modules are disabled unless we explicitly enable - # them - s.compiler_flags = "-fcxx-modules" - - s.prefix_header_file = 'SignalServiceKit/src/TSPrefix.h' - s.xcconfig = { 'OTHER_CFLAGS' => '$(inherited) -DSQLITE_HAS_CODEC' } - - s.resources = ["SignalServiceKit/Resources/Certificates/*", "SignalServiceKit/src/Loki/Mnemonic/*.txt"] - - s.dependency 'SessionCurve25519Kit', '~> 2.1.3' - s.dependency 'CocoaLumberjack' - s.dependency 'CryptoSwift', '~> 1.3' - s.dependency 'AFNetworking' - s.dependency 'SessionAxolotlKit', '~> 1.0.7' - s.dependency 'Mantle' - s.dependency 'YapDatabase/SQLCipher' - s.dependency 'Starscream' - s.dependency 'libPhoneNumber-iOS' - s.dependency 'GRKOpenSSLFramework' - s.dependency 'SAMKeychain' - s.dependency 'Reachability' - s.dependency 'SwiftProtobuf', '~> 1.5.0' - s.dependency 'SessionCoreKit', '~> 1.0.0' - s.dependency 'SessionMetadataKit', '~> 1.0.9' - s.dependency 'PromiseKit', '~> 6.0' - - s.test_spec 'Tests' do |test_spec| - test_spec.source_files = 'SignalServiceKit/tests/**/*.{h,m,swift}' - end -end diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index aa6d3e741..03eb3a462 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -27,7 +27,6 @@ 340872D622397E6800CB25B0 /* AttachmentCaptionToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872D522397E6800CB25B0 /* AttachmentCaptionToolbar.swift */; }; 340872D822397F4600CB25B0 /* AttachmentCaptionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872D722397F4500CB25B0 /* AttachmentCaptionViewController.swift */; }; 340872DA22397FEB00CB25B0 /* AttachmentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 340872D922397FEB00CB25B0 /* AttachmentTextView.swift */; }; - 340B02BA1FA0D6C700F9CFEC /* ConversationViewItemTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 340B02B91FA0D6C700F9CFEC /* ConversationViewItemTest.m */; }; 340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87B204DAC8C007AEB0F /* NotificationSettingsOptionsViewController.m */; }; 340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87C204DAC8C007AEB0F /* NotificationSettingsViewController.m */; }; 340FC8AB204DAC8D007AEB0F /* DomainFrontingCountryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC87D204DAC8C007AEB0F /* DomainFrontingCountryViewController.m */; }; @@ -50,7 +49,6 @@ 34129B8621EF877A005457A8 /* LinkPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34129B8521EF8779005457A8 /* LinkPreviewView.swift */; }; 341341EF2187467A00192D59 /* ConversationViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 341341EE2187467900192D59 /* ConversationViewModel.m */; }; 341F2C0F1F2B8AE700D07D6B /* DebugUIMisc.m in Sources */ = {isa = PBXBuildFile; fileRef = 341F2C0E1F2B8AE700D07D6B /* DebugUIMisc.m */; }; - 3421981C21061D2E00C57195 /* ByteParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3421981B21061D2E00C57195 /* ByteParserTest.swift */; }; 34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34277A5C20751BDC006049F2 /* OWSQuotedMessageView.m */; }; 3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */; }; 342950822124C9750000B063 /* OWSTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 3429507E2124C9740000B063 /* OWSTextField.m */; }; @@ -134,13 +132,9 @@ 3478506B1FD9B78A007B8332 /* NoopCallMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347850671FD9B78A007B8332 /* NoopCallMessageHandler.swift */; }; 347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 3478506F1FDAEB16007B8332 /* OWSUserProfile.m */; }; 347850721FDAEB17007B8332 /* OWSUserProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 347850701FDAEB16007B8332 /* OWSUserProfile.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 34843B2421432293004DED45 /* SignalBaseTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34843B2221432292004DED45 /* SignalBaseTest.m */; }; - 34843B26214327C9004DED45 /* OWSOrphanDataCleanerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34843B25214327C9004DED45 /* OWSOrphanDataCleanerTest.m */; }; - 34843B2C214FE296004DED45 /* MockEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 34843B2A214FE295004DED45 /* MockEnvironment.m */; }; 348570A820F67575004FF32B /* OWSMessageHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 348570A620F67574004FF32B /* OWSMessageHeaderView.m */; }; 3488F9362191CC4000E524CC /* ConversationMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3488F9352191CC4000E524CC /* ConversationMediaView.swift */; }; 348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348BB25C20A0C5530047AEC2 /* ContactShareViewHelper.swift */; }; - 3491D9A121022DB7001EF5A1 /* CDSSigningCertificateTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3491D9A021022DB7001EF5A1 /* CDSSigningCertificateTest.m */; }; 3496744D2076768700080B5F /* OWSMessageBubbleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3496744C2076768700080B5F /* OWSMessageBubbleView.m */; }; 3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496744E2076ACCE00080B5F /* LongTextViewController.swift */; }; 3496955C219B605E00DCFE74 /* ImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34969559219B605E00DCFE74 /* ImagePickerController.swift */; }; @@ -226,8 +220,6 @@ 34BBC85A220C7ADA00857249 /* ImageEditorTextItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BBC855220C7ADA00857249 /* ImageEditorTextItem.swift */; }; 34BBC85B220C7ADA00857249 /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BBC856220C7ADA00857249 /* OrderedDictionary.swift */; }; 34BBC85D220D19D600857249 /* ImageEditorPanGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BBC85C220D19D600857249 /* ImageEditorPanGestureRecognizer.swift */; }; - 34BBC861220E883300857249 /* ImageEditorModelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BBC85F220E883200857249 /* ImageEditorModelTest.swift */; }; - 34BBC862220E883300857249 /* ImageEditorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BBC860220E883200857249 /* ImageEditorTest.swift */; }; 34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2A1F74C12700D7438D /* DebugUIStress.m */; }; 34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2D1F7ABCE000D7438D /* GifPickerViewController.swift */; }; 34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BECE2F1F7ABCF800D7438D /* GifPickerLayout.swift */; }; @@ -241,10 +233,6 @@ 34C3C7932040B0DD0000134C /* OWSAudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 34C3C7912040B0DC0000134C /* OWSAudioPlayer.m */; }; 34C4E2572118957600BEA353 /* OWSWebRTCDataProtos.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C4E2552118957600BEA353 /* OWSWebRTCDataProtos.pb.swift */; }; 34C4E2582118957600BEA353 /* WebRTCProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C4E2562118957600BEA353 /* WebRTCProto.swift */; }; - 34C6B0A91FA0E46F00D35993 /* test-gif.gif in Resources */ = {isa = PBXBuildFile; fileRef = 34C6B0A51FA0E46F00D35993 /* test-gif.gif */; }; - 34C6B0AB1FA0E46F00D35993 /* test-mp3.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 34C6B0A71FA0E46F00D35993 /* test-mp3.mp3 */; }; - 34C6B0AC1FA0E46F00D35993 /* test-mp4.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 34C6B0A81FA0E46F00D35993 /* test-mp4.mp4 */; }; - 34C6B0AE1FA0E4AA00D35993 /* test-jpg.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 34C6B0AD1FA0E4AA00D35993 /* test-jpg.jpg */; }; 34CA631B2097806F00E526A0 /* OWSContactShareView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CA631A2097806E00E526A0 /* OWSContactShareView.m */; }; 34CF0787203E6B78005C4D61 /* busy_tone_ansi.caf in Resources */ = {isa = PBXBuildFile; fileRef = 34CF0783203E6B77005C4D61 /* busy_tone_ansi.caf */; }; 34CF0788203E6B78005C4D61 /* ringback_tone_ansi.caf in Resources */ = {isa = PBXBuildFile; fileRef = 34CF0784203E6B77005C4D61 /* ringback_tone_ansi.caf */; }; @@ -274,7 +262,6 @@ 34D920E720E179C200D51158 /* OWSMessageFooterView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D920E620E179C200D51158 /* OWSMessageFooterView.m */; }; 34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */; }; 34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */; }; - 34DB0BED2011548B007B313F /* OWSDatabaseConverterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DB0BEC2011548B007B313F /* OWSDatabaseConverterTest.m */; }; 34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DBEFFF206BD5A400025978 /* OWSMessageTextView.m */; }; 34DBF004206BD5A500025978 /* OWSBubbleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DBF001206BD5A500025978 /* OWSBubbleView.m */; }; 34DBF007206C3CB200025978 /* OWSBubbleShapeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DBF006206C3CB200025978 /* OWSBubbleShapeView.m */; }; @@ -283,7 +270,6 @@ 34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */; }; 34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */; }; 34E88D262098C5AE00A608F4 /* ContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E88D252098C5AE00A608F4 /* ContactViewController.swift */; }; - 34E8A8D12085238A00B272B1 /* ProtoParsingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E8A8D02085238900B272B1 /* ProtoParsingTest.m */; }; 34EA69402194933900702471 /* MediaDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34EA693F2194933900702471 /* MediaDownloadView.swift */; }; 34EA69422194DE8000702471 /* MediaUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34EA69412194DE7F00702471 /* MediaUploadView.swift */; }; 34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */; }; @@ -329,7 +315,6 @@ 452B999020A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452B998F20A34B6B006F2F9E /* AddContactShareToExistingContactViewController.swift */; }; 452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; }; 452C7CA72037628B003D51A5 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170D51E315310003FC1F2 /* Weak.swift */; }; - 452D1AF12081059C00A67F7F /* StringAdditionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452D1AF02081059C00A67F7F /* StringAdditionsTest.swift */; }; 452EC6DF205E9E30000E787C /* MediaGalleryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452EC6DE205E9E30000E787C /* MediaGalleryViewController.swift */; }; 452EC6E1205FF5DC000E787C /* Bench.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452EC6E0205FF5DC000E787C /* Bench.swift */; }; 452ECA4D1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */; }; @@ -340,22 +325,16 @@ 453518991FC63DBF00210559 /* SignalMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 453518921FC63DBF00210559 /* SignalMessaging.framework */; }; 4535189A1FC63DBF00210559 /* SignalMessaging.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 453518921FC63DBF00210559 /* SignalMessaging.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 453518A21FC63E2900210559 /* SignalMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 453518921FC63DBF00210559 /* SignalMessaging.framework */; }; - 45360B901F9527DA00FA666C /* SearcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45360B8F1F9527DA00FA666C /* SearcherTest.swift */; }; - 45360B911F952AA900FA666C /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */; }; 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4539B5851F79348F007141FF /* PushRegistrationManager.swift */; }; 4541B71D209D3B7A0008608F /* ContactShareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4541B71A209D2DAE0008608F /* ContactShareViewModel.swift */; }; 4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF53208D40AC007B4E76 /* LoadingViewController.swift */; }; 454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454A84032059C787008B8C75 /* MediaTileViewController.swift */; }; 454A965A1FD6017E008D2A0E /* SignalAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D913491F62D4A500722898 /* SignalAttachment.swift */; }; - 454EBAB41F2BE14C00ACE0BB /* OWSAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */; }; 4551DB5A205C562300C8AE75 /* Collection+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4551DB59205C562300C8AE75 /* Collection+OWS.swift */; }; 4556FA681F54AA9500AF40DD /* DebugUIProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4556FA671F54AA9500AF40DD /* DebugUIProfile.swift */; }; 455A16DD1F1FEA0000F86704 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 455A16DB1F1FEA0000F86704 /* Metal.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 455A16DE1F1FEA0000F86704 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 455A16DC1F1FEA0000F86704 /* MetalKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 455AC69E1F4F8B0300134004 /* ImageCacheTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 455AC69D1F4F8B0300134004 /* ImageCacheTest.swift */; }; 45638BDC1F3DD0D400128435 /* DebugUICalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45638BDB1F3DD0D400128435 /* DebugUICalling.swift */; }; - 45666F581D9B2880008FE134 /* OWSScrubbingLogFormatterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 45666F571D9B2880008FE134 /* OWSScrubbingLogFormatterTest.m */; }; - 456F6E2F1E261D1000FD2210 /* PeerConnectionClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 456F6E2E1E261D1000FD2210 /* PeerConnectionClientTest.swift */; }; 4574A5D61DD6704700C6B692 /* CallService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4574A5D51DD6704700C6B692 /* CallService.swift */; }; 4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */ = {isa = PBXBuildFile; fileRef = 4579431D1E7C8CE9008ED0C0 /* Pastelog.m */; }; 45794E861E00620000066731 /* CallUIAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45794E851E00620000066731 /* CallUIAdapter.swift */; }; @@ -365,7 +344,6 @@ 4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4585C4671ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift */; }; 458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458DE9D51DEE3FD00071BB03 /* PeerConnectionClient.swift */; }; 458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */; }; - 458E383A1D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 458E38391D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m */; }; 459311FC1D75C948008DD4F0 /* OWSDeviceTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 459311FB1D75C948008DD4F0 /* OWSDeviceTableViewCell.m */; }; 459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 459B775A207BA3A80071D0AB /* OWSQuotedReplyModel.m */; }; 459B775D207BA4810071D0AB /* OWSQuotedReplyModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 459B7759207BA3A80071D0AB /* OWSQuotedReplyModel.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -411,7 +389,6 @@ 45DDA6242090CEB500DE97F8 /* ConversationHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DDA6232090CEB500DE97F8 /* ConversationHeaderView.swift */; }; 45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */; }; 45E5A6991F61E6DE001E4A8A /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */; }; - 45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */; }; 45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */; }; 45F32C222057297A00A300D5 /* MediaDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 45B9EE9B200E91FB005D2F2D /* MediaDetailViewController.m */; }; 45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F32C1D205718B000A300D5 /* MediaPageViewController.swift */; }; @@ -424,7 +401,6 @@ 45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FBC5D01DF8592E00E9B410 /* SignalCall.swift */; }; 4AC4EA13C8A444455DAB351F /* Pods_SignalMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */; }; 4C04392A220A9EC800BAEA63 /* VoiceNoteLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C043929220A9EC800BAEA63 /* VoiceNoteLock.swift */; }; - 4C04F58421C860C50090D0BB /* MantlePerfTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C04F58321C860C50090D0BB /* MantlePerfTest.swift */; }; 4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */; }; 4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */; }; 4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */; }; @@ -435,8 +411,6 @@ 4C2F454F214C00E1004871FF /* AvatarTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2F454E214C00E1004871FF /* AvatarTableViewCell.swift */; }; 4C3E245C21F29FCE000AE092 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5F792211E1F06008C2708 /* Toast.swift */; }; 4C3E245D21F2B395000AE092 /* DirectionalPanGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */; }; - 4C3EF7FD2107DDEE0007EBF7 /* ParamParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF7FC2107DDEE0007EBF7 /* ParamParserTest.swift */; }; - 4C3EF802210918740007EBF7 /* SSKProtoEnvelopeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */; }; 4C4AE6A1224AF35700D4AF6F /* SendMediaNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */; }; 4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */; }; 4C586926224FAB83003FD070 /* AVAudioSession+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C586925224FAB83003FD070 /* AVAudioSession+OWS.m */; }; @@ -473,14 +447,8 @@ 7BF3FF002505B8E400609570 /* PlaceholderIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF3FEFF2505B8E400609570 /* PlaceholderIcon.swift */; }; 945AA2B82B621254F69FA9E8 /* Pods_SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9117261809D69B3D7C26B8F1 /* Pods_SessionUtilitiesKit.framework */; }; 9EE44C6B4D4A069B86112387 /* Pods_SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9559C3068280BA2383F547F7 /* Pods_SessionSnodeKit.framework */; }; - A10FDF79184FB4BB007FF963 /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; }; A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; }; - A123C14916F902EE000AE905 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A163E8AA16F3F6A90094D68B /* Security.framework */; }; A163E8AB16F3F6AA0094D68B /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A163E8AA16F3F6A90094D68B /* Security.framework */; }; - A194D3B917A08CD1004BD3A9 /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4D17A0652C000A904E /* AddressBook.framework */; }; - A194D3BA17A08CD5004BD3A9 /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4F17A06537000A904E /* AddressBookUI.framework */; }; - A1A018521805C5E800A052A6 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; }; - A1A018531805C60D00A052A6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A091169C9E5E00537ABF /* CoreGraphics.framework */; }; A1C32D5017A06538000A904E /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4F17A06537000A904E /* AddressBookUI.framework */; }; A1C32D5117A06544000A904E /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4D17A0652C000A904E /* AddressBook.framework */; }; A33A4BA9D050805FE156E3ED /* Pods_SessionProtocolKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2183DCA28E0620BC73FCC554 /* Pods_SessionProtocolKit.framework */; }; @@ -498,7 +466,6 @@ B10C9B611A7049EC00ECA2BF /* play_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = B10C9B5D1A7049EC00ECA2BF /* play_icon.png */; }; B10C9B621A7049EC00ECA2BF /* play_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B10C9B5E1A7049EC00ECA2BF /* play_icon@2x.png */; }; B3E0C9C6F1633B1ABCE5AD0B /* Pods_SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D547348A367C8A14D37FC0 /* Pods_SignalUtilitiesKit.framework */; }; - B60EDE041A05A01700D73516 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B60EDE031A05A01700D73516 /* AudioToolbox.framework */; }; B633C5861A1D190B0059AC12 /* call@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B633C5041A1D190B0059AC12 /* call@2x.png */; }; B633C58D1A1D190B0059AC12 /* contact_default_feed.png in Resources */ = {isa = PBXBuildFile; fileRef = B633C50B1A1D190B0059AC12 /* contact_default_feed.png */; }; B633C59D1A1D190B0059AC12 /* endcall@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B633C51B1A1D190B0059AC12 /* endcall@2x.png */; }; @@ -506,12 +473,8 @@ B633C5C41A1D190B0059AC12 /* mute_on@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B633C5421A1D190B0059AC12 /* mute_on@2x.png */; }; B633C5CE1A1D190B0059AC12 /* quit@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B633C54C1A1D190B0059AC12 /* quit@2x.png */; }; B633C5D21A1D190B0059AC12 /* savephoto@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B633C5501A1D190B0059AC12 /* savephoto@2x.png */; }; - B660F6D41C29868000687D6E /* whisperFake.cer in Resources */ = {isa = PBXBuildFile; fileRef = B660F69F1C29868000687D6E /* whisperFake.cer */; }; - B660F6DB1C29868000687D6E /* FunctionalUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B660F6AD1C29868000687D6E /* FunctionalUtilTest.m */; }; - B660F6E01C29868000687D6E /* UtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B660F6B41C29868000687D6E /* UtilTest.m */; }; B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B66DBF4919D5BBC8006EA940 /* Images.xcassets */; }; B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B67EBF5C19194AC60084CCFD /* Settings.bundle */; }; - B69CD25119773E79005CE69A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B69CD25019773E79005CE69A /* XCTest.framework */; }; B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6B226961BE4B7D200860F4D /* ContactsUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; B6F509971AA53F760068F56A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; }; @@ -742,7 +705,7 @@ C33FDCA7255A582000E217F9 /* SSKMessageSenderJobRecord.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDAED255A580500E217F9 /* SSKMessageSenderJobRecord.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDCA8255A582000E217F9 /* OWSFingerprintBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAEE255A580500E217F9 /* OWSFingerprintBuilder.m */; }; C33FDCA9255A582000E217F9 /* NSData+Image.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAEF255A580500E217F9 /* NSData+Image.m */; }; - C33FDCAA255A582000E217F9 /* ContactsUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDAF0255A580500E217F9 /* ContactsUpdater.h */; }; + C33FDCAA255A582000E217F9 /* ContactsUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDAF0255A580500E217F9 /* ContactsUpdater.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDCAB255A582000E217F9 /* OWSThumbnailService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAF1255A580500E217F9 /* OWSThumbnailService.swift */; }; C33FDCAC255A582000E217F9 /* ProxiedContentDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAF2255A580500E217F9 /* ProxiedContentDownloader.swift */; }; C33FDCAD255A582000E217F9 /* OWSPrimaryStorage+SessionStore.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAF3255A580500E217F9 /* OWSPrimaryStorage+SessionStore.m */; }; @@ -891,7 +854,7 @@ C33FDD45255A582000E217F9 /* Storage+SessionManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB8B255A581200E217F9 /* Storage+SessionManagement.swift */; }; C33FDD46255A582000E217F9 /* TSInvalidIdentityKeyErrorMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB8C255A581200E217F9 /* TSInvalidIdentityKeyErrorMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD47255A582000E217F9 /* DeviceLinkingSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB8D255A581200E217F9 /* DeviceLinkingSessionDelegate.swift */; }; - C33FDD48255A582000E217F9 /* OWSContact+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB8E255A581200E217F9 /* OWSContact+Private.h */; }; + C33FDD48255A582000E217F9 /* OWSContact+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB8E255A581200E217F9 /* OWSContact+Private.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB8F255A581200E217F9 /* ParamParser.swift */; }; C33FDD4A255A582000E217F9 /* OWSMessageDecrypter.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB90255A581200E217F9 /* OWSMessageDecrypter.m */; }; C33FDD4B255A582000E217F9 /* ProtoUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB91255A581200E217F9 /* ProtoUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -973,7 +936,7 @@ C33FDD9E255A582000E217F9 /* OWSOutgoingSyncMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBE4255A581A00E217F9 /* OWSOutgoingSyncMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD9F255A582000E217F9 /* OWSDevicesService.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBE5255A581A00E217F9 /* OWSDevicesService.m */; }; C33FDDA0255A582000E217F9 /* OWSOutgoingNullMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBE6255A581A00E217F9 /* OWSOutgoingNullMessage.m */; }; - C33FDDA1255A582000E217F9 /* NSTimer+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBE7255A581A00E217F9 /* NSTimer+OWS.h */; }; + C33FDDA1255A582000E217F9 /* NSTimer+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBE7255A581A00E217F9 /* NSTimer+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDDA2255A582000E217F9 /* Storage+OnionRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBE8255A581A00E217F9 /* Storage+OnionRequests.swift */; }; C33FDDA3255A582000E217F9 /* TSInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBE9255A581A00E217F9 /* TSInteraction.m */; }; C33FDDA4255A582000E217F9 /* SessionRequestMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBEA255A581A00E217F9 /* SessionRequestMessage.swift */; }; @@ -1064,6 +1027,10 @@ C36B8707243C50C60049991D /* SignalMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 453518921FC63DBF00210559 /* SignalMessaging.framework */; }; C38EEF0A255B49A8007E1867 /* SSKProtoEnvelope+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EEF09255B49A8007E1867 /* SSKProtoEnvelope+Conversion.swift */; }; C38EEFD6255B5BA2007E1867 /* OldSnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EEFD5255B5BA2007E1867 /* OldSnodeAPI.swift */; }; + C38EF00C255B61CC007E1867 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; + C38EF00D255B61D3007E1867 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; + C38EF00E255B61DC007E1867 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; + C38EF1A4255B67A2007E1867 /* SessionProtocolKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A8622553B41A00C340D1 /* SessionProtocolKit.framework */; }; C396DAEF2518408B00FF6DC5 /* ParsingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C396DAE82518408900FF6DC5 /* ParsingState.swift */; }; C396DAF02518408B00FF6DC5 /* String+Lines.swift in Sources */ = {isa = PBXBuildFile; fileRef = C396DAE92518408A00FF6DC5 /* String+Lines.swift */; }; C396DAF12518408B00FF6DC5 /* EnumeratedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C396DAEA2518408A00FF6DC5 /* EnumeratedView.swift */; }; @@ -1246,22 +1213,14 @@ C3DFFAC823E970080058DAF8 /* OpenGroupSuggestionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC723E970080058DAF8 /* OpenGroupSuggestionSheet.swift */; }; C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; }; C3E7134F251C867C009649BB /* Sodium+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E7134E251C867C009649BB /* Sodium+Conversion.swift */; }; - CC875800737563D6891B741D /* Pods_SignalTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 748A5CAEDD7C919FC64C6807 /* Pods_SignalTests.framework */; }; - D202868116DBE0E7009068E9 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */; }; - D202868216DBE0F4009068E9 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */; }; - D202868316DBE0FC009068E9 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; - D202868416DBE108009068E9 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1FDCBEE16DAA6C300868894 /* AVFoundation.framework */; }; D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; D2179CFE16BB0B480006F3AB /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */; }; D221A08E169C9E5E00537ABF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A08D169C9E5E00537ABF /* UIKit.framework */; }; D221A090169C9E5E00537ABF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A08F169C9E5E00537ABF /* Foundation.framework */; }; D221A09A169C9E5E00537ABF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D221A099169C9E5E00537ABF /* main.m */; }; - D221A0AD169C9E5F00537ABF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A08D169C9E5E00537ABF /* UIKit.framework */; }; - D221A0AE169C9E5F00537ABF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A08F169C9E5E00537ABF /* Foundation.framework */; }; D221A0E8169DFFC500537ABF /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A0E7169DFFC500537ABF /* AVFoundation.framework */; }; D24B5BD5169F568C00681372 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D24B5BD4169F568C00681372 /* AudioToolbox.framework */; }; D2AEACDC16C426DA00C364C0 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */; }; - E1368CBE18A1C36B00109378 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; }; EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */ = {isa = PBXBuildFile; fileRef = EF764C341DB67CC5000D9A87 /* UIViewController+Permissions.m */; }; FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; }; FC5CDF391A3393DD00B47253 /* error_white@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FC5CDF371A3393DD00B47253 /* error_white@2x.png */; }; @@ -1278,13 +1237,6 @@ remoteGlobalIDString = 453518911FC63DBF00210559; remoteInfo = SignalMessaging; }; - 3478506D1FD9CFF4007B8332 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D221A080169C9E5E00537ABF /* Project object */; - proxyType = 1; - remoteGlobalIDString = 453518911FC63DBF00210559; - remoteInfo = SignalMessaging; - }; 453518701FC635DD00210559 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D221A080169C9E5E00537ABF /* Project object */; @@ -1306,13 +1258,6 @@ remoteGlobalIDString = 7BC01A3A241F40AB00BC7C55; remoteInfo = LokiPushNotificationService; }; - B6AFCEBA19A93DA60098CFCB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D221A080169C9E5E00537ABF /* Project object */; - proxyType = 1; - remoteGlobalIDString = D221A088169C9E5E00537ABF; - remoteInfo = Signal; - }; C331FF202558F9D300070591 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D221A080169C9E5E00537ABF /* Project object */; @@ -2757,7 +2702,6 @@ D221A095169C9E5E00537ABF /* Signal-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Signal-Info.plist"; sourceTree = ""; }; D221A099169C9E5E00537ABF /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; D221A09B169C9E5E00537ABF /* Signal-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Prefix.pch"; sourceTree = ""; }; - D221A0AA169C9E5F00537ABF /* SignalTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SignalTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D221A0E7169DFFC500537ABF /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = ../../../../../../System/Library/Frameworks/AVFoundation.framework; sourceTree = ""; }; D24B5BD4169F568C00681372 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = ../../../../../../System/Library/Frameworks/AudioToolbox.framework; sourceTree = ""; }; D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; @@ -2783,6 +2727,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C38EF00E255B61DC007E1867 /* SignalUtilitiesKit.framework in Frameworks */, C3402FE52559036600EA6424 /* SessionUIKit.framework in Frameworks */, 453518A21FC63E2900210559 /* SignalMessaging.framework in Frameworks */, 2AE2882E4C2B96BFFF9EE27C /* Pods_SignalShareExtension.framework in Frameworks */, @@ -2793,6 +2738,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C38EF1A4255B67A2007E1867 /* SessionProtocolKit.framework in Frameworks */, + C38EF00D255B61D3007E1867 /* SignalUtilitiesKit.framework in Frameworks */, C331003D255900F200070591 /* SessionUIKit.framework in Frameworks */, 4AC4EA13C8A444455DAB351F /* Pods_SignalMessaging.framework in Frameworks */, ); @@ -2802,6 +2749,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C38EF00C255B61CC007E1867 /* SignalUtilitiesKit.framework in Frameworks */, C36B8707243C50C60049991D /* SignalMessaging.framework in Frameworks */, 390650A6D345BFE01E006DB0 /* Pods_LokiPushNotificationService.framework in Frameworks */, ); @@ -2867,6 +2815,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C33FD9B2255A548A00E217F9 /* SignalUtilitiesKit.framework in Frameworks */, 4CC1ECF9211A47CE00CC13BE /* StoreKit.framework in Frameworks */, 455A16DD1F1FEA0000F86704 /* Metal.framework in Frameworks */, 455A16DE1F1FEA0000F86704 /* MetalKit.framework in Frameworks */, @@ -2893,7 +2842,6 @@ C331FF222558F9D300070591 /* SessionUIKit.framework in Frameworks */, D2179CFE16BB0B480006F3AB /* SystemConfiguration.framework in Frameworks */, D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */, - C33FD9B2255A548A00E217F9 /* SignalUtilitiesKit.framework in Frameworks */, D221A08E169C9E5E00537ABF /* UIKit.framework in Frameworks */, D221A090169C9E5E00537ABF /* Foundation.framework in Frameworks */, D221A0E8169DFFC500537ABF /* AVFoundation.framework in Frameworks */, @@ -2902,29 +2850,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - D221A0A6169C9E5F00537ABF /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - B60EDE041A05A01700D73516 /* AudioToolbox.framework in Frameworks */, - B69CD25119773E79005CE69A /* XCTest.framework in Frameworks */, - E1368CBE18A1C36B00109378 /* MessageUI.framework in Frameworks */, - A10FDF79184FB4BB007FF963 /* MediaPlayer.framework in Frameworks */, - A1A018531805C60D00A052A6 /* CoreGraphics.framework in Frameworks */, - A1A018521805C5E800A052A6 /* QuartzCore.framework in Frameworks */, - A123C14916F902EE000AE905 /* Security.framework in Frameworks */, - A194D3BA17A08CD5004BD3A9 /* AddressBookUI.framework in Frameworks */, - A194D3B917A08CD1004BD3A9 /* AddressBook.framework in Frameworks */, - D202868416DBE108009068E9 /* AVFoundation.framework in Frameworks */, - D202868316DBE0FC009068E9 /* CoreTelephony.framework in Frameworks */, - D202868216DBE0F4009068E9 /* SystemConfiguration.framework in Frameworks */, - D202868116DBE0E7009068E9 /* CFNetwork.framework in Frameworks */, - D221A0AD169C9E5F00537ABF /* UIKit.framework in Frameworks */, - D221A0AE169C9E5F00537ABF /* Foundation.framework in Frameworks */, - CC875800737563D6891B741D /* Pods_SignalTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -5159,7 +5084,6 @@ isa = PBXGroup; children = ( D221A089169C9E5E00537ABF /* Session.app */, - D221A0AA169C9E5F00537ABF /* SignalTests.xctest */, 453518681FC635DD00210559 /* SignalShareExtension.appex */, 453518921FC63DBF00210559 /* SignalMessaging.framework */, 7BC01A3B241F40AB00BC7C55 /* LokiPushNotificationService.appex */, @@ -5807,28 +5731,6 @@ productReference = D221A089169C9E5E00537ABF /* Session.app */; productType = "com.apple.product-type.application"; }; - D221A0A9169C9E5F00537ABF /* SignalTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = D221A0BF169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "SignalTests" */; - buildPhases = ( - 6565655F4068F9E5CDC5687F /* [CP] Check Pods Manifest.lock */, - D221A0A5169C9E5F00537ABF /* Sources */, - D221A0A6169C9E5F00537ABF /* Frameworks */, - D221A0A7169C9E5F00537ABF /* Resources */, - B4E9B04E862FB64FC9A8F79B /* [CP] Embed Pods Frameworks */, - 451DE9FB1DC18D4500810E42 /* [Carthage] Copy Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 3478506E1FD9CFF4007B8332 /* PBXTargetDependency */, - B6AFCEBB19A93DA60098CFCB /* PBXTargetDependency */, - ); - name = SignalTests; - productName = RedPhoneTests; - productReference = D221A0AA169C9E5F00537ABF /* SignalTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -5938,12 +5840,6 @@ }; }; }; - D221A0A9169C9E5F00537ABF = { - DevelopmentTeam = 8SQ45X653X; - LastSwiftMigration = 1020; - ProvisioningStyle = Automatic; - TestTargetID = D221A088169C9E5E00537ABF; - }; }; }; buildConfigurationList = D221A083169C9E5E00537ABF /* Build configuration list for PBXProject "Signal" */; @@ -5972,7 +5868,6 @@ projectRoot = ""; targets = ( D221A088169C9E5E00537ABF /* Signal */, - D221A0A9169C9E5F00537ABF /* SignalTests */, 453518671FC635DD00210559 /* SignalShareExtension */, 453518911FC63DBF00210559 /* SignalMessaging */, 7BC01A3A241F40AB00BC7C55 /* LokiPushNotificationService */, @@ -6137,18 +6032,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - D221A0A7169C9E5F00537ABF /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 34C6B0AE1FA0E4AA00D35993 /* test-jpg.jpg in Resources */, - B660F6D41C29868000687D6E /* whisperFake.cer in Resources */, - 34C6B0A91FA0E46F00D35993 /* test-gif.gif in Resources */, - 34C6B0AC1FA0E46F00D35993 /* test-mp4.mp4 in Resources */, - 34C6B0AB1FA0E46F00D35993 /* test-mp3.mp3 in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -6239,21 +6122,6 @@ shellPath = /bin/sh; shellScript = "/usr/local/bin/carthage copy-frameworks\n"; }; - 451DE9FB1DC18D4500810E42 /* [Carthage] Copy Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/ThirdParty/WebRTC/Build/WebRTC.framework", - ); - name = "[Carthage] Copy Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/usr/local/bin/carthage copy-frameworks\n"; - }; 4B4609DACEC6E462A2394D2F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -6287,30 +6155,24 @@ "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", "${BUILT_PRODUCTS_DIR}/FeedKit/FeedKit.framework", - "${PODS_ROOT}/GRKOpenSSLFramework/OpenSSL-iOS/bin/openssl.framework", "${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework", "${BUILT_PRODUCTS_DIR}/NVActivityIndicatorView/NVActivityIndicatorView.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit/PromiseKit.framework", "${BUILT_PRODUCTS_DIR}/PureLayout/PureLayout.framework", "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework", - "${BUILT_PRODUCTS_DIR}/SAMKeychain/SAMKeychain.framework", "${BUILT_PRODUCTS_DIR}/SQLCipher/SQLCipher.framework", "${BUILT_PRODUCTS_DIR}/SSZipArchive/SSZipArchive.framework", - "${BUILT_PRODUCTS_DIR}/SessionAxolotlKit/SessionAxolotlKit.framework", - "${BUILT_PRODUCTS_DIR}/SessionCoreKit/SessionCoreKit.framework", - "${BUILT_PRODUCTS_DIR}/SessionCurve25519Kit/SessionCurve25519Kit.framework", - "${BUILT_PRODUCTS_DIR}/SessionHKDFKit/SessionHKDFKit.framework", - "${BUILT_PRODUCTS_DIR}/SessionMetadataKit/SessionMetadataKit.framework", - "${BUILT_PRODUCTS_DIR}/SessionServiceKit/SessionServiceKit.framework", "${BUILT_PRODUCTS_DIR}/Sodium/Sodium.framework", "${BUILT_PRODUCTS_DIR}/Starscream/Starscream.framework", - "${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework", "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework", "${BUILT_PRODUCTS_DIR}/YapDatabase/YapDatabase.framework", "${BUILT_PRODUCTS_DIR}/ZXingObjC/ZXingObjC.framework", - "${BUILT_PRODUCTS_DIR}/libPhoneNumber-iOS/libPhoneNumber_iOS.framework", "${BUILT_PRODUCTS_DIR}/Curve25519Kit/Curve25519Kit.framework", + "${BUILT_PRODUCTS_DIR}/libPhoneNumber-iOS/libPhoneNumber_iOS.framework", + "${PODS_ROOT}/GRKOpenSSLFramework/OpenSSL-iOS/bin/openssl.framework", "${BUILT_PRODUCTS_DIR}/HKDFKit/HKDFKit.framework", + "${BUILT_PRODUCTS_DIR}/SAMKeychain/SAMKeychain.framework", + "${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -6318,30 +6180,24 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FeedKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mantle.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NVActivityIndicatorView.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PureLayout.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SAMKeychain.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLCipher.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SSZipArchive.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SessionAxolotlKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SessionCoreKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SessionCurve25519Kit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SessionHKDFKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SessionMetadataKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SessionServiceKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sodium.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Starscream.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YapDatabase.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZXingObjC.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libPhoneNumber_iOS.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Curve25519Kit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libPhoneNumber_iOS.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HKDFKit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SAMKeychain.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -6370,24 +6226,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 6565655F4068F9E5CDC5687F /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-SignalTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 7E2D14F857C70F98DED3B8E9 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -6454,66 +6292,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - B4E9B04E862FB64FC9A8F79B /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-SignalTests/Pods-SignalTests-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework", - "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", - "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", - "${PODS_ROOT}/GRKOpenSSLFramework/OpenSSL-iOS/bin/openssl.framework", - "${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework", - "${BUILT_PRODUCTS_DIR}/PromiseKit/PromiseKit.framework", - "${BUILT_PRODUCTS_DIR}/PureLayout/PureLayout.framework", - "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework", - "${BUILT_PRODUCTS_DIR}/SAMKeychain/SAMKeychain.framework", - "${BUILT_PRODUCTS_DIR}/SQLCipher/SQLCipher.framework", - "${BUILT_PRODUCTS_DIR}/SessionAxolotlKit/SessionAxolotlKit.framework", - "${BUILT_PRODUCTS_DIR}/SessionCoreKit/SessionCoreKit.framework", - "${BUILT_PRODUCTS_DIR}/SessionCurve25519Kit/SessionCurve25519Kit.framework", - "${BUILT_PRODUCTS_DIR}/SessionHKDFKit/SessionHKDFKit.framework", - "${BUILT_PRODUCTS_DIR}/SessionMetadataKit/SessionMetadataKit.framework", - "${BUILT_PRODUCTS_DIR}/SessionServiceKit/SessionServiceKit.framework", - "${BUILT_PRODUCTS_DIR}/Starscream/Starscream.framework", - "${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework", - "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework", - "${BUILT_PRODUCTS_DIR}/YapDatabase/YapDatabase.framework", - "${BUILT_PRODUCTS_DIR}/ZXingObjC/ZXingObjC.framework", - "${BUILT_PRODUCTS_DIR}/libPhoneNumber-iOS/libPhoneNumber_iOS.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mantle.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PureLayout.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SAMKeychain.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLCipher.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SessionAxolotlKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SessionCoreKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SessionCurve25519Kit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SessionHKDFKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SessionMetadataKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SessionServiceKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Starscream.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YapDatabase.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZXingObjC.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libPhoneNumber_iOS.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SignalTests/Pods-SignalTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; E185AC3DC0F55CFE87DEC852 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -7426,37 +7204,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - D221A0A5169C9E5F00537ABF /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 456F6E2F1E261D1000FD2210 /* PeerConnectionClientTest.swift in Sources */, - 3491D9A121022DB7001EF5A1 /* CDSSigningCertificateTest.m in Sources */, - 34BBC861220E883300857249 /* ImageEditorModelTest.swift in Sources */, - 340B02BA1FA0D6C700F9CFEC /* ConversationViewItemTest.m in Sources */, - 458E383A1D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m in Sources */, - 3421981C21061D2E00C57195 /* ByteParserTest.swift in Sources */, - 34843B26214327C9004DED45 /* OWSOrphanDataCleanerTest.m in Sources */, - 4C04F58421C860C50090D0BB /* MantlePerfTest.swift in Sources */, - 45360B901F9527DA00FA666C /* SearcherTest.swift in Sources */, - 34BBC862220E883300857249 /* ImageEditorTest.swift in Sources */, - 34DB0BED2011548B007B313F /* OWSDatabaseConverterTest.m in Sources */, - 34843B2C214FE296004DED45 /* MockEnvironment.m in Sources */, - 45360B911F952AA900FA666C /* MarqueeLabel.swift in Sources */, - 454EBAB41F2BE14C00ACE0BB /* OWSAnalytics.swift in Sources */, - 45666F581D9B2880008FE134 /* OWSScrubbingLogFormatterTest.m in Sources */, - B660F6E01C29868000687D6E /* UtilTest.m in Sources */, - 4C3EF7FD2107DDEE0007EBF7 /* ParamParserTest.swift in Sources */, - B660F6DB1C29868000687D6E /* FunctionalUtilTest.m in Sources */, - 45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */, - 34843B2421432293004DED45 /* SignalBaseTest.m in Sources */, - 4C3EF802210918740007EBF7 /* SSKProtoEnvelopeTest.swift in Sources */, - 452D1AF12081059C00A67F7F /* StringAdditionsTest.swift in Sources */, - 455AC69E1F4F8B0300134004 /* ImageCacheTest.swift in Sources */, - 34E8A8D12085238A00B272B1 /* ProtoParsingTest.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -7465,11 +7212,6 @@ target = 453518911FC63DBF00210559 /* SignalMessaging */; targetProxy = 34480B391FD0950000BC14EF /* PBXContainerItemProxy */; }; - 3478506E1FD9CFF4007B8332 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 453518911FC63DBF00210559 /* SignalMessaging */; - targetProxy = 3478506D1FD9CFF4007B8332 /* PBXContainerItemProxy */; - }; 453518711FC635DD00210559 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 453518671FC635DD00210559 /* SignalShareExtension */; @@ -7485,11 +7227,6 @@ target = 7BC01A3A241F40AB00BC7C55 /* LokiPushNotificationService */; targetProxy = 7BC01A40241F40AB00BC7C55 /* PBXContainerItemProxy */; }; - B6AFCEBB19A93DA60098CFCB /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D221A088169C9E5E00537ABF /* Signal */; - targetProxy = B6AFCEBA19A93DA60098CFCB /* PBXContainerItemProxy */; - }; C331FF212558F9D300070591 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C331FF1A2558F9D300070591 /* SessionUIKit */; @@ -8083,7 +7820,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SignalUtilitiesKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -8156,7 +7893,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = SignalUtilitiesKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -8984,123 +8721,6 @@ }; name = "App Store Release"; }; - D221A0C0169C9E5F00537ABF /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AD2AB1207E8888E4262D781B /* Pods-SignalTests.debug.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Session.app/Session"; - CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = 8SQ45X653X; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)", - ); - GCC_GENERATE_TEST_COVERAGE_FILES = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "Signal/Signal-Prefix.pch"; - GCC_VERSION = ""; - GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = NO; - GCC_WARN_STRICT_SELECTOR_MATCH = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - HEADER_SEARCH_PATHS = ( - "${PODS_HEADERS_SEARCH_PATHS}", - "$(inherited)", - "\"${SRCROOT}/Signal/lib/speex/include\"", - "\"${SRCROOT}/Signal/lib/ogg/include\"", - "\"${SRCROOT}/Signal/lib/debug/include\"", - "\"$(SRCROOT)/libtommath\"", - "\"$(SRCROOT)/libtomcrypt/headers\"", - "\"$(SRCROOT)/spandsp/spandsp/spandsp\"", - "\"$(SRCROOT)/Libraries\"/**", - "\"$(TARGET_TEMP_DIR)/../$(PROJECT_NAME).build/DerivedSources\"", - ); - INFOPLIST_FILE = "Signal/test/Supporting Files/SignalTests-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)", - ); - OTHER_LDFLAGS = ( - "-all_load", - "-ObjC", - "$(inherited)", - ); - PRODUCT_BUNDLE_IDENTIFIER = "org.whispersystems.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = SignalTests; - PROVISIONING_PROFILE = ""; - SWIFT_OBJC_BRIDGING_HEADER = "Signal/test/SignalTests-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUNDLE_LOADER)"; - VALID_ARCHS = "arm64 armv7s armv7 i386 x86_64"; - }; - name = Debug; - }; - D221A0C1169C9E5F00537ABF /* App Store Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = E85DB184824BA9DC302EC8B3 /* Pods-SignalTests.app store release.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Session.app/Session"; - CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = 8SQ45X653X; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)", - ); - GCC_GENERATE_TEST_COVERAGE_FILES = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "Signal/Signal-Prefix.pch"; - GCC_VERSION = ""; - GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = NO; - GCC_WARN_STRICT_SELECTOR_MATCH = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - HEADER_SEARCH_PATHS = ( - "${PODS_HEADERS_SEARCH_PATHS}", - "$(inherited)", - "\"${SRCROOT}/Signal/lib/speex/include\"", - "\"${SRCROOT}/Signal/lib/ogg/include\"", - "\"${SRCROOT}/Signal/lib/debug/include\"", - "\"$(SRCROOT)/libtommath\"", - "\"$(SRCROOT)/libtomcrypt/headers\"", - "\"$(SRCROOT)/Libraries\"/**", - "\"$(TARGET_TEMP_DIR)/../$(PROJECT_NAME).build/DerivedSources\"", - ); - INFOPLIST_FILE = "Signal/test/Supporting Files/SignalTests-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)", - ); - OTHER_LDFLAGS = ( - "-all_load", - "-ObjC", - "$(inherited)", - ); - PRODUCT_BUNDLE_IDENTIFIER = "org.whispersystems.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = SignalTests; - PROVISIONING_PROFILE = ""; - SWIFT_OBJC_BRIDGING_HEADER = "Signal/test/SignalTests-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUNDLE_LOADER)"; - VALID_ARCHS = "arm64 armv7s armv7 i386 x86_64"; - }; - name = "App Store Release"; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -9203,15 +8823,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = "App Store Release"; }; - D221A0BF169C9E5F00537ABF /* Build configuration list for PBXNativeTarget "SignalTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D221A0C0169C9E5F00537ABF /* Debug */, - D221A0C1169C9E5F00537ABF /* App Store Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = "App Store Release"; - }; /* End XCConfigurationList section */ }; rootObject = D221A080169C9E5E00537ABF /* Project object */; diff --git a/SignalMessaging/Models/OWSContactOffersInteraction.h b/SignalMessaging/Models/OWSContactOffersInteraction.h index 71bba6802..bbe4635d9 100644 --- a/SignalMessaging/Models/OWSContactOffersInteraction.h +++ b/SignalMessaging/Models/OWSContactOffersInteraction.h @@ -2,7 +2,7 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // -#import +#import @class YapDatabaseReadWriteTransaction; diff --git a/SignalMessaging/Models/TSUnreadIndicatorInteraction.h b/SignalMessaging/Models/TSUnreadIndicatorInteraction.h index 772568de9..06b75058e 100644 --- a/SignalMessaging/Models/TSUnreadIndicatorInteraction.h +++ b/SignalMessaging/Models/TSUnreadIndicatorInteraction.h @@ -2,7 +2,7 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/SignalMessaging-Prefix.pch b/SignalMessaging/SignalMessaging-Prefix.pch index cb9007d47..0a71e4403 100644 --- a/SignalMessaging/SignalMessaging-Prefix.pch +++ b/SignalMessaging/SignalMessaging-Prefix.pch @@ -15,8 +15,6 @@ static const NSUInteger ddLogLevel = DDLogLevelInfo; #endif - #import - #import - #import - #import + #import + #import #endif diff --git a/SignalMessaging/SignalMessaging.h b/SignalMessaging/SignalMessaging.h index 624a38429..d02f57190 100644 --- a/SignalMessaging/SignalMessaging.h +++ b/SignalMessaging/SignalMessaging.h @@ -57,4 +57,4 @@ FOUNDATION_EXPORT const unsigned char SignalMessagingVersionString[]; #import #import #import -#import +#import diff --git a/SignalMessaging/ViewControllers/ContactShareApprovalViewController.swift b/SignalMessaging/ViewControllers/ContactShareApprovalViewController.swift index 58a9d93fd..7b73f2ebc 100644 --- a/SignalMessaging/ViewControllers/ContactShareApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/ContactShareApprovalViewController.swift @@ -3,7 +3,7 @@ // import Foundation -import SessionServiceKit + @objc public protocol ContactShareApprovalViewControllerDelegate: class { diff --git a/SignalMessaging/ViewControllers/EditContactShareNameViewController.swift b/SignalMessaging/ViewControllers/EditContactShareNameViewController.swift index a96f90578..9cd8b4fb3 100644 --- a/SignalMessaging/ViewControllers/EditContactShareNameViewController.swift +++ b/SignalMessaging/ViewControllers/EditContactShareNameViewController.swift @@ -3,7 +3,6 @@ // import Foundation -import SessionServiceKit @objc public protocol ContactNameFieldViewDelegate: class { diff --git a/SignalMessaging/ViewControllers/MediaMessageView.swift b/SignalMessaging/ViewControllers/MediaMessageView.swift index e54730edf..e3db921e0 100644 --- a/SignalMessaging/ViewControllers/MediaMessageView.swift +++ b/SignalMessaging/ViewControllers/MediaMessageView.swift @@ -5,7 +5,7 @@ import Foundation import MediaPlayer import YYImage -import SessionServiceKit + import SessionUIKit @objc diff --git a/SignalMessaging/ViewControllers/ModalActivityIndicatorViewController.swift b/SignalMessaging/ViewControllers/ModalActivityIndicatorViewController.swift index b78fa553b..0ee8d9088 100644 --- a/SignalMessaging/ViewControllers/ModalActivityIndicatorViewController.swift +++ b/SignalMessaging/ViewControllers/ModalActivityIndicatorViewController.swift @@ -4,7 +4,7 @@ import Foundation import MediaPlayer -import SessionServiceKit + // A modal view that be used during blocking interactions (e.g. waiting on response from // service or on the completion of a long-running local operation). diff --git a/SignalMessaging/ViewControllers/NewNonContactConversationViewController.m b/SignalMessaging/ViewControllers/NewNonContactConversationViewController.m index 06b980b21..d0a40fa2b 100644 --- a/SignalMessaging/ViewControllers/NewNonContactConversationViewController.m +++ b/SignalMessaging/ViewControllers/NewNonContactConversationViewController.m @@ -6,7 +6,7 @@ #import "BlockListUIUtils.h" #import "ContactsViewHelper.h" #import -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/ViewControllers/SelectRecipientViewController.m b/SignalMessaging/ViewControllers/SelectRecipientViewController.m index 8020cca15..bb59048f0 100644 --- a/SignalMessaging/ViewControllers/SelectRecipientViewController.m +++ b/SignalMessaging/ViewControllers/SelectRecipientViewController.m @@ -13,11 +13,11 @@ #import #import #import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/ViewControllers/SelectThreadViewController.m b/SignalMessaging/ViewControllers/SelectThreadViewController.m index d1edc2bfa..414ab2a15 100644 --- a/SignalMessaging/ViewControllers/SelectThreadViewController.m +++ b/SignalMessaging/ViewControllers/SelectThreadViewController.m @@ -16,12 +16,12 @@ #import "UIFont+OWS.h" #import "UIView+OWS.h" #import -#import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import #import #import diff --git a/SignalMessaging/ViewControllers/SharingThreadPickerViewController.m b/SignalMessaging/ViewControllers/SharingThreadPickerViewController.m index 4c2417c13..0e7421591 100644 --- a/SignalMessaging/ViewControllers/SharingThreadPickerViewController.m +++ b/SignalMessaging/ViewControllers/SharingThreadPickerViewController.m @@ -10,10 +10,10 @@ #import "UIFont+OWS.h" #import "UIView+OWS.h" #import -#import -#import -#import -#import +#import +#import +#import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/ViewControllers/ViewControllerUtils.m b/SignalMessaging/ViewControllers/ViewControllerUtils.m index f303c6fa0..671a82028 100644 --- a/SignalMessaging/ViewControllers/ViewControllerUtils.m +++ b/SignalMessaging/ViewControllers/ViewControllerUtils.m @@ -6,8 +6,8 @@ #import "PhoneNumber.h" #import #import -#import -#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/ViewModels/OWSQuotedReplyModel.h b/SignalMessaging/ViewModels/OWSQuotedReplyModel.h index afdea45fe..d478b1bd2 100644 --- a/SignalMessaging/ViewModels/OWSQuotedReplyModel.h +++ b/SignalMessaging/ViewModels/OWSQuotedReplyModel.h @@ -2,7 +2,7 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/ViewModels/OWSQuotedReplyModel.m b/SignalMessaging/ViewModels/OWSQuotedReplyModel.m index 167a0a900..ab1763df3 100644 --- a/SignalMessaging/ViewModels/OWSQuotedReplyModel.m +++ b/SignalMessaging/ViewModels/OWSQuotedReplyModel.m @@ -5,16 +5,16 @@ #import "OWSQuotedReplyModel.h" #import "ConversationViewItem.h" #import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/Views/ContactCellView.m b/SignalMessaging/Views/ContactCellView.m index 6fef73843..beb29e107 100644 --- a/SignalMessaging/Views/ContactCellView.m +++ b/SignalMessaging/Views/ContactCellView.m @@ -8,11 +8,11 @@ #import "UIFont+OWS.h" #import "UIView+OWS.h" #import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/Views/ContactTableViewCell.m b/SignalMessaging/Views/ContactTableViewCell.m index 372d7fae8..bd35c076c 100644 --- a/SignalMessaging/Views/ContactTableViewCell.m +++ b/SignalMessaging/Views/ContactTableViewCell.m @@ -8,7 +8,7 @@ #import "UIColor+OWS.h" #import "UIFont+OWS.h" #import "UIView+OWS.h" -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/Views/ContactsViewHelper.m b/SignalMessaging/Views/ContactsViewHelper.m index 93d9191a6..3b9001bb3 100644 --- a/SignalMessaging/Views/ContactsViewHelper.m +++ b/SignalMessaging/Views/ContactsViewHelper.m @@ -7,13 +7,13 @@ #import "UIUtil.h" #import #import -#import -#import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import +#import @import ContactsUI; diff --git a/SignalMessaging/Views/OWSFlatButton.swift b/SignalMessaging/Views/OWSFlatButton.swift index 934492344..cba7379d1 100644 --- a/SignalMessaging/Views/OWSFlatButton.swift +++ b/SignalMessaging/Views/OWSFlatButton.swift @@ -3,7 +3,7 @@ // import Foundation -import SessionServiceKit + @objc public class OWSFlatButton: UIView { diff --git a/SignalMessaging/Views/ThreadViewHelper.m b/SignalMessaging/Views/ThreadViewHelper.m index 7bc53b468..9b570dd23 100644 --- a/SignalMessaging/Views/ThreadViewHelper.m +++ b/SignalMessaging/Views/ThreadViewHelper.m @@ -3,12 +3,12 @@ // #import "ThreadViewHelper.h" -#import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import #import #import #import diff --git a/SignalMessaging/appearance/OWSConversationColor.h b/SignalMessaging/appearance/OWSConversationColor.h index 4ee2c6a08..0af19bf4b 100644 --- a/SignalMessaging/appearance/OWSConversationColor.h +++ b/SignalMessaging/appearance/OWSConversationColor.h @@ -2,7 +2,7 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // -#import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/appearance/OWSConversationColor.m b/SignalMessaging/appearance/OWSConversationColor.m index e1f3715af..f53cff8a5 100644 --- a/SignalMessaging/appearance/OWSConversationColor.m +++ b/SignalMessaging/appearance/OWSConversationColor.m @@ -5,7 +5,7 @@ #import "OWSConversationColor.h" #import "Theme.h" #import "UIColor+OWS.h" -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/appearance/Theme.m b/SignalMessaging/appearance/Theme.m index 0fdbb76a8..1e90d7af3 100644 --- a/SignalMessaging/appearance/Theme.m +++ b/SignalMessaging/appearance/Theme.m @@ -5,9 +5,9 @@ #import "Theme.h" #import "UIColor+OWS.h" #import "UIUtil.h" -#import -#import -#import +#import +#import +#import #import #import diff --git a/SignalMessaging/attachments/AttachmentSharing.m b/SignalMessaging/attachments/AttachmentSharing.m index 5403825d9..264cdcd74 100644 --- a/SignalMessaging/attachments/AttachmentSharing.m +++ b/SignalMessaging/attachments/AttachmentSharing.m @@ -4,9 +4,9 @@ #import "AttachmentSharing.h" #import "UIUtil.h" -#import -#import -#import + +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/attachments/SignalAttachment.swift b/SignalMessaging/attachments/SignalAttachment.swift index 0e18dc536..42f48edd3 100644 --- a/SignalMessaging/attachments/SignalAttachment.swift +++ b/SignalMessaging/attachments/SignalAttachment.swift @@ -4,7 +4,7 @@ import Foundation import MobileCoreServices -import SessionServiceKit + import PromiseKit import AVFoundation diff --git a/SignalMessaging/categories/NSAttributedString+OWS.m b/SignalMessaging/categories/NSAttributedString+OWS.m index df3105d9b..84ea2a773 100644 --- a/SignalMessaging/categories/NSAttributedString+OWS.m +++ b/SignalMessaging/categories/NSAttributedString+OWS.m @@ -4,7 +4,7 @@ #import "NSAttributedString+OWS.h" #import "UIView+OWS.h" -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/categories/UIColor+OWS.m b/SignalMessaging/categories/UIColor+OWS.m index 5d126a00a..b935233fd 100644 --- a/SignalMessaging/categories/UIColor+OWS.m +++ b/SignalMessaging/categories/UIColor+OWS.m @@ -4,7 +4,7 @@ #import "UIColor+OWS.h" #import "OWSMath.h" -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/categories/UIFont+OWS.m b/SignalMessaging/categories/UIFont+OWS.m index e62887483..39617cd13 100644 --- a/SignalMessaging/categories/UIFont+OWS.m +++ b/SignalMessaging/categories/UIFont+OWS.m @@ -3,7 +3,6 @@ // #import "UIFont+OWS.h" -#import NS_ASSUME_NONNULL_BEGIN @@ -123,9 +122,7 @@ NS_ASSUME_NONNULL_BEGIN UIFontTextStyleCaption1 : @(18.0), UIFontTextStyleCaption2 : @(17.0), } mutableCopy]; - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 0)) { - map[UIFontTextStyleLargeTitle] = @(40.0); - } + map[UIFontTextStyleLargeTitle] = @(40.0); maxPointSizeMap = map; }); @@ -144,11 +141,7 @@ NS_ASSUME_NONNULL_BEGIN + (UIFont *)ows_dynamicTypeLargeTitle1ClampedFont { - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 0)) { - return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleLargeTitle]; - } else { - return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleTitle1]; - } + return [UIFont preferredFontForTextStyleClamped:UIFontTextStyleLargeTitle]; } + (UIFont *)ows_dynamicTypeTitle1ClampedFont diff --git a/SignalMessaging/categories/UIView+OWS.h b/SignalMessaging/categories/UIView+OWS.h index b7f0cbf63..4b3dddf5c 100644 --- a/SignalMessaging/categories/UIView+OWS.h +++ b/SignalMessaging/categories/UIView+OWS.h @@ -3,7 +3,7 @@ // #import -#import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/categories/UIView+OWS.m b/SignalMessaging/categories/UIView+OWS.m index 3b986233f..09da5dabd 100644 --- a/SignalMessaging/categories/UIView+OWS.m +++ b/SignalMessaging/categories/UIView+OWS.m @@ -4,9 +4,8 @@ #import "UIView+OWS.h" #import "OWSMath.h" -#import #import -#import +#import NS_ASSUME_NONNULL_BEGIN @@ -551,15 +550,6 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) - (BOOL)applyScrollViewInsetsFix { - // Fix a bug that only affects iOS 11.0.x and 11.1.x. - // The symptom is a fix weird animation that happens when using the interactive pop gesture. - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 0) && !SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 2)) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpartial-availability" - self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; -#pragma clang diagnostic pop - return YES; - } return NO; } diff --git a/SignalMessaging/categories/UIViewController+OWS.m b/SignalMessaging/categories/UIViewController+OWS.m index dbb0c759a..386c15de0 100644 --- a/SignalMessaging/categories/UIViewController+OWS.m +++ b/SignalMessaging/categories/UIViewController+OWS.m @@ -7,9 +7,8 @@ #import "UIUtil.h" #import "UIView+OWS.h" #import "UIViewController+OWS.h" -#import #import -#import +#import NS_ASSUME_NONNULL_BEGIN @@ -106,21 +105,19 @@ NS_ASSUME_NONNULL_BEGIN = CGRectMake(0, 0, backImage.size.width + kExtraRightPadding, backImage.size.height + kExtraHeightPadding); backButton.frame = buttonFrame; - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 1)) { - // In iOS 11.1 beta, the hot area of custom bar button items is _only_ - // the bounds of the custom view, making them very hard to hit. - // - // TODO: Remove this hack if the bug is fixed in iOS 11.1 by the time - // it goes to production (or in a later release), - // since it has two negative side effects: 1) the layout of the - // back button isn't consistent with the iOS default back buttons - // 2) we can't add the unread count badge to the back button - // with this hack. - return [[UIBarButtonItem alloc] initWithImage:backImage - style:UIBarButtonItemStylePlain - target:target - action:selector]; - } + // In iOS 11.1 beta, the hot area of custom bar button items is _only_ + // the bounds of the custom view, making them very hard to hit. + // + // TODO: Remove this hack if the bug is fixed in iOS 11.1 by the time + // it goes to production (or in a later release), + // since it has two negative side effects: 1) the layout of the + // back button isn't consistent with the iOS default back buttons + // 2) we can't add the unread count badge to the back button + // with this hack. + return [[UIBarButtonItem alloc] initWithImage:backImage + style:UIBarButtonItemStylePlain + target:target + action:selector]; UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithCustomView:backButton diff --git a/SignalMessaging/contacts/OWSContactsManager.h b/SignalMessaging/contacts/OWSContactsManager.h index ee5078e0f..63d3b5975 100644 --- a/SignalMessaging/contacts/OWSContactsManager.h +++ b/SignalMessaging/contacts/OWSContactsManager.h @@ -2,8 +2,8 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // -#import -#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index 09c1d39e3..e86b86b5d 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -9,18 +9,18 @@ #import "OWSProfileManager.h" #import "OWSUserProfile.h" #import "ViewControllerUtils.h" -#import -#import + + #import #import #import -#import -#import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import +#import @import Contacts; @@ -224,15 +224,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan updatedContacts:(NSArray *)contacts isUserRequested:(BOOL)isUserRequested { - BOOL shouldClearStaleCache; - // On iOS 11.2, only clear the contacts cache if the fetch was initiated by the user. - // iOS 11.2 rarely returns partial fetches and we use the cache to prevent contacts from - // periodically disappearing from the UI. - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 2) && !SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 3)) { - shouldClearStaleCache = isUserRequested; - } else { - shouldClearStaleCache = YES; - } + BOOL shouldClearStaleCache = YES; [self updateWithContacts:contacts isUserRequested:isUserRequested shouldClearStaleCache:shouldClearStaleCache]; } diff --git a/SignalMessaging/contacts/OWSSyncManager.h b/SignalMessaging/contacts/OWSSyncManager.h index d5203b02f..cdea1f23c 100644 --- a/SignalMessaging/contacts/OWSSyncManager.h +++ b/SignalMessaging/contacts/OWSSyncManager.h @@ -2,7 +2,7 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/contacts/OWSSyncManager.m b/SignalMessaging/contacts/OWSSyncManager.m index 618ec69be..5f00ef7dd 100644 --- a/SignalMessaging/contacts/OWSSyncManager.m +++ b/SignalMessaging/contacts/OWSSyncManager.m @@ -9,22 +9,22 @@ #import "OWSProfileManager.h" #import "OWSReadReceiptManager.h" #import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/contacts/SystemContactsFetcher.swift b/SignalMessaging/contacts/SystemContactsFetcher.swift index 65cba8433..92aa203ca 100644 --- a/SignalMessaging/contacts/SystemContactsFetcher.swift +++ b/SignalMessaging/contacts/SystemContactsFetcher.swift @@ -5,7 +5,7 @@ import Foundation import Contacts import ContactsUI -import SessionServiceKit + enum Result { case success(T) diff --git a/SignalMessaging/environment/AppSetup.m b/SignalMessaging/environment/AppSetup.m index dd4e98dc2..7496be57b 100644 --- a/SignalMessaging/environment/AppSetup.m +++ b/SignalMessaging/environment/AppSetup.m @@ -5,27 +5,26 @@ #import "AppSetup.h" #import "Environment.h" #import "VersionMigrations.h" -#import #import #import #import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/environment/Environment.h b/SignalMessaging/environment/Environment.h index f3e0454fc..25171462d 100644 --- a/SignalMessaging/environment/Environment.h +++ b/SignalMessaging/environment/Environment.h @@ -2,7 +2,7 @@ // Copyright (c) 2019 Open Whisper Systems. All rights reserved. // -#import +#import @class OWSAudioSession; @class OWSContactsManager; diff --git a/SignalMessaging/environment/Environment.m b/SignalMessaging/environment/Environment.m index 6a074fd1d..87f237f01 100644 --- a/SignalMessaging/environment/Environment.m +++ b/SignalMessaging/environment/Environment.m @@ -4,8 +4,8 @@ #import "Environment.h" #import "OWSPreferences.h" -#import -#import +#import +#import static Environment *sharedEnvironment = nil; diff --git a/SignalMessaging/environment/NoopCallMessageHandler.swift b/SignalMessaging/environment/NoopCallMessageHandler.swift index fda57385a..7ef576d77 100644 --- a/SignalMessaging/environment/NoopCallMessageHandler.swift +++ b/SignalMessaging/environment/NoopCallMessageHandler.swift @@ -2,7 +2,7 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // -import SessionServiceKit + @objc public class NoopCallMessageHandler: NSObject, OWSCallMessageHandler { diff --git a/SignalMessaging/environment/OWSSounds.m b/SignalMessaging/environment/OWSSounds.m index 8d0ed2589..4c80a527d 100644 --- a/SignalMessaging/environment/OWSSounds.m +++ b/SignalMessaging/environment/OWSSounds.m @@ -6,10 +6,10 @@ #import "Environment.h" #import "OWSAudioPlayer.h" #import -#import -#import -#import -#import +#import +#import +#import +#import #import NSString *const kOWSSoundsStorageNotificationCollection = @"kOWSSoundsStorageNotificationCollection"; diff --git a/SignalMessaging/environment/SignalKeyingStorage.m b/SignalMessaging/environment/SignalKeyingStorage.m index 13989f4e1..ace4559ae 100644 --- a/SignalMessaging/environment/SignalKeyingStorage.m +++ b/SignalMessaging/environment/SignalKeyingStorage.m @@ -3,9 +3,9 @@ // #import "SignalKeyingStorage.h" -#import -#import -#import +#import +#import +#import #define SignalKeyingCollection @"SignalKeyingCollection" diff --git a/SignalMessaging/environment/VersionMigrations.m b/SignalMessaging/environment/VersionMigrations.m index d0f7287b6..17713c4b3 100644 --- a/SignalMessaging/environment/VersionMigrations.m +++ b/SignalMessaging/environment/VersionMigrations.m @@ -7,15 +7,15 @@ #import "OWSDatabaseMigrationRunner.h" #import "SignalKeyingStorage.h" #import -#import -#import -#import -#import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import +#import +#import +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/environment/migrations/OWSDatabaseMigration.h b/SignalMessaging/environment/migrations/OWSDatabaseMigration.h index 9fcbcd185..ec59a44cd 100644 --- a/SignalMessaging/environment/migrations/OWSDatabaseMigration.h +++ b/SignalMessaging/environment/migrations/OWSDatabaseMigration.h @@ -2,7 +2,7 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/environment/migrations/OWSDatabaseMigration.m b/SignalMessaging/environment/migrations/OWSDatabaseMigration.m index 04997bda7..ca97b49f5 100644 --- a/SignalMessaging/environment/migrations/OWSDatabaseMigration.m +++ b/SignalMessaging/environment/migrations/OWSDatabaseMigration.m @@ -3,9 +3,9 @@ // #import "OWSDatabaseMigration.h" -#import -#import -#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/environment/migrations/OWSDatabaseMigrationRunner.m b/SignalMessaging/environment/migrations/OWSDatabaseMigrationRunner.m index c3622fa07..ca260b7ba 100644 --- a/SignalMessaging/environment/migrations/OWSDatabaseMigrationRunner.m +++ b/SignalMessaging/environment/migrations/OWSDatabaseMigrationRunner.m @@ -5,7 +5,7 @@ #import "OWSDatabaseMigrationRunner.h" #import "OWSDatabaseMigration.h" #import -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/environment/migrations/OWSResaveCollectionDBMigration.m b/SignalMessaging/environment/migrations/OWSResaveCollectionDBMigration.m index ee0db59d3..a6cc9c67f 100644 --- a/SignalMessaging/environment/migrations/OWSResaveCollectionDBMigration.m +++ b/SignalMessaging/environment/migrations/OWSResaveCollectionDBMigration.m @@ -5,7 +5,7 @@ #import "OWSResaveCollectionDBMigration.h" #import #import -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/profiles/OWSProfileManager.h b/SignalMessaging/profiles/OWSProfileManager.h index afe8b2faf..2631674be 100644 --- a/SignalMessaging/profiles/OWSProfileManager.h +++ b/SignalMessaging/profiles/OWSProfileManager.h @@ -2,7 +2,7 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index c64840fdc..8ce8ced60 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -6,32 +6,32 @@ #import "Environment.h" #import "OWSUserProfile.h" #import -#import -#import -#import + + + #import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -430,7 +430,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); NSData *encryptedAvatarData = [self encryptProfileData:avatarData profileKey:newProfileKey]; OWSAssertDebug(encryptedAvatarData.length > 0); - [[LKFileServerAPI uploadProfilePicture:encryptedAvatarData] + [[SNFileServerAPI uploadProfilePicture:encryptedAvatarData] .thenOn(dispatch_get_main_queue(), ^(NSString *downloadURL) { [self.localUserProfile updateWithProfileKey:newProfileKey dbConnection:self.dbConnection completion:^{ successBlock(downloadURL); @@ -468,12 +468,12 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); [SSKEnvironment.shared.primaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { publicChats = [LKDatabaseUtilities getAllPublicChats:transaction]; }]; - - NSSet *servers = [NSSet setWithArray:[publicChats.allValues map:^NSString *(LKPublicChat *publicChat) { return publicChat.server; }]]; + + NSSet *servers = [NSSet setWithArray:[publicChats.allValues map:^NSString *(SNOpenGroup *publicChat) { return publicChat.server; }]]; for (NSString *server in servers) { - [[LKPublicChatAPI setDisplayName:localProfileName on:server] retainUntilComplete]; - [[LKPublicChatAPI setProfilePictureURL:avatarURL usingProfileKey:self.localProfileKey.keyData on:server] retainUntilComplete]; + [[SNOpenGroupAPI setDisplayName:localProfileName on:server] retainUntilComplete]; + [[SNOpenGroupAPI setProfilePictureURL:avatarURL usingProfileKey:self.localProfileKey.keyData on:server] retainUntilComplete]; } successBlock(); @@ -1158,7 +1158,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); OWSLogVerbose(@"downloading profile avatar: %@", userProfile.uniqueId); NSString *profilePictureURL = userProfile.avatarUrlPath; - [[LKFileServerAPI downloadAttachmentFrom:profilePictureURL].then(^(NSData *data) { + [[SNFileServerAPI downloadAttachmentFrom:profilePictureURL].then(^(NSData *data) { @synchronized(self.currentAvatarDownloads) { [self.currentAvatarDownloads removeObject:userProfile.recipientId]; diff --git a/SignalMessaging/profiles/OWSUserProfile.h b/SignalMessaging/profiles/OWSUserProfile.h index 843b0c6e5..1c79e4531 100644 --- a/SignalMessaging/profiles/OWSUserProfile.h +++ b/SignalMessaging/profiles/OWSUserProfile.h @@ -2,7 +2,7 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/profiles/OWSUserProfile.m b/SignalMessaging/profiles/OWSUserProfile.m index c5280bfaa..973fe3295 100644 --- a/SignalMessaging/profiles/OWSUserProfile.m +++ b/SignalMessaging/profiles/OWSUserProfile.m @@ -4,16 +4,16 @@ #import "OWSUserProfile.h" #import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import + + +#import +#import +#import +#import +#import +#import +#import +#import #import #import diff --git a/SignalMessaging/profiles/ProfileFetcherJob.swift b/SignalMessaging/profiles/ProfileFetcherJob.swift index 6d8f78b43..89e11bf99 100644 --- a/SignalMessaging/profiles/ProfileFetcherJob.swift +++ b/SignalMessaging/profiles/ProfileFetcherJob.swift @@ -4,8 +4,6 @@ import Foundation import PromiseKit -import SessionServiceKit -import SessionMetadataKit @objc public class ProfileFetcherJob: NSObject { diff --git a/SignalMessaging/utils/BlockListUIUtils.m b/SignalMessaging/utils/BlockListUIUtils.m index c827ae9fd..8163454db 100644 --- a/SignalMessaging/utils/BlockListUIUtils.m +++ b/SignalMessaging/utils/BlockListUIUtils.m @@ -6,11 +6,11 @@ #import "OWSContactsManager.h" #import "PhoneNumber.h" #import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/utils/DebugLogger.m b/SignalMessaging/utils/DebugLogger.m index a4ce5a61a..42984cd24 100644 --- a/SignalMessaging/utils/DebugLogger.m +++ b/SignalMessaging/utils/DebugLogger.m @@ -4,9 +4,9 @@ #import "DebugLogger.h" #import "OWSScrubbingLogFormatter.h" -#import -#import -#import + +#import +#import #pragma mark Logging - Production logging wants us to write some logs to a file in case we need it for debugging. #import diff --git a/SignalMessaging/utils/DeviceSleepManager.swift b/SignalMessaging/utils/DeviceSleepManager.swift index 5b3771c5d..89211e010 100644 --- a/SignalMessaging/utils/DeviceSleepManager.swift +++ b/SignalMessaging/utils/DeviceSleepManager.swift @@ -3,7 +3,7 @@ // import Foundation -import SessionServiceKit + // This entity has responsibility for blocking the device from sleeping if // certain behaviors (e.g. recording or playing voice messages) are in progress. diff --git a/SignalMessaging/utils/FullTextSearcher.swift b/SignalMessaging/utils/FullTextSearcher.swift index 8555e28f3..285b5c332 100644 --- a/SignalMessaging/utils/FullTextSearcher.swift +++ b/SignalMessaging/utils/FullTextSearcher.swift @@ -3,7 +3,7 @@ // import Foundation -import SessionServiceKit + public typealias MessageSortKey = UInt64 public struct ConversationSortKey: Comparable { diff --git a/SignalMessaging/utils/OWSAudioPlayer.m b/SignalMessaging/utils/OWSAudioPlayer.m index 93f1d2477..c866ba9c1 100644 --- a/SignalMessaging/utils/OWSAudioPlayer.m +++ b/SignalMessaging/utils/OWSAudioPlayer.m @@ -6,7 +6,6 @@ #import "TSAttachmentStream.h" #import #import -#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/utils/OWSContactAvatarBuilder.h b/SignalMessaging/utils/OWSContactAvatarBuilder.h index 0e896c41b..8f53274b5 100644 --- a/SignalMessaging/utils/OWSContactAvatarBuilder.h +++ b/SignalMessaging/utils/OWSContactAvatarBuilder.h @@ -3,7 +3,7 @@ // #import "OWSAvatarBuilder.h" -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/utils/OWSContactAvatarBuilder.m b/SignalMessaging/utils/OWSContactAvatarBuilder.m index 65e3a3616..1f821a3e6 100644 --- a/SignalMessaging/utils/OWSContactAvatarBuilder.m +++ b/SignalMessaging/utils/OWSContactAvatarBuilder.m @@ -10,7 +10,7 @@ #import "UIColor+OWS.h" #import "UIFont+OWS.h" #import -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/utils/OWSGroupAvatarBuilder.m b/SignalMessaging/utils/OWSGroupAvatarBuilder.m index 22738e134..ff0903033 100644 --- a/SignalMessaging/utils/OWSGroupAvatarBuilder.m +++ b/SignalMessaging/utils/OWSGroupAvatarBuilder.m @@ -7,8 +7,8 @@ #import "TSGroupThread.h" #import "UIColor+OWS.h" #import -#import -#import +#import + NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/utils/OWSPreferences.m b/SignalMessaging/utils/OWSPreferences.m index 3a128f556..a25727c4b 100644 --- a/SignalMessaging/utils/OWSPreferences.m +++ b/SignalMessaging/utils/OWSPreferences.m @@ -3,15 +3,15 @@ // #import "OWSPreferences.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/utils/ThreadUtil.m b/SignalMessaging/utils/ThreadUtil.m index 9c3557733..dff849fa9 100644 --- a/SignalMessaging/utils/ThreadUtil.m +++ b/SignalMessaging/utils/ThreadUtil.m @@ -8,25 +8,23 @@ #import "OWSQuotedReplyModel.h" #import "OWSUnreadIndicator.h" #import "TSUnreadIndicatorInteraction.h" -#import -#import #import #import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalMessaging/utils/UIUtil.h b/SignalMessaging/utils/UIUtil.h index dc61e8a0a..425c60ca4 100644 --- a/SignalMessaging/utils/UIUtil.h +++ b/SignalMessaging/utils/UIUtil.h @@ -4,8 +4,8 @@ #import "UIColor+OWS.h" #import "UIFont+OWS.h" -#import -#import +#import +#import #define ACCESSIBILITY_IDENTIFIER_WITH_NAME(_root_view, _variable_name) \ ([NSString stringWithFormat:@"%@.%@", _root_view.class, _variable_name]) diff --git a/SignalMessaging/utils/UIUtil.m b/SignalMessaging/utils/UIUtil.m index b0d0a531d..0ec765fb3 100644 --- a/SignalMessaging/utils/UIUtil.m +++ b/SignalMessaging/utils/UIUtil.m @@ -5,7 +5,7 @@ #import "UIUtil.h" #import "Theme.h" #import "UIColor+OWS.h" -#import +#import #import #import diff --git a/SignalServiceKit/.clang-format b/SignalServiceKit/.clang-format deleted file mode 100644 index 6d3ed1c24..000000000 --- a/SignalServiceKit/.clang-format +++ /dev/null @@ -1,15 +0,0 @@ ---- -BasedOnStyle: WebKit -AllowShortFunctionsOnASingleLine: false -BinPackArguments: false -BinPackParameters: false -ColumnLimit: 120 -IndentCaseLabels: true -MaxEmptyLinesToKeep: 2 -ObjCSpaceAfterProperty: true -ObjCSpaceBeforeProtocolList: true -PointerBindsToType: false -SpacesBeforeTrailingComments: 1 -TabWidth: 8 -UseTab: Never -... diff --git a/SignalServiceKit/.gitignore b/SignalServiceKit/.gitignore deleted file mode 100644 index fe9a179d5..000000000 --- a/SignalServiceKit/.gitignore +++ /dev/null @@ -1,30 +0,0 @@ -# Xcode -# -.DS_Store -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate -xcshareddata - -Pods/ - -# CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control -# -#Pods/ diff --git a/SignalServiceKit/.travis.yml b/SignalServiceKit/.travis.yml deleted file mode 100644 index e8235d5ec..000000000 --- a/SignalServiceKit/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: objective-c - -osx_image: xcode8.3 - -env: - -EARLY_START_SIMULATOR=1 # early starting simulator reduces false negatives due to test timeouts - -before_install: - - brew update - - bundle - - bundle exec pod repo update --silent - -after_failure: - - sleep 10 # This prevents the occasional output truncation that happens when piping to xcpretty. - -script: make scan_test - diff --git a/SignalServiceKit/LICENSE b/SignalServiceKit/LICENSE deleted file mode 100644 index 94a045322..000000000 --- a/SignalServiceKit/LICENSE +++ /dev/null @@ -1,621 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS diff --git a/SignalServiceKit/Resources/Certificates/DigiCertGlobalRootG2.crt b/SignalServiceKit/Resources/Certificates/DigiCertGlobalRootG2.crt deleted file mode 100644 index 1e927a7afe06c270670d6bc25c4d465db2e0a7e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 914 zcmXqLV(v3&Vk%p}%*4pVB*1L-@!4|0l?!%jq->Ps{Pom;myJ`a&7m8Ce;an;7{S44N3Zn3@aHzbQTHIERDpF(!1g)GQaJ{9Zplk- zye@a|@AU_#r!O{6V{hxNd-12ggQTEo);V)vP&-Tc!p2{-S^Rub$w8x&gcgr9D_VnJeCX{_;|J2P} zlz02&x_&wBDQX2hucLu6-|8C^Skma^IW)V_ne~}jhi)FnV1keK@Hao#ijU%z$=y=4>cw?qzQ zVDbWnG9yDmy2g}zK9A(rhIe13OI+=&wK>}DRsV9|ZEf-U8{&)0wTy%<8`GUyls~zq zo4)DT|D%6HB9Fdi`2qHg8zibR_3{p5LfoBYYm zyzW}IGxc}J?B04s;o;2qU^mg*8M8lpHn?hM_^7vK-s7%Yx2D;d3NMZS@UtMGz}-N8^%tYFG2u-sL@JUi+4LlJwK^__B94 Uy+>lhq8Hsv%iC;f{?uq20B`eLrT_o{ diff --git a/SignalServiceKit/Resources/Certificates/DigiCertSHA2HighAssuranceServerCA.crt b/SignalServiceKit/Resources/Certificates/DigiCertSHA2HighAssuranceServerCA.crt deleted file mode 100644 index 07e025defbc58b3190f341c520ba3d26fc4cd59e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1205 zcmXqLV%ccW#5{8WGZP~dlK{)Z=S%LyeEOVwK-+0Ytmu3LUN%mxHjlRNyo`+8tPBP@ zhTI06Y|No7Y{E>T!G@v+!XOTpFpo=SdZu$~QHg?QUb3O2fjCH*TUemHyj(8@D3uJ9 z(o4?IHPklH1W7RqE5UVmWTs~*I2IR|7A58-rz*IHDFo%`mnb+p8pw(B8X6lK7#SHF z82~|)IIocf64#)BQrGAk=t5kh26s)chog}Knv;T4i^@`qKrU)xR6-6CMpg#qCPsb+ zpg0#(6C)$Twg>u$RM<}BvczA1&y~Wqsa8oXRcxJ;!|Zc+=FcxxYQ+Ag`%e+pmJ z6#3AwEr*!2)?b+Mj@#k-kqg#ECq(AGpRiGI<+@8Jb~szzwxWesTb{xMOw~L7_oU1J4`}F!Gjy0uA z=IlBvzDdr-G_^ih)$*bY*Q{h|$w?&{T;_)k1$msdj9RqqVeG-X3lw%JE}E>CFXE-> zc>Hbd>`xy0pP9cd%9s?aANQDvnUR5UaTB8_FhpDpgn)r7E6m9FpM}GK4M;IDG8piI z#P~sCEWjk!W*`gVtFnk0h_G>JvoW%=vNJQnSxg2dFg7D2i-v)kfeMUoz}O~{QBqQ1 zrLUi#TwH*X$_%_ghRU#*(C=); zJgL8}r#)AFQtzHw^v7=1RY&eySIrjgm&tSYtdG0tQNw)mZ@1@y8EQ@rudhuKT#~{S z9&+TX>@}vi>^1L>G|Unz`V}_$`v3g~t!xJSx$FJ@hqKOAdE0te`}eZa6>D_G-q%*D zZ+d-bE;swDRjrp97b|EuMmlR;YJ9xv+nZ|Z@@)=D4Xlo?jPJ$S?Cu>fo}GRmduo12QE73Bf@fZ`o}sjXBuJcDSO_leo|B)Hn4{qAXdoxfYiMd>&w5_#oi3ezbtFq_)0yDAqTb!|CnOW)&(2+8m#p1lnwi>i zLh`7a(vRS&`yMxV2^`aUvunD7;Bm{D0xSzYBvwsT?ePmLeYEW@$0?CN=ajNG`<-`V zo<5zq?!d--)$3i2&+oD;+G599H^R+(cAfZHERlwVC|~f)E%eJ_!-E86!5W#v50uxWfPO$^DSd* z+1JKxyOX4LMK$Uh@PVZH85#exumF<}n}Hr1hc+7{D=RxABa4!Of`J^2Z@}0llu=Sr zV5P60u2)={o1CJToS$nT1kx`I($8VQ2Ber684OH8V)86{208{>3p5s}wW*+JOaf`t zPcF(S)=w`<2PZH+AkRP?q)mZEz<|$yhm8y93Kqs|l}xOR$Y}wXnt^G7k%7aHWqCur zfS7X})7tA!4PQ3&U0S#KwP5Xp7Kz&D6GVS|)~}e!I&pW^$!j*|5}dQP9iODYZfeZ= zL?gL~cgq8#Iqfr!NSAe{9}I}tAiPRgO4?}YzE^@%eLRvqU(SqGFhBE8D>G1Rs`tu! zo=pb?3cl!kezQkK<$~Xur8nc`awi>JzQ)6}@#oVgb5b+Uwl8rEu-6M;Ix$cvg0F{j z;hL%?LQB;@&k~qk8_LY~JuMvwMYS7!L^@ IR6HgI001d%k^lez diff --git a/SignalServiceKit/Resources/Certificates/GSR2.crt b/SignalServiceKit/Resources/Certificates/GSR2.crt deleted file mode 100644 index 4d937187ede7ff4298754f14e5399502f844f389..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 958 zcmXqLV%}xY#I$GuGZP~d6E_P35HRw$sXgO0;AP{~YV&CO&dbQi%F1BiW2j&t$HpAW z!YwTBo|B)Hm=m0to~IC$pI@Tj?5Lot5M*R1Y#<0y!6nRvPyrKUMiw-X6X!KBGc+zmHFpYDPz}E}G_kLH`6?d!&>ZtZw zcFJ>E+=}HrQG$D_nqKegdAWJbG*$NLUNg1W^|#2C@9*N@%2XpgZO74_RyupG3GI9x zS^MY$TU&gbXVzBBxDJyydn$N1X0+t2IP1M-K`l?E?}r__rxj0K&55pkxInXI;m^xc zJWs16O;p%(m;36?Ge2Lcb7>b(JT_N)&56@59xV8xKcUx3U|cJ ze=wOVaC%qNx%I2BeqY78Dq-)PoALLHRwf9?F)*I=`enc56+5#cQ(x05=0mglnV1xMeab8n24W4^4Mt6mzzAeynAsTIE|W36ZQ`2;UOQa4S2wOv_rG>< zhm3Q@W}aDpUg_B6B})5xYkr;2Dyw#I$IE7hB-d>#IVSI1Y3waA>(Gz(4!_%X3r(aB zhTL+qe{flAb#92#SF5D63i-Px$JdDU|DrA7C*cV9Tr zASvT|K)2_`58WjtVV*}>xB83T%X6u)Y+q5g`*Wql#NA)==N|mfW7-qT`1^3~&i5|4 zm2QT0T>n-!9sK`(b)LeJfRpiaeP@gJ@H}Naxxg{2IQZAnfGv5d{J(u9zWfck*_XNT rg&_aEjvK#aJ-@HIH}$+i#i0bD+o}#dS%2!BEF4mVzRzoXI9U$>x7>1) diff --git a/SignalServiceKit/Resources/Certificates/GSR4.crt b/SignalServiceKit/Resources/Certificates/GSR4.crt deleted file mode 100644 index 160d545fc28b8cebba1b65d475f09a4544933af4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 485 zcmXqLVti=O#Mr)onTe5!Nl?pTiOe)EmU~W{G?sF|Fqy$*z{SR))#h=|mW7$gAiz+? zK#7exl!aSZ+C3*fDKRHFGd)ki)!A7gC_leM!P!wkS0Tv6P}o2aq>4+J3!w@o$c!v# zAScdiXk=(;XlwumQR2MD76yifmIlU#CI;qFG_Wz!K$wjk>?0;dsMnYo*_oXfSauw% zyxgCo$~RxnQc{L_$+}rjE!jE6P9<;L+4iF={9yPwr(eYjzo$C=mpjO?DX*CC|8d2q z5=&OyEVN&7W#_pb*~Lx<4hDQckIV8iGX7@)1{foo0Y8W@4C1pIFas$AS&#r9ix`VY z$cE~*t~(w%)c@wn`@PTjq|M@%tH{C4?7?8*%B09}XVSt2!pqJxZ+Lb@ar%T`BH_Bs zZ{FEl&O5atr)!|u1Q{C0iSrto8JHOv8Gu2QIIl4>mwFa8F)1N?f{~ShxrvFN!JvtW zi>Zl;kzt!4Q@7mXh2^uY@9dvqw|(kz2b+a|n|{_Bu{9Nb`_@zc`^D`&S$fl_FYfXF zsr_x^&vPxZ8-E_1e((LxPm5pf5$cRvzA9+-4`ub!OE%6)zI}aW-3P9?*BQ&c9E)=2 z^%XFF?6*;P@d=3>?>B!qC)}T#qN%FPzHrMWTfKw_=Pz-#?>_7C!S~Ris*bMRyUs^S zUR&S4fqla~!;eh|58T&VmYXBVxAjJynv^TE?S{%FQ#97{*Q-5U!SQd}3!987!S9-_ zIZO-fEVJZF7{4>_sPCwMre_)Tp0q&T+-`Y2u=B$wjsBd}Tb9T~Y zg_p*Af@ZEa+N!x(RcnjK&!UH?QocWH66k#~_fS{IFN?IUKgmBCe^&ghjw%0D*8c0w z{f&H;Q^U9HReOJp@#1SSuHAjyTaO;?3SE79&8vytnr9@J7^(VceN1(^z;tfaInOPx z=GQcDaHxLnyn1=g;{Mvs#$P^5|6lDpe9rs9)*GAqT+f#scYK8i} zI8PCVMGW<`Rw!;;xpC@-r8{}W1aBSB@vw1tP{$i}Hq7SYbk?1xz291T&pBd$DO5O5 zWy7_?UtbRHI`dZg>%T_F8w@jStxl|~nqe-%I+cl;k%4islYxT)A26B9@-s62XJG+m z1~vnJ5MLO?XEk63QU0I87yo|l9B$+J+7C|sz0}7g?^v?5W>w3Tr=R4WnFkro;l7wCyInyCSyYJWA~ci6%)MF-+N!5Klyo+*|}L6JWbEfUl96{km6X*QOV6i@v)a6P9JX^s%P}&yL#Py{$_&M#g=~dU$u?w-lFk&jOnj z(~b$+eLm;@ZjOe$>aG84(x%=u<&Sx;;xlv{zT20+}Nx+-K!?5Xp7~A?d7K{ z|7KcEGJl(Ak?L0WGGT9J$Mkp$p)c>&M>m~lyZUfuZV$_+vR$6v@?Pf3#V>fT-c;uB zW7*}17=9h6TTaKSJ4}uryx}_gpxx0#*_+7$p5IJbvmQts&2wMv=%hLOTLSMT!^_Dt zlqW|qG|us?p{IzMhLx@Cxmo?H&l(C7wYOHE`Kc<__|PzZ z;o>^6C3D2Grc3NCD_Y7uPuA?2{zCnI1rzyKJiGPd$Rvjx)w=4fuWfMokg>FX zO6mWSs23}kYIrywt@P$z(8`^(^Zi1@Nx$EJ3uNicd9ku&;coHGTmO3U*Vw#imQZl^ zyE=K-7L$$gVrKF@&r&SKeC|B>CwpRLlC8t5Q%6mhwyf*yFgCrTBV2q#{o{qAccs=! Y_hw|IMSnb&>>et!Ybi%y=`s&h0Er}BD*ylh diff --git a/SignalServiceKit/Resources/Certificates/GTSR2.crt b/SignalServiceKit/Resources/Certificates/GTSR2.crt deleted file mode 100644 index 79e2a480be60cbd7aa27a96251abfe7058d4b9b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1374 zcmXqLVvRCrVs=`<%*4pVB#`I6@>ta7=L$y+X4z*1eabiBW#iOp^Jx3d%gD&X%3$Db z$Zf#M#vIDRCd?EXY^Y?Q0OD{7OS!|u1Q{8~iSrto8JHOv8Gu2QIIl4>mwFa8F)1N?f{~ShxrvFN!JvtW zi>Zl;k>T9Ezsr8Vc_U(C!_F3!6Zu`z^zQeAb3<2MK5M;Xa?yLPU%7L?eQ0fnUg!}t z-}318cN%NnpDxP~*?E60W6n(ZBTBOK{}q)#(1~9Egk`^f(E=%x880j|-b_`kE?f94 zb7!mM-p-zXYu}o^D&F(I`O)dxgO|>EZSG~A*X=p^{=w)Ad2t`~4D_1*{MxfcoXg{B z$LyoCv+r#?SK+$HZmaal>!r5_uUH3W8S=G+lh?yyL0M&+@8_f_B7JzY?LC6PbS+s3}<*2995 z8;3F${yfO@a^a);-Jv#RZ11xC&P?hs@SU~SpW91(e>3(hr~CB66RuIQyFPpf7IhpNXsIAwOa zBk{M~_6w)qiT%F3%;(;urB@!^|H>9}Pj9aGmdOUI8qeL8x)WH;>tFEl={7M%$;wU7 zI=M?zPF-EShx4Gs!v9x77Pi{_6;WZ{)P8x%b|z*<2FArs1`Y;%z+@`R&&c?ng$0-y z*bMkVd|?ou)qojD8OVYJ_*lePM0Wo_)u%k)f99#%k201m6;&6PyUB-~%79q~n93L# zwq<@;R{n3^JMPA{4-c$7{?k;}p=tFqmM!;Bf0yNN{!l0E+uQT*v9Z!&%RdhH^BldM zg7=Aae7s~4cO+w`%#5JPJUu{!wHr&?c&_;{|_J>l{H=_}_XluLwet>c({ z{=zq#bY{^+&Yz8yU*7~TnSZjMvAg(4dD)%jMPGS5rB;;Pn>0J+$7Y?Q>X+FMa5OBL zG3hbmG^SNd@A|EeUd-h(HYl#x=^rAtpPPN*l9*?wPsX(L^FI(R*QryhyLH6-_Hwau z_6=uCUlciAo7ytBt@+cF31>OouXHmP+=@EJ?DXZW`JxZf0U_ReH(KsVYRqhOU&kqJ z?{V>P{-X^E;$Pn>GcCQv5S=?=^}9tcrLA8ccTsok%bx#rNz0z{pV`yb*af;S5?pqs z^&MBnC%XWPDwQNTaRu|-EZ^;%vrqn667hB0?>T-FyQeZeU$}NQ`#ZC2l}naw2PZcN zTxxV%5uX&UvpXlHgCp;~a7g@J-6QGAm(O>a@1Lvi<#$!{>;-a-3;~Cp@F+zF3A}X< zYiIZ&60}^ldvebrU$=VoV}-L-?mfBH*3Da&t#I(byoaebvKB9U(b{dr&LOkwYk%l0 ziPE~KZyM7sDi$v^b>VKXjh+*iuO&Y1VzY8bTJE#9M+HXuK}ye0Jg+~ZlMt(ac>O$A X*267K+yClt1o=1JUHsun8z&P0@xWTd diff --git a/SignalServiceKit/Resources/Certificates/GTSR3.crt b/SignalServiceKit/Resources/Certificates/GTSR3.crt deleted file mode 100644 index 310219dfa956b8ac16752f89ea95c10e1b993d87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 528 zcmXqLV&XAqVw|{unTe5!Ng&UCJkHs&Ff$vt8*&?P zvN4CUun9AT1{*3FD1bOz!jkU!`RO^S3L!CsYPX($*IK(K0eNdA_hVrHO#`? z?jgYnLHYS53PHvOa^k#(W(H|pmZF|x60H?lA&F()yw z$om_&8*6qgY`J&(xMGB3=c}-}+TN|lWPQ~4)T;#+fvqRG5YpEeX)~) zg8?7VE`M}hrI(PPU<}2@9d^eRLC~o4K6LX77nu57&S32y? zNI3d%M~L-7GjY`$2loQC%rl)-(yAKxRpm`ekV?Q(`P(l}pDcUxrYY}ht3>dN9gk;e Uu9|kaS^4CW>cq`wm|nyJ0COp=ApigX diff --git a/SignalServiceKit/Resources/Certificates/GTSR4.crt b/SignalServiceKit/Resources/Certificates/GTSR4.crt deleted file mode 100644 index 13d993b9e85009d8c276340f3e375fcab5568907..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 526 zcmXqLV&XDrVw|{unTe5!Ng&UC<%#Yo+g|Lp*1EB1)4`brTx=X#Z64=rS(up(+zq)6 zIN6v(S=fY`LW2#J3=}{dE@4Uc{QUHsRE3bD(&7?@;MAhB%;eN!1s@-0LlFZZkQ!!T zZugL2g`oWW5``cW137VCLo)+2BO?Pah!W>DM&?q_qA~*kHg>T4nHbqvwHsL&l$etk zSU#5&FVE;sSm(UM^k}0&wYTTo-v<-VJ-g?&H~VU$gy}kmyT#u-CmKY(wt3fDv&f;$ zdgZYl?l-iaPnwuj{BmQF<$TTgi)LJokBewaI#U`uuza;ap9t6a2L{-?Pgs zl5d(Nte71a!_crvKeGN?`%hKpw&$7BL9-@2EPQ-Jm)9@<+X>H`+dfN$t<&CCBJ%qD zZ?(r#yJV;7Wm!i|>2weKuexAzL2&b}mJ*S}>GN1JtnT+YM9F&jop+vS{XFa%L&52w zch$A)te)>N{OS7bP{MiPNG9!uGbZl$cix+jn#*)vXX3JD&+oNLc-Ex(Ew;*Cx%Hii z-Y#C|9meeDD+ZAek_f&-n9TI)D_j}ew*jYqV9Z;Uo_wC|NGvNvHXns%A{7~Pv#u&3(uFc z-8&&E+u?A&)=l=m-0_F!{wv=;_NzD4Zza=ncR@G1FMsz|YpU&DnV4j*u}xR&n?PI2 z$%5sV)^67!V7OH{5j|q J(7RD%7XbdRdH(3{l0*gPoW!C;Ln#9ZkSG_Apl4o5YL0?)eo;Yw zQDRAEex9L$fgVVXnMWNW7wjIP;8;?UT3iB_R|rZ4DlJh6&P>nC%u82rb~I2B=QT7l zG&D3ZG&MFiHjI+sH!?7>G&C{>B2!CK%cuc&VH4vb}N1&V(emSVr*oX zZnf$NTOyF(ZJns-kxW#Y_dO|m-$t{Rq`bC$bhWyQI-ow;=P^%ar1 zS^CMUL31zioNPDWP(DYB{m~rvh3gnRJ=}9V=DRLk7+ZDX&9ODdT-R=~ZBL*3bq=TX z(M2yab3!UYq^Hj-^$xcb*%x|Q|I2~7_th0=nw*`GgmbQlsHTe zS#Fbvil@^zzrWB_Zm1QQU7VBk#lCEBIey)CU zK9HGLqVF8!qYq53`bfzYl3JmO)!9)mxhTg#7Gwh-ix`VYg>!Y;vbsGN*4H_izc#J0 zZf6X%F^~sIE3-%#h&5nWzz0&m&&c?ng$0;1*}yqWRv5(RFkk~xOpFZ3sSTJ#fvK&L zp(0*4;phpjFItsl0*aQRx0+6Fn%DVYf|lyCUM zG;uQ2D_>?g<>vIMCtkUhHQBuCy7+M7`4vgOcC0kId~-u^6g$&}(D;(43Xho$KASW= zUwiWU2|0xVL#O==M>SS(yBRnBP0(sO&K%UZ>gm*^Gh2=QR!p!<xKr3x zML25nkrXNY4i1yK#m1U(d}Z&vk17=zCH4JkxITB%YCl^+h0njEE=r#54e?rdWm#KX zcKE(E;e`fudNw=Rd6!gs|LMqlAR_#V)70@^SEYvFb~m%5FGYgR^Sq1v^xMJfEcelt z({q|bSc-l4>W(GE+&{1X#d6z~!ubXId_49G{BH{_JFRtbwal!KKfWAZ(08|Ju{QVC s3u{&L|BBAau-1x_mlgb#6s*sDJ>=AT-lfG+a#0;Vb)7ebBu-uj01{LUm;e9( diff --git a/SignalServiceKit/Resources/Certificates/textsecure.cer b/SignalServiceKit/Resources/Certificates/textsecure.cer deleted file mode 100644 index ed2f4fd6c302d96a2606dadd9a149186b0d0130f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1011 zcmXqLVt#MX#B_ZDGZP~d6DLFGF5T5$Pd+CZ@Un4gwRyCC=VfGMWo0mE>^0;z;ACSC zWnmL$3Jo?CHV_1HIC!|66LT`t@{95^6Ai@-L_mVQW#%uS5^3a-v|X(J4jQ-gS2cfSICu2v*394c?fR|m={2oe>%}kllD)3wuxU&` z(-To6o~pjL-_E)^bvkUXY*`yS<aT*7()5W&Uv{X2#<=T1h6^Arm-fX5Zz_ zEhtc7SQvP;IqCqzr)B3Hf6iQ0#Qy7f$#w7fUlcxH&}-+Oe{h;U^9-{xdA3j78pa&k z(t@|Ry(_%2;E03m%uo7z3YZ0V$LU>7I3T&#efHITceMEzG#%-E*-&>xekYH;^W5(> z++C-iU)OL?-k6sZ^(1L|iqY38YgRFM##r?3+q-vpC#QSJkAf93Px`NXkYiYyfvFx5P*L-g4t*a?e|KVfRU= len(event_name) - 1: - break - nextChar = event_name[index + 1] - event_name = event_name[:index] + nextChar.upper() + event_name[index + 2:] - return event_name - - -event_names = [] - -def process(filepath, c_macros, swift_macros): - - short_filepath = filepath[len(git_repo_path):] - if short_filepath.startswith(os.sep): - short_filepath = short_filepath[len(os.sep):] - - filename = os.path.basename(filepath) - if filename.startswith('.'): - return - if filename == 'OWSAnalytics.h': - return - file_ext = os.path.splitext(filename)[1] - - is_swift = file_ext in ('.swift') - - - if is_swift: - macros = swift_macros - else: - macros = c_macros - - # print short_filepath, is_swift - - with open(filepath, 'rt') as f: - text = f.read() - - replacement_map = {} - - position = 0 - has_printed_filename = False - while True: - best_match = None - best_macro = None - for macro in macros: - pattern = r'''%s\(([^,\)]+)[,\)]''' % macro - # print '\t pattern', pattern - matcher = re.compile(pattern) - # matcher = re.compile(r'#define (OWSProd)') - match = matcher.search(text, pos=position) - if match: - event_name = match.group(1).strip() - - # Ignore swift func definitions - if is_swift and ':' in event_name: - continue - - # print '\t', 'event_name', event_name - - if not best_match: - pass - elif best_match.start(1) > match.start(1): - pass - else: - continue - - best_match = match - best_macro = macro - # TODO: - if not best_match: - break - - position = best_match.end(1) - if not has_printed_filename: - has_printed_filename = True - print short_filepath - - raw_event_name = best_match.group(1).strip() - if is_swift: - pattern = r'^"(.+)"$' - else: - pattern = r'^@"(.+)"$' - # print 'pattern:', pattern - matcher = re.compile(pattern) - # matcher = re.compile(r'#define (OWSProd)') - match = matcher.search(raw_event_name) - if match: - event_name = match.group(1).strip() - else: - print '\t', 'Ignoring event: _%s_' % raw_event_name - continue - event_names.append(event_name) - print '\t', 'event_name', event_name - - if is_swift: - before = '"%s"' % event_name - after = 'OWSAnalyticsEvents.%s()' % objc_name_for_event_name(event_name) - else: - before = '@"%s"' % event_name - after = '[OWSAnalyticsEvents %s]' % objc_name_for_event_name(event_name) - replacement_map[before] = after - - # macros.append(macro) - - # break - - # print 'replacement_map', replacement_map - - for before in replacement_map: - after = replacement_map[before] - text = text.replace(before, after) - - # if original_text == text: - # return - - print 'Updating:', short_filepath - - with open(filepath, 'wt') as f: - f.write(text) - - -def should_ignore_path(path): - ignore_paths = [ - os.path.join(git_repo_path, '.git') - ] - for ignore_path in ignore_paths: - if path.startswith(ignore_path): - return True - for component in splitall(path): - if component.startswith('.'): - return True - if component.endswith('.framework'): - return True - if component in ('Pods', 'ThirdParty', 'Carthage',): - return True - - return False - - -def process_if_appropriate(filepath, c_macros, swift_macros): - filename = os.path.basename(filepath) - if filename.startswith('.'): - return - file_ext = os.path.splitext(filename)[1] - if file_ext not in ('.h', '.hpp', '.cpp', '.m', '.mm', '.pch', '.swift'): - return - if should_ignore_path(filepath): - return - process(filepath, c_macros, swift_macros) - - -def extract_macros(filepath): - - filename = os.path.basename(filepath) - file_ext = os.path.splitext(filename)[1] - is_swift = file_ext in ('.swift') - - macros = [] - - with open(filepath, 'rt') as f: - text = f.read() - - lines = text.split('\n') - for line in lines: - # Match lines of this form: - # #define OWSProdCritical(__eventName) ... - - if is_swift: - matcher = re.compile(r'func (OWSProd[^\(]+)\(.+[,\)]') - else: - matcher = re.compile(r'#define (OWSProd[^\(]+)\(.+[,\)]') - # matcher = re.compile(r'#define (OWSProd)') - match = matcher.search(line) - if match: - macro = match.group(1).strip() - # print 'macro', macro - macros.append(macro) - - return macros - - -def update_event_names(header_file_path, source_file_path): - # global event_names - # event_names = sorted(set(event_names)) - code_generation_marker = '#pragma mark - Code Generation Marker' - - # Source - filepath = source_file_path - with open(filepath, 'rt') as f: - text = f.read() - - code_generation_start = text.find(code_generation_marker) - code_generation_end = text.rfind(code_generation_marker) - if code_generation_start < 0: - print 'Could not find marker in file:', file - sys.exit(1) - if code_generation_end < 0 or code_generation_end == code_generation_start: - print 'Could not find marker in file:', file - sys.exit(1) - - event_name_map = {} - - print - print 'Parsing old generated code' - print - - old_generated = text[code_generation_start + len(code_generation_marker):code_generation_end] - # print 'old_generated', old_generated - for split in old_generated.split('+'): - split = split.strip() - # print 'split:', split - if not split: - continue - - # Example: - #(NSString *)call_service_call_already_set - #{ - # return @"call_service_call_already_set"; - #} - - pattern = r'\(NSString \*\)([^\s\r\n\t]+)[\s\r\n\t]' - matcher = re.compile(pattern) - match = matcher.search(split) - if not match: - print 'Could not parse:', split - print 'In file:', filepath - sys.exit(1) - - method_name = match.group(1).strip() - print 'method_name:', method_name - - pattern = r'return @"(.+)";' - matcher = re.compile(pattern) - match = matcher.search(split) - if not match: - print 'Could not parse:', split - print 'In file:', filepath - sys.exit(1) - - event_name = match.group(1).strip() - print 'event_name:', event_name - - event_name_map[event_name] = method_name - - print - - - all_event_names = sorted(set(event_name_map.keys() + event_names)) - print 'all_event_names', all_event_names - - generated = code_generation_marker - for event_name in all_event_names: - # Example: - # + (NSString *)call_service_call_already_set; - if event_name in event_name_map: - objc_name = event_name_map[event_name] - else: - objc_name = objc_name_for_event_name(event_name) - text_for_event = '''+ (NSString *)%s -{ - return @"%s"; -}''' % (objc_name, event_name) - generated = generated + '\n\n' + text_for_event - generated = generated + '\n\n' + code_generation_marker - print 'generated', generated - new_text = text[:code_generation_start] + generated + text[code_generation_end + len(code_generation_marker):] - print 'text', new_text - with open(filepath, 'wt') as f: - f.write(new_text) - - - # Header - filepath = header_file_path - with open(filepath, 'rt') as f: - text = f.read() - - code_generation_start = text.find(code_generation_marker) - code_generation_end = text.rfind(code_generation_marker) - if code_generation_start < 0: - print 'Could not find marker in file:', file - sys.exit(1) - if code_generation_end < 0 or code_generation_end == code_generation_start: - print 'Could not find marker in file:', file - sys.exit(1) - - generated = code_generation_marker - for event_name in all_event_names: - # Example: - # + (NSString *)call_service_call_already_set; - objc_name = objc_name_for_event_name(event_name) - text_for_event = '+ (NSString *)%s;' % (objc_name,) - generated = generated + '\n\n' + text_for_event - generated = generated + '\n\n' + code_generation_marker - print 'generated', generated - new_text = text[:code_generation_start] + generated + text[code_generation_end + len(code_generation_marker):] - print 'text', new_text - with open(filepath, 'wt') as f: - f.write(new_text) - - - -if __name__ == "__main__": - # print 'git_repo_path', git_repo_path - - macros_header_file_path = os.path.join(git_repo_path, 'SignalServiceKit', 'src', 'Util', 'OWSAnalytics.h') - if not os.path.exists(macros_header_file_path): - print 'Macros header does not exist:', macros_header_file_path - sys.exit(1) - c_macros = extract_macros(macros_header_file_path) - print 'c_macros:', c_macros - - macros_header_file_path = os.path.join(git_repo_path, 'Signal', 'src', 'util', 'OWSAnalytics.swift') - if not os.path.exists(macros_header_file_path): - print 'Macros header does not exist:', macros_header_file_path - sys.exit(1) - swift_macros = extract_macros(macros_header_file_path) - print 'swift_macros:', swift_macros - - event_names_header_file_path = os.path.join(git_repo_path, 'SignalServiceKit', 'src', 'Util', 'OWSAnalyticsEvents.h') - if not os.path.exists(event_names_header_file_path): - print 'event_names_header_file_path does not exist:', event_names_header_file_path - sys.exit(1) - - event_names_source_file_path = os.path.join(git_repo_path, 'SignalServiceKit', 'src', 'Util', 'OWSAnalyticsEvents.m') - if not os.path.exists(event_names_source_file_path): - print 'event_names_source_file_path does not exist:', event_names_source_file_path - sys.exit(1) - - for rootdir, dirnames, filenames in os.walk(git_repo_path): - for filename in filenames: - file_path = os.path.abspath(os.path.join(rootdir, filename)) - process_if_appropriate(file_path, c_macros, swift_macros) - - print - print 'event_names', sorted(set(event_names)) - update_event_names(event_names_header_file_path, event_names_source_file_path) diff --git a/SignalServiceKit/protobuf/Fingerprint.proto b/SignalServiceKit/protobuf/Fingerprint.proto deleted file mode 100644 index 1464c4b8d..000000000 --- a/SignalServiceKit/protobuf/Fingerprint.proto +++ /dev/null @@ -1,24 +0,0 @@ -// iOS - since we use a modern proto-compiler, we must specify -// the legacy proto format. -syntax = "proto2"; - -// iOS - package name determines class prefix -package FingerprintProtos; - -option java_package = "org.whispersystems.libsignal.fingerprint"; -option java_outer_classname = "FingerprintProtos"; - -message LogicalFingerprint { - // @required - optional bytes identityData = 1; -// optional bytes identifier = 2; -} - -message LogicalFingerprints { - // @required - optional uint32 version = 1; - // @required - optional LogicalFingerprint localFingerprint = 2; - // @required - optional LogicalFingerprint remoteFingerprint = 3; -} diff --git a/SignalServiceKit/protobuf/Makefile b/SignalServiceKit/protobuf/Makefile deleted file mode 100644 index fd5eb7ead..000000000 --- a/SignalServiceKit/protobuf/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -# See README.md in this dir for prerequisite setup. - -PROTOC=protoc \ - --proto_path='./' -WRAPPER_SCRIPT=../../Scripts/ProtoWrappers.py \ - --proto-dir='./' --verbose - -all: signal_service_protos provisioning_protos fingerprint_protos websocket_protos signal_ios_protos - -signal_service_protos: SignalService.proto - $(PROTOC) --swift_out=../src/Protos/Generated \ - SignalService.proto - $(WRAPPER_SCRIPT) --dst-dir=../src/Protos/Generated \ - --wrapper-prefix=SSKProto --proto-prefix=SignalServiceProtos --proto-file=SignalService.proto - -provisioning_protos: Provisioning.proto - $(PROTOC) --swift_out=../src/Protos/Generated \ - Provisioning.proto - $(WRAPPER_SCRIPT) --dst-dir=../src/Protos/Generated \ - --wrapper-prefix=ProvisioningProto --proto-prefix=ProvisioningProtos --proto-file=Provisioning.proto - -fingerprint_protos: Fingerprint.proto - $(PROTOC) --swift_out=../src/Protos/Generated \ - Fingerprint.proto - $(WRAPPER_SCRIPT) --dst-dir=../src/Protos/Generated \ - --wrapper-prefix=FingerprintProto --proto-prefix=FingerprintProtos --proto-file=Fingerprint.proto - -websocket_protos: WebSocketResources.proto - $(PROTOC) --swift_out=../src/Protos/Generated \ - WebSocketResources.proto - $(WRAPPER_SCRIPT) --dst-dir=../src/Protos/Generated \ - --wrapper-prefix=WebSocketProto --proto-prefix=WebSocketProtos --proto-file=WebSocketResources.proto - -signal_ios_protos: SignalIOS.proto - $(PROTOC) --swift_out=../src/Protos/Generated \ - SignalIOS.proto - $(WRAPPER_SCRIPT) --dst-dir=../src/Protos/Generated \ - --wrapper-prefix=SignalIOSProto --proto-prefix=IOSProtos --proto-file=SignalIOS.proto diff --git a/SignalServiceKit/protobuf/Provisioning.proto b/SignalServiceKit/protobuf/Provisioning.proto deleted file mode 100644 index cd80a1d3f..000000000 --- a/SignalServiceKit/protobuf/Provisioning.proto +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ - -// iOS - since we use a modern proto-compiler, we must specify -// the legacy proto format. -syntax = "proto2"; - -package ProvisioningProtos; - -option java_package = "org.whispersystems.signalservice.internal.push"; -option java_outer_classname = "ProvisioningProtos"; - -message ProvisionEnvelope { - // @required - optional bytes publicKey = 1; - // @required - optional bytes body = 2; // Encrypted ProvisionMessage -} - -message ProvisionMessage { - // @required - optional bytes identityKeyPublic = 1; - // @required - optional bytes identityKeyPrivate = 2; - // @required - optional string number = 3; - // @required - optional string provisioningCode = 4; - // @required - optional string userAgent = 5; - // @required - optional bytes profileKey = 6; - // @required - optional bool readReceipts = 7; -} diff --git a/SignalServiceKit/protobuf/README.md b/SignalServiceKit/protobuf/README.md deleted file mode 100644 index 5bbc5fdc4..000000000 --- a/SignalServiceKit/protobuf/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# SignalServiceKit Protobufs - -These protobuf definitions are copied from Session-Android, but modified -to match some iOS conventions. - -## Prequisites - -Install Apple's `swift-protobuf` (*not* the similarly named `protobuf-swift`) - - brew install swift-protobuf - -This should install an up to date protobuf package as a dependency. Note that -since we use the legacy proto2 format, we need to specify this in our .proto -files. - - syntax = "proto2"; - -## Building Protobuf - - cd SignalServiceKit/protobuf - make - diff --git a/SignalServiceKit/protobuf/SignalIOS.proto b/SignalServiceKit/protobuf/SignalIOS.proto deleted file mode 100644 index 303c067b5..000000000 --- a/SignalServiceKit/protobuf/SignalIOS.proto +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ - -// iOS - since we use a modern proto-compiler, we must specify -// the legacy proto format. -syntax = "proto2"; - -// iOS - package name determines class prefix -package IOSProtos; - -message BackupSnapshot { - message BackupEntity { - enum Type { - UNKNOWN = 0; - MIGRATION = 1; - THREAD = 2; - INTERACTION = 3; - ATTACHMENT = 4; - MISC = 5; - } - // @required - optional Type type = 1; - // @required - optional bytes entityData = 2; - // @required - optional string collection = 3; - // @required - optional string key = 4; - } - - repeated BackupEntity entity = 1; -} - -message DeviceName { - // @required - optional bytes ephemeralPublic = 1; - // @required - optional bytes syntheticIv = 2; - // @required - optional bytes ciphertext = 3; -} - diff --git a/SignalServiceKit/protobuf/SignalService.proto b/SignalServiceKit/protobuf/SignalService.proto deleted file mode 100644 index 92a9f0616..000000000 --- a/SignalServiceKit/protobuf/SignalService.proto +++ /dev/null @@ -1,475 +0,0 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ - -// iOS - since we use a modern proto-compiler, we must specify -// the legacy proto format. -syntax = "proto2"; - -// iOS - package name determines class prefix -package SignalServiceProtos; - -option java_package = "org.whispersystems.signalservice.internal.push"; -option java_outer_classname = "SignalServiceProtos"; - -message Envelope { - enum Type { - UNKNOWN = 0; - CIPHERTEXT = 1; - KEY_EXCHANGE = 2; - PREKEY_BUNDLE = 3; - RECEIPT = 5; - UNIDENTIFIED_SENDER = 6; - CLOSED_GROUP_CIPHERTEXT = 7; // Loki - FALLBACK_MESSAGE = 101; // Loki: Encrypted using the fallback session cipher. Contains a pre key bundle if it's a session request. - } - - // @required - optional Type type = 1; - optional string source = 2; - optional uint32 sourceDevice = 7; - optional string relay = 3; - // @required - optional uint64 timestamp = 5; - optional bytes legacyMessage = 6; // Contains an encrypted DataMessage - optional bytes content = 8; // Contains an encrypted Content - // We may eventually want to make this required. - optional string serverGuid = 9; - // We may eventually want to make this required. - optional uint64 serverTimestamp = 10; -} - -message TypingMessage { - enum Action { - STARTED = 0; - STOPPED = 1; - } - - // @required - optional uint64 timestamp = 1; - // @required - optional Action action = 2; - optional bytes groupId = 3; -} - -message Content { - optional DataMessage dataMessage = 1; - optional SyncMessage syncMessage = 2; - optional CallMessage callMessage = 3; - optional NullMessage nullMessage = 4; - optional ReceiptMessage receiptMessage = 5; - optional TypingMessage typingMessage = 6; - optional PrekeyBundleMessage prekeyBundleMessage = 101; // Loki - optional LokiDeviceLinkMessage lokiDeviceLinkMessage = 103; // Loki -} - -message PrekeyBundleMessage { // Loki - optional bytes identityKey = 1; - optional uint32 deviceID = 2; - optional uint32 prekeyID = 3; - optional uint32 signedKeyID = 4; - optional bytes prekey = 5; - optional bytes signedKey = 6; - optional bytes signature = 7; -} - -message LokiDeviceLinkMessage { // Loki - optional string masterPublicKey = 1; - optional string slavePublicKey = 2; - optional bytes slaveSignature = 3; - optional bytes masterSignature = 4; -} - -message CallMessage { - message Offer { - // @required - optional uint64 id = 1; - // Signal-iOS renamed the description field to avoid - // conflicts with [NSObject description]. - // @required - optional string sessionDescription = 2; - } - - message Answer { - // @required - optional uint64 id = 1; - // Signal-iOS renamed the description field to avoid - // conflicts with [NSObject description]. - // @required - optional string sessionDescription = 2; - } - - message IceUpdate { - // @required - optional uint64 id = 1; - // @required - optional string sdpMid = 2; - // @required - optional uint32 sdpMLineIndex = 3; - // @required - optional string sdp = 4; - } - - message Busy { - // @required - optional uint64 id = 1; - } - - message Hangup { - // @required - optional uint64 id = 1; - } - - optional Offer offer = 1; - optional Answer answer = 2; - repeated IceUpdate iceUpdate = 3; - optional Hangup hangup = 4; - optional Busy busy = 5; - // Signal-iOS sends profile key with call messages - // for earlier discovery - optional bytes profileKey = 6; -} - -message ClosedGroupCiphertextMessageWrapper { - // @required - optional bytes ciphertext = 1; - // @required - optional bytes ephemeralPublicKey = 2; -} - -message DataMessage { - enum Flags { - END_SESSION = 1; - EXPIRATION_TIMER_UPDATE = 2; - PROFILE_KEY_UPDATE = 4; - UNLINK_DEVICE = 128; - } - - message Quote { - message QuotedAttachment { - enum Flags { - VOICE_MESSAGE = 1; - } - - optional string contentType = 1; - optional string fileName = 2; - optional AttachmentPointer thumbnail = 3; - optional uint32 flags = 4; - } - - // @required - optional uint64 id = 1; - // @required - optional string author = 2; - optional string text = 3; - repeated QuotedAttachment attachments = 4; - } - - message Contact { - message Name { - optional string givenName = 1; - optional string familyName = 2; - optional string prefix = 3; - optional string suffix = 4; - optional string middleName = 5; - optional string displayName = 6; - } - - message Phone { - enum Type { - HOME = 1; - MOBILE = 2; - WORK = 3; - CUSTOM = 4; - } - - optional string value = 1; - optional Type type = 2; - optional string label = 3; - } - - message Email { - enum Type { - HOME = 1; - MOBILE = 2; - WORK = 3; - CUSTOM = 4; - } - - optional string value = 1; - optional Type type = 2; - optional string label = 3; - } - - message PostalAddress { - enum Type { - HOME = 1; - WORK = 2; - CUSTOM = 3; - } - - optional Type type = 1; - optional string label = 2; - optional string street = 3; - optional string pobox = 4; - optional string neighborhood = 5; - optional string city = 6; - optional string region = 7; - optional string postcode = 8; - optional string country = 9; - } - - message Avatar { - optional AttachmentPointer avatar = 1; - optional bool isProfile = 2; - } - - optional Name name = 1; - repeated Phone number = 3; - repeated Email email = 4; - repeated PostalAddress address = 5; - optional Avatar avatar = 6; - optional string organization = 7; - } - - message Preview { - // @required - optional string url = 1; - optional string title = 2; - optional AttachmentPointer image = 3; - } - - message LokiProfile { // Loki - optional string displayName = 1; - optional string profilePicture = 2; - } - - message ClosedGroupUpdate { // Loki - enum Type { - NEW = 0; // groupPublicKey, name, groupPrivateKey, senderKeys, members, admins - INFO = 1; // groupPublicKey, name, senderKeys, members, admins - SENDER_KEY_REQUEST = 2; // groupPublicKey - SENDER_KEY = 3; // groupPublicKey, senderKeys - } - - message SenderKey { - // @required - optional bytes chainKey = 1; - // @required - optional uint32 keyIndex = 2; - // @required - optional bytes publicKey = 3; - } - - optional string name = 1; - // @required - optional bytes groupPublicKey = 2; - optional bytes groupPrivateKey = 3; - repeated SenderKey senderKeys = 4; - repeated bytes members = 5; - repeated bytes admins = 6; - // @required - optional Type type = 7; - } - - optional string body = 1; - repeated AttachmentPointer attachments = 2; - optional GroupContext group = 3; - optional uint32 flags = 4; - optional uint32 expireTimer = 5; - optional bytes profileKey = 6; - optional uint64 timestamp = 7; - optional Quote quote = 8; - repeated Contact contact = 9; - repeated Preview preview = 10; - optional LokiProfile profile = 101; // Loki: The current user's profile - optional ClosedGroupUpdate closedGroupUpdate = 103; // Loki - optional PublicChatInfo publicChatInfo = 999; // Loki: Internal public chat info -} - -message NullMessage { - optional bytes padding = 1; -} - -message ReceiptMessage { - enum Type { - DELIVERY = 0; - READ = 1; - } - - // @required - optional Type type = 1; - repeated uint64 timestamp = 2; -} - -message Verified { - enum State { - DEFAULT = 0; - VERIFIED = 1; - UNVERIFIED = 2; - } - - // @required - optional string destination = 1; - optional bytes identityKey = 2; - optional State state = 3; - optional bytes nullMessage = 4; -} - -message SyncMessage { - message Sent { - message UnidentifiedDeliveryStatus { - optional string destination = 1; - optional bool unidentified = 2; - } - optional string destination = 1; - optional uint64 timestamp = 2; - optional DataMessage message = 3; - optional uint64 expirationStartTimestamp = 4; - repeated UnidentifiedDeliveryStatus unidentifiedStatus = 5; - optional bool isRecipientUpdate = 6 [default = false]; - } - - message Contacts { - optional AttachmentPointer blob = 1; - // Signal-iOS renamed this property. - optional bool isComplete = 2 [default = false]; - optional bytes data = 101; // Loki - } - - message Groups { - optional AttachmentPointer blob = 1; - optional bytes data = 101; // Loki - } - - message OpenGroupDetails { // Loki - // @required - optional string url = 1; - // @required - optional uint64 channelID = 2; - } - - message Blocked { - repeated string numbers = 1; - repeated bytes groupIds = 2; - } - - message Request { - enum Type { - UNKNOWN = 0; - CONTACTS = 1; - GROUPS = 2; - BLOCKED = 3; - CONFIGURATION = 4; - } - - // @required - optional Type type = 1; - } - - message Read { - // @required - optional string sender = 1; - // @required - optional uint64 timestamp = 2; - } - - message Configuration { - optional bool readReceipts = 1; - optional bool unidentifiedDeliveryIndicators = 2; - optional bool typingIndicators = 3; - optional bool linkPreviews = 4; - } - - optional Sent sent = 1; - optional Contacts contacts = 2; - optional Groups groups = 3; - optional Request request = 4; - repeated Read read = 5; - optional Blocked blocked = 6; - optional Verified verified = 7; - optional Configuration configuration = 9; - optional bytes padding = 8; - repeated OpenGroupDetails openGroups = 100; -} - -message AttachmentPointer { - enum Flags { - VOICE_MESSAGE = 1; - } - - // @required - optional fixed64 id = 1; - optional string contentType = 2; - optional bytes key = 3; - optional uint32 size = 4; - optional bytes thumbnail = 5; - optional bytes digest = 6; - optional string fileName = 7; - optional uint32 flags = 8; - optional uint32 width = 9; - optional uint32 height = 10; - optional string caption = 11; - optional string url = 101; // Loki -} - -message GroupContext { - enum Type { - UNKNOWN = 0; - UPDATE = 1; - DELIVER = 2; - QUIT = 3; - REQUEST_INFO = 4; - } - // @required - optional bytes id = 1; - // @required - optional Type type = 2; - optional string name = 3; - repeated string members = 4; - optional AttachmentPointer avatar = 5; - repeated string admins = 6; // Loki -} - -message ContactDetails { - message Avatar { - optional string contentType = 1; - optional uint32 length = 2; - } - - // @required - optional string number = 1; - optional string name = 2; - optional Avatar avatar = 3; - optional string color = 4; - optional Verified verified = 5; - optional bytes profileKey = 6; - optional bool blocked = 7; - optional uint32 expireTimer = 8; - optional string nickname = 101; // Loki -} - -message GroupDetails { - message Avatar { - optional string contentType = 1; - optional uint32 length = 2; - } - - // @required - optional bytes id = 1; - optional string name = 2; - repeated string members = 3; - optional Avatar avatar = 4; - optional bool active = 5 [default = true]; - optional uint32 expireTimer = 6; - optional string color = 7; - optional bool blocked = 8; - repeated string admins = 9; // Loki -} - -// Internal - DO NOT SEND -message PublicChatInfo { - optional uint64 serverID = 1; -} diff --git a/SignalServiceKit/protobuf/WebSocketResources.proto b/SignalServiceKit/protobuf/WebSocketResources.proto deleted file mode 100644 index 709e200d7..000000000 --- a/SignalServiceKit/protobuf/WebSocketResources.proto +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ - -// iOS - since we use a modern proto-compiler, we must specify -// the legacy proto format. -syntax = "proto2"; - -// iOS - package name determines class prefix -package WebSocketProtos; - -option java_package = "org.whispersystems.signalservice.internal.websocket"; -option java_outer_classname = "WebSocketProtos"; - -message WebSocketRequestMessage { - // @required - optional string verb = 1; - // @required - optional string path = 2; - optional bytes body = 3; - repeated string headers = 5; - // @required - optional uint64 requestId = 4; -} - -message WebSocketResponseMessage { - // @required - optional uint64 requestId = 1; - // @required - optional uint32 status = 2; - optional string message = 3; - repeated string headers = 5; - optional bytes body = 4; -} - -message WebSocketMessage { - enum Type { - UNKNOWN = 0; - REQUEST = 1; - RESPONSE = 2; - } - - // @required - optional Type type = 1; - optional WebSocketRequestMessage request = 2; - optional WebSocketResponseMessage response = 3; -} diff --git a/SignalServiceKit/src/Account/AccountServiceClient.swift b/SignalServiceKit/src/Account/AccountServiceClient.swift deleted file mode 100644 index da9c5ee06..000000000 --- a/SignalServiceKit/src/Account/AccountServiceClient.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit - -// TODO define actual type, and validate length -public typealias IdentityKey = Data - -/// based on libsignal-service-java's AccountManager class -@objc(SSKAccountServiceClient) -public class AccountServiceClient: NSObject { - - static var shared = AccountServiceClient() - - private let serviceClient: SignalServiceClient - - override init() { - self.serviceClient = SignalServiceRestClient() - } - - public func getPreKeysCount() -> Promise { - return serviceClient.getAvailablePreKeys() - } - - public func setPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise { - return serviceClient.registerPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords) - } - - public func setSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise { - return serviceClient.setCurrentSignedPreKey(signedPreKey) - } -} diff --git a/SignalServiceKit/src/Account/CreatePreKeysOperation.swift b/SignalServiceKit/src/Account/CreatePreKeysOperation.swift deleted file mode 100644 index 5a8c75bcf..000000000 --- a/SignalServiceKit/src/Account/CreatePreKeysOperation.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit - -@objc(SSKCreatePreKeysOperation) -public class CreatePreKeysOperation: OWSOperation { - - private var accountServiceClient: AccountServiceClient { - return AccountServiceClient.shared - } - - private var primaryStorage: OWSPrimaryStorage { - return OWSPrimaryStorage.shared() - } - - private var identityKeyManager: OWSIdentityManager { - return OWSIdentityManager.shared() - } - - public override func run() { - Logger.debug("") - - if identityKeyManager.identityKeyPair() == nil { - identityKeyManager.generateNewIdentityKeyPair() - } - - SessionManagementProtocol.createPreKeys() - reportSuccess() - - /* Loki: Original code - * ================ - let identityKey: Data = self.identityKeyManager.identityKeyPair()!.publicKey - let signedPreKeyRecord: SignedPreKeyRecord = self.primaryStorage.generateRandomSignedRecord() - let preKeyRecords: [PreKeyRecord] = self.primaryStorage.generatePreKeyRecords() - - self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) - self.primaryStorage.storePreKeyRecords(preKeyRecords) - - firstly { - self.accountServiceClient.setPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords) - }.done { - signedPreKeyRecord.markAsAcceptedByService() - self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) - self.primaryStorage.setCurrentSignedPrekeyId(signedPreKeyRecord.id) - - Logger.debug("done") - self.reportSuccess() - }.catch { error in - self.reportError(error) - }.retainUntilComplete() - * ================ - */ - } -} diff --git a/SignalServiceKit/src/Account/PreKeyRefreshOperation.swift b/SignalServiceKit/src/Account/PreKeyRefreshOperation.swift deleted file mode 100644 index a23349651..000000000 --- a/SignalServiceKit/src/Account/PreKeyRefreshOperation.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit - -// We generate 100 one-time prekeys at a time. We should replenish -// whenever ~2/3 of them have been consumed. -let kEphemeralPreKeysMinimumCount: UInt = 35 - -@objc(SSKRefreshPreKeysOperation) -public class RefreshPreKeysOperation: OWSOperation { - - private var tsAccountManager: TSAccountManager { - return TSAccountManager.sharedInstance() - } - - private var accountServiceClient: AccountServiceClient { - return AccountServiceClient.shared - } - - private var primaryStorage: OWSPrimaryStorage { - return OWSPrimaryStorage.shared() - } - - private var identityKeyManager: OWSIdentityManager { - return OWSIdentityManager.shared() - } - - public override func run() { - Logger.debug("") - - guard tsAccountManager.isRegistered() else { - Logger.debug("Skipping pre key refresh; user isn't registered.") - return - } - - // Loki: Doing this on the global queue to match Signal - DispatchQueue.global().async { - SessionManagementProtocol.refreshSignedPreKey() - self.reportSuccess() - } - - /* Loki: Original code - * ================ - firstly { - self.accountServiceClient.getPreKeysCount() - }.then(on: DispatchQueue.global()) { preKeysCount -> Promise in - Logger.debug("preKeysCount: \(preKeysCount)") - guard preKeysCount < kEphemeralPreKeysMinimumCount || self.primaryStorage.currentSignedPrekeyId() == nil else { - Logger.debug("Available keys sufficient: \(preKeysCount)") - return Promise.value(()) - } - - let identityKey: Data = self.identityKeyManager.identityKeyPair()!.publicKey - let signedPreKeyRecord: SignedPreKeyRecord = self.primaryStorage.generateRandomSignedRecord() - let preKeyRecords: [PreKeyRecord] = self.primaryStorage.generatePreKeyRecords() - - self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) - self.primaryStorage.storePreKeyRecords(preKeyRecords) - - return firstly { - self.accountServiceClient.setPreKeys(identityKey: identityKey, signedPreKeyRecord: signedPreKeyRecord, preKeyRecords: preKeyRecords) - }.done { - signedPreKeyRecord.markAsAcceptedByService() - self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) - self.primaryStorage.setCurrentSignedPrekeyId(signedPreKeyRecord.id) - - TSPreKeyManager.clearPreKeyUpdateFailureCount() - TSPreKeyManager.clearSignedPreKeyRecords() - } - }.done { - Logger.debug("done") - self.reportSuccess() - }.catch { error in - self.reportError(error) - }.retainUntilComplete() - * ================ - */ - } - - public override func didSucceed() { - TSPreKeyManager.refreshPreKeysDidSucceed() - } - - override public func didFail(error: Error) { - switch error { - case let networkManagerError as NetworkManagerError: - guard !networkManagerError.isNetworkError else { - Logger.debug("Don't report SPK rotation failure w/ network error") - return - } - - guard networkManagerError.statusCode >= 400 && networkManagerError.statusCode <= 599 else { - Logger.debug("Don't report SPK rotation failure w/ non application error") - return - } - - TSPreKeyManager.incrementPreKeyUpdateFailureCount() - default: - Logger.debug("Don't report SPK rotation failure w/ non NetworkManager error: \(error)") - } - } -} diff --git a/SignalServiceKit/src/Account/RotateSignedKeyOperation.swift b/SignalServiceKit/src/Account/RotateSignedKeyOperation.swift deleted file mode 100644 index 1fc9368dd..000000000 --- a/SignalServiceKit/src/Account/RotateSignedKeyOperation.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit - -@objc(SSKRotateSignedPreKeyOperation) -public class RotateSignedPreKeyOperation: OWSOperation { - private var tsAccountManager: TSAccountManager { - return TSAccountManager.sharedInstance() - } - - private var accountServiceClient: AccountServiceClient { - return AccountServiceClient.shared - } - - private var primaryStorage: OWSPrimaryStorage { - return OWSPrimaryStorage.shared() - } - - public override func run() { - Logger.debug("") - - guard tsAccountManager.isRegistered() else { - Logger.debug("skipping - not registered") - return - } - - // Loki: Doing this on the global queue to match Signal - DispatchQueue.global().async { - SessionManagementProtocol.rotateSignedPreKey() - self.reportSuccess() - } - - /* Loki: Original code - * ================ - let signedPreKeyRecord: SignedPreKeyRecord = self.primaryStorage.generateRandomSignedRecord() - - self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) - firstly { - return self.accountServiceClient.setSignedPreKey(signedPreKeyRecord) - }.done(on: DispatchQueue.global()) { - Logger.info("Successfully uploaded signed PreKey") - signedPreKeyRecord.markAsAcceptedByService() - self.primaryStorage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) - self.primaryStorage.setCurrentSignedPrekeyId(signedPreKeyRecord.id) - - TSPreKeyManager.clearPreKeyUpdateFailureCount() - TSPreKeyManager.clearSignedPreKeyRecords() - - Logger.debug("done") - self.reportSuccess() - }.catch { error in - self.reportError(error) - }.retainUntilComplete() - * ================ - */ - } - - override public func didFail(error: Error) { - switch error { - case let networkManagerError as NetworkManagerError: - guard !networkManagerError.isNetworkError else { - Logger.debug("don't report SPK rotation failure w/ network error") - return - } - - guard networkManagerError.statusCode >= 400 && networkManagerError.statusCode <= 599 else { - Logger.debug("don't report SPK rotation failure w/ non application error") - return - } - - TSPreKeyManager.incrementPreKeyUpdateFailureCount() - default: - Logger.debug("don't report SPK rotation failure w/ non NetworkManager error: \(error)") - } - } -} diff --git a/SignalServiceKit/src/Account/TSAccountManager.h b/SignalServiceKit/src/Account/TSAccountManager.h deleted file mode 100644 index c8dd602cc..000000000 --- a/SignalServiceKit/src/Account/TSAccountManager.h +++ /dev/null @@ -1,171 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSConstants.h" - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const TSRegistrationErrorDomain; -extern NSString *const TSRegistrationErrorUserInfoHTTPStatus; -extern NSString *const RegistrationStateDidChangeNotification; -extern NSString *const kNSNotificationName_LocalNumberDidChange; - -@class AnyPromise; -@class OWSPrimaryStorage; -@class TSNetworkManager; -@class YapDatabaseReadTransaction; -@class YapDatabaseReadWriteTransaction; - -typedef NS_ENUM(NSUInteger, OWSRegistrationState) { - OWSRegistrationState_Unregistered, - OWSRegistrationState_PendingBackupRestore, - OWSRegistrationState_Registered, - OWSRegistrationState_Deregistered, - OWSRegistrationState_Reregistering, -}; - -@interface TSAccountManager : NSObject - -@property (nonatomic, nullable) NSString *phoneNumberAwaitingVerification; - -#pragma mark - Initializers - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -+ (instancetype)sharedInstance; - -- (OWSRegistrationState)registrationState; - -/** - * Returns if a user is registered or not - * - * @return registered or not - */ -- (BOOL)isRegistered; -- (BOOL)isRegisteredAndReady; - -/** - * Returns current phone number for this device, which may not yet have been registered. - * - * @return E164 formatted phone number - */ -+ (nullable NSString *)localNumber; -- (nullable NSString *)localNumber; - -// A variant of localNumber that never opens a "sneaky" transaction. -- (nullable NSString *)storedOrCachedLocalNumber:(YapDatabaseReadTransaction *)transaction; - -/** - * Symmetric key that's used to encrypt message payloads from the server, - * - * @return signaling key - */ -+ (nullable NSString *)signalingKey; -- (nullable NSString *)signalingKey; - -/** - * The server auth token allows the Signal client to connect to the Signal server - * - * @return server authentication token - */ -+ (nullable NSString *)serverAuthToken; -- (nullable NSString *)serverAuthToken; - -/** - * The registration ID is unique to an installation of TextSecure, it allows to know if the app was reinstalled - * - * @return registrationID; - */ - -+ (uint32_t)getOrGenerateRegistrationId:(YapDatabaseReadWriteTransaction *)transaction; -- (uint32_t)getOrGenerateRegistrationId; -- (uint32_t)getOrGenerateRegistrationId:(YapDatabaseReadWriteTransaction *)transaction; - -#pragma mark - Register with phone number - -- (void)registerWithPhoneNumber:(NSString *)phoneNumber - captchaToken:(nullable NSString *)captchaToken - success:(void (^)(void))successBlock - failure:(void (^)(NSError *error))failureBlock - smsVerification:(BOOL)isSMS; - -- (void)rerequestSMSWithCaptchaToken:(nullable NSString *)captchaToken - success:(void (^)(void))successBlock - failure:(void (^)(NSError *error))failureBlock; - -- (void)rerequestVoiceWithCaptchaToken:(nullable NSString *)captchaToken - success:(void (^)(void))successBlock - failure:(void (^)(NSError *error))failureBlock; - -- (void)verifyAccountWithCode:(NSString *)verificationCode - pin:(nullable NSString *)pin - success:(void (^)(void))successBlock - failure:(void (^)(NSError *error))failureBlock; - -// Called once registration is complete - meaning the following have succeeded: -// - obtained signal server credentials -// - uploaded pre-keys -// - uploaded push tokens -- (void)didRegister; - -#if TARGET_OS_IPHONE - -/** - * Register's the device's push notification token with the server - * - * @param pushToken Apple's Push Token - */ -- (void)registerForPushNotificationsWithPushToken:(NSString *)pushToken - voipToken:(NSString *)voipToken - isForcedUpdate:(BOOL)isForcedUpdate - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler - NS_SWIFT_NAME(registerForPushNotifications(pushToken:voipToken:isForcedUpdate:success:failure:)); - -#endif - -+ (void)unregisterTextSecureWithSuccess:(void (^)(void))success failure:(void (^)(NSError *error))failureBlock; - -#pragma mark - De-Registration - -// De-registration reflects whether or not the "last known contact" -// with the service was: -// -// * A 403 from the service, indicating de-registration. -// * A successful auth'd request _or_ websocket connection indicating -// valid registration. -- (BOOL)isDeregistered; -- (void)setIsDeregistered:(BOOL)isDeregistered; - -- (BOOL)hasPendingBackupRestoreDecision; -- (void)setHasPendingBackupRestoreDecision:(BOOL)value; - -#pragma mark - Re-registration - -// Re-registration is the process of re-registering _with the same phone number_. - -// Returns YES on success. -- (BOOL)resetForReregistration; -- (nullable NSString *)reregisterationPhoneNumber; -- (BOOL)isReregistering; - -#pragma mark - Manual Message Fetch - -- (BOOL)isManualMessageFetchEnabled; -- (AnyPromise *)setIsManualMessageFetchEnabled:(BOOL)value __attribute__((warn_unused_result)); - -#ifdef DEBUG -- (void)registerForTestsWithLocalNumber:(NSString *)localNumber; -#endif - -- (AnyPromise *)updateAccountAttributes __attribute__((warn_unused_result)); - -// This should only be used during the registration process. -- (AnyPromise *)performUpdateAccountAttributes __attribute__((warn_unused_result)); - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Account/TSAccountManager.m b/SignalServiceKit/src/Account/TSAccountManager.m deleted file mode 100644 index c5b014ad8..000000000 --- a/SignalServiceKit/src/Account/TSAccountManager.m +++ /dev/null @@ -1,776 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSAccountManager.h" -#import "AppContext.h" -#import "AppReadiness.h" -#import "NSNotificationCenter+OWS.h" -#import "NSURLSessionDataTask+StatusCode.h" -#import "OWSError.h" -#import "OWSPrimaryStorage+SessionStore.h" -#import "OWSRequestFactory.h" -#import "ProfileManagerProtocol.h" -#import "SSKEnvironment.h" -#import "TSNetworkManager.h" -#import "TSPreKeyManager.h" -#import "YapDatabaseConnection+OWS.h" -#import "YapDatabaseTransaction+OWS.h" -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const TSRegistrationErrorDomain = @"TSRegistrationErrorDomain"; -NSString *const TSRegistrationErrorUserInfoHTTPStatus = @"TSHTTPStatus"; -NSString *const RegistrationStateDidChangeNotification = @"RegistrationStateDidChangeNotification"; -NSString *const kNSNotificationName_LocalNumberDidChange = @"kNSNotificationName_LocalNumberDidChange"; - -NSString *const TSAccountManager_RegisteredNumberKey = @"TSStorageRegisteredNumberKey"; -NSString *const TSAccountManager_IsDeregisteredKey = @"TSAccountManager_IsDeregisteredKey"; -NSString *const TSAccountManager_ReregisteringPhoneNumberKey = @"TSAccountManager_ReregisteringPhoneNumberKey"; -NSString *const TSAccountManager_LocalRegistrationIdKey = @"TSStorageLocalRegistrationId"; -NSString *const TSAccountManager_HasPendingRestoreDecisionKey = @"TSAccountManager_HasPendingRestoreDecisionKey"; - -NSString *const TSAccountManager_UserAccountCollection = @"TSStorageUserAccountCollection"; -NSString *const TSAccountManager_ServerAuthToken = @"TSStorageServerAuthToken"; -NSString *const TSAccountManager_ServerSignalingKey = @"TSStorageServerSignalingKey"; -NSString *const TSAccountManager_ManualMessageFetchKey = @"TSAccountManager_ManualMessageFetchKey"; -NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountManager_NeedsAccountAttributesUpdateKey"; - -@interface TSAccountManager () - -@property (atomic, readonly) BOOL isRegistered; - -@property (nonatomic, nullable) NSString *cachedLocalNumber; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@property (nonatomic, nullable) NSNumber *cachedIsDeregistered; - -@property (nonatomic) Reachability *reachability; - -@end - -#pragma mark - - -@implementation TSAccountManager - -@synthesize isRegistered = _isRegistered; - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - if (!self) { - return self; - } - - _dbConnection = [primaryStorage newDatabaseConnection]; - self.reachability = [Reachability reachabilityForInternetConnection]; - - OWSSingletonAssert(); - - if (!CurrentAppContext().isMainApp) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(yapDatabaseModifiedExternally:) - name:YapDatabaseModifiedExternallyNotification - object:nil]; - } - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - [[self updateAccountAttributesIfNecessary] retainUntilComplete]; - }]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(reachabilityChanged) - name:kReachabilityChangedNotification - object:nil]; - - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -+ (instancetype)sharedInstance -{ - OWSAssertDebug(SSKEnvironment.shared.tsAccountManager); - - return SSKEnvironment.shared.tsAccountManager; -} - -#pragma mark - Dependencies - -- (TSNetworkManager *)networkManager -{ - OWSAssertDebug(SSKEnvironment.shared.networkManager); - - return SSKEnvironment.shared.networkManager; -} - -- (id)profileManager { - OWSAssertDebug(SSKEnvironment.shared.profileManager); - - return SSKEnvironment.shared.profileManager; -} - -#pragma mark - - -- (void)setPhoneNumberAwaitingVerification:(NSString *_Nullable)phoneNumberAwaitingVerification -{ - _phoneNumberAwaitingVerification = phoneNumberAwaitingVerification; - - [[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotificationName_LocalNumberDidChange - object:nil - userInfo:nil]; -} - -- (OWSRegistrationState)registrationState -{ - if (!self.isRegistered) { - return OWSRegistrationState_Unregistered; - } else if (self.isDeregistered) { - if (self.isReregistering) { - return OWSRegistrationState_Reregistering; - } else { - return OWSRegistrationState_Deregistered; - } - } else if (self.isDeregistered) { - return OWSRegistrationState_PendingBackupRestore; - } else { - return OWSRegistrationState_Registered; - } -} - -- (BOOL)isRegistered -{ - @synchronized (self) { - if (_isRegistered) { - return YES; - } else { - // Cache this once it's true since it's called alot, involves a dbLookup, and once set - it doesn't change. - _isRegistered = [self storedLocalNumber] != nil; - } - return _isRegistered; - } -} - -- (BOOL)isRegisteredAndReady -{ - return self.registrationState == OWSRegistrationState_Registered; -} - -- (void)didRegister -{ - OWSLogInfo(@"didRegister"); - NSString *phoneNumber = self.phoneNumberAwaitingVerification; - - if (!phoneNumber) { - OWSFail(@"phoneNumber was unexpectedly nil"); - } - - [self storeLocalNumber:phoneNumber]; - - // Warm these cached values. - [self isRegistered]; - [self localNumber]; - [self isDeregistered]; - - [self postRegistrationStateDidChangeNotification]; -} - -+ (nullable NSString *)localNumber -{ - return [[self sharedInstance] localNumber]; -} - -- (nullable NSString *)localNumber -{ - NSString *awaitingVerif = self.phoneNumberAwaitingVerification; - if (awaitingVerif) { - return awaitingVerif; - } - - // Cache this since we access this a lot, and once set it will not change. - @synchronized(self) - { - if (self.cachedLocalNumber == nil) { - self.cachedLocalNumber = self.storedLocalNumber; - } - } - - return self.cachedLocalNumber; -} - -- (nullable NSString *)storedLocalNumber -{ - @synchronized (self) { - return [self.dbConnection stringForKey:TSAccountManager_RegisteredNumberKey - inCollection:TSAccountManager_UserAccountCollection]; - } -} - -- (nullable NSString *)storedOrCachedLocalNumber:(YapDatabaseReadTransaction *)transaction -{ - @synchronized(self) { - if (self.cachedLocalNumber) { - return self.cachedLocalNumber; - } - } - - return [transaction stringForKey:TSAccountManager_RegisteredNumberKey - inCollection:TSAccountManager_UserAccountCollection]; -} - -- (void)storeLocalNumber:(NSString *)localNumber -{ - @synchronized (self) { - [self.dbConnection setObject:localNumber - forKey:TSAccountManager_RegisteredNumberKey - inCollection:TSAccountManager_UserAccountCollection]; - - [self.dbConnection removeObjectForKey:TSAccountManager_ReregisteringPhoneNumberKey - inCollection:TSAccountManager_UserAccountCollection]; - - self.phoneNumberAwaitingVerification = nil; - - self.cachedLocalNumber = localNumber; - } -} - -+ (uint32_t)getOrGenerateRegistrationId:(YapDatabaseReadWriteTransaction *)transaction -{ - return [[self sharedInstance] getOrGenerateRegistrationId:transaction]; -} - -- (uint32_t)getOrGenerateRegistrationId -{ - __block uint32_t result; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - result = [self getOrGenerateRegistrationId:transaction]; - }]; - return result; -} - -- (uint32_t)getOrGenerateRegistrationId:(YapDatabaseReadWriteTransaction *)transaction -{ - // Unlike other methods in this class, there's no need for a `@synchronized` block - // here, since we're already in a write transaction, and all writes occur on a serial queue. - // - // Since other code in this class which uses @synchronized(self) also needs to open write - // transaction, using @synchronized(self) here, inside of a WriteTransaction risks deadlock. - uint32_t registrationID = [[transaction objectForKey:TSAccountManager_LocalRegistrationIdKey - inCollection:TSAccountManager_UserAccountCollection] unsignedIntValue]; - - if (registrationID == 0) { - registrationID = (uint32_t)arc4random_uniform(16380) + 1; - OWSLogWarn(@"Generated a new registrationID: %u", registrationID); - - [transaction setObject:[NSNumber numberWithUnsignedInteger:registrationID] - forKey:TSAccountManager_LocalRegistrationIdKey - inCollection:TSAccountManager_UserAccountCollection]; - } - return registrationID; -} - -- (void)registerForPushNotificationsWithPushToken:(NSString *)pushToken - voipToken:(NSString *)voipToken - isForcedUpdate:(BOOL)isForcedUpdate - success:(void (^)(void))successHandler - failure:(void (^)(NSError *))failureHandler -{ - [self registerForPushNotificationsWithPushToken:pushToken - voipToken:voipToken - isForcedUpdate:isForcedUpdate - success:successHandler - failure:failureHandler - remainingRetries:3]; -} - -- (void)registerForPushNotificationsWithPushToken:(NSString *)pushToken - voipToken:(NSString *)voipToken - isForcedUpdate:(BOOL)isForcedUpdate - success:(void (^)(void))successHandler - failure:(void (^)(NSError *))failureHandler - remainingRetries:(int)remainingRetries -{ - BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"]; - NSData *pushTokenAsData = [NSData dataFromHexString:pushToken]; - AnyPromise *promise = isUsingFullAPNs ? [LKPushNotificationManager registerWithToken:pushTokenAsData hexEncodedPublicKey:self.localNumber isForcedUpdate:isForcedUpdate] - : [LKPushNotificationManager unregisterWithToken:pushTokenAsData isForcedUpdate:isForcedUpdate]; - promise - .then(^() { - successHandler(); - }) - .catch(^(NSError *error) { - if (remainingRetries > 0) { - [self registerForPushNotificationsWithPushToken:pushToken voipToken:voipToken isForcedUpdate:isForcedUpdate success:successHandler failure:failureHandler - remainingRetries:remainingRetries - 1]; - } else { - if (!IsNSErrorNetworkFailure(error)) { - OWSProdError([OWSAnalyticsEvents accountsErrorRegisterPushTokensFailed]); - } - failureHandler(error); - } - }); -} - -- (void)registerWithPhoneNumber:(NSString *)phoneNumber - captchaToken:(nullable NSString *)captchaToken - success:(void (^)(void))successBlock - failure:(void (^)(NSError *error))failureBlock - smsVerification:(BOOL)isSMS - -{ - if ([self isRegistered]) { - failureBlock([NSError errorWithDomain:@"tsaccountmanager.verify" code:4000 userInfo:nil]); - return; - } - - // The country code of TSAccountManager.phoneNumberAwaitingVerification is used to - // determine whether or not to use domain fronting, so it needs to be set _before_ - // we make our verification code request. - self.phoneNumberAwaitingVerification = phoneNumber; - - TSRequest *request = - [OWSRequestFactory requestVerificationCodeRequestWithPhoneNumber:phoneNumber - captchaToken:captchaToken - transport:(isSMS ? TSVerificationTransportSMS - : TSVerificationTransportVoice)]; - [[TSNetworkManager sharedManager] makeRequest:request - success:^(NSURLSessionDataTask *task, id responseObject) { - OWSLogInfo(@"Successfully requested verification code request for number: %@ method:%@", - phoneNumber, - isSMS ? @"SMS" : @"Voice"); - successBlock(); - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - if (!IsNSErrorNetworkFailure(error)) { - OWSProdError([OWSAnalyticsEvents accountsErrorVerificationCodeRequestFailed]); - } - OWSLogError(@"Failed to request verification code request with error:%@", error); - failureBlock(error); - }]; -} - -- (void)rerequestSMSWithCaptchaToken:(nullable NSString *)captchaToken - success:(void (^)(void))successBlock - failure:(void (^)(NSError *error))failureBlock -{ - // TODO: Can we remove phoneNumberAwaitingVerification? - NSString *number = self.phoneNumberAwaitingVerification; - OWSAssertDebug(number); - - [self registerWithPhoneNumber:number - captchaToken:captchaToken - success:successBlock - failure:failureBlock - smsVerification:YES]; -} - -- (void)rerequestVoiceWithCaptchaToken:(nullable NSString *)captchaToken - success:(void (^)(void))successBlock - failure:(void (^)(NSError *error))failureBlock -{ - NSString *number = self.phoneNumberAwaitingVerification; - OWSAssertDebug(number); - - [self registerWithPhoneNumber:number - captchaToken:captchaToken - success:successBlock - failure:failureBlock - smsVerification:NO]; -} - -- (void)verifyAccountWithCode:(NSString *)verificationCode - pin:(nullable NSString *)pin - success:(void (^)(void))successBlock - failure:(void (^)(NSError *error))failureBlock -{ - NSString *authToken = [[self class] generateNewAccountAuthenticationToken]; - NSString *phoneNumber = self.phoneNumberAwaitingVerification; - - OWSAssertDebug(authToken); - OWSAssertDebug(phoneNumber); - - TSRequest *request = [OWSRequestFactory verifyCodeRequestWithVerificationCode:verificationCode - forNumber:phoneNumber - pin:pin - authKey:authToken]; - - [self.networkManager makeRequest:request - success:^(NSURLSessionDataTask *task, id responseObject) { - NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response; - long statuscode = response.statusCode; - - switch (statuscode) { - case 200: - case 204: { - OWSLogInfo(@"Verification code accepted."); - - [self storeServerAuthToken:authToken]; - - [[[SignalServiceRestClient new] updateAccountAttributesObjC] - .thenInBackground(^{ - return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) { - [TSPreKeyManager - createPreKeysWithSuccess:^{ - resolve(@(1)); - } - failure:^(NSError *error) { - resolve(error); - }]; - }]; - }) - .then(^{ - [self.profileManager fetchLocalUsersProfile]; - }) - .then(^{ - successBlock(); - }) - .catchInBackground(^(NSError *error) { - OWSLogError(@"Error: %@", error); - failureBlock(error); - }) retainUntilComplete]; - - break; - } - default: { - OWSLogError(@"Unexpected status while verifying code: %ld", statuscode); - NSError *error = OWSErrorMakeUnableToProcessServerResponseError(); - failureBlock(error); - break; - } - } - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - if (!IsNSErrorNetworkFailure(error)) { - OWSProdError([OWSAnalyticsEvents accountsErrorVerifyAccountRequestFailed]); - } - OWSAssertDebug([error.domain isEqualToString:TSNetworkManagerErrorDomain]); - - OWSLogWarn(@"Error verifying code: %@", error.debugDescription); - - switch (error.code) { - case 403: { - NSError *userError = OWSErrorWithCodeDescription(OWSErrorCodeUserError, - NSLocalizedString(@"REGISTRATION_VERIFICATION_FAILED_WRONG_CODE_DESCRIPTION", - "Error message indicating that registration failed due to a missing or incorrect " - "verification code.")); - failureBlock(userError); - break; - } - case 413: { - // In the case of the "rate limiting" error, we want to show the - // "recovery suggestion", not the error's "description." - NSError *userError - = OWSErrorWithCodeDescription(OWSErrorCodeUserError, error.localizedRecoverySuggestion); - failureBlock(userError); - break; - } - case 423: { - NSString *localizedMessage = NSLocalizedString(@"REGISTRATION_VERIFICATION_FAILED_WRONG_PIN", - "Error message indicating that registration failed due to a missing or incorrect 2FA PIN."); - OWSLogError(@"2FA PIN required: %ld", (long)error.code); - NSError *error - = OWSErrorWithCodeDescription(OWSErrorCodeRegistrationMissing2FAPIN, localizedMessage); - failureBlock(error); - break; - } - default: { - OWSLogError(@"verifying code failed with unknown error: %@", error); - failureBlock(error); - break; - } - } - }]; -} - -#pragma mark Server keying material - -+ (NSString *)generateNewAccountAuthenticationToken { - NSData *authToken = [Randomness generateRandomBytes:16]; - NSString *authTokenPrint = [[NSData dataWithData:authToken] hexadecimalString]; - return authTokenPrint; -} - -+ (nullable NSString *)signalingKey -{ - return [[self sharedInstance] signalingKey]; -} - -- (nullable NSString *)signalingKey -{ - return [self.dbConnection stringForKey:TSAccountManager_ServerSignalingKey - inCollection:TSAccountManager_UserAccountCollection]; -} - -+ (nullable NSString *)serverAuthToken -{ - return [[self sharedInstance] serverAuthToken]; -} - -- (nullable NSString *)serverAuthToken -{ - return [self.dbConnection stringForKey:TSAccountManager_ServerAuthToken - inCollection:TSAccountManager_UserAccountCollection]; -} - -- (void)storeServerAuthToken:(NSString *)authToken -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [transaction setObject:authToken - forKey:TSAccountManager_ServerAuthToken - inCollection:TSAccountManager_UserAccountCollection]; - }]; -} - -+ (void)unregisterTextSecureWithSuccess:(void (^)(void))success failure:(void (^)(NSError *error))failureBlock -{ - TSRequest *request = [OWSRequestFactory unregisterAccountRequest]; - [[TSNetworkManager sharedManager] makeRequest:request - success:^(NSURLSessionDataTask *task, id responseObject) { - OWSLogInfo(@"Successfully unregistered"); - success(); - - // This is called from `[AppSettingsViewController proceedToUnregistration]` whose - // success handler calls `[Environment resetAppData]`. - // This method, after calling that success handler, fires - // `RegistrationStateDidChangeNotification` which is only safe to fire after - // the data store is reset. - - [self.sharedInstance postRegistrationStateDidChangeNotification]; - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - if (!IsNSErrorNetworkFailure(error)) { - OWSProdError([OWSAnalyticsEvents accountsErrorUnregisterAccountRequestFailed]); - } - OWSLogError(@"Failed to unregister with error: %@", error); - failureBlock(error); - }]; -} - -- (void)yapDatabaseModifiedExternally:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - OWSLogVerbose(@""); - - // Any database write by the main app might reflect a deregistration, - // so clear the cached "is registered" state. This will significantly - // erode the value of this cache in the SAE. - @synchronized(self) - { - _isRegistered = NO; - } -} - -#pragma mark - De-Registration - -- (BOOL)isDeregistered -{ - // Cache this since we access this a lot, and once set it will not change. - @synchronized(self) { - if (self.cachedIsDeregistered == nil) { - self.cachedIsDeregistered = @([self.dbConnection boolForKey:TSAccountManager_IsDeregisteredKey - inCollection:TSAccountManager_UserAccountCollection - defaultValue:NO]); - } - - OWSAssertDebug(self.cachedIsDeregistered); - return self.cachedIsDeregistered.boolValue; - } -} - -- (void)setIsDeregistered:(BOOL)isDeregistered -{ - @synchronized(self) { - if (self.cachedIsDeregistered && self.cachedIsDeregistered.boolValue == isDeregistered) { - return; - } - - OWSLogWarn(@"isDeregistered: %d", isDeregistered); - - self.cachedIsDeregistered = @(isDeregistered); - } - - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [transaction setObject:@(isDeregistered) - forKey:TSAccountManager_IsDeregisteredKey - inCollection:TSAccountManager_UserAccountCollection]; - }]; - - [self postRegistrationStateDidChangeNotification]; -} - -#pragma mark - Re-registration - -- (BOOL)resetForReregistration -{ - @synchronized(self) { - NSString *_Nullable localNumber = self.localNumber; - if (!localNumber) { - OWSFailDebug(@"can't re-register without valid local number."); - return NO; - } - - _isRegistered = NO; - _cachedLocalNumber = nil; - _phoneNumberAwaitingVerification = nil; - _cachedIsDeregistered = nil; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [transaction removeAllObjectsInCollection:TSAccountManager_UserAccountCollection]; - - [[OWSPrimaryStorage sharedManager] resetSessionStore:transaction]; - - [transaction setObject:localNumber - forKey:TSAccountManager_ReregisteringPhoneNumberKey - inCollection:TSAccountManager_UserAccountCollection]; - }]; - - [self postRegistrationStateDidChangeNotification]; - - return YES; - } -} - -- (nullable NSString *)reregisterationPhoneNumber -{ - OWSAssertDebug([self isReregistering]); - - NSString *_Nullable result = [self.dbConnection stringForKey:TSAccountManager_ReregisteringPhoneNumberKey - inCollection:TSAccountManager_UserAccountCollection]; - OWSAssertDebug(result); - return result; -} - -- (BOOL)isReregistering -{ - return nil != - [self.dbConnection stringForKey:TSAccountManager_ReregisteringPhoneNumberKey - inCollection:TSAccountManager_UserAccountCollection]; -} - -- (BOOL)hasPendingBackupRestoreDecision -{ - return [self.dbConnection boolForKey:TSAccountManager_HasPendingRestoreDecisionKey - inCollection:TSAccountManager_UserAccountCollection - defaultValue:NO]; -} - -- (void)setHasPendingBackupRestoreDecision:(BOOL)value -{ - OWSLogInfo(@"%d", value); - - [self.dbConnection setBool:value - forKey:TSAccountManager_HasPendingRestoreDecisionKey - inCollection:TSAccountManager_UserAccountCollection]; - - [self postRegistrationStateDidChangeNotification]; -} - -- (BOOL)isManualMessageFetchEnabled -{ - return [self.dbConnection boolForKey:TSAccountManager_ManualMessageFetchKey - inCollection:TSAccountManager_UserAccountCollection - defaultValue:NO]; -} - -- (AnyPromise *)setIsManualMessageFetchEnabled:(BOOL)value { - [self.dbConnection setBool:value - forKey:TSAccountManager_ManualMessageFetchKey - inCollection:TSAccountManager_UserAccountCollection]; - - // Try to update the account attributes to reflect this change. - return [self updateAccountAttributes]; -} - -- (void)registerForTestsWithLocalNumber:(NSString *)localNumber -{ - OWSAssertDebug(localNumber.length > 0); - - [self storeLocalNumber:localNumber]; -} - -#pragma mark - Account Attributes - -- (AnyPromise *)updateAccountAttributes { - // Enqueue a "account attribute update", recording the "request time". - [self.dbConnection setObject:[NSDate new] - forKey:TSAccountManager_NeedsAccountAttributesUpdateKey - inCollection:TSAccountManager_UserAccountCollection]; - - return [self updateAccountAttributesIfNecessary]; -} - -- (AnyPromise *)updateAccountAttributesIfNecessary { - if (!self.isRegistered) { - return [AnyPromise promiseWithValue:@(1)]; - } - - return [AnyPromise promiseWithValue:@(1)]; - - NSDate *_Nullable updateRequestDate = - [self.dbConnection objectForKey:TSAccountManager_NeedsAccountAttributesUpdateKey - inCollection:TSAccountManager_UserAccountCollection]; - if (!updateRequestDate) { - return [AnyPromise promiseWithValue:@(1)]; - } - AnyPromise *promise = [self performUpdateAccountAttributes]; - promise = promise.then(^(id value) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - // Clear the update request unless a new update has been requested - // while this update was in flight. - NSDate *_Nullable latestUpdateRequestDate = - [transaction objectForKey:TSAccountManager_NeedsAccountAttributesUpdateKey - inCollection:TSAccountManager_UserAccountCollection]; - if (latestUpdateRequestDate && [latestUpdateRequestDate isEqual:updateRequestDate]) { - [transaction removeObjectForKey:TSAccountManager_NeedsAccountAttributesUpdateKey - inCollection:TSAccountManager_UserAccountCollection]; - } - }]; - }); - return promise; -} - -- (AnyPromise *)performUpdateAccountAttributes -{ - AnyPromise *promise = [[SignalServiceRestClient new] updateAccountAttributesObjC]; - promise = promise.then(^(id value) { - // Fetch the local profile, as we may have changed its - // account attributes. Specifically, we need to determine - // if all devices for our account now support UD for sync - // messages. - [self.profileManager fetchLocalUsersProfile]; - }); - [promise retainUntilComplete]; - return promise; -} - -- (void)reachabilityChanged { - OWSAssertIsOnMainThread(); - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - [[self updateAccountAttributesIfNecessary] retainUntilComplete]; - }]; -} - -#pragma mark - Notifications - -- (void)postRegistrationStateDidChangeNotification -{ - OWSAssertIsOnMainThread(); - - [[NSNotificationCenter defaultCenter] postNotificationNameAsync:RegistrationStateDidChangeNotification - object:nil - userInfo:nil]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Account/TSPreKeyManager.h b/SignalServiceKit/src/Account/TSPreKeyManager.h deleted file mode 100644 index 4f9253f71..000000000 --- a/SignalServiceKit/src/Account/TSPreKeyManager.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSAccountManager.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface TSPreKeyManager : NSObject - -#pragma mark - State Tracking - -+ (BOOL)isAppLockedDueToPreKeyUpdateFailures; - -+ (void)incrementPreKeyUpdateFailureCount; - -+ (void)clearPreKeyUpdateFailureCount; - -+ (void)clearSignedPreKeyRecords; - -// This should only be called from the TSPreKeyManager.operationQueue -+ (void)refreshPreKeysDidSucceed; - -#pragma mark - Check/Request Initiation - -+ (void)rotateSignedPreKeyWithSuccess:(void (^)(void))successHandler failure:(void (^)(NSError *error))failureHandler; - -+ (void)createPreKeysWithSuccess:(void (^)(void))successHandler failure:(void (^)(NSError *error))failureHandler; - -+ (void)checkPreKeys; - -+ (void)checkPreKeysIfNecessary; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Account/TSPreKeyManager.m b/SignalServiceKit/src/Account/TSPreKeyManager.m deleted file mode 100644 index d09764100..000000000 --- a/SignalServiceKit/src/Account/TSPreKeyManager.m +++ /dev/null @@ -1,298 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSPreKeyManager.h" -#import "AppContext.h" -#import "NSURLSessionDataTask+StatusCode.h" -#import "OWSIdentityManager.h" -#import "OWSPrimaryStorage+SignedPreKeyStore.h" -#import "SSKEnvironment.h" -#import "TSNetworkManager.h" -#import "TSStorageHeaders.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -// Time before deletion of signed prekeys (measured in seconds) -#define kSignedPreKeysDeletionTime (7 * kDayInterval) - -// Time before rotation of signed prekeys (measured in seconds) -#define kSignedPreKeyRotationTime (2 * kDayInterval) - -// How often we check prekey state on app activation. -#define kPreKeyCheckFrequencySeconds (12 * kHourInterval) - -// This global should only be accessed on prekeyQueue. -static NSDate *lastPreKeyCheckTimestamp = nil; - -// Maximum number of failures while updating signed prekeys -// before the message sending is disabled. -static const NSUInteger kMaxPrekeyUpdateFailureCount = 5; - -// Maximum amount of time that can elapse without updating signed prekeys -// before the message sending is disabled. -#define kSignedPreKeyUpdateFailureMaxFailureDuration (10 * kDayInterval) - -#pragma mark - - -@implementation TSPreKeyManager - -#pragma mark - Dependencies - -+ (TSAccountManager *)tsAccountManager -{ - OWSAssertDebug(SSKEnvironment.shared.tsAccountManager); - - return SSKEnvironment.shared.tsAccountManager; -} - -#pragma mark - State Tracking - -+ (BOOL)isAppLockedDueToPreKeyUpdateFailures -{ - // Only disable message sending if we have failed more than N times - // over a period of at least M days. - OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; - return ([primaryStorage prekeyUpdateFailureCount] >= kMaxPrekeyUpdateFailureCount && - [primaryStorage firstPrekeyUpdateFailureDate] != nil - && fabs([[primaryStorage firstPrekeyUpdateFailureDate] timeIntervalSinceNow]) - >= kSignedPreKeyUpdateFailureMaxFailureDuration); -} - -+ (void)incrementPreKeyUpdateFailureCount -{ - // Record a prekey update failure. - OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; - int failureCount = [primaryStorage incrementPrekeyUpdateFailureCount]; - OWSLogInfo(@"new failureCount: %d", failureCount); - - if (failureCount == 1 || ![primaryStorage firstPrekeyUpdateFailureDate]) { - // If this is the "first" failure, record the timestamp of that - // failure. - [primaryStorage setFirstPrekeyUpdateFailureDate:[NSDate new]]; - } -} - -+ (void)clearPreKeyUpdateFailureCount -{ - OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; - [primaryStorage clearFirstPrekeyUpdateFailureDate]; - [primaryStorage clearPrekeyUpdateFailureCount]; -} - -+ (void)refreshPreKeysDidSucceed -{ - lastPreKeyCheckTimestamp = [NSDate new]; -} - -#pragma mark - Check/Request Initiation - -+ (NSOperationQueue *)operationQueue -{ - static dispatch_once_t onceToken; - static NSOperationQueue *operationQueue; - - // PreKey state lives in two places - on the client and on the service. - // Some of our pre-key operations depend on the service state, e.g. we need to check our one-time-prekey count - // before we decide to upload new ones. This potentially entails multiple async operations, all of which should - // complete before starting any other pre-key operation. That's why a dispatch_queue is insufficient for - // coordinating PreKey operations and instead we use NSOperation's on a serial NSOperationQueue. - dispatch_once(&onceToken, ^{ - operationQueue = [NSOperationQueue new]; - operationQueue.name = @"TSPreKeyManager"; - operationQueue.maxConcurrentOperationCount = 1; - }); - return operationQueue; -} - -+ (void)checkPreKeysIfNecessary -{ - if (!CurrentAppContext().isMainAppAndActive) { - return; - } - if (!self.tsAccountManager.isRegisteredAndReady) { - return; - } - - SSKRefreshPreKeysOperation *refreshOperation = [SSKRefreshPreKeysOperation new]; - - __weak SSKRefreshPreKeysOperation *weakRefreshOperation = refreshOperation; - NSBlockOperation *checkIfRefreshNecessaryOperation = [NSBlockOperation blockOperationWithBlock:^{ - BOOL shouldCheck = (lastPreKeyCheckTimestamp == nil - || fabs([lastPreKeyCheckTimestamp timeIntervalSinceNow]) >= kPreKeyCheckFrequencySeconds); - if (!shouldCheck) { - [weakRefreshOperation cancel]; - } - }]; - - [refreshOperation addDependency:checkIfRefreshNecessaryOperation]; - - SSKRotateSignedPreKeyOperation *rotationOperation = [SSKRotateSignedPreKeyOperation new]; - - __weak SSKRotateSignedPreKeyOperation *weakRotationOperation = rotationOperation; - NSBlockOperation *checkIfRotationNecessaryOperation = [NSBlockOperation blockOperationWithBlock:^{ - OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; - SignedPreKeyRecord *_Nullable signedPreKey = [primaryStorage currentSignedPreKey]; - - BOOL shouldCheck - = !signedPreKey || fabs(signedPreKey.generatedAt.timeIntervalSinceNow) >= kSignedPreKeyRotationTime; - if (!shouldCheck) { - [weakRotationOperation cancel]; - } - }]; - - [rotationOperation addDependency:checkIfRotationNecessaryOperation]; - - // Order matters here - if we rotated *before* refreshing, we'd risk uploading - // two SPK's in a row since RefreshPreKeysOperation can also upload a new SPK. - [checkIfRotationNecessaryOperation addDependency:refreshOperation]; - - NSArray *operations = - @[ checkIfRefreshNecessaryOperation, refreshOperation, checkIfRotationNecessaryOperation, rotationOperation ]; - [self.operationQueue addOperations:operations waitUntilFinished:NO]; -} - -+ (void)createPreKeysWithSuccess:(void (^)(void))successHandler failure:(void (^)(NSError *error))failureHandler -{ - OWSAssertDebug(!self.tsAccountManager.isRegisteredAndReady); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - SSKCreatePreKeysOperation *operation = [SSKCreatePreKeysOperation new]; - [self.operationQueue addOperations:@[ operation ] waitUntilFinished:YES]; - - NSError *_Nullable error = operation.failingError; - if (error) { - dispatch_async(dispatch_get_main_queue(), ^{ - failureHandler(error); - }); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - successHandler(); - }); - } - }); -} - -+ (void)rotateSignedPreKeyWithSuccess:(void (^)(void))successHandler failure:(void (^)(NSError *error))failureHandler -{ - OWSAssertDebug(!self.tsAccountManager.isRegisteredAndReady); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - SSKRotateSignedPreKeyOperation *operation = [SSKRotateSignedPreKeyOperation new]; - [self.operationQueue addOperations:@[ operation ] waitUntilFinished:YES]; - - NSError *_Nullable error = operation.failingError; - if (error) { - dispatch_async(dispatch_get_main_queue(), ^{ - failureHandler(error); - }); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - successHandler(); - }); - } - }); -} - -+ (void)checkPreKeys -{ - if (!CurrentAppContext().isMainApp) { return; } - if (!self.tsAccountManager.isRegisteredAndReady) { return; } - SSKRefreshPreKeysOperation *operation = [SSKRefreshPreKeysOperation new]; - [self.operationQueue addOperation:operation]; -} - -+ (void)clearSignedPreKeyRecords { - OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; - NSNumber *_Nullable currentSignedPrekeyId = [primaryStorage currentSignedPrekeyId]; - [self clearSignedPreKeyRecordsWithKeyId:currentSignedPrekeyId]; -} - -+ (void)clearSignedPreKeyRecordsWithKeyId:(NSNumber *_Nullable)keyId -{ - if (!keyId) { - // currentSignedPreKeyId should only be nil before we've completed registration. - // We have this guard here for robustness, but we should never get here. - OWSFailDebug(@"Ignoring request to clear signed preKeys since no keyId was specified"); - return; - } - - OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; - SignedPreKeyRecord *currentRecord = [primaryStorage loadSignedPrekeyOrNil:keyId.intValue]; - if (!currentRecord) { - OWSFailDebug(@"Couldn't find signed prekey for id: %@", keyId); - } - NSArray *allSignedPrekeys = [primaryStorage loadSignedPreKeys]; - NSArray *oldSignedPrekeys - = (currentRecord != nil ? [self removeCurrentRecord:currentRecord fromRecords:allSignedPrekeys] - : allSignedPrekeys); - - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - dateFormatter.dateStyle = NSDateFormatterMediumStyle; - dateFormatter.timeStyle = NSDateFormatterMediumStyle; - dateFormatter.locale = [NSLocale systemLocale]; - - // Sort the signed prekeys in ascending order of generation time. - oldSignedPrekeys = [oldSignedPrekeys sortedArrayUsingComparator:^NSComparisonResult( - SignedPreKeyRecord *_Nonnull left, SignedPreKeyRecord *_Nonnull right) { - return [left.generatedAt compare:right.generatedAt]; - }]; - - NSUInteger oldSignedPreKeyCount = oldSignedPrekeys.count; - - int oldAcceptedSignedPreKeyCount = 0; - for (SignedPreKeyRecord *signedPrekey in oldSignedPrekeys) { - if (signedPrekey.wasAcceptedByService) { - oldAcceptedSignedPreKeyCount++; - } - } - - // Iterate the signed prekeys in ascending order so that we try to delete older keys first. - for (SignedPreKeyRecord *signedPrekey in oldSignedPrekeys) { - // Always keep at least 3 keys, accepted or otherwise. - if (oldSignedPreKeyCount <= 3) { - continue; - } - - // Never delete signed prekeys until they are N days old. - if (fabs([signedPrekey.generatedAt timeIntervalSinceNow]) < kSignedPreKeysDeletionTime) { - continue; - } - - // We try to keep a minimum of 3 "old, accepted" signed prekeys. - if (signedPrekey.wasAcceptedByService) { - if (oldAcceptedSignedPreKeyCount <= 3) { - continue; - } else { - oldAcceptedSignedPreKeyCount--; - } - } - - if (signedPrekey.wasAcceptedByService) { - OWSProdInfo([OWSAnalyticsEvents prekeysDeletedOldAcceptedSignedPrekey]); - } else { - OWSProdInfo([OWSAnalyticsEvents prekeysDeletedOldUnacceptedSignedPrekey]); - } - - oldSignedPreKeyCount--; - [primaryStorage removeSignedPreKey:signedPrekey.Id]; - } -} - -+ (NSArray *)removeCurrentRecord:(SignedPreKeyRecord *)currentRecord fromRecords:(NSArray *)allRecords { - NSMutableArray *oldRecords = [NSMutableArray array]; - - for (SignedPreKeyRecord *record in allRecords) { - if (currentRecord.Id != record.Id) { - [oldRecords addObject:record]; - } - } - - return oldRecords; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/CDSQuote.h b/SignalServiceKit/src/Contacts/CDSQuote.h deleted file mode 100644 index 6f17f7a5c..000000000 --- a/SignalServiceKit/src/Contacts/CDSQuote.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface CDSQuote : NSObject - -@property (nonatomic, readonly) uint16_t version; -@property (nonatomic, readonly) uint16_t signType; -@property (nonatomic, readonly) BOOL isSigLinkable; -@property (nonatomic, readonly) uint32_t gid; -@property (nonatomic, readonly) uint16_t qeSvn; -@property (nonatomic, readonly) uint16_t pceSvn; -@property (nonatomic, readonly) NSData *basename; -@property (nonatomic, readonly) NSData *cpuSvn; -@property (nonatomic, readonly) uint64_t flags; -@property (nonatomic, readonly) uint64_t xfrm; -@property (nonatomic, readonly) NSData *mrenclave; -@property (nonatomic, readonly) NSData *mrsigner; -@property (nonatomic, readonly) uint16_t isvProdId; -@property (nonatomic, readonly) uint16_t isvSvn; -@property (nonatomic, readonly) NSData *reportData; -@property (nonatomic, readonly) NSData *signature; - -+ (nullable CDSQuote *)parseQuoteFromData:(NSData *)quoteData; - -- (BOOL)isDebugQuote; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/CDSQuote.m b/SignalServiceKit/src/Contacts/CDSQuote.m deleted file mode 100644 index c9afd359e..000000000 --- a/SignalServiceKit/src/Contacts/CDSQuote.m +++ /dev/null @@ -1,191 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "CDSQuote.h" -#import "ByteParser.h" - -NS_ASSUME_NONNULL_BEGIN - -static const long SGX_FLAGS_INITTED = 0x0000000000000001L; -static const long SGX_FLAGS_DEBUG = 0x0000000000000002L; -static const long SGX_FLAGS_MODE64BIT = 0x0000000000000004L; -static const long __unused SGX_FLAGS_PROVISION_KEY = 0x0000000000000004L; -static const long __unused SGX_FLAGS_EINITTOKEN_KEY = 0x0000000000000004L; -static const long SGX_FLAGS_RESERVED = 0xFFFFFFFFFFFFFFC8L; -static const long __unused SGX_XFRM_LEGACY = 0x0000000000000003L; -static const long __unused SGX_XFRM_AVX = 0x0000000000000006L; -static const long SGX_XFRM_RESERVED = 0xFFFFFFFFFFFFFFF8L; - -#pragma mark - - -@interface CDSQuote () - -@property (nonatomic) uint16_t version; -@property (nonatomic) uint16_t signType; -@property (nonatomic) BOOL isSigLinkable; -@property (nonatomic) uint32_t gid; -@property (nonatomic) uint16_t qeSvn; -@property (nonatomic) uint16_t pceSvn; -@property (nonatomic) NSData *basename; -@property (nonatomic) NSData *cpuSvn; -@property (nonatomic) uint64_t flags; -@property (nonatomic) uint64_t xfrm; -@property (nonatomic) NSData *mrenclave; -@property (nonatomic) NSData *mrsigner; -@property (nonatomic) uint16_t isvProdId; -@property (nonatomic) uint16_t isvSvn; -@property (nonatomic) NSData *reportData; -@property (nonatomic) NSData *signature; - -@end - -#pragma mark - - -@implementation CDSQuote - -+ (nullable CDSQuote *)parseQuoteFromData:(NSData *)quoteData -{ - ByteParser *_Nullable parser = [[ByteParser alloc] initWithData:quoteData littleEndian:YES]; - - // NOTE: This version is separate from and does _NOT_ match the signature body entity version. - uint16_t version = parser.nextShort; - if (version < 1 || version > 2) { - OWSFailDebug(@"unexpected quote version: %d", (int)version); - return nil; - } - - uint16_t signType = parser.nextShort; - if ((signType & ~1) != 0) { - OWSFailDebug(@"invalid signType: %d", (int)signType); - return nil; - } - - BOOL isSigLinkable = signType == 1; - uint32_t gid = parser.nextInt; - uint16_t qeSvn = parser.nextShort; - - uint16_t pceSvn = 0; - if (version > 1) { - pceSvn = parser.nextShort; - } else { - if (![parser readZero:2]) { - OWSFailDebug(@"non-zero pceSvn."); - return nil; - } - } - - if (![parser readZero:4]) { - OWSFailDebug(@"non-zero xeid."); - return nil; - } - - NSData *_Nullable basename = [parser readBytes:32]; - if (!basename) { - OWSFailDebug(@"couldn't read basename."); - return nil; - } - - // report_body - - NSData *_Nullable cpuSvn = [parser readBytes:16]; - if (!cpuSvn) { - OWSFailDebug(@"couldn't read cpuSvn."); - return nil; - } - if (![parser readZero:4]) { - OWSFailDebug(@"non-zero misc_select."); - return nil; - } - if (![parser readZero:28]) { - OWSFailDebug(@"non-zero reserved1."); - return nil; - } - - uint64_t flags = parser.nextLong; - if ((flags & SGX_FLAGS_RESERVED) != 0 || (flags & SGX_FLAGS_INITTED) == 0 || (flags & SGX_FLAGS_MODE64BIT) == 0) { - OWSFailDebug(@"invalid flags."); - return nil; - } - - uint64_t xfrm = parser.nextLong; - if ((xfrm & SGX_XFRM_RESERVED) != 0) { - OWSFailDebug(@"invalid xfrm."); - return nil; - } - - NSData *_Nullable mrenclave = [parser readBytes:32]; - if (!mrenclave) { - OWSFailDebug(@"couldn't read mrenclave."); - return nil; - } - if (![parser readZero:32]) { - OWSFailDebug(@"non-zero reserved2."); - return nil; - } - NSData *_Nullable mrsigner = [parser readBytes:32]; - if (!mrsigner) { - OWSFailDebug(@"couldn't read mrsigner."); - return nil; - } - if (![parser readZero:96]) { - OWSFailDebug(@"non-zero reserved3."); - return nil; - } - uint16_t isvProdId = parser.nextShort; - uint16_t isvSvn = parser.nextShort; - if (![parser readZero:60]) { - OWSFailDebug(@"non-zero reserved4."); - return nil; - } - NSData *_Nullable reportData = [parser readBytes:64]; - if (!reportData) { - OWSFailDebug(@"couldn't read reportData."); - return nil; - } - - // quote signature - uint32_t signatureLength = parser.nextInt; - if (signatureLength != quoteData.length - 436) { - OWSFailDebug(@"invalid signatureLength."); - return nil; - } - NSData *_Nullable signature = [parser readBytes:signatureLength]; - if (!signature) { - OWSFailDebug(@"couldn't read signature."); - return nil; - } - - if (parser.hasError) { - return nil; - } - - CDSQuote *quote = [CDSQuote new]; - quote.version = version; - quote.signType = signType; - quote.isSigLinkable = isSigLinkable; - quote.gid = gid; - quote.qeSvn = qeSvn; - quote.pceSvn = pceSvn; - quote.basename = basename; - quote.cpuSvn = cpuSvn; - quote.flags = flags; - quote.xfrm = xfrm; - quote.mrenclave = mrenclave; - quote.mrsigner = mrsigner; - quote.isvProdId = isvProdId; - quote.isvSvn = isvSvn; - quote.reportData = reportData; - quote.signature = signature; - - return quote; -} - -- (BOOL)isDebugQuote -{ - return (self.flags & SGX_FLAGS_DEBUG) != 0; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/CDSSigningCertificate.h b/SignalServiceKit/src/Contacts/CDSSigningCertificate.h deleted file mode 100644 index ef9721a4b..000000000 --- a/SignalServiceKit/src/Contacts/CDSSigningCertificate.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSUInteger, CDSSigningCertificateErrorCode) { - // AssertionError's indicate either developer or some serious system error that should never happen. - // - // Do not use this for an "expected" error, e.g. something that could be induced by user input which - // we specifically need to handle gracefull. - CDSSigningCertificateError_AssertionError = 1, - - CDSSigningCertificateError_InvalidPEMSupplied, - CDSSigningCertificateError_CouldNotExtractLeafCertificate, - CDSSigningCertificateError_InvalidDistinguishedName, - CDSSigningCertificateError_UntrustedCertificate -}; - -NSError *CDSSigningCertificateErrorMake(CDSSigningCertificateErrorCode code, NSString *localizedDescription); - -@interface CDSSigningCertificate : NSObject - -+ (nullable CDSSigningCertificate *)parseCertificateFromPem:(NSString *)certificatePem error:(NSError **)error; - -- (BOOL)verifySignatureOfBody:(NSString *)body signature:(NSData *)theirSignature; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/CDSSigningCertificate.m b/SignalServiceKit/src/Contacts/CDSSigningCertificate.m deleted file mode 100644 index 6628b4d1d..000000000 --- a/SignalServiceKit/src/Contacts/CDSSigningCertificate.m +++ /dev/null @@ -1,395 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "CDSSigningCertificate.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSError *CDSSigningCertificateErrorMake(CDSSigningCertificateErrorCode code, NSString *localizedDescription) -{ - return [NSError errorWithDomain:@"CDSSigningCertificate" - code:code - userInfo:@{ NSLocalizedDescriptionKey : localizedDescription }]; -} - -@interface CDSSigningCertificate () - -@property (nonatomic) SecPolicyRef policy; -@property (nonatomic) SecTrustRef trust; -@property (nonatomic) SecKeyRef publicKey; - -@end - -#pragma mark - - -@implementation CDSSigningCertificate - -- (instancetype)init -{ - if (self = [super init]) { - _policy = NULL; - _trust = NULL; - _publicKey = NULL; - } - - return self; -} - -- (void)dealloc -{ - if (_policy) { - CFRelease(_policy); - _policy = NULL; - } - if (_trust) { - CFRelease(_trust); - _trust = NULL; - } - if (_publicKey) { - CFRelease(_publicKey); - _publicKey = NULL; - } -} - -+ (nullable CDSSigningCertificate *)parseCertificateFromPem:(NSString *)certificatePem error:(NSError **)error -{ - OWSAssertDebug(certificatePem); - *error = nil; - - CDSSigningCertificate *signingCertificate = [CDSSigningCertificate new]; - - NSArray *_Nullable anchorCertificates = [self anchorCertificates]; - if (anchorCertificates.count < 1) { - OWSFailDebug(@"Could not load anchor certificates."); - *error = CDSSigningCertificateErrorMake( - CDSSigningCertificateError_AssertionError, @"Could not load anchor certificates."); - return nil; - } - - NSArray *_Nullable certificateDerDatas = [self convertPemToDer:certificatePem]; - - if (certificateDerDatas.count < 1) { - OWSFailDebug(@"Could not parse PEM."); - *error = CDSSigningCertificateErrorMake(CDSSigningCertificateError_InvalidPEMSupplied, @"Could not parse PEM."); - return nil; - } - - // The leaf is always the first certificate. - NSData *_Nullable leafCertificateData = [certificateDerDatas firstObject]; - if (!leafCertificateData) { - OWSFailDebug(@"Could not extract leaf certificate data."); - *error = CDSSigningCertificateErrorMake( - CDSSigningCertificateError_CouldNotExtractLeafCertificate, @"Could not extract leaf certificate data."); - return nil; - } - if (![self verifyDistinguishedNameOfCertificate:leafCertificateData]) { - OWSFailDebugUnlessRunningTests(@"Leaf certificate has invalid name."); - *error = CDSSigningCertificateErrorMake( - CDSSigningCertificateError_InvalidDistinguishedName, @"Could not extract leaf certificate data."); - return nil; - } - - NSMutableArray *certificates = [NSMutableArray new]; - for (NSData *certificateDerData in certificateDerDatas) { - SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(certificateDerData)); - if (!certificate) { - OWSFailDebug(@"Could not create SecCertificate."); - *error = CDSSigningCertificateErrorMake( - CDSSigningCertificateError_AssertionError, @"Could not create SecCertificate."); - return nil; - } - [certificates addObject:(__bridge_transfer id)certificate]; - } - - SecPolicyRef policy = SecPolicyCreateBasicX509(); - signingCertificate.policy = policy; - if (!policy) { - OWSFailDebug(@"Could not create policy."); - *error = CDSSigningCertificateErrorMake(CDSSigningCertificateError_AssertionError, @"Could not create policy."); - return nil; - } - - SecTrustRef trust; - OSStatus status = SecTrustCreateWithCertificates((__bridge CFTypeRef)certificates, policy, &trust); - signingCertificate.trust = trust; - if (status != errSecSuccess) { - OWSFailDebug(@"Creating trust did not succeed."); - *error = CDSSigningCertificateErrorMake( - CDSSigningCertificateError_AssertionError, @"Creating trust did not succeed."); - return nil; - } - if (!trust) { - OWSFailDebug(@"Could not create trust."); - *error = CDSSigningCertificateErrorMake(CDSSigningCertificateError_AssertionError, @"Could not create trust."); - return nil; - } - - status = SecTrustSetNetworkFetchAllowed(trust, NO); - if (status != errSecSuccess) { - OWSFailDebug(@"trust fetch could not be configured."); - *error = CDSSigningCertificateErrorMake( - CDSSigningCertificateError_AssertionError, @"trust fetch could not be configured."); - return nil; - } - - status = SecTrustSetAnchorCertificatesOnly(trust, YES); - if (status != errSecSuccess) { - OWSFailDebug(@"trust anchor certs could not be configured."); - *error = CDSSigningCertificateErrorMake( - CDSSigningCertificateError_AssertionError, @"trust anchor certs could not be configured."); - return nil; - } - - NSMutableArray *pinnedCertificates = [NSMutableArray array]; - for (NSData *certificateData in anchorCertificates) { - SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(certificateData)); - if (!certificate) { - OWSFailDebug(@"Could not create pinned SecCertificate."); - *error = CDSSigningCertificateErrorMake( - CDSSigningCertificateError_AssertionError, @"Could not create pinned SecCertificate."); - return nil; - } - - [pinnedCertificates addObject:(__bridge_transfer id)certificate]; - } - status = SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)pinnedCertificates); - if (status != errSecSuccess) { - OWSFailDebug(@"The anchor certificates couldn't be set."); - *error = CDSSigningCertificateErrorMake( - CDSSigningCertificateError_AssertionError, @"The anchor certificates couldn't be set."); - return nil; - } - - SecTrustResultType result; - status = SecTrustEvaluate(trust, &result); - if (status != errSecSuccess) { - OWSFailDebug(@"Could not evaluate certificates."); - *error = CDSSigningCertificateErrorMake( - CDSSigningCertificateError_AssertionError, @"Could not evaluate certificates."); - return nil; - } - - // `kSecTrustResultUnspecified` is confusingly named. It indicates success. - // See the comments in the header where it is defined. - BOOL isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); - if (!isValid) { - OWSFailDebug(@"Certificate was not trusted."); - *error = CDSSigningCertificateErrorMake( - CDSSigningCertificateError_UntrustedCertificate, @"Certificate was not trusted."); - return nil; - } - - SecKeyRef publicKey = SecTrustCopyPublicKey(trust); - signingCertificate.publicKey = publicKey; - if (!publicKey) { - OWSFailDebug(@"Could not extract public key."); - *error = CDSSigningCertificateErrorMake( - CDSSigningCertificateError_AssertionError, @"Could not extract public key."); - return nil; - } - - return signingCertificate; -} - -// PEM is just a series of blocks of base-64 encoded DER data. -// -// https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail -+ (nullable NSArray *)convertPemToDer:(NSString *)pemString -{ - NSMutableArray *certificateDatas = [NSMutableArray new]; - - NSError *error; - // We use ? for non-greedy matching. - NSRegularExpression *_Nullable regex = [NSRegularExpression - regularExpressionWithPattern:@"-----BEGIN.*?-----(.+?)-----END.*?-----" - options:NSRegularExpressionCaseInsensitive | NSRegularExpressionDotMatchesLineSeparators - error:&error]; - if (!regex || error) { - OWSFailDebug(@"could parse regex: %@.", error); - return nil; - } - - [regex enumerateMatchesInString:pemString - options:0 - range:NSMakeRange(0, pemString.length) - usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *stop) { - if (result.numberOfRanges != 2) { - OWSFailDebug(@"invalid PEM regex match."); - return; - } - NSString *_Nullable derString = [pemString substringWithRange:[result rangeAtIndex:1]]; - if (derString.length < 1) { - OWSFailDebug(@"empty PEM match."); - return; - } - // dataFromBase64String will ignore whitespace, which is - // necessary. - NSData *_Nullable derData = [NSData dataFromBase64String:derString]; - if (derData.length < 1) { - OWSFailDebugUnlessRunningTests(@"could not parse PEM match."); - return; - } - [certificateDatas addObject:derData]; - }]; - - return certificateDatas; -} - -+ (nullable NSArray *)anchorCertificates -{ - static NSArray *anchorCertificates = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // We need to use an Intel certificate as the anchor for IAS verification. - NSData *_Nullable anchorCertificate = [self certificateDataForService:@"ias-root"]; - if (!anchorCertificate) { - OWSFail(@"could not load anchor certificate."); - } else { - anchorCertificates = @[ anchorCertificate ]; - } - }); - return anchorCertificates; -} - -+ (nullable NSData *)certificateDataForService:(NSString *)service -{ - NSBundle *bundle = [NSBundle bundleForClass:self.class]; - NSString *path = [bundle pathForResource:service ofType:@"cer"]; - - if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { - OWSFailDebug(@"could not locate certificate file."); - return nil; - } - - NSData *_Nullable certificateData = [NSData dataWithContentsOfFile:path]; - return certificateData; -} - -- (BOOL)verifySignatureOfBody:(NSString *)body signature:(NSData *)signature -{ - OWSAssertDebug(self.publicKey); - - NSData *bodyData = [body dataUsingEncoding:NSUTF8StringEncoding]; - - size_t signedHashBytesSize = SecKeyGetBlockSize(self.publicKey); - const void *signedHashBytes = [signature bytes]; - - NSData *_Nullable hashData = [Cryptography computeSHA256Digest:bodyData]; - if (hashData.length != CC_SHA256_DIGEST_LENGTH) { - OWSFailDebug(@"could not SHA256 for signature verification."); - return NO; - } - size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH; - const void *hashBytes = [hashData bytes]; - - OSStatus status = SecKeyRawVerify( - self.publicKey, kSecPaddingPKCS1SHA256, hashBytes, hashBytesSize, signedHashBytes, signedHashBytesSize); - - BOOL isValid = status == errSecSuccess; - if (!isValid) { - OWSFailDebugUnlessRunningTests(@"signatures do not match."); - return NO; - } - return YES; -} - -+ (BOOL)verifyDistinguishedNameOfCertificate:(NSData *)certificateData -{ - OWSAssertDebug(certificateData); - - // The Security framework doesn't offer access to certificate properties - // with API available on iOS 9. We use OpenSSL to extract the name. - NSDictionary *_Nullable properties = [self propertiesForCertificate:certificateData]; - if (!properties) { - OWSFailDebug(@"Could not retrieve certificate properties."); - return NO; - } - // NSString *expectedDistinguishedName - // = @"CN=Intel SGX Attestation Report Signing,O=Intel Corporation,L=Santa Clara,ST=CA,C=US"; - NSDictionary *expectedProperties = @{ - @(SN_commonName) : // "CN" - @"Intel SGX Attestation Report Signing", - @(SN_organizationName) : // "O" - @"Intel Corporation", - @(SN_localityName) : // "L" - @"Santa Clara", - @(SN_stateOrProvinceName) : // "ST" - @"CA", - @(SN_countryName) : // "C" - @"US", - }; - - if (![properties isEqualToDictionary:expectedProperties]) { - OWSFailDebugUnlessRunningTests(@"Unexpected certificate properties. %@ != %@", expectedProperties, properties); - return NO; - } - return YES; -} - -+ (nullable NSDictionary *)propertiesForCertificate:(NSData *)certificateData -{ - OWSAssertDebug(certificateData); - - if (certificateData.length >= UINT32_MAX) { - OWSFailDebug(@"certificate data is too long."); - return nil; - } - const unsigned char *certificateDataBytes = (const unsigned char *)[certificateData bytes]; - X509 *_Nullable certificateX509 = d2i_X509(NULL, &certificateDataBytes, [certificateData length]); - if (!certificateX509) { - OWSFailDebug(@"could not parse certificate."); - return nil; - } - - X509_NAME *_Nullable subjectName = X509_get_subject_name(certificateX509); - if (!subjectName) { - OWSFailDebug(@"could not extract subject name."); - return nil; - } - - NSMutableDictionary *certificateProperties = [NSMutableDictionary new]; - for (NSString *oid in @[ - @(SN_commonName), // "CN" - @(SN_organizationName), // "O" - @(SN_localityName), // "L" - @(SN_stateOrProvinceName), // "ST" - @(SN_countryName), // "C" - ]) { - int nid = OBJ_txt2nid(oid.UTF8String); - int index = X509_NAME_get_index_by_NID(subjectName, nid, -1); - - X509_NAME_ENTRY *_Nullable entry = X509_NAME_get_entry(subjectName, index); - if (!entry) { - OWSFailDebug(@"could not extract entry."); - return nil; - } - - ASN1_STRING *_Nullable entryData = X509_NAME_ENTRY_get_data(entry); - if (!entryData) { - OWSFailDebug(@"could not extract entry data."); - return nil; - } - - unsigned char *entryName = ASN1_STRING_data(entryData); - if (entryName == NULL) { - OWSFailDebug(@"could not extract entry string."); - return nil; - } - NSString *_Nullable entryString = [NSString stringWithUTF8String:(char *)entryName]; - if (!entryString) { - OWSFailDebug(@"could not parse entry name data."); - return nil; - } - certificateProperties[oid] = entryString; - } - return certificateProperties; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/Contact.h b/SignalServiceKit/src/Contacts/Contact.h deleted file mode 100644 index ff7455f8d..000000000 --- a/SignalServiceKit/src/Contacts/Contact.h +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * An adapter for the system contacts - */ - -@class CNContact; -@class PhoneNumber; -@class SignalRecipient; -@class UIImage; -@class YapDatabaseReadTransaction; - -@interface Contact : MTLModel - -@property (nullable, readonly, nonatomic) NSString *firstName; -@property (nullable, readonly, nonatomic) NSString *lastName; -@property (readonly, nonatomic) NSString *fullName; -@property (readonly, nonatomic) NSString *comparableNameFirstLast; -@property (readonly, nonatomic) NSString *comparableNameLastFirst; -@property (readonly, nonatomic) NSArray *parsedPhoneNumbers; -@property (readonly, nonatomic) NSArray *userTextPhoneNumbers; -@property (readonly, nonatomic) NSArray *emails; -@property (readonly, nonatomic) NSString *uniqueId; -@property (nonatomic, readonly) BOOL isSignalContact; -@property (nonatomic, readonly) NSString *cnContactId; - -- (NSArray *)signalRecipientsWithTransaction:(YapDatabaseReadTransaction *)transaction; -// TODO: Remove this method. -- (NSArray *)textSecureIdentifiers; - -#if TARGET_OS_IOS - -- (instancetype)initWithSystemContact:(CNContact *)cnContact NS_AVAILABLE_IOS(9_0); -+ (nullable Contact *)contactWithVCardData:(NSData *)data; -+ (nullable CNContact *)cnContactWithVCardData:(NSData *)data; - -- (NSString *)nameForPhoneNumber:(NSString *)recipientId; - -#endif // TARGET_OS_IOS - -+ (NSComparator)comparatorSortingNamesByFirstThenLast:(BOOL)firstNameOrdering; -+ (NSString *)formattedFullNameWithCNContact:(CNContact *)cnContact NS_SWIFT_NAME(formattedFullName(cnContact:)); -+ (nullable NSString *)localizedStringForCNLabel:(nullable NSString *)cnLabel; - -+ (CNContact *)mergeCNContact:(CNContact *)oldCNContact - newCNContact:(CNContact *)newCNContact NS_SWIFT_NAME(merge(cnContact:newCNContact:)); - -+ (nullable NSData *)avatarDataForCNContact:(nullable CNContact *)cnContact; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/Contact.m b/SignalServiceKit/src/Contacts/Contact.m deleted file mode 100644 index b7f17f64a..000000000 --- a/SignalServiceKit/src/Contacts/Contact.m +++ /dev/null @@ -1,433 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "Contact.h" -#import "OWSPrimaryStorage.h" -#import "PhoneNumber.h" -#import "SSKEnvironment.h" -#import "SignalRecipient.h" -#import "TSAccountManager.h" -#import -#import - -@import Contacts; - -NS_ASSUME_NONNULL_BEGIN - -@interface Contact () - -@property (nonatomic, readonly) NSMutableDictionary *phoneNumberNameMap; -@property (nonatomic, readonly) NSUInteger imageHash; - -@end - -#pragma mark - - -@implementation Contact - -@synthesize comparableNameFirstLast = _comparableNameFirstLast; -@synthesize comparableNameLastFirst = _comparableNameLastFirst; - -#if TARGET_OS_IOS - -- (instancetype)initWithSystemContact:(CNContact *)cnContact -{ - self = [super init]; - if (!self) { - return self; - } - - _cnContactId = cnContact.identifier; - _firstName = cnContact.givenName.ows_stripped; - _lastName = cnContact.familyName.ows_stripped; - _fullName = [Contact formattedFullNameWithCNContact:cnContact]; - - NSMutableArray *phoneNumbers = [NSMutableArray new]; - NSMutableDictionary *phoneNumberNameMap = [NSMutableDictionary new]; - const NSUInteger kMaxPhoneNumbersConsidered = 50; - - NSArray *consideredPhoneNumbers; - if (cnContact.phoneNumbers.count <= kMaxPhoneNumbersConsidered) { - consideredPhoneNumbers = cnContact.phoneNumbers; - } else { - OWSLogInfo(@"For perf, only considering the first %lu phone numbers for contact with many numbers.", (unsigned long)kMaxPhoneNumbersConsidered); - consideredPhoneNumbers = [cnContact.phoneNumbers subarrayWithRange:NSMakeRange(0, kMaxPhoneNumbersConsidered)]; - } - for (CNLabeledValue *phoneNumberField in consideredPhoneNumbers) { - if ([phoneNumberField.value isKindOfClass:[CNPhoneNumber class]]) { - CNPhoneNumber *phoneNumber = (CNPhoneNumber *)phoneNumberField.value; - [phoneNumbers addObject:phoneNumber.stringValue]; - if ([phoneNumberField.label isEqualToString:CNLabelHome]) { - phoneNumberNameMap[phoneNumber.stringValue] - = NSLocalizedString(@"PHONE_NUMBER_TYPE_HOME", @"Label for 'Home' phone numbers."); - } else if ([phoneNumberField.label isEqualToString:CNLabelWork]) { - phoneNumberNameMap[phoneNumber.stringValue] - = NSLocalizedString(@"PHONE_NUMBER_TYPE_WORK", @"Label for 'Work' phone numbers."); - } else if ([phoneNumberField.label isEqualToString:CNLabelPhoneNumberiPhone]) { - phoneNumberNameMap[phoneNumber.stringValue] - = NSLocalizedString(@"PHONE_NUMBER_TYPE_IPHONE", @"Label for 'iPhone' phone numbers."); - } else if ([phoneNumberField.label isEqualToString:CNLabelPhoneNumberMobile]) { - phoneNumberNameMap[phoneNumber.stringValue] - = NSLocalizedString(@"PHONE_NUMBER_TYPE_MOBILE", @"Label for 'Mobile' phone numbers."); - } else if ([phoneNumberField.label isEqualToString:CNLabelPhoneNumberMain]) { - phoneNumberNameMap[phoneNumber.stringValue] - = NSLocalizedString(@"PHONE_NUMBER_TYPE_MAIN", @"Label for 'Main' phone numbers."); - } else if ([phoneNumberField.label isEqualToString:CNLabelPhoneNumberHomeFax]) { - phoneNumberNameMap[phoneNumber.stringValue] - = NSLocalizedString(@"PHONE_NUMBER_TYPE_HOME_FAX", @"Label for 'HomeFAX' phone numbers."); - } else if ([phoneNumberField.label isEqualToString:CNLabelPhoneNumberWorkFax]) { - phoneNumberNameMap[phoneNumber.stringValue] - = NSLocalizedString(@"PHONE_NUMBER_TYPE_WORK_FAX", @"Label for 'Work FAX' phone numbers."); - } else if ([phoneNumberField.label isEqualToString:CNLabelPhoneNumberOtherFax]) { - phoneNumberNameMap[phoneNumber.stringValue] - = NSLocalizedString(@"PHONE_NUMBER_TYPE_OTHER_FAX", @"Label for 'Other FAX' phone numbers."); - } else if ([phoneNumberField.label isEqualToString:CNLabelPhoneNumberPager]) { - phoneNumberNameMap[phoneNumber.stringValue] - = NSLocalizedString(@"PHONE_NUMBER_TYPE_PAGER", @"Label for 'Pager' phone numbers."); - } else if ([phoneNumberField.label isEqualToString:CNLabelOther]) { - phoneNumberNameMap[phoneNumber.stringValue] - = NSLocalizedString(@"PHONE_NUMBER_TYPE_OTHER", @"Label for 'Other' phone numbers."); - } else if (phoneNumberField.label.length > 0 && ![phoneNumberField.label hasPrefix:@"_$"]) { - // We'll reach this case for: - // - // * User-defined custom labels, which we want to display. - // * Labels like "_$!!$_", which I'm guessing are synced from other platforms. - // We don't want to display these labels. Even some of iOS' default labels (like Radio) show - // up this way. - phoneNumberNameMap[phoneNumber.stringValue] = phoneNumberField.label; - } - } - } - - _userTextPhoneNumbers = [phoneNumbers copy]; - _phoneNumberNameMap = [NSMutableDictionary new]; - _parsedPhoneNumbers = - [self parsedPhoneNumbersFromUserTextPhoneNumbers:phoneNumbers phoneNumberNameMap:phoneNumberNameMap]; - - NSMutableArray *emailAddresses = [NSMutableArray new]; - for (CNLabeledValue *emailField in cnContact.emailAddresses) { - if ([emailField.value isKindOfClass:[NSString class]]) { - [emailAddresses addObject:(NSString *)emailField.value]; - } - } - _emails = [emailAddresses copy]; - - NSData *_Nullable avatarData = [Contact avatarDataForCNContact:cnContact]; - if (avatarData) { - NSUInteger hashValue = 0; - NSData *_Nullable hashData = [Cryptography computeSHA256Digest:avatarData truncatedToBytes:sizeof(hashValue)]; - if (!hashData) { - OWSFailDebug(@"could not compute hash for avatar."); - } - [hashData getBytes:&hashValue length:sizeof(hashValue)]; - _imageHash = hashValue; - } else { - _imageHash = 0; - } - - return self; -} - -- (NSString *)uniqueId -{ - return self.cnContactId; -} - -+ (nullable Contact *)contactWithVCardData:(NSData *)data -{ - CNContact *_Nullable cnContact = [self cnContactWithVCardData:data]; - - if (!cnContact) { - OWSLogError(@"Could not parse vcard data."); - return nil; - } - - return [[self alloc] initWithSystemContact:cnContact]; -} - -#endif // TARGET_OS_IOS - -- (NSArray *)parsedPhoneNumbersFromUserTextPhoneNumbers:(NSArray *)userTextPhoneNumbers - phoneNumberNameMap:(nullable NSDictionary *) - phoneNumberNameMap -{ - OWSAssertDebug(self.phoneNumberNameMap); - - NSMutableDictionary *parsedPhoneNumberMap = [NSMutableDictionary new]; - NSMutableArray *parsedPhoneNumbers = [NSMutableArray new]; - for (NSString *phoneNumberString in userTextPhoneNumbers) { - for (PhoneNumber *phoneNumber in - [PhoneNumber tryParsePhoneNumbersFromsUserSpecifiedText:phoneNumberString - clientPhoneNumber:[TSAccountManager localNumber]]) { - [parsedPhoneNumbers addObject:phoneNumber]; - parsedPhoneNumberMap[phoneNumber.toE164] = phoneNumber; - NSString *phoneNumberName = phoneNumberNameMap[phoneNumberString]; - if (phoneNumberName) { - self.phoneNumberNameMap[phoneNumber.toE164] = phoneNumberName; - } - } - } - return [parsedPhoneNumbers sortedArrayUsingSelector:@selector(compare:)]; -} - -- (NSString *)comparableNameFirstLast { - if (_comparableNameFirstLast == nil) { - // Combine the two names with a tab separator, which has a lower ascii code than space, so that first names - // that contain a space ("Mary Jo\tCatlett") will sort after those that do not ("Mary\tOliver") - _comparableNameFirstLast = [self combineLeftName:_firstName withRightName:_lastName usingSeparator:@"\t"]; - } - - return _comparableNameFirstLast; -} - -- (NSString *)comparableNameLastFirst { - if (_comparableNameLastFirst == nil) { - // Combine the two names with a tab separator, which has a lower ascii code than space, so that last names - // that contain a space ("Van Der Beek\tJames") will sort after those that do not ("Van\tJames") - _comparableNameLastFirst = [self combineLeftName:_lastName withRightName:_firstName usingSeparator:@"\t"]; - } - - return _comparableNameLastFirst; -} - -- (NSString *)combineLeftName:(NSString *)leftName withRightName:(NSString *)rightName usingSeparator:(NSString *)separator { - const BOOL leftNameNonEmpty = (leftName.length > 0); - const BOOL rightNameNonEmpty = (rightName.length > 0); - - if (leftNameNonEmpty && rightNameNonEmpty) { - return [NSString stringWithFormat:@"%@%@%@", leftName, separator, rightName]; - } else if (leftNameNonEmpty) { - return [leftName copy]; - } else if (rightNameNonEmpty) { - return [rightName copy]; - } else { - return @""; - } -} - -- (NSString *)description { - return [NSString stringWithFormat:@"%@: %@", self.fullName, self.userTextPhoneNumbers]; -} - -- (BOOL)isSignalContact { - NSArray *identifiers = [self textSecureIdentifiers]; - - return [identifiers count] > 0; -} - -- (NSArray *)signalRecipientsWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - __block NSMutableArray *result = [NSMutableArray array]; - - for (PhoneNumber *number in [self.parsedPhoneNumbers sortedArrayUsingSelector:@selector(compare:)]) { - SignalRecipient *_Nullable signalRecipient = [SignalRecipient registeredRecipientForRecipientId:number.toE164 - mustHaveDevices:YES - transaction:transaction]; - if (signalRecipient) { - [result addObject:signalRecipient]; - } - } - - return [result copy]; -} - -- (NSArray *)textSecureIdentifiers { - __block NSMutableArray *identifiers = [NSMutableArray array]; - - [OWSPrimaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - for (PhoneNumber *number in self.parsedPhoneNumbers) { - if ([SignalRecipient isRegisteredRecipient:number.toE164 transaction:transaction]) { - [identifiers addObject:number.toE164]; - } - } - }]; - return [identifiers copy]; -} - -+ (NSComparator)comparatorSortingNamesByFirstThenLast:(BOOL)firstNameOrdering { - return ^NSComparisonResult(id obj1, id obj2) { - Contact *contact1 = (Contact *)obj1; - Contact *contact2 = (Contact *)obj2; - - if (firstNameOrdering) { - return [contact1.comparableNameFirstLast caseInsensitiveCompare:contact2.comparableNameFirstLast]; - } else { - return [contact1.comparableNameLastFirst caseInsensitiveCompare:contact2.comparableNameLastFirst]; - } - }; -} - -+ (NSString *)formattedFullNameWithCNContact:(CNContact *)cnContact -{ - return [CNContactFormatter stringFromContact:cnContact style:CNContactFormatterStyleFullName].ows_stripped; -} - -- (NSString *)nameForPhoneNumber:(NSString *)recipientId -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug([self.textSecureIdentifiers containsObject:recipientId]); - - NSString *value = self.phoneNumberNameMap[recipientId]; - OWSAssertDebug(value); - if (!value) { - return NSLocalizedString(@"PHONE_NUMBER_TYPE_UNKNOWN", - @"Label used when we don't what kind of phone number it is (e.g. mobile/work/home)."); - } - return value; -} - -+ (nullable NSData *)avatarDataForCNContact:(nullable CNContact *)cnContact -{ - if (cnContact.thumbnailImageData) { - return cnContact.thumbnailImageData.copy; - } else if (cnContact.imageData) { - // This only occurs when sharing a contact via the share extension - return cnContact.imageData.copy; - } else { - return nil; - } -} - -// This method is used to de-bounce system contact fetch notifications -// by checking for changes in the contact data. -- (NSUInteger)hash -{ - // base hash is some arbitrary number - NSUInteger hash = 1825038313; - - hash = hash ^ self.fullName.hash; - - hash = hash ^ self.imageHash; - - for (PhoneNumber *phoneNumber in self.parsedPhoneNumbers) { - hash = hash ^ phoneNumber.toE164.hash; - } - - for (NSString *email in self.emails) { - hash = hash ^ email.hash; - } - - return hash; -} - -#pragma mark - CNContactConverters - -+ (nullable CNContact *)cnContactWithVCardData:(NSData *)data -{ - OWSAssertDebug(data); - - NSError *error; - NSArray *_Nullable contacts = [CNContactVCardSerialization contactsWithData:data error:&error]; - if (!contacts || error) { - OWSFailDebug(@"could not parse vcard: %@", error); - return nil; - } - if (contacts.count < 1) { - OWSFailDebug(@"empty vcard: %@", error); - return nil; - } - if (contacts.count > 1) { - OWSFailDebug(@"more than one contact in vcard: %@", error); - } - return contacts.firstObject; -} - -+ (CNContact *)mergeCNContact:(CNContact *)oldCNContact newCNContact:(CNContact *)newCNContact -{ - OWSAssertDebug(oldCNContact); - OWSAssertDebug(newCNContact); - - Contact *oldContact = [[Contact alloc] initWithSystemContact:oldCNContact]; - - CNMutableContact *_Nullable mergedCNContact = [oldCNContact mutableCopy]; - if (!mergedCNContact) { - OWSFailDebug(@"mergedCNContact was unexpectedly nil"); - return [CNContact new]; - } - - // Name - NSString *formattedFullName = [self.class formattedFullNameWithCNContact:mergedCNContact]; - - // merged all or nothing - do not try to piece-meal merge. - if (formattedFullName.length == 0) { - mergedCNContact.namePrefix = newCNContact.namePrefix.ows_stripped; - mergedCNContact.givenName = newCNContact.givenName.ows_stripped; - mergedCNContact.middleName = newCNContact.middleName.ows_stripped; - mergedCNContact.familyName = newCNContact.familyName.ows_stripped; - mergedCNContact.nameSuffix = newCNContact.nameSuffix.ows_stripped; - } - - if (mergedCNContact.organizationName.ows_stripped.length < 1) { - mergedCNContact.organizationName = newCNContact.organizationName.ows_stripped; - } - - // Phone Numbers - NSSet *existingParsedPhoneNumberSet = [NSSet setWithArray:oldContact.parsedPhoneNumbers]; - NSSet *existingUnparsedPhoneNumberSet = [NSSet setWithArray:oldContact.userTextPhoneNumbers]; - - NSMutableArray *> *mergedPhoneNumbers = [mergedCNContact.phoneNumbers mutableCopy]; - for (CNLabeledValue *labeledPhoneNumber in newCNContact.phoneNumbers) { - NSString *_Nullable unparsedPhoneNumber = labeledPhoneNumber.value.stringValue; - if ([existingUnparsedPhoneNumberSet containsObject:unparsedPhoneNumber]) { - // Skip phone number if "unparsed" form is a duplicate. - continue; - } - PhoneNumber *_Nullable parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:labeledPhoneNumber.value.stringValue]; - if (parsedPhoneNumber && [existingParsedPhoneNumberSet containsObject:parsedPhoneNumber]) { - // Skip phone number if "parsed" form is a duplicate. - continue; - } - [mergedPhoneNumbers addObject:labeledPhoneNumber]; - } - mergedCNContact.phoneNumbers = mergedPhoneNumbers; - - // Emails - NSSet *existingEmailSet = [NSSet setWithArray:oldContact.emails]; - NSMutableArray *> *mergedEmailAddresses = [mergedCNContact.emailAddresses mutableCopy]; - for (CNLabeledValue *labeledEmail in newCNContact.emailAddresses) { - NSString *normalizedValue = labeledEmail.value.ows_stripped; - if (![existingEmailSet containsObject:normalizedValue]) { - [mergedEmailAddresses addObject:labeledEmail]; - } - } - mergedCNContact.emailAddresses = mergedEmailAddresses; - - // Address - // merged all or nothing - do not try to piece-meal merge. - if (mergedCNContact.postalAddresses.count == 0) { - mergedCNContact.postalAddresses = newCNContact.postalAddresses; - } - - // Avatar - if (!mergedCNContact.imageData) { - mergedCNContact.imageData = newCNContact.imageData; - } - - return [mergedCNContact copy]; -} - -+ (nullable NSString *)localizedStringForCNLabel:(nullable NSString *)cnLabel -{ - if (cnLabel.length == 0) { - return nil; - } - - NSString *_Nullable localizedLabel = [CNLabeledValue localizedStringForLabel:cnLabel]; - - // Docs for localizedStringForLabel say it returns: - // > The localized string if a Contacts framework defined label, otherwise just returns the label. - // But in practice, at least on iOS11, if the label is not one of CNContacts known labels (like CNLabelHome) - // kUnlocalizedStringLabel is returned, rather than the unadultered label. - NSString *const kUnlocalizedStringLabel = @"__ABUNLOCALIZEDSTRING"; - - if ([localizedLabel isEqual:kUnlocalizedStringLabel]) { - return cnLabel; - } - - return localizedLabel; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/ContactDiscoveryService.h b/SignalServiceKit/src/Contacts/ContactDiscoveryService.h deleted file mode 100644 index 2b19fc8bd..000000000 --- a/SignalServiceKit/src/Contacts/ContactDiscoveryService.h +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -extern NSErrorUserInfoKey const ContactDiscoveryServiceErrorKey_Reason; -extern NSErrorDomain const ContactDiscoveryServiceErrorDomain; -typedef NS_ERROR_ENUM(ContactDiscoveryServiceErrorDomain, ContactDiscoveryServiceError){ - ContactDiscoveryServiceErrorAttestationFailed = 100, ContactDiscoveryServiceErrorAssertionError = 101 -}; - -@class ECKeyPair; -@class OWSAES256Key; - -@interface RemoteAttestationAuth : NSObject - -@property (nonatomic, readonly) NSString *username; -@property (nonatomic, readonly) NSString *password; - -@end - -#pragma mark - - -@interface RemoteAttestationKeys : NSObject - -@property (nonatomic, readonly) ECKeyPair *keyPair; -@property (nonatomic, readonly) NSData *serverEphemeralPublic; -@property (nonatomic, readonly) NSData *serverStaticPublic; - -@property (nonatomic, readonly) OWSAES256Key *clientKey; -@property (nonatomic, readonly) OWSAES256Key *serverKey; - -@end - -#pragma mark - - -@interface RemoteAttestation : NSObject - -@property (nonatomic, readonly) RemoteAttestationKeys *keys; -@property (nonatomic, readonly) NSArray *cookies; -@property (nonatomic, readonly) NSData *requestId; -@property (nonatomic, readonly) NSString *enclaveId; -@property (nonatomic, readonly) RemoteAttestationAuth *auth; - -@end - -#pragma mark - - -@interface ContactDiscoveryService : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initDefault NS_DESIGNATED_INITIALIZER; - -+ (instancetype)shared; - -- (void)testService; -- (void)performRemoteAttestationWithSuccess:(void (^)(RemoteAttestation *_Nonnull remoteAttestation))successHandler - failure:(void (^)(NSError *_Nonnull error))failureHandler; -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/ContactDiscoveryService.m b/SignalServiceKit/src/Contacts/ContactDiscoveryService.m deleted file mode 100644 index 293e75593..000000000 --- a/SignalServiceKit/src/Contacts/ContactDiscoveryService.m +++ /dev/null @@ -1,775 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "ContactDiscoveryService.h" -#import "CDSQuote.h" -#import "CDSSigningCertificate.h" -#import "NSError+MessageSending.h" -#import "OWSError.h" -#import "OWSRequestFactory.h" -#import "SSKEnvironment.h" -#import "TSNetworkManager.h" -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSErrorUserInfoKey const ContactDiscoveryServiceErrorKey_Reason = @"ContactDiscoveryServiceErrorKey_Reason"; -NSErrorDomain const ContactDiscoveryServiceErrorDomain = @"SignalServiceKit.ContactDiscoveryService"; - -NSError *ContactDiscoveryServiceErrorMakeWithReason(NSInteger code, NSString *reason) -{ - OWSCFailDebug(@"Error: %@", reason); - - return [NSError errorWithDomain:ContactDiscoveryServiceErrorDomain - code:code - userInfo:@{ ContactDiscoveryServiceErrorKey_Reason : reason }]; -} - -@interface RemoteAttestationAuth () - -@property (nonatomic) NSString *username; -@property (nonatomic) NSString *password; - -@end - -#pragma mark - - -@implementation RemoteAttestationAuth - -@end - -#pragma mark - - -@interface RemoteAttestationKeys () - -@property (nonatomic) ECKeyPair *keyPair; -@property (nonatomic) NSData *serverEphemeralPublic; -@property (nonatomic) NSData *serverStaticPublic; - -@property (nonatomic) OWSAES256Key *clientKey; -@property (nonatomic) OWSAES256Key *serverKey; - -@end - -#pragma mark - - -@implementation RemoteAttestationKeys - -+ (nullable RemoteAttestationKeys *)keysForKeyPair:(ECKeyPair *)keyPair - serverEphemeralPublic:(NSData *)serverEphemeralPublic - serverStaticPublic:(NSData *)serverStaticPublic - error:(NSError **)error -{ - if (!keyPair) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"Missing keyPair"); - return nil; - } - if (serverEphemeralPublic.length < 1) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"Invalid serverEphemeralPublic"); - return nil; - } - if (serverStaticPublic.length < 1) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"Invalid serverStaticPublic"); - return nil; - } - RemoteAttestationKeys *keys = [RemoteAttestationKeys new]; - keys.keyPair = keyPair; - keys.serverEphemeralPublic = serverEphemeralPublic; - keys.serverStaticPublic = serverStaticPublic; - if (![keys deriveKeys]) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"failed to derive keys"); - return nil; - } - return keys; -} - -// Returns YES on success. -- (BOOL)deriveKeys -{ - NSData *ephemeralToEphemeral; - NSData *ephemeralToStatic; - @try { - ephemeralToEphemeral = - [Curve25519 throws_generateSharedSecretFromPublicKey:self.serverEphemeralPublic andKeyPair:self.keyPair]; - ephemeralToStatic = - [Curve25519 throws_generateSharedSecretFromPublicKey:self.serverStaticPublic andKeyPair:self.keyPair]; - } @catch (NSException *exception) { - OWSFailDebug(@"could not generate shared secrets: %@", exception); - return NO; - } - - NSData *masterSecret = [ephemeralToEphemeral dataByAppendingData:ephemeralToStatic]; - NSData *publicKeys = [NSData join:@[ - self.keyPair.publicKey, - self.serverEphemeralPublic, - self.serverStaticPublic, - ]]; - - NSData *_Nullable derivedMaterial; - @try { - derivedMaterial = - [HKDFKit throws_deriveKey:masterSecret info:nil salt:publicKeys outputSize:(int)kAES256_KeyByteLength * 2]; - } @catch (NSException *exception) { - OWSFailDebug(@"could not derive service key: %@", exception); - return NO; - } - - if (!derivedMaterial) { - OWSFailDebug(@"missing derived service key."); - return NO; - } - if (derivedMaterial.length != kAES256_KeyByteLength * 2) { - OWSFailDebug(@"derived service key has unexpected length."); - return NO; - } - - NSData *_Nullable clientKeyData = - [derivedMaterial subdataWithRange:NSMakeRange(kAES256_KeyByteLength * 0, kAES256_KeyByteLength)]; - OWSAES256Key *_Nullable clientKey = [OWSAES256Key keyWithData:clientKeyData]; - if (!clientKey) { - OWSFailDebug(@"clientKey has unexpected length."); - return NO; - } - - NSData *_Nullable serverKeyData = - [derivedMaterial subdataWithRange:NSMakeRange(kAES256_KeyByteLength * 1, kAES256_KeyByteLength)]; - OWSAES256Key *_Nullable serverKey = [OWSAES256Key keyWithData:serverKeyData]; - if (!serverKey) { - OWSFailDebug(@"serverKey has unexpected length."); - return NO; - } - - self.clientKey = clientKey; - self.serverKey = serverKey; - - return YES; -} - -@end - -#pragma mark - - -@interface RemoteAttestation () - -@property (nonatomic) RemoteAttestationKeys *keys; -@property (nonatomic) NSArray *cookies; -@property (nonatomic) NSData *requestId; -@property (nonatomic) NSString *enclaveId; -@property (nonatomic) RemoteAttestationAuth *auth; - -@end - -#pragma mark - - -@implementation RemoteAttestation - -@end - -#pragma mark - - -@interface SignatureBodyEntity : NSObject - -@property (nonatomic) NSData *isvEnclaveQuoteBody; -@property (nonatomic) NSString *isvEnclaveQuoteStatus; -@property (nonatomic) NSString *timestamp; -@property (nonatomic) NSNumber *version; - -@end - -#pragma mark - - -@implementation SignatureBodyEntity - -@end - -#pragma mark - - -@interface NSDictionary (CDS) - -@end - -#pragma mark - - -@implementation NSDictionary (CDS) - -- (nullable NSString *)stringForKey:(NSString *)key -{ - NSString *_Nullable valueString = self[key]; - if (![valueString isKindOfClass:[NSString class]]) { - OWSFailDebug(@"couldn't parse string for key: %@", key); - return nil; - } - return valueString; -} - -- (nullable NSNumber *)numberForKey:(NSString *)key -{ - NSNumber *_Nullable value = self[key]; - if (![value isKindOfClass:[NSNumber class]]) { - OWSFailDebug(@"couldn't parse number for key: %@", key); - return nil; - } - return value; -} - -- (nullable NSData *)base64DataForKey:(NSString *)key -{ - NSString *_Nullable valueString = self[key]; - if (![valueString isKindOfClass:[NSString class]]) { - OWSFailDebug(@"couldn't parse base 64 value for key: %@", key); - return nil; - } - NSData *_Nullable valueData = [[NSData alloc] initWithBase64EncodedString:valueString options:0]; - if (!valueData) { - OWSFailDebug(@"couldn't decode base 64 value for key: %@", key); - return nil; - } - return valueData; -} - -- (nullable NSData *)base64DataForKey:(NSString *)key expectedLength:(NSUInteger)expectedLength -{ - NSData *_Nullable valueData = [self base64DataForKey:key]; - if (valueData && valueData.length != expectedLength) { - OWSLogDebug(@"decoded base 64 value for key: %@, has unexpected length: %lu != %lu", - key, - (unsigned long)valueData.length, - (unsigned long)expectedLength); - OWSFailDebug(@"decoded base 64 value for key has unexpected length: %lu != %lu", - (unsigned long)valueData.length, - (unsigned long)expectedLength); - return nil; - } - return valueData; -} - -@end - -#pragma mark - - -@implementation ContactDiscoveryService - -+ (instancetype)shared -{ - OWSAssertDebug(SSKEnvironment.shared.contactDiscoveryService); - - return SSKEnvironment.shared.contactDiscoveryService; -} - -- (instancetype)initDefault -{ - self = [super init]; - if (!self) { - return self; - } - - OWSSingletonAssert(); - - return self; -} - -- (void)testService -{ - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self - performRemoteAttestationWithSuccess:^(RemoteAttestation *remoteAttestation) { - OWSLogDebug(@"succeeded"); - } - failure:^(NSError *error) { - OWSLogDebug(@"failed with error: %@", error); - }]; - }); -} - -- (void)performRemoteAttestationWithSuccess:(void (^)(RemoteAttestation *remoteAttestation))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - [self - getRemoteAttestationAuthWithSuccess:^(RemoteAttestationAuth *auth) { - [self performRemoteAttestationWithAuth:auth success:successHandler failure:failureHandler]; - } - failure:failureHandler]; -} - -- (void)getRemoteAttestationAuthWithSuccess:(void (^)(RemoteAttestationAuth *))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - TSRequest *request = [OWSRequestFactory remoteAttestationAuthRequest]; - [[TSNetworkManager sharedManager] makeRequest:request - success:^(NSURLSessionDataTask *task, id responseDict) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - RemoteAttestationAuth *_Nullable auth = [self parseAuthParams:responseDict]; - if (!auth) { - OWSLogError(@"remote attestation auth could not be parsed: %@", responseDict); - NSError *error = OWSErrorMakeUnableToProcessServerResponseError(); - failureHandler(error); - return; - } - - successHandler(auth); - }); - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response; - OWSLogVerbose(@"remote attestation auth failure: %lu", (unsigned long)response.statusCode); - failureHandler(error); - }]; -} - -- (nullable RemoteAttestationAuth *)parseAuthParams:(id)response -{ - if (![response isKindOfClass:[NSDictionary class]]) { - return nil; - } - - NSDictionary *responseDict = response; - NSString *_Nullable password = [responseDict stringForKey:@"password"]; - if (password.length < 1) { - OWSFailDebug(@"missing or empty password."); - return nil; - } - - NSString *_Nullable username = [responseDict stringForKey:@"username"]; - if (username.length < 1) { - OWSFailDebug(@"missing or empty username."); - return nil; - } - - RemoteAttestationAuth *result = [RemoteAttestationAuth new]; - result.username = username; - result.password = password; - return result; -} - -- (void)performRemoteAttestationWithAuth:(RemoteAttestationAuth *)auth - success:(void (^)(RemoteAttestation *remoteAttestation))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - return; // Loki: Do nothing - - ECKeyPair *keyPair = [Curve25519 generateKeyPair]; - - NSString *enclaveId = @"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9"; - - TSRequest *request = [OWSRequestFactory remoteAttestationRequest:keyPair - enclaveId:enclaveId - authUsername:auth.username - authPassword:auth.password]; - - [[TSNetworkManager sharedManager] makeRequest:request - success:^(NSURLSessionDataTask *task, id responseJson) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSError *_Nullable error; - RemoteAttestation *_Nullable attestation = [self parseAttestationResponseJson:responseJson - response:task.response - keyPair:keyPair - enclaveId:enclaveId - auth:auth - error:&error]; - - if (!attestation) { - if (!error) { - OWSFailDebug(@"error was unexpectedly nil"); - error = ContactDiscoveryServiceErrorMakeWithReason(ContactDiscoveryServiceErrorAssertionError, - @"failure when parsing attestation - no reason given"); - } else { - OWSFailDebug(@"error with attestation: %@", error); - } - error.isRetryable = NO; - failureHandler(error); - return; - } - - successHandler(attestation); - }); - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - failureHandler(error); - }]; -} - -- (nullable RemoteAttestation *)parseAttestationResponseJson:(id)responseJson - response:(NSURLResponse *)response - keyPair:(ECKeyPair *)keyPair - enclaveId:(NSString *)enclaveId - auth:(RemoteAttestationAuth *)auth - error:(NSError **)error -{ - OWSAssertDebug(responseJson); - OWSAssertDebug(response); - OWSAssertDebug(keyPair); - OWSAssertDebug(enclaveId.length > 0); - - if (![response isKindOfClass:[NSHTTPURLResponse class]]) { - *error = ContactDiscoveryServiceErrorMakeWithReason(ContactDiscoveryServiceErrorAssertionError, @"unexpected response type."); - return nil; - } - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - NSArray *cookies = - [NSHTTPCookie cookiesWithResponseHeaderFields:httpResponse.allHeaderFields forURL:httpResponse.URL]; - if (cookies.count < 1) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"couldn't parse cookie."); - return nil; - } - - if (![responseJson isKindOfClass:[NSDictionary class]]) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"invalid json response"); - return nil; - } - NSDictionary *responseDict = responseJson; - NSData *_Nullable serverEphemeralPublic = - [responseDict base64DataForKey:@"serverEphemeralPublic" expectedLength:32]; - if (!serverEphemeralPublic) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"couldn't parse serverEphemeralPublic."); - return nil; - } - NSData *_Nullable serverStaticPublic = [responseDict base64DataForKey:@"serverStaticPublic" expectedLength:32]; - if (!serverStaticPublic) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"couldn't parse serverStaticPublic."); - return nil; - } - NSData *_Nullable encryptedRequestId = [responseDict base64DataForKey:@"ciphertext"]; - if (!encryptedRequestId) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"couldn't parse encryptedRequestId."); - return nil; - } - NSData *_Nullable encryptedRequestIv = [responseDict base64DataForKey:@"iv" expectedLength:12]; - if (!encryptedRequestIv) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"couldn't parse encryptedRequestIv."); - return nil; - } - NSData *_Nullable encryptedRequestTag = [responseDict base64DataForKey:@"tag" expectedLength:16]; - if (!encryptedRequestTag) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"couldn't parse encryptedRequestTag."); - return nil; - } - NSData *_Nullable quoteData = [responseDict base64DataForKey:@"quote"]; - if (!quoteData) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"couldn't parse quote data."); - return nil; - } - NSString *_Nullable signatureBody = [responseDict stringForKey:@"signatureBody"]; - if (![signatureBody isKindOfClass:[NSString class]]) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"couldn't parse signatureBody."); - return nil; - } - NSData *_Nullable signature = [responseDict base64DataForKey:@"signature"]; - if (!signature) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"couldn't parse signature."); - return nil; - } - NSString *_Nullable encodedCertificates = [responseDict stringForKey:@"certificates"]; - if (![encodedCertificates isKindOfClass:[NSString class]]) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"couldn't parse encodedCertificates."); - return nil; - } - NSString *_Nullable certificates = [encodedCertificates stringByRemovingPercentEncoding]; - if (!certificates) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"couldn't parse certificates."); - return nil; - } - - RemoteAttestationKeys *_Nullable keys = [RemoteAttestationKeys keysForKeyPair:keyPair - serverEphemeralPublic:serverEphemeralPublic - serverStaticPublic:serverStaticPublic - error:error]; - if (!keys || *error != nil) { - if (*error == nil) { - OWSFailDebug(@"missing error specifics"); - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"Couldn't derive keys. No reason given"); - } - return nil; - } - - CDSQuote *_Nullable quote = [CDSQuote parseQuoteFromData:quoteData]; - if (!quote) { - OWSFailDebug(@"couldn't parse quote."); - return nil; - } - NSData *_Nullable requestId = [self decryptRequestId:encryptedRequestId - encryptedRequestIv:encryptedRequestIv - encryptedRequestTag:encryptedRequestTag - keys:keys]; - if (!requestId) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"couldn't decrypt request id."); - return nil; - } - - if (![self verifyServerQuote:quote keys:keys enclaveId:enclaveId]) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAttestationFailed, @"couldn't verify quote."); - return nil; - } - - if (![self verifyIasSignatureWithCertificates:certificates - signatureBody:signatureBody - signature:signature - quoteData:quoteData - error:error]) { - - if (*error == nil) { - OWSFailDebug(@"missing error specifics"); - *error = ContactDiscoveryServiceErrorMakeWithReason(ContactDiscoveryServiceErrorAssertionError, - @"verifyIasSignatureWithCertificates failed. No reason given"); - } - return nil; - } - - RemoteAttestation *result = [RemoteAttestation new]; - result.cookies = cookies; - result.keys = keys; - result.requestId = requestId; - result.enclaveId = enclaveId; - result.auth = auth; - - OWSLogVerbose(@"remote attestation complete."); - - return result; -} - -- (BOOL)verifyIasSignatureWithCertificates:(NSString *)certificates - signatureBody:(NSString *)signatureBody - signature:(NSData *)signature - quoteData:(NSData *)quoteData - error:(NSError **)error -{ - OWSAssertDebug(certificates.length > 0); - OWSAssertDebug(signatureBody.length > 0); - OWSAssertDebug(signature.length > 0); - OWSAssertDebug(quoteData); - - NSError *signingError; - CDSSigningCertificate *_Nullable certificate = - [CDSSigningCertificate parseCertificateFromPem:certificates error:&signingError]; - if (signingError) { - *error = signingError; - return NO; - } - - if (!certificate) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"could not parse signing certificate."); - return NO; - } - if (![certificate verifySignatureOfBody:signatureBody signature:signature]) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAttestationFailed, @"could not verify signature."); - return NO; - } - - SignatureBodyEntity *_Nullable signatureBodyEntity = [self parseSignatureBodyEntity:signatureBody]; - if (!signatureBodyEntity) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"could not parse signature body."); - return NO; - } - - // Compare the first N bytes of the quote data with the signed quote body. - const NSUInteger kQuoteBodyComparisonLength = 432; - if (signatureBodyEntity.isvEnclaveQuoteBody.length < kQuoteBodyComparisonLength) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"isvEnclaveQuoteBody has unexpected length."); - return NO; - } - // NOTE: This version is separate from and does _NOT_ match the CDS quote version. - const NSUInteger kSignatureBodyVersion = 3; - if (![signatureBodyEntity.version isEqual:@(kSignatureBodyVersion)]) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"signatureBodyEntity has unexpected version."); - return NO; - } - if (quoteData.length < kQuoteBodyComparisonLength) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"quoteData has unexpected length."); - return NO; - } - NSData *isvEnclaveQuoteBodyForComparison = - [signatureBodyEntity.isvEnclaveQuoteBody subdataWithRange:NSMakeRange(0, kQuoteBodyComparisonLength)]; - NSData *quoteDataForComparison = [quoteData subdataWithRange:NSMakeRange(0, kQuoteBodyComparisonLength)]; - if (![isvEnclaveQuoteBodyForComparison ows_constantTimeIsEqualToData:quoteDataForComparison]) { - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAttestationFailed, @"isvEnclaveQuoteBody and quoteData do not match."); - return NO; - } - - if (![@"OK" isEqualToString:signatureBodyEntity.isvEnclaveQuoteStatus]) { - NSString *reason = - [NSString stringWithFormat:@"invalid isvEnclaveQuoteStatus: %@", signatureBodyEntity.isvEnclaveQuoteStatus]; - *error = ContactDiscoveryServiceErrorMakeWithReason(ContactDiscoveryServiceErrorAttestationFailed, reason); - return NO; - } - - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; - [dateFormatter setTimeZone:timeZone]; - [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSSSS"]; - - // Specify parsing locale - // from: https://developer.apple.com/library/archive/qa/qa1480/_index.html - // Q: I'm using NSDateFormatter to parse an Internet-style date, but this fails for some users in some regions. - // I've set a specific date format string; shouldn't that force NSDateFormatter to work independently of the user's - // region settings? A: No. While setting a date format string will appear to work for most users, it's not the right - // solution to this problem. There are many places where format strings behave in unexpected ways. [...] - NSLocale *enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; - [dateFormatter setLocale:enUSPOSIXLocale]; - NSDate *timestampDate = [dateFormatter dateFromString:signatureBodyEntity.timestamp]; - if (!timestampDate) { - OWSFailDebug(@"Could not parse signature body timestamp: %@", signatureBodyEntity.timestamp); - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAssertionError, @"could not parse signature body timestamp."); - return NO; - } - - // Only accept signatures from the last 24 hours. - NSDateComponents *dayComponent = [[NSDateComponents alloc] init]; - dayComponent.day = 1; - NSCalendar *calendar = [NSCalendar currentCalendar]; - NSDate *timestampDatePlus1Day = [calendar dateByAddingComponents:dayComponent toDate:timestampDate options:0]; - - NSDate *now = [NSDate new]; - BOOL isExpired = [now isAfterDate:timestampDatePlus1Day]; - - if (isExpired) { - OWSFailDebug(@"Signature is expired: %@", signatureBodyEntity.timestamp); - *error = ContactDiscoveryServiceErrorMakeWithReason( - ContactDiscoveryServiceErrorAttestationFailed, @"Signature is expired."); - return NO; - } - - return YES; -} - -- (nullable SignatureBodyEntity *)parseSignatureBodyEntity:(NSString *)signatureBody -{ - OWSAssertDebug(signatureBody.length > 0); - - NSError *error = nil; - NSDictionary *_Nullable jsonDict = - [NSJSONSerialization JSONObjectWithData:[signatureBody dataUsingEncoding:NSUTF8StringEncoding] - options:0 - error:&error]; - if (error || ![jsonDict isKindOfClass:[NSDictionary class]]) { - OWSFailDebug(@"could not parse signature body JSON: %@.", error); - return nil; - } - NSString *_Nullable timestamp = [jsonDict stringForKey:@"timestamp"]; - if (timestamp.length < 1) { - OWSFailDebug(@"could not parse signature timestamp."); - return nil; - } - NSData *_Nullable isvEnclaveQuoteBody = [jsonDict base64DataForKey:@"isvEnclaveQuoteBody"]; - if (isvEnclaveQuoteBody.length < 1) { - OWSFailDebug(@"could not parse signature isvEnclaveQuoteBody."); - return nil; - } - NSString *_Nullable isvEnclaveQuoteStatus = [jsonDict stringForKey:@"isvEnclaveQuoteStatus"]; - if (isvEnclaveQuoteStatus.length < 1) { - OWSFailDebug(@"could not parse signature isvEnclaveQuoteStatus."); - return nil; - } - NSNumber *_Nullable version = [jsonDict numberForKey:@"version"]; - if (!version) { - OWSFailDebug(@"could not parse signature version."); - return nil; - } - - SignatureBodyEntity *result = [SignatureBodyEntity new]; - result.isvEnclaveQuoteBody = isvEnclaveQuoteBody; - result.isvEnclaveQuoteStatus = isvEnclaveQuoteStatus; - result.timestamp = timestamp; - result.version = version; - return result; -} - -- (BOOL)verifyServerQuote:(CDSQuote *)quote keys:(RemoteAttestationKeys *)keys enclaveId:(NSString *)enclaveId -{ - OWSAssertDebug(quote); - OWSAssertDebug(keys); - OWSAssertDebug(enclaveId.length > 0); - - if (quote.reportData.length < keys.serverStaticPublic.length) { - OWSFailDebug(@"reportData has unexpected length: %lu != %lu.", - (unsigned long)quote.reportData.length, - (unsigned long)keys.serverStaticPublic.length); - return NO; - } - - NSData *_Nullable theirServerPublicStatic = - [quote.reportData subdataWithRange:NSMakeRange(0, keys.serverStaticPublic.length)]; - if (theirServerPublicStatic.length != keys.serverStaticPublic.length) { - OWSFailDebug(@"could not extract server public static."); - return NO; - } - if (![keys.serverStaticPublic ows_constantTimeIsEqualToData:theirServerPublicStatic]) { - OWSFailDebug(@"server public statics do not match."); - return NO; - } - // It's easier to compare as hex data than parsing hexadecimal. - NSData *_Nullable ourEnclaveIdHexData = [enclaveId dataUsingEncoding:NSUTF8StringEncoding]; - NSData *_Nullable theirEnclaveIdHexData = - [quote.mrenclave.hexadecimalString dataUsingEncoding:NSUTF8StringEncoding]; - if (!ourEnclaveIdHexData || !theirEnclaveIdHexData - || ![ourEnclaveIdHexData ows_constantTimeIsEqualToData:theirEnclaveIdHexData]) { - OWSFailDebug(@"enclave ids do not match."); - return NO; - } - if (quote.isDebugQuote) { - OWSFailDebug(@"quote has invalid isDebugQuote value."); - return NO; - } - return YES; -} - -- (nullable NSData *)decryptRequestId:(NSData *)encryptedRequestId - encryptedRequestIv:(NSData *)encryptedRequestIv - encryptedRequestTag:(NSData *)encryptedRequestTag - keys:(RemoteAttestationKeys *)keys -{ - OWSAssertDebug(encryptedRequestId.length > 0); - OWSAssertDebug(encryptedRequestIv.length > 0); - OWSAssertDebug(encryptedRequestTag.length > 0); - OWSAssertDebug(keys); - - OWSAES256Key *_Nullable key = keys.serverKey; - if (!key) { - OWSFailDebug(@"invalid server key."); - return nil; - } - NSData *_Nullable decryptedData = [Cryptography decryptAESGCMWithInitializationVector:encryptedRequestIv - ciphertext:encryptedRequestId - additionalAuthenticatedData:nil - authTag:encryptedRequestTag - key:key]; - if (!decryptedData) { - OWSFailDebug(@"couldn't decrypt request id."); - return nil; - } - return decryptedData; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/ContactsUpdater.h b/SignalServiceKit/src/Contacts/ContactsUpdater.h deleted file mode 100644 index 732b2c5c4..000000000 --- a/SignalServiceKit/src/Contacts/ContactsUpdater.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "SignalRecipient.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface ContactsUpdater : NSObject - -+ (instancetype)sharedUpdater; - -// This asynchronously tries to verify whether or not a group of possible -// contact ids correspond to service accounts. -// -// The failure callback is only invoked if the lookup fails. Otherwise, -// the success callback is invoked with the (possibly empty) set of contacts -// that were found. -- (void)lookupIdentifiers:(NSArray *)identifiers - success:(void (^)(NSArray *recipients))success - failure:(void (^)(NSError *error))failure; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/ContactsUpdater.m b/SignalServiceKit/src/Contacts/ContactsUpdater.m deleted file mode 100644 index 22ee254a4..000000000 --- a/SignalServiceKit/src/Contacts/ContactsUpdater.m +++ /dev/null @@ -1,119 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "ContactsUpdater.h" -#import "OWSError.h" -#import "OWSPrimaryStorage.h" -#import "OWSRequestFactory.h" -#import "PhoneNumber.h" -#import "SSKEnvironment.h" -#import "TSNetworkManager.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ContactsUpdater () - -@property (nonatomic, readonly) NSOperationQueue *contactIntersectionQueue; - -@end - -#pragma mark - - -@implementation ContactsUpdater - -+ (instancetype)sharedUpdater { - OWSAssertDebug(SSKEnvironment.shared.contactsUpdater); - - return SSKEnvironment.shared.contactsUpdater; -} - -- (instancetype)init -{ - self = [super init]; - if (!self) { - return self; - } - - _contactIntersectionQueue = [NSOperationQueue new]; - _contactIntersectionQueue.maxConcurrentOperationCount = 1; - _contactIntersectionQueue.name = self.logTag; - - OWSSingletonAssert(); - - return self; -} - -- (void)lookupIdentifiers:(NSArray *)identifiers - success:(void (^)(NSArray *recipients))success - failure:(void (^)(NSError *error))failure -{ - if (identifiers.count < 1) { - OWSFailDebug(@"Cannot lookup zero identifiers"); - DispatchMainThreadSafe(^{ - failure( - OWSErrorWithCodeDescription(OWSErrorCodeInvalidMethodParameters, @"Cannot lookup zero identifiers")); - }); - return; - } - - [self contactIntersectionWithSet:[NSSet setWithArray:identifiers] - success:^(NSSet *recipients) { - if (recipients.count == 0) { - OWSLogInfo(@"no contacts are Signal users"); - } - DispatchMainThreadSafe(^{ - success(recipients.allObjects); - }); - } - failure:^(NSError *error) { - DispatchMainThreadSafe(^{ - failure(error); - }); - }]; -} - -- (void)contactIntersectionWithSet:(NSSet *)recipientIdsToLookup - success:(void (^)(NSSet *recipients))success - failure:(void (^)(NSError *error))failure -{ - OWSLegacyContactDiscoveryOperation *operation = - [[OWSLegacyContactDiscoveryOperation alloc] initWithRecipientIdsToLookup:recipientIdsToLookup.allObjects]; - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSArray *operationAndDependencies = [operation.dependencies arrayByAddingObject:operation]; - [self.contactIntersectionQueue addOperations:operationAndDependencies waitUntilFinished:YES]; - - if (operation.failingError != nil) { - failure(operation.failingError); - return; - } - - NSSet *registeredRecipientIds = operation.registeredRecipientIds; - - NSMutableSet *recipients = [NSMutableSet new]; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - for (NSString *recipientId in recipientIdsToLookup) { - if ([registeredRecipientIds containsObject:recipientId]) { - SignalRecipient *recipient = - [SignalRecipient markRecipientAsRegisteredAndGet:recipientId transaction:transaction]; - [recipients addObject:recipient]; - } else { - [SignalRecipient markRecipientAsUnregistered:recipientId transaction:transaction]; - } - } - }]; - - dispatch_async(dispatch_get_main_queue(), ^{ - success([recipients copy]); - }); - }); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/OWSContactDiscoveryOperation.swift b/SignalServiceKit/src/Contacts/OWSContactDiscoveryOperation.swift deleted file mode 100644 index cbefc9e8e..000000000 --- a/SignalServiceKit/src/Contacts/OWSContactDiscoveryOperation.swift +++ /dev/null @@ -1,535 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation - -@objc(OWSLegacyContactDiscoveryOperation) -public class LegacyContactDiscoveryBatchOperation: OWSOperation { - - @objc - public var registeredRecipientIds: Set - - private let recipientIdsToLookup: [String] - private var networkManager: TSNetworkManager { - return TSNetworkManager.shared() - } - - // MARK: Initializers - - @objc - public required init(recipientIdsToLookup: [String]) { - self.recipientIdsToLookup = recipientIdsToLookup - self.registeredRecipientIds = Set() - - super.init() - - Logger.debug("with recipientIdsToLookup: \(recipientIdsToLookup.count)") - } - - // MARK: OWSOperation Overrides - - // Called every retry, this is where the bulk of the operation's work should go. - override public func run() { - Logger.debug("") - - guard !isCancelled else { - Logger.info("no work to do, since we were canceled") - self.reportCancelled() - return - } - - var phoneNumbersByHashes: [String: String] = [:] - - for recipientId in recipientIdsToLookup { - guard let hash = Cryptography.truncatedSHA1Base64EncodedWithoutPadding(recipientId) else { - owsFailDebug("could not hash recipient id: \(recipientId)") - continue - } - assert(phoneNumbersByHashes[hash] == nil) - phoneNumbersByHashes[hash] = recipientId - } - - let hashes: [String] = Array(phoneNumbersByHashes.keys) - - let request = OWSRequestFactory.contactsIntersectionRequest(withHashesArray: hashes) - - self.networkManager.makeRequest(request, - success: { (task, responseDict) in - do { - self.registeredRecipientIds = try self.parse(response: responseDict, phoneNumbersByHashes: phoneNumbersByHashes) - self.reportSuccess() - } catch { - self.reportError(error) - } - }, - failure: { (task, error) in - guard let response = task.response as? HTTPURLResponse else { - let responseError: NSError = OWSErrorMakeUnableToProcessServerResponseError() as NSError - responseError.isRetryable = true - self.reportError(responseError) - return - } - - guard response.statusCode != 413 else { - let rateLimitError = OWSErrorWithCodeDescription(OWSErrorCode.contactsUpdaterRateLimit, "Contacts Intersection Rate Limit") - self.reportError(rateLimitError) - return - } - - self.reportError(error) - }) - } - - // Called at most one time. - override public func didSucceed() { - // Compare against new CDS service - let modernCDSOperation = CDSOperation(recipientIdsToLookup: self.recipientIdsToLookup) - let cdsFeedbackOperation = CDSFeedbackOperation(legacyRegisteredRecipientIds: self.registeredRecipientIds) - cdsFeedbackOperation.addDependency(modernCDSOperation) - - let operations = modernCDSOperation.dependencies + [modernCDSOperation, cdsFeedbackOperation] - CDSOperation.operationQueue.addOperations(operations, waitUntilFinished: false) - } - - // MARK: Private Helpers - - private func parse(response: Any?, phoneNumbersByHashes: [String: String]) throws -> Set { - - guard let responseDict = response as? [String: AnyObject] else { - let responseError: NSError = OWSErrorMakeUnableToProcessServerResponseError() as NSError - responseError.isRetryable = true - - throw responseError - } - - guard let contactDicts = responseDict["contacts"] as? [[String: AnyObject]] else { - let responseError: NSError = OWSErrorMakeUnableToProcessServerResponseError() as NSError - responseError.isRetryable = true - - throw responseError - } - - var registeredRecipientIds: Set = Set() - - for contactDict in contactDicts { - guard let hash = contactDict["token"] as? String, hash.count > 0 else { - owsFailDebug("hash was unexpectedly nil") - continue - } - - guard let recipientId = phoneNumbersByHashes[hash], recipientId.count > 0 else { - owsFailDebug("recipientId was unexpectedly nil") - continue - } - - guard recipientIdsToLookup.contains(recipientId) else { - owsFailDebug("unexpected recipientId") - continue - } - - registeredRecipientIds.insert(recipientId) - } - - return registeredRecipientIds - } - -} - -enum ContactDiscoveryError: Error { - case parseError(description: String) - case assertionError(description: String) - case clientError(underlyingError: Error) - case serverError(underlyingError: Error) -} - -@objc(OWSCDSOperation) -class CDSOperation: OWSOperation { - - let batchSize = 2048 - static let operationQueue: OperationQueue = { - let queue = OperationQueue() - queue.maxConcurrentOperationCount = 5 - queue.name = CDSOperation.logTag() - return queue - }() - - let recipientIdsToLookup: [String] - - @objc - var registeredRecipientIds: Set - - @objc - required init(recipientIdsToLookup: [String]) { - self.recipientIdsToLookup = recipientIdsToLookup - self.registeredRecipientIds = Set() - - super.init() - - Logger.debug("with recipientIdsToLookup: \(recipientIdsToLookup.count)") - for batchIds in recipientIdsToLookup.chunked(by: batchSize) { - let batchOperation = CDSBatchOperation(recipientIdsToLookup: batchIds) - self.addDependency(batchOperation) - } - } - - // MARK: Mandatory overrides - - // Called every retry, this is where the bulk of the operation's work should go. - override func run() { - Logger.debug("") - - for dependency in self.dependencies { - guard let batchOperation = dependency as? CDSBatchOperation else { - owsFailDebug("unexpected dependency: \(dependency)") - continue - } - - self.registeredRecipientIds.formUnion(batchOperation.registeredRecipientIds) - } - - self.reportSuccess() - } - -} - -public -class CDSBatchOperation: OWSOperation { - - private let recipientIdsToLookup: [String] - private(set) var registeredRecipientIds: Set - - private var networkManager: TSNetworkManager { - return TSNetworkManager.shared() - } - - private var contactDiscoveryService: ContactDiscoveryService { - return ContactDiscoveryService.shared() - } - - // MARK: Initializers - - public required init(recipientIdsToLookup: [String]) { - self.recipientIdsToLookup = Set(recipientIdsToLookup).map { $0 } - self.registeredRecipientIds = Set() - - super.init() - - Logger.debug("with recipientIdsToLookup: \(recipientIdsToLookup.count)") - } - - // MARK: OWSOperationOverrides - - // Called every retry, this is where the bulk of the operation's work should go. - override public func run() { - Logger.debug("") - - guard !isCancelled else { - Logger.info("no work to do, since we were canceled") - self.reportCancelled() - return - } - - contactDiscoveryService.performRemoteAttestation(success: { (remoteAttestation: RemoteAttestation) in - self.makeContactDiscoveryRequest(remoteAttestation: remoteAttestation) - }, - failure: self.reportError) - } - - private func makeContactDiscoveryRequest(remoteAttestation: RemoteAttestation) { - return // Loki: Do nothing - - guard !isCancelled else { - Logger.info("no work to do, since we were canceled") - self.reportCancelled() - return - } - - let encryptionResult: AES25GCMEncryptionResult - do { - encryptionResult = try encryptAddresses(recipientIds: recipientIdsToLookup, remoteAttestation: remoteAttestation) - } catch { - reportError(error) - return - } - - let request = OWSRequestFactory.enclaveContactDiscoveryRequest(withId: remoteAttestation.requestId, - addressCount: UInt(recipientIdsToLookup.count), - encryptedAddressData: encryptionResult.ciphertext, - cryptIv: encryptionResult.initializationVector, - cryptMac: encryptionResult.authTag, - enclaveId: remoteAttestation.enclaveId, - authUsername: remoteAttestation.auth.username, - authPassword: remoteAttestation.auth.password, - cookies: remoteAttestation.cookies) - - self.networkManager.makeRequest(request, - success: { (task, responseDict) in - do { - self.registeredRecipientIds = try self.handle(response: responseDict, remoteAttestation: remoteAttestation) - self.reportSuccess() - } catch { - self.reportError(error) - } - }, - failure: { (task, error) in - guard let response = task.response as? HTTPURLResponse else { - let responseError = OWSErrorMakeUnableToProcessServerResponseError() as NSError - responseError.isRetryable = true - self.reportError(responseError) - return - } - - guard response.statusCode != 413 else { - let rateLimitError: NSError = OWSErrorWithCodeDescription(OWSErrorCode.contactsUpdaterRateLimit, "Contacts Intersection Rate Limit") as NSError - - // TODO CDS ratelimiting, handle Retry-After header if available - rateLimitError.isRetryable = false - self.reportError(rateLimitError) - return - } - - guard response.statusCode / 100 != 4 else { - let clientError: NSError = ContactDiscoveryError.clientError(underlyingError: error) as NSError - clientError.isRetryable = (error as NSError).isRetryable - self.reportError(clientError) - return - } - - guard response.statusCode / 100 != 5 else { - let serverError = ContactDiscoveryError.serverError(underlyingError: error) as NSError - serverError.isRetryable = (error as NSError).isRetryable - - // TODO CDS ratelimiting, handle Retry-After header if available - self.reportError(serverError) - return - } - - self.reportError(error) - }) - } - - func encryptAddresses(recipientIds: [String], remoteAttestation: RemoteAttestation) throws -> AES25GCMEncryptionResult { - - let addressPlainTextData = try type(of: self).encodePhoneNumbers(recipientIds: recipientIds) - - guard let encryptionResult = Cryptography.encryptAESGCM(plainTextData: addressPlainTextData, - additionalAuthenticatedData: remoteAttestation.requestId, - key: remoteAttestation.keys.clientKey) else { - - throw ContactDiscoveryError.assertionError(description: "Encryption failure") - } - - return encryptionResult - } - - class func encodePhoneNumbers(recipientIds: [String]) throws -> Data { - var output = Data() - - for recipientId in recipientIds { - guard recipientId.prefix(1) == "+" else { - throw ContactDiscoveryError.assertionError(description: "unexpected id format") - } - - let numericPortionIndex = recipientId.index(after: recipientId.startIndex) - let numericPortion = recipientId.suffix(from: numericPortionIndex) - - guard let numericIdentifier = UInt64(numericPortion), numericIdentifier > 99 else { - throw ContactDiscoveryError.assertionError(description: "unexpectedly short identifier") - } - - var bigEndian: UInt64 = CFSwapInt64HostToBig(numericIdentifier) - let buffer = UnsafeBufferPointer(start: &bigEndian, count: 1) - output.append(buffer) - } - - return output - } - - func handle(response: Any?, remoteAttestation: RemoteAttestation) throws -> Set { - let isIncludedData: Data = try parseAndDecrypt(response: response, remoteAttestation: remoteAttestation) - guard let isIncluded: [Bool] = type(of: self).boolArray(data: isIncludedData) else { - throw ContactDiscoveryError.assertionError(description: "isIncluded was unexpectedly nil") - } - - return try match(recipientIds: self.recipientIdsToLookup, isIncluded: isIncluded) - } - - class func boolArray(data: Data) -> [Bool]? { - var bools: [Bool]? - data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in - let buffer = UnsafeBufferPointer(start: bytes, count: data.count) - bools = Array(buffer) - } - - return bools - } - - func match(recipientIds: [String], isIncluded: [Bool]) throws -> Set { - guard recipientIds.count == isIncluded.count else { - throw ContactDiscoveryError.assertionError(description: "length mismatch for isIncluded/recipientIds") - } - - let includedRecipientIds: [String] = (0.. Data { - - guard let params = ParamParser(responseObject: response) else { - throw ContactDiscoveryError.parseError(description: "missing response dict") - } - - let cipherText = try params.requiredBase64EncodedData(key: "data") - let initializationVector = try params.requiredBase64EncodedData(key: "iv") - let authTag = try params.requiredBase64EncodedData(key: "mac") - - guard let plainText = Cryptography.decryptAESGCM(withInitializationVector: initializationVector, - ciphertext: cipherText, - additionalAuthenticatedData: nil, - authTag: authTag, - key: remoteAttestation.keys.serverKey) else { - throw ContactDiscoveryError.parseError(description: "decryption failed") - } - - return plainText - } -} - -class CDSFeedbackOperation: OWSOperation { - - enum FeedbackResult { - case ok - case mismatch - case attestationError(reason: String) - case unexpectedError(reason: String) - } - - private let legacyRegisteredRecipientIds: Set - - var networkManager: TSNetworkManager { - return TSNetworkManager.shared() - } - - // MARK: Initializers - - required init(legacyRegisteredRecipientIds: Set) { - self.legacyRegisteredRecipientIds = legacyRegisteredRecipientIds - - super.init() - - Logger.debug("") - } - - // MARK: OWSOperation Overrides - - override func checkForPreconditionError() -> Error? { - // override super with no-op - // In this rare case, we want to proceed even though our dependency might have an - // error so we can report the details of that error to the feedback service. - return nil - } - - // Called every retry, this is where the bulk of the operation's work should go. - override func run() { - - guard !isCancelled else { - Logger.info("no work to do, since we were canceled") - self.reportCancelled() - return - } - - guard let cdsOperation = dependencies.first as? CDSOperation else { - let error = OWSErrorMakeAssertionError("cdsOperation was unexpectedly nil") - self.reportError(error) - return - } - - if let error = cdsOperation.failingError { - switch error { - case TSNetworkManagerError.failedConnection: - // Don't submit feedback for connectivity errors - self.reportSuccess() - case ContactDiscoveryError.serverError, ContactDiscoveryError.clientError: - // Server already has this information, no need submit feedback - self.reportSuccess() - case let cdsError as ContactDiscoveryServiceError: - let reason = cdsError.reason - switch cdsError.code { - case .assertionError: - self.makeRequest(result: .unexpectedError(reason: "CDS assertionError: \(reason ?? "unknown")")) - case .attestationFailed: - self.makeRequest(result: .attestationError(reason: "CDS attestationFailed: \(reason ?? "unknown")")) - } - case ContactDiscoveryError.assertionError(let assertionDescription): - self.makeRequest(result: .unexpectedError(reason: "assertionError: \(assertionDescription)")) - case ContactDiscoveryError.parseError(description: let parseErrorDescription): - self.makeRequest(result: .unexpectedError(reason: "parseError: \(parseErrorDescription)")) - default: - let nsError = error as NSError - let reason = "unexpectedError code:\(nsError.code)" - self.makeRequest(result: .unexpectedError(reason: reason)) - } - - return - } - - if cdsOperation.registeredRecipientIds == legacyRegisteredRecipientIds { - self.makeRequest(result: .ok) - return - } else { - self.makeRequest(result: .mismatch) - return - } - } - - func makeRequest(result: FeedbackResult) { - let reason: String? - switch result { - case .ok: - reason = nil - case .mismatch: - reason = nil - case .attestationError(let attestationErrorReason): - reason = attestationErrorReason - case .unexpectedError(let unexpectedErrorReason): - reason = unexpectedErrorReason - } - let request = OWSRequestFactory.cdsFeedbackRequest(status: result.statusPath, reason: reason) - self.networkManager.makeRequest(request, - success: { _, _ in self.reportSuccess() }, - failure: { _, error in self.reportError(error) }) - } -} - -extension Array { - func chunked(by chunkSize: Int) -> [[Element]] { - return stride(from: 0, to: self.count, by: chunkSize).map { - Array(self[$0.. *)validDurationsSeconds; -+ (uint32_t)maxDurationSeconds; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.m b/SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.m deleted file mode 100644 index 95072d9d9..000000000 --- a/SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.m +++ /dev/null @@ -1,133 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDisappearingMessagesConfiguration.h" -#import "NSString+SSK.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSDisappearingMessagesConfiguration () - -// Transient record lifecycle attributes. -@property (atomic) NSDictionary *originalDictionaryValue; -@property (atomic, getter=isNewRecord) BOOL newRecord; - -@end - -@implementation OWSDisappearingMessagesConfiguration - -- (instancetype)initDefaultWithThreadId:(NSString *)threadId -{ - return [self initWithThreadId:threadId - enabled:NO - durationSeconds:OWSDisappearingMessagesConfigurationDefaultExpirationDuration]; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - - _originalDictionaryValue = [self dictionaryValue]; - _newRecord = NO; - - return self; -} - -- (instancetype)initWithThreadId:(NSString *)threadId enabled:(BOOL)isEnabled durationSeconds:(uint32_t)seconds -{ - self = [super initWithUniqueId:threadId]; - if (!self) { - return self; - } - - _enabled = isEnabled; - _durationSeconds = seconds; - _newRecord = YES; - _originalDictionaryValue = self.dictionaryValue; - - return self; -} - -+ (instancetype)fetchOrBuildDefaultWithThreadId:(NSString *)threadId - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSDisappearingMessagesConfiguration *savedConfiguration = - [self fetchObjectWithUniqueID:threadId transaction:transaction]; - if (savedConfiguration) { - return savedConfiguration; - } else { - return [[self alloc] initDefaultWithThreadId:threadId]; - } -} - -+ (NSArray *)validDurationsSeconds -{ - return @[ - @(5 * kSecondInterval), - @(10 * kSecondInterval), - @(30 * kSecondInterval), - @(1 * kMinuteInterval), - @(5 * kMinuteInterval), - @(30 * kMinuteInterval), - @(1 * kHourInterval), - @(6 * kHourInterval), - @(12 * kHourInterval), - @(24 * kHourInterval), - @(1 * kWeekInterval) - ]; -} - -+ (uint32_t)maxDurationSeconds -{ - static uint32_t max; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - max = [[self.validDurationsSeconds valueForKeyPath:@"@max.intValue"] unsignedIntValue]; - - // It's safe to update this assert if we add a larger duration - OWSAssertDebug(max == 1 * kWeekInterval); - }); - - return max; -} - -- (NSUInteger)durationIndex -{ - return [[self.class validDurationsSeconds] indexOfObject:@(self.durationSeconds)]; -} - -- (NSString *)durationString -{ - return [NSString formatDurationSeconds:self.durationSeconds useShortFormat:NO]; -} - -#pragma mark - Dirty Tracking - -+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey -{ - // Don't persist transient properties - if ([propertyKey isEqualToString:@"originalDictionaryValue"] - ||[propertyKey isEqualToString:@"newRecord"]) { - return MTLPropertyStorageNone; - } else { - return [super storageBehaviorForPropertyWithKey:propertyKey]; - } -} - -- (BOOL)dictionaryValueDidChange -{ - return ![self.originalDictionaryValue isEqual:[self dictionaryValue]]; -} - -- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [super saveWithTransaction:transaction]; - self.originalDictionaryValue = [self dictionaryValue]; - self.newRecord = NO; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/PhoneNumber.h b/SignalServiceKit/src/Contacts/PhoneNumber.h deleted file mode 100644 index 8418a5c69..000000000 --- a/SignalServiceKit/src/Contacts/PhoneNumber.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -#define COUNTRY_CODE_PREFIX @"+" - -/** - * - * PhoneNumber is used to deal with the nitty details of parsing/canonicalizing phone numbers. - * Everything that expects a valid phone number should take a PhoneNumber, not a string, to avoid stringly typing. - * - */ -@interface PhoneNumber : NSObject - -+ (nullable PhoneNumber *)phoneNumberFromE164:(NSString *)text; - -+ (nullable PhoneNumber *)tryParsePhoneNumberFromUserSpecifiedText:(NSString *)text; -+ (nullable PhoneNumber *)tryParsePhoneNumberFromE164:(NSString *)text; - -// This will try to parse the input text as a phone number using -// the default region and the country code for this client's phone -// number. -// -// Order matters; better results will appear first. -+ (NSArray *)tryParsePhoneNumbersFromsUserSpecifiedText:(NSString *)text - clientPhoneNumber:(NSString *)clientPhoneNumber; - -+ (NSString *)removeFormattingCharacters:(NSString *)inputString; -+ (NSString *)bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString *)input; -+ (NSString *)bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString *)input - withSpecifiedCountryCodeString:(NSString *)countryCodeString; -+ (NSString *)bestEffortLocalizedPhoneNumberWithE164:(NSString *)phoneNumber; - -+ (NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString; - -- (NSURL *)toSystemDialerURL; -- (NSString *)toE164; -- (nullable NSNumber *)getCountryCode; -@property (nonatomic, readonly, nullable) NSString *nationalNumber; -- (BOOL)isValid; - -- (NSComparisonResult)compare:(PhoneNumber *)other; - -+ (NSString *)defaultCountryCode; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/PhoneNumber.m b/SignalServiceKit/src/Contacts/PhoneNumber.m deleted file mode 100644 index 1a54ee8b1..000000000 --- a/SignalServiceKit/src/Contacts/PhoneNumber.m +++ /dev/null @@ -1,582 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "PhoneNumber.h" -#import "PhoneNumberUtil.h" -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const RPDefaultsKeyPhoneNumberString = @"RPDefaultsKeyPhoneNumberString"; -static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneNumberCanonical"; - -@interface PhoneNumber () - -@property (nonatomic, readonly) NBPhoneNumber *phoneNumber; -@property (nonatomic, readonly) NSString *e164; - -@end - -#pragma mark - - -@implementation PhoneNumber - -- (instancetype)initWithPhoneNumber:(NBPhoneNumber *)phoneNumber e164:(NSString *)e164 -{ - if (self = [self init]) { - OWSAssertDebug(phoneNumber); - OWSAssertDebug(e164.length > 0); - - _phoneNumber = phoneNumber; - _e164 = e164; - } - return self; -} - -+ (nullable PhoneNumber *)phoneNumberFromText:(NSString *)text andRegion:(NSString *)regionCode { - OWSAssertDebug(text != nil); - OWSAssertDebug(regionCode != nil); - - PhoneNumberUtil *phoneUtil = [PhoneNumberUtil sharedThreadLocal]; - - NSError *parseError = nil; - NBPhoneNumber *number = [phoneUtil parse:text defaultRegion:regionCode error:&parseError]; - - if (parseError) { - return nil; - } - - NSError *toE164Error; - NSString *e164 = [phoneUtil format:number numberFormat:NBEPhoneNumberFormatE164 error:&toE164Error]; - if (toE164Error) { - OWSLogDebug(@"Issue while formatting number: %@", [toE164Error description]); - return nil; - } - - return [[PhoneNumber alloc] initWithPhoneNumber:number e164:e164]; -} - -+ (nullable PhoneNumber *)phoneNumberFromUserSpecifiedText:(NSString *)text { - OWSAssertDebug(text != nil); - - return [PhoneNumber phoneNumberFromText:text andRegion:[self defaultCountryCode]]; -} - -+ (NSString *)defaultCountryCode -{ - NSLocale *locale = [NSLocale currentLocale]; - - NSString *_Nullable countryCode = nil; -#if TARGET_OS_IPHONE - countryCode = [[PhoneNumberUtil sharedThreadLocal].nbPhoneNumberUtil countryCodeByCarrier]; - - if ([countryCode isEqualToString:@"ZZ"]) { - countryCode = [locale objectForKey:NSLocaleCountryCode]; - } -#else - countryCode = [locale objectForKey:NSLocaleCountryCode]; -#endif - if (!countryCode) { - OWSFailDebug(@"Could not identify country code for locale: %@", locale); - countryCode = @"US"; - } - return countryCode; -} - -+ (nullable PhoneNumber *)phoneNumberFromE164:(NSString *)text { - return [[PhoneNumber alloc] initWithPhoneNumber:[NBPhoneNumber new] e164:text]; - // Loki: Original code: - // ======== -// OWSAssertDebug(text != nil); -// OWSAssertDebug([text hasPrefix:COUNTRY_CODE_PREFIX]); -// PhoneNumber *number = [PhoneNumber phoneNumberFromText:text andRegion:@"ZZ"]; -// OWSAssertDebug(number != nil); -// return number; - // ======== -} - -+ (NSString *)bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString *)input { - return [PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:input - withSpecifiedRegionCode:[self defaultCountryCode]]; -} - -+ (NSString *)bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString *)input - withSpecifiedCountryCodeString:(NSString *)countryCodeString { - return [PhoneNumber - bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:input - withSpecifiedRegionCode: - [PhoneNumber regionCodeFromCountryCodeString:countryCodeString]]; -} - -+ (NSString *)bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:(NSString *)input - withSpecifiedRegionCode:(NSString *)regionCode { - NBAsYouTypeFormatter *formatter = [[NBAsYouTypeFormatter alloc] initWithRegionCode:regionCode]; - - NSString *result = input; - for (NSUInteger i = 0; i < input.length; i++) { - result = [formatter inputDigit:[input substringWithRange:NSMakeRange(i, 1)]]; - } - return result; -} - -+ (NSString *)formatIntAsEN:(int)value -{ - static NSNumberFormatter *formatter = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - formatter = [NSNumberFormatter new]; - formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"]; - }); - return [formatter stringFromNumber:@(value)]; -} - -+ (NSString *)bestEffortLocalizedPhoneNumberWithE164:(NSString *)phoneNumber -{ - OWSAssertDebug(phoneNumber); - - if (![phoneNumber hasPrefix:COUNTRY_CODE_PREFIX]) { - return phoneNumber; - } - - PhoneNumber *_Nullable parsedPhoneNumber = [self tryParsePhoneNumberFromE164:phoneNumber]; - if (!parsedPhoneNumber) { - OWSLogWarn(@"could not parse phone number."); - return phoneNumber; - } - NSNumber *_Nullable countryCode = [parsedPhoneNumber getCountryCode]; - if (!countryCode) { - OWSLogWarn(@"parsed phone number has no country code."); - return phoneNumber; - } - NSString *countryCodeString = [self formatIntAsEN:countryCode.intValue]; - if (countryCodeString.length < 1) { - OWSLogWarn(@"invalid country code."); - return phoneNumber; - } - NSString *_Nullable formattedPhoneNumber = - [self bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:phoneNumber - withSpecifiedRegionCode:countryCodeString]; - if (!countryCode) { - OWSLogWarn(@"could not format phone number."); - return phoneNumber; - } - return formattedPhoneNumber; -} - -+ (NSString *)regionCodeFromCountryCodeString:(NSString *)countryCodeString { - NBPhoneNumberUtil *phoneUtil = [PhoneNumberUtil sharedThreadLocal].nbPhoneNumberUtil; - NSString *regionCode = - [phoneUtil getRegionCodeForCountryCode:@([[countryCodeString substringFromIndex:1] integerValue])]; - return regionCode; -} - -+ (nullable PhoneNumber *)tryParsePhoneNumberFromUserSpecifiedText:(NSString *)text { - OWSAssertDebug(text != nil); - - if ([text isEqualToString:@""]) { - return nil; - } - NSString *sanitizedString = [self removeFormattingCharacters:text]; - - return [self phoneNumberFromUserSpecifiedText:sanitizedString]; -} - -+ (nullable NSString *)nationalPrefixTransformRuleForDefaultRegion -{ - static NSString *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSString *defaultCountryCode = [self defaultCountryCode]; - NBMetadataHelper *helper = [[NBMetadataHelper alloc] init]; - NBPhoneMetaData *defaultRegionMetadata = [helper getMetadataForRegion:defaultCountryCode]; - result = defaultRegionMetadata.nationalPrefixTransformRule; - }); - return result; -} - -// clientPhoneNumber is the local user's phone number and should never change. -+ (nullable NSString *)nationalPrefixTransformRuleForClientPhoneNumber:(NSString *)clientPhoneNumber -{ - if (clientPhoneNumber.length < 1) { - return nil; - } - static NSString *result = nil; - static NSString *cachedClientPhoneNumber = nil; - static dispatch_once_t onceToken; - - // clientPhoneNumber is the local user's phone number and should never change. - static void (^updateCachedClientPhoneNumber)(void); - updateCachedClientPhoneNumber = ^(void) { - NSNumber *localCallingCode = [[PhoneNumber phoneNumberFromE164:clientPhoneNumber] getCountryCode]; - if (localCallingCode != nil) { - NSString *localCallingCodePrefix = [NSString stringWithFormat:@"+%@", localCallingCode]; - NSString *localCountryCode = - [PhoneNumberUtil.sharedThreadLocal probableCountryCodeForCallingCode:localCallingCodePrefix]; - if (localCountryCode && ![localCountryCode isEqualToString:[self defaultCountryCode]]) { - NBMetadataHelper *helper = [[NBMetadataHelper alloc] init]; - NBPhoneMetaData *localNumberRegionMetadata = [helper getMetadataForRegion:localCountryCode]; - result = localNumberRegionMetadata.nationalPrefixTransformRule; - } else { - result = nil; - } - } - cachedClientPhoneNumber = [clientPhoneNumber copy]; - }; - -#ifdef DEBUG - // For performance, we want to cahce this result, but it breaks tests since local number - // can change. - if (CurrentAppContext().isRunningTests) { - updateCachedClientPhoneNumber(); - } else { - dispatch_once(&onceToken, ^{ - updateCachedClientPhoneNumber(); - }); - } -#else - dispatch_once(&onceToken, ^{ - updateCachedClientPhoneNumber(); - }); - OWSAssertDebug([cachedClientPhoneNumber isEqualToString:clientPhoneNumber]); -#endif - - return result; -} - -+ (NSArray *)tryParsePhoneNumbersFromsUserSpecifiedText:(NSString *)text - clientPhoneNumber:(NSString *)clientPhoneNumber -{ - NSMutableArray *result = - [[self tryParsePhoneNumbersFromNormalizedText:text clientPhoneNumber:clientPhoneNumber] mutableCopy]; - - // A handful of countries (Mexico, Argentina, etc.) require a "national" prefix after - // their country calling code. - // - // It's a bit hacky, but we reconstruct these national prefixes from libPhoneNumber's - // parsing logic. It's okay if we botch this a little. The risk is that we end up with - // some misformatted numbers with extra non-numeric regex syntax. These erroneously - // parsed numbers will never be presented to the user, since they'll never survive the - // contacts intersection. - // - // 1. Try to apply a "national prefix" using the phone's region. - NSString *nationalPrefixTransformRuleForDefaultRegion = [self nationalPrefixTransformRuleForDefaultRegion]; - if ([nationalPrefixTransformRuleForDefaultRegion containsString:@"$1"]) { - NSString *normalizedText = - [nationalPrefixTransformRuleForDefaultRegion stringByReplacingOccurrencesOfString:@"$1" withString:text]; - if (![normalizedText containsString:@"$"]) { - [result addObjectsFromArray:[self tryParsePhoneNumbersFromNormalizedText:normalizedText - clientPhoneNumber:clientPhoneNumber]]; - } - } - - // 2. Try to apply a "national prefix" using the region that corresponds to the - // calling code for the local phone number. - NSString *nationalPrefixTransformRuleForClientPhoneNumber = - [self nationalPrefixTransformRuleForClientPhoneNumber:clientPhoneNumber]; - if ([nationalPrefixTransformRuleForClientPhoneNumber containsString:@"$1"]) { - NSString *normalizedText = - [nationalPrefixTransformRuleForClientPhoneNumber stringByReplacingOccurrencesOfString:@"$1" - withString:text]; - if (![normalizedText containsString:@"$"]) { - [result addObjectsFromArray:[self tryParsePhoneNumbersFromNormalizedText:normalizedText - clientPhoneNumber:clientPhoneNumber]]; - } - } - - return [result copy]; -} - -+ (NSArray *)tryParsePhoneNumbersFromNormalizedText:(NSString *)text - clientPhoneNumber:(NSString *)clientPhoneNumber -{ - OWSAssertDebug(text != nil); - - text = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - if ([text isEqualToString:@""]) { - return nil; - } - - NSString *sanitizedString = [self removeFormattingCharacters:text]; - OWSAssertDebug(sanitizedString != nil); - - NSMutableArray *result = [NSMutableArray new]; - NSMutableSet *phoneNumberSet = [NSMutableSet new]; - void (^tryParsingWithCountryCode)(NSString *, NSString *) = ^(NSString *text, - NSString *countryCode) { - PhoneNumber *phoneNumber = [PhoneNumber phoneNumberFromText:text - andRegion:countryCode]; - if (phoneNumber && [phoneNumber toE164] && ![phoneNumberSet containsObject:[phoneNumber toE164]]) { - [result addObject:phoneNumber]; - [phoneNumberSet addObject:[phoneNumber toE164]]; - } - }; - - tryParsingWithCountryCode(sanitizedString, [self defaultCountryCode]); - - if ([sanitizedString hasPrefix:@"+"]) { - // If the text starts with "+", don't try prepending - // anything else. - return result; - } - - // Try just adding "+" and parsing it. - tryParsingWithCountryCode([NSString stringWithFormat:@"+%@", sanitizedString], [self defaultCountryCode]); - - // Order matters; better results should appear first so prefer - // matches with the same country code as this client's phone number. - if (clientPhoneNumber.length == 0) { - OWSFailDebug(@"clientPhoneNumber had unexpected length"); - return result; - } - - // Note that NBPhoneNumber uses "country code" to refer to what we call a - // "calling code" (i.e. 44 in +44123123). Within SSK we use "country code" - // (and sometimes "region code") to refer to a country's ISO 2-letter code - // (ISO 3166-1 alpha-2). - NSNumber *callingCodeForLocalNumber = [[PhoneNumber phoneNumberFromE164:clientPhoneNumber] getCountryCode]; - if (callingCodeForLocalNumber == nil) { - OWSFailDebug(@"callingCodeForLocalNumber was unexpectedly nil"); - return result; - } - - NSString *callingCodePrefix = [NSString stringWithFormat:@"+%@", callingCodeForLocalNumber]; - - tryParsingWithCountryCode([callingCodePrefix stringByAppendingString:sanitizedString], [self defaultCountryCode]); - - // Try to determine what the country code is for the local phone number - // and also try parsing the phone number using that country code if it - // differs from the device's region code. - // - // For example, a French person living in Italy might have an - // Italian phone number but use French region/language for their - // phone. They're likely to have both Italian and French contacts. - NSString *localCountryCode = - [PhoneNumberUtil.sharedThreadLocal probableCountryCodeForCallingCode:callingCodePrefix]; - if (localCountryCode && ![localCountryCode isEqualToString:[self defaultCountryCode]]) { - tryParsingWithCountryCode([callingCodePrefix stringByAppendingString:sanitizedString], localCountryCode); - } - - NSString *_Nullable phoneNumberByApplyingMissingAreaCode = - [self applyMissingAreaCodeWithCallingCodeForReferenceNumber:callingCodeForLocalNumber - referenceNumber:clientPhoneNumber - sanitizedInputText:sanitizedString]; - if (phoneNumberByApplyingMissingAreaCode) { - tryParsingWithCountryCode(phoneNumberByApplyingMissingAreaCode, localCountryCode); - } - - return result; -} - -#pragma mark - missing area code - -+ (nullable NSString *)applyMissingAreaCodeWithCallingCodeForReferenceNumber:(NSNumber *)callingCodeForReferenceNumber - referenceNumber:(NSString *)referenceNumber - sanitizedInputText:(NSString *)sanitizedInputText -{ - if ([callingCodeForReferenceNumber isEqual:@(55)]) { - return - [self applyMissingBrazilAreaCodeWithReferenceNumber:referenceNumber sanitizedInputText:sanitizedInputText]; - } else if ([callingCodeForReferenceNumber isEqual:@(1)]) { - return [self applyMissingUnitedStatesAreaCodeWithReferenceNumber:referenceNumber - sanitizedInputText:sanitizedInputText]; - } else { - return nil; - } -} - -#pragma mark - missing brazil area code - -+ (nullable NSString *)applyMissingBrazilAreaCodeWithReferenceNumber:(NSString *)referenceNumber - sanitizedInputText:(NSString *)sanitizedInputText -{ - NSError *error; - NSRegularExpression *missingAreaCodeRegex = - [[NSRegularExpression alloc] initWithPattern:@"^(9?\\d{8})$" options:0 error:&error]; - if (error) { - OWSFailDebug(@"failure: %@", error); - return nil; - } - - if ([missingAreaCodeRegex firstMatchInString:sanitizedInputText - options:0 - range:NSMakeRange(0, sanitizedInputText.length)] - == nil) { - } - - NSString *_Nullable referenceAreaCode = [self brazilAreaCodeFromReferenceNumberE164:referenceNumber]; - if (!referenceAreaCode) { - return nil; - } - return [NSString stringWithFormat:@"+55%@%@", referenceAreaCode, sanitizedInputText]; -} - -+ (nullable NSString *)brazilAreaCodeFromReferenceNumberE164:(NSString *)referenceNumberE164 -{ - NSError *error; - NSRegularExpression *areaCodeRegex = - [[NSRegularExpression alloc] initWithPattern:@"^\\+55(\\d{2})9?\\d{8}" options:0 error:&error]; - if (error) { - OWSFailDebug(@"failure: %@", error); - return nil; - } - - NSArray *matches = - [areaCodeRegex matchesInString:referenceNumberE164 options:0 range:NSMakeRange(0, referenceNumberE164.length)]; - if (matches.count == 0) { - OWSFailDebug(@"failure: unexpectedly unable to extract area code from US number"); - return nil; - } - NSTextCheckingResult *match = matches[0]; - - NSRange firstCaptureRange = [match rangeAtIndex:1]; - return [referenceNumberE164 substringWithRange:firstCaptureRange]; -} - -#pragma mark - missing US area code - -+ (nullable NSString *)applyMissingUnitedStatesAreaCodeWithReferenceNumber:(NSString *)referenceNumber - sanitizedInputText:(NSString *)sanitizedInputText -{ - NSError *error; - NSRegularExpression *missingAreaCodeRegex = - [[NSRegularExpression alloc] initWithPattern:@"^(\\d{7})$" options:0 error:&error]; - if (error) { - OWSFailDebug(@"failure: %@", error); - return nil; - } - - if ([missingAreaCodeRegex firstMatchInString:sanitizedInputText - options:0 - range:NSMakeRange(0, sanitizedInputText.length)] - == nil) { - // area code isn't missing - return nil; - } - - NSString *_Nullable referenceAreaCode = [self unitedStateAreaCodeFromReferenceNumberE164:referenceNumber]; - if (!referenceAreaCode) { - return nil; - } - return [NSString stringWithFormat:@"+1%@%@", referenceAreaCode, sanitizedInputText]; -} - -+ (nullable NSString *)unitedStateAreaCodeFromReferenceNumberE164:(NSString *)referenceNumberE164 -{ - NSError *error; - NSRegularExpression *areaCodeRegex = - [[NSRegularExpression alloc] initWithPattern:@"^\\+1(\\d{3})" options:0 error:&error]; - if (error) { - OWSFailDebug(@"failure: %@", error); - return nil; - } - - NSArray *matches = - [areaCodeRegex matchesInString:referenceNumberE164 options:0 range:NSMakeRange(0, referenceNumberE164.length)]; - if (matches.count == 0) { - OWSFailDebug(@"failure: unexpectedly unable to extract area code from US number"); - return nil; - } - NSTextCheckingResult *match = matches[0]; - - NSRange firstCaptureRange = [match rangeAtIndex:1]; - return [referenceNumberE164 substringWithRange:firstCaptureRange]; -} - -#pragma mark - - -+ (NSString *)removeFormattingCharacters:(NSString *)inputString { - char outputString[inputString.length + 1]; - - int outputLength = 0; - for (NSUInteger i = 0; i < inputString.length; i++) { - unichar c = [inputString characterAtIndex:i]; - if (c == '+' || (c >= '0' && c <= '9')) { - outputString[outputLength++] = (char)c; - } - } - - outputString[outputLength] = 0; - return [NSString stringWithUTF8String:(void *)outputString]; -} - -+ (nullable PhoneNumber *)tryParsePhoneNumberFromE164:(NSString *)text { - OWSAssertDebug(text != nil); - if (![text hasPrefix:COUNTRY_CODE_PREFIX]) { - return nil; - } - - return [self phoneNumberFromE164:text]; -} - -- (NSURL *)toSystemDialerURL { - NSString *link = [NSString stringWithFormat:@"telprompt://%@", self.e164]; - return [NSURL URLWithString:link]; -} - -- (NSString *)toE164 { - return self.e164; -} - -- (nullable NSNumber *)getCountryCode { - return self.phoneNumber.countryCode; -} - -- (nullable NSString *)nationalNumber -{ - NSError *error; - NSString *nationalNumber = [[PhoneNumberUtil sharedThreadLocal] format:self.phoneNumber - numberFormat:NBEPhoneNumberFormatNATIONAL - error:&error]; - if (error) { - OWSLogVerbose(@"error parsing number into national format: %@", error); - return nil; - } - - return nationalNumber; -} - -- (BOOL)isValid -{ - return [[PhoneNumberUtil sharedThreadLocal].nbPhoneNumberUtil isValidNumber:self.phoneNumber]; -} - -- (NSString *)description { - return self.e164; -} - -- (void)encodeWithCoder:(NSCoder *)encoder { - [encoder encodeObject:self.phoneNumber forKey:RPDefaultsKeyPhoneNumberString]; - [encoder encodeObject:self.e164 forKey:RPDefaultsKeyPhoneNumberCanonical]; -} - -- (id)initWithCoder:(NSCoder *)decoder { - if ((self = [super init])) { - _phoneNumber = [decoder decodeObjectForKey:RPDefaultsKeyPhoneNumberString]; - _e164 = [decoder decodeObjectForKey:RPDefaultsKeyPhoneNumberCanonical]; - } - return self; -} - -- (NSComparisonResult)compare:(PhoneNumber *)other -{ - return [self.toE164 compare:other.toE164]; -} - -- (BOOL)isEqual:(id)other -{ - if (![other isMemberOfClass:[self class]]) { - return NO; - } - PhoneNumber *otherPhoneNumber = (PhoneNumber *)other; - - return [self.phoneNumber isEqual:otherPhoneNumber.phoneNumber]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.h b/SignalServiceKit/src/Contacts/PhoneNumberUtil.h deleted file mode 100644 index 377fa7498..000000000 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "PhoneNumber.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface PhoneNumberUtil : NSObject - -@property (nonatomic, retain) NBPhoneNumberUtil *nbPhoneNumberUtil; - -- (instancetype)init NS_UNAVAILABLE; - -+ (instancetype)sharedThreadLocal; - -+ (BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString; - -+ (NSString *)callingCodeFromCountryCode:(NSString *)countryCode; -+ (nullable NSString *)countryNameFromCountryCode:(NSString *)countryCode; -+ (NSArray *)countryCodesForSearchTerm:(nullable NSString *)searchTerm; - -// Returns a list of country codes for a calling code in descending -// order of population. -- (NSArray *)countryCodesFromCallingCode:(NSString *)callingCode; -// Returns the most likely country code for a calling code based on population. -- (NSString *)probableCountryCodeForCallingCode:(NSString *)callingCode; - -+ (NSUInteger)translateCursorPosition:(NSUInteger)offset - from:(NSString *)source - to:(NSString *)target - stickingRightward:(bool)preferHigh; - -+ (NSString *)examplePhoneNumberForCountryCode:(NSString *)countryCode; - -- (nullable NBPhoneNumber *)parse:(NSString *)numberToParse defaultRegion:(NSString *)defaultRegion error:(NSError **)error; -- (NSString *)format:(NBPhoneNumber *)phoneNumber - numberFormat:(NBEPhoneNumberFormat)numberFormat - error:(NSError **)error; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/PhoneNumberUtil.m b/SignalServiceKit/src/Contacts/PhoneNumberUtil.m deleted file mode 100644 index cbf6bfa31..000000000 --- a/SignalServiceKit/src/Contacts/PhoneNumberUtil.m +++ /dev/null @@ -1,609 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "PhoneNumberUtil.h" -#import "ContactsManagerProtocol.h" -#import "FunctionalUtil.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface PhoneNumberUtil () - -@property (nonatomic, readonly) NSMutableDictionary *countryCodesFromCallingCodeCache; -@property (nonatomic, readonly) NSCache *parsedPhoneNumberCache; - -@end - -#pragma mark - - -@implementation PhoneNumberUtil - -+ (PhoneNumberUtil *)sharedThreadLocal -{ - NSString *key = PhoneNumberUtil.logTag; - PhoneNumberUtil *_Nullable threadLocal = NSThread.currentThread.threadDictionary[key]; - if (!threadLocal) { - threadLocal = [PhoneNumberUtil new]; - NSThread.currentThread.threadDictionary[key] = threadLocal; - } - return threadLocal; -} - -- (instancetype)init { - self = [super init]; - - if (self) { - _nbPhoneNumberUtil = [[NBPhoneNumberUtil alloc] init]; - _countryCodesFromCallingCodeCache = [NSMutableDictionary new]; - _parsedPhoneNumberCache = [NSCache new]; - } - - return self; -} - -- (nullable NBPhoneNumber *)parse:(NSString *)numberToParse - defaultRegion:(NSString *)defaultRegion - error:(NSError **)error -{ - NSString *hashKey = [NSString stringWithFormat:@"numberToParse:%@defaultRegion:%@", numberToParse, defaultRegion]; - - NBPhoneNumber *result = [self.parsedPhoneNumberCache objectForKey:hashKey]; - - if (!result) { - result = [self.nbPhoneNumberUtil parse:numberToParse defaultRegion:defaultRegion error:error]; - if (error && *error) { - OWSAssertDebug(!result); - return nil; - } - - OWSAssertDebug(result); - - if (result) { - [self.parsedPhoneNumberCache setObject:result forKey:hashKey]; - } else { - [self.parsedPhoneNumberCache setObject:[NSNull null] forKey:hashKey]; - } - } - - if ([result class] == [NSNull class]) { - return nil; - } else { - return result; - } -} - -- (NSString *)format:(NBPhoneNumber *)phoneNumber - numberFormat:(NBEPhoneNumberFormat)numberFormat - error:(NSError **)error -{ - return [self.nbPhoneNumberUtil format:phoneNumber numberFormat:numberFormat error:error]; -} - -// country code -> country name -+ (nullable NSString *)countryNameFromCountryCode:(NSString *)countryCode -{ - OWSAssertDebug(countryCode); - - NSDictionary *countryCodeComponent = @{NSLocaleCountryCode : countryCode}; - NSString *identifier = [NSLocale localeIdentifierFromComponents:countryCodeComponent]; - NSString *countryName = [NSLocale.currentLocale displayNameForKey:NSLocaleIdentifier value:identifier]; - if (countryName.length < 1) { - countryName = [NSLocale.systemLocale displayNameForKey:NSLocaleIdentifier value:identifier]; - } - if (countryName.length < 1) { - countryName = NSLocalizedString(@"UNKNOWN_VALUE", "Indicates an unknown or unrecognizable value."); - } - return countryName; -} - -// country code -> calling code -+ (NSString *)callingCodeFromCountryCode:(NSString *)countryCode -{ - if ([countryCode isEqualToString:@"AQ"]) { - // Antarctica - return @"+672"; - } else if ([countryCode isEqualToString:@"BV"]) { - // Bouvet Island - return @"+55"; - } else if ([countryCode isEqualToString:@"IC"]) { - // Canary Islands - return @"+34"; - } else if ([countryCode isEqualToString:@"EA"]) { - // Ceuta & Melilla - return @"+34"; - } else if ([countryCode isEqualToString:@"CP"]) { - // Clipperton Island - // - // This country code should be filtered - it does not appear to have a calling code. - return nil; - } else if ([countryCode isEqualToString:@"DG"]) { - // Diego Garcia - return @"+246"; - } else if ([countryCode isEqualToString:@"TF"]) { - // French Southern Territories - return @"+262"; - } else if ([countryCode isEqualToString:@"HM"]) { - // Heard & McDonald Islands - return @"+672"; - } else if ([countryCode isEqualToString:@"XK"]) { - // Kosovo - return @"+383"; - } else if ([countryCode isEqualToString:@"PN"]) { - // Pitcairn Islands - return @"+64"; - } else if ([countryCode isEqualToString:@"GS"]) { - // So. Georgia & So. Sandwich Isl. - return @"+500"; - } else if ([countryCode isEqualToString:@"UM"]) { - // U.S. Outlying Islands - return @"+1"; - } - - NSString *callingCode = - [NSString stringWithFormat:@"%@%@", - COUNTRY_CODE_PREFIX, - [[[self sharedThreadLocal] nbPhoneNumberUtil] getCountryCodeForRegion:countryCode]]; - return callingCode; -} - -- (NSDictionary *)countryCodeToPopulationMap -{ - static dispatch_once_t onceToken; - static NSDictionary *instance = nil; - dispatch_once(&onceToken, ^{ - instance = @{ - @"AD" : @(84000), - @"AE" : @(4975593), - @"AF" : @(29121286), - @"AG" : @(86754), - @"AI" : @(13254), - @"AL" : @(2986952), - @"AM" : @(2968000), - @"AN" : @(300000), - @"AO" : @(13068161), - @"AQ" : @(0), - @"AR" : @(41343201), - @"AS" : @(57881), - @"AT" : @(8205000), - @"AU" : @(21515754), - @"AW" : @(71566), - @"AX" : @(26711), - @"AZ" : @(8303512), - @"BA" : @(4590000), - @"BB" : @(285653), - @"BD" : @(156118464), - @"BE" : @(10403000), - @"BF" : @(16241811), - @"BG" : @(7148785), - @"BH" : @(738004), - @"BI" : @(9863117), - @"BJ" : @(9056010), - @"BL" : @(8450), - @"BM" : @(65365), - @"BN" : @(395027), - @"BO" : @(9947418), - @"BQ" : @(18012), - @"BR" : @(201103330), - @"BS" : @(301790), - @"BT" : @(699847), - @"BV" : @(0), - @"BW" : @(2029307), - @"BY" : @(9685000), - @"BZ" : @(314522), - @"CA" : @(33679000), - @"CC" : @(628), - @"CD" : @(70916439), - @"CF" : @(4844927), - @"CG" : @(3039126), - @"CH" : @(7581000), - @"CI" : @(21058798), - @"CK" : @(21388), - @"CL" : @(16746491), - @"CM" : @(19294149), - @"CN" : @(1330044000), - @"CO" : @(47790000), - @"CR" : @(4516220), - @"CS" : @(10829175), - @"CU" : @(11423000), - @"CV" : @(508659), - @"CW" : @(141766), - @"CX" : @(1500), - @"CY" : @(1102677), - @"CZ" : @(10476000), - @"DE" : @(81802257), - @"DJ" : @(740528), - @"DK" : @(5484000), - @"DM" : @(72813), - @"DO" : @(9823821), - @"DZ" : @(34586184), - @"EC" : @(14790608), - @"EE" : @(1291170), - @"EG" : @(80471869), - @"EH" : @(273008), - @"ER" : @(5792984), - @"ES" : @(46505963), - @"ET" : @(88013491), - @"FI" : @(5244000), - @"FJ" : @(875983), - @"FK" : @(2638), - @"FM" : @(107708), - @"FO" : @(48228), - @"FR" : @(64768389), - @"GA" : @(1545255), - @"GB" : @(62348447), - @"GD" : @(107818), - @"GE" : @(4630000), - @"GF" : @(195506), - @"GG" : @(65228), - @"GH" : @(24339838), - @"GI" : @(27884), - @"GL" : @(56375), - @"GM" : @(1593256), - @"GN" : @(10324025), - @"GP" : @(443000), - @"GQ" : @(1014999), - @"GR" : @(11000000), - @"GS" : @(30), - @"GT" : @(13550440), - @"GU" : @(159358), - @"GW" : @(1565126), - @"GY" : @(748486), - @"HK" : @(6898686), - @"HM" : @(0), - @"HN" : @(7989415), - @"HR" : @(4284889), - @"HT" : @(9648924), - @"HU" : @(9982000), - @"ID" : @(242968342), - @"IE" : @(4622917), - @"IL" : @(7353985), - @"IM" : @(75049), - @"IN" : @(1173108018), - @"IO" : @(4000), - @"IQ" : @(29671605), - @"IR" : @(76923300), - @"IS" : @(308910), - @"IT" : @(60340328), - @"JE" : @(90812), - @"JM" : @(2847232), - @"JO" : @(6407085), - @"JP" : @(127288000), - @"KE" : @(40046566), - @"KG" : @(5776500), - @"KH" : @(14453680), - @"KI" : @(92533), - @"KM" : @(773407), - @"KN" : @(51134), - @"KP" : @(22912177), - @"KR" : @(48422644), - @"KW" : @(2789132), - @"KY" : @(44270), - @"KZ" : @(15340000), - @"LA" : @(6368162), - @"LB" : @(4125247), - @"LC" : @(160922), - @"LI" : @(35000), - @"LK" : @(21513990), - @"LR" : @(3685076), - @"LS" : @(1919552), - @"LT" : @(2944459), - @"LU" : @(497538), - @"LV" : @(2217969), - @"LY" : @(6461454), - @"MA" : @(33848242), - @"MC" : @(32965), - @"MD" : @(4324000), - @"ME" : @(666730), - @"MF" : @(35925), - @"MG" : @(21281844), - @"MH" : @(65859), - @"MK" : @(2062294), - @"ML" : @(13796354), - @"MM" : @(53414374), - @"MN" : @(3086918), - @"MO" : @(449198), - @"MP" : @(53883), - @"MQ" : @(432900), - @"MR" : @(3205060), - @"MS" : @(9341), - @"MT" : @(403000), - @"MU" : @(1294104), - @"MV" : @(395650), - @"MW" : @(15447500), - @"MX" : @(112468855), - @"MY" : @(28274729), - @"MZ" : @(22061451), - @"NA" : @(2128471), - @"NC" : @(216494), - @"NE" : @(15878271), - @"NF" : @(1828), - @"NG" : @(154000000), - @"NI" : @(5995928), - @"NL" : @(16645000), - @"NO" : @(5009150), - @"NP" : @(28951852), - @"NR" : @(10065), - @"NU" : @(2166), - @"NZ" : @(4252277), - @"OM" : @(2967717), - @"PA" : @(3410676), - @"PE" : @(29907003), - @"PF" : @(270485), - @"PG" : @(6064515), - @"PH" : @(99900177), - @"PK" : @(184404791), - @"PL" : @(38500000), - @"PM" : @(7012), - @"PN" : @(46), - @"PR" : @(3916632), - @"PS" : @(3800000), - @"PT" : @(10676000), - @"PW" : @(19907), - @"PY" : @(6375830), - @"QA" : @(840926), - @"RE" : @(776948), - @"RO" : @(21959278), - @"RS" : @(7344847), - @"RU" : @(140702000), - @"RW" : @(11055976), - @"SA" : @(25731776), - @"SB" : @(559198), - @"SC" : @(88340), - @"SD" : @(35000000), - @"SE" : @(9828655), - @"SG" : @(4701069), - @"SH" : @(7460), - @"SI" : @(2007000), - @"SJ" : @(2550), - @"SK" : @(5455000), - @"SL" : @(5245695), - @"SM" : @(31477), - @"SN" : @(12323252), - @"SO" : @(10112453), - @"SR" : @(492829), - @"SS" : @(8260490), - @"ST" : @(175808), - @"SV" : @(6052064), - @"SX" : @(37429), - @"SY" : @(22198110), - @"SZ" : @(1354051), - @"TC" : @(20556), - @"TD" : @(10543464), - @"TF" : @(140), - @"TG" : @(6587239), - @"TH" : @(67089500), - @"TJ" : @(7487489), - @"TK" : @(1466), - @"TL" : @(1154625), - @"TM" : @(4940916), - @"TN" : @(10589025), - @"TO" : @(122580), - @"TR" : @(77804122), - @"TT" : @(1328019), - @"TV" : @(10472), - @"TW" : @(22894384), - @"TZ" : @(41892895), - @"UA" : @(45415596), - @"UG" : @(33398682), - @"UM" : @(0), - @"US" : @(310232863), - @"UY" : @(3477000), - @"UZ" : @(27865738), - @"VA" : @(921), - @"VC" : @(104217), - @"VE" : @(27223228), - @"VG" : @(21730), - @"VI" : @(108708), - @"VN" : @(89571130), - @"VU" : @(221552), - @"WF" : @(16025), - @"WS" : @(192001), - @"XK" : @(1800000), - @"YE" : @(23495361), - @"YT" : @(159042), - @"ZA" : @(49000000), - @"ZM" : @(13460305), - @"ZW" : @(13061000), - }; - }); - return instance; -} - -- (NSArray *)countryCodesSortedByPopulationDescending -{ - NSDictionary *countryCodeToPopulationMap = [self countryCodeToPopulationMap]; - NSArray *result = [NSLocale.ISOCountryCodes - sortedArrayUsingComparator:^NSComparisonResult(NSString *_Nonnull left, NSString *_Nonnull right) { - int leftPopulation = [countryCodeToPopulationMap[left] intValue]; - int rightPopulation = [countryCodeToPopulationMap[right] intValue]; - // Invert the values for a descending sort. - return [@(-leftPopulation) compare:@(-rightPopulation)]; - }]; - return result; -} - -- (NSArray *)countryCodesFromCallingCode:(NSString *)callingCode -{ - @synchronized(self) - { - OWSAssertDebug(callingCode.length > 0); - - NSArray *result = self.countryCodesFromCallingCodeCache[callingCode]; - if (!result) { - NSMutableArray *countryCodes = [NSMutableArray new]; - for (NSString *countryCode in [self countryCodesSortedByPopulationDescending]) { - NSString *callingCodeForCountryCode = [PhoneNumberUtil callingCodeFromCountryCode:countryCode]; - if ([callingCode isEqualToString:callingCodeForCountryCode]) { - [countryCodes addObject:countryCode]; - } - } - result = [countryCodes copy]; - self.countryCodesFromCallingCodeCache[callingCode] = result; - } - return result; - } -} - -- (NSString *)probableCountryCodeForCallingCode:(NSString *)callingCode -{ - OWSAssertDebug(callingCode.length > 0); - - NSArray *countryCodes = [self countryCodesFromCallingCode:callingCode]; - return (countryCodes.count > 0 ? countryCodes[0] : nil); -} - -+ (BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString { - NSCharacterSet *whitespaceSet = NSCharacterSet.whitespaceCharacterSet; - NSArray *queryStrings = [queryString componentsSeparatedByCharactersInSet:whitespaceSet]; - NSArray *nameStrings = [nameString componentsSeparatedByCharactersInSet:whitespaceSet]; - - return [queryStrings all:^int(NSString *query) { - if (query.length == 0) - return YES; - return [nameStrings any:^int(NSString *nameWord) { - NSStringCompareOptions searchOpts = NSCaseInsensitiveSearch | NSAnchoredSearch; - return [nameWord rangeOfString:query options:searchOpts].location != NSNotFound; - }]; - }]; -} - -// search term -> country codes -+ (NSArray *)countryCodesForSearchTerm:(nullable NSString *)searchTerm { - searchTerm = [searchTerm stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - - NSArray *countryCodes = NSLocale.ISOCountryCodes; - - countryCodes = [countryCodes filter:^int(NSString *countryCode) { - NSString *countryName = [self countryNameFromCountryCode:countryCode]; - NSString *callingCode = [self callingCodeFromCountryCode:countryCode]; - - if (countryName.length < 1 || callingCode.length < 1 || [callingCode isEqualToString:@"+0"]) { - // Filter out countries without a valid calling code. - return NO; - } - - if (searchTerm.length < 1) { - return YES; - } - - if ([self name:countryName matchesQuery:searchTerm]) { - return YES; - } - - if ([self name:countryCode matchesQuery:searchTerm]) { - return YES; - } - - // We rely on the already internationalized string; as that is what - // the user would see entered (i.e. with COUNTRY_CODE_PREFIX). - - if ([callingCode containsString:searchTerm]) { - return YES; - } - - return NO; - }]; - - return [self sortedCountryCodesByName:countryCodes]; -} - -+ (NSArray *)sortedCountryCodesByName:(NSArray *)countryCodesByISOCode { - return [countryCodesByISOCode sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { - return [[self countryNameFromCountryCode:obj1] caseInsensitiveCompare:[self countryNameFromCountryCode:obj2]]; - }]; -} - -// black magic -+ (NSUInteger)translateCursorPosition:(NSUInteger)offset - from:(NSString *)source - to:(NSString *)target - stickingRightward:(bool)preferHigh { - OWSAssertDebug(source != nil); - OWSAssertDebug(target != nil); - OWSAssertDebug(offset <= source.length); - - NSUInteger n = source.length; - NSUInteger m = target.length; - - int moves[n + 1][m + 1]; - { - // Wagner-Fischer algorithm for computing edit distance, with a tweaks: - // - Tracks best moves at each location, to allow reconstruction of edit path - // - Does not allow substitutions - // - Over-values digits relative to other characters, so they're "harder" to delete or insert - const int DIGIT_VALUE = 10; - NSUInteger scores[n + 1][m + 1]; - moves[0][0] = 0; // (match) move up and left - scores[0][0] = 0; - for (NSUInteger i = 1; i <= n; i++) { - scores[i][0] = i; - moves[i][0] = -1; // (deletion) move left - } - for (NSUInteger j = 1; j <= m; j++) { - scores[0][j] = j; - moves[0][j] = +1; // (insertion) move up - } - - NSCharacterSet *digits = NSCharacterSet.decimalDigitCharacterSet; - for (NSUInteger i = 1; i <= n; i++) { - unichar c1 = [source characterAtIndex:i - 1]; - bool isDigit1 = [digits characterIsMember:c1]; - for (NSUInteger j = 1; j <= m; j++) { - unichar c2 = [target characterAtIndex:j - 1]; - bool isDigit2 = [digits characterIsMember:c2]; - if (c1 == c2) { - scores[i][j] = scores[i - 1][j - 1]; - moves[i][j] = 0; // move up-and-left - } else { - NSUInteger del = scores[i - 1][j] + (isDigit1 ? DIGIT_VALUE : 1); - NSUInteger ins = scores[i][j - 1] + (isDigit2 ? DIGIT_VALUE : 1); - bool isDel = del < ins; - scores[i][j] = isDel ? del : ins; - moves[i][j] = isDel ? -1 : +1; - } - } - } - } - - // Backtrack to find desired corresponding offset - for (NSUInteger i = n, j = m;; i -= 1) { - if (i == offset && preferHigh) - return j; // early exit - while (moves[i][j] == +1) - j -= 1; // zip upward - if (i == offset) - return j; // late exit - if (moves[i][j] == 0) - j -= 1; - } -} - -+ (NSString *)examplePhoneNumberForCountryCode:(NSString *)countryCode -{ - PhoneNumberUtil *sharedUtil = [self sharedThreadLocal]; - - // Signal users are very likely using mobile devices, so prefer that kind of example. - NSError *error; - NBPhoneNumber *nbPhoneNumber = - [sharedUtil.nbPhoneNumberUtil getExampleNumberForType:countryCode type:NBEPhoneNumberTypeMOBILE error:&error]; - OWSAssertDebug(!error); - if (!nbPhoneNumber) { - // For countries that with similar mobile and land lines, use "line or mobile" - // examples. - nbPhoneNumber = [sharedUtil.nbPhoneNumberUtil getExampleNumberForType:countryCode - type:NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE - error:&error]; - OWSAssertDebug(!error); - } - NSString *result = (nbPhoneNumber - ? [sharedUtil.nbPhoneNumberUtil format:nbPhoneNumber numberFormat:NBEPhoneNumberFormatE164 error:&error] - : nil); - OWSAssertDebug(!error); - return result; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/SignalAccount.h b/SignalServiceKit/src/Contacts/SignalAccount.h deleted file mode 100644 index 5b416f9a4..000000000 --- a/SignalServiceKit/src/Contacts/SignalAccount.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSYapDatabaseObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@class Contact; -@class SignalRecipient; -@class YapDatabaseReadTransaction; - -// This class represents a single valid Signal account. -// -// * Contacts with multiple signal accounts will correspond to -// multiple instances of SignalAccount. -// * For non-contacts, the contact property will be nil. -@interface SignalAccount : TSYapDatabaseObject - -// An E164 value identifying the signal account. -// -// This is the key property of this class and it -// will always be non-null. -@property (nonatomic, readonly) NSString *recipientId; - -// This property is optional and will not be set for -// non-contact account. -@property (nonatomic, nullable) Contact *contact; - -@property (nonatomic) BOOL hasMultipleAccountContact; - -// For contacts with more than one signal account, -// this is a label for the account. -@property (nonatomic) NSString *multipleAccountLabelText; - -- (nullable NSString *)contactFullName; - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithSignalRecipient:(SignalRecipient *)signalRecipient; - -- (instancetype)initWithRecipientId:(NSString *)recipientId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/SignalAccount.m b/SignalServiceKit/src/Contacts/SignalAccount.m deleted file mode 100644 index fb34ee5da..000000000 --- a/SignalServiceKit/src/Contacts/SignalAccount.m +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "SignalAccount.h" -#import "Contact.h" -#import "NSString+SSK.h" -#import "OWSPrimaryStorage.h" -#import "SignalRecipient.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface SignalAccount () - -@property (nonatomic) NSString *recipientId; - -@end - -#pragma mark - - -@implementation SignalAccount - -- (instancetype)initWithSignalRecipient:(SignalRecipient *)signalRecipient -{ - OWSAssertDebug(signalRecipient); - return [self initWithRecipientId:signalRecipient.recipientId]; -} - -- (instancetype)initWithRecipientId:(NSString *)recipientId -{ - if (self = [super init]) { - OWSAssertDebug(recipientId.length > 0); - - _recipientId = recipientId; - } - return self; -} - -- (nullable NSString *)uniqueId -{ - return _recipientId; -} - -- (nullable NSString *)contactFullName -{ - return self.contact.fullName.filterStringForDisplay; -} - -- (NSString *)multipleAccountLabelText -{ - return _multipleAccountLabelText.filterStringForDisplay; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/SignalRecipient.h b/SignalServiceKit/src/Contacts/SignalRecipient.h deleted file mode 100644 index 3fd396f73..000000000 --- a/SignalServiceKit/src/Contacts/SignalRecipient.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSYapDatabaseObject.h" - -NS_ASSUME_NONNULL_BEGIN - -// SignalRecipient serves two purposes: -// -// a) It serves as a cache of "known" Signal accounts. When the service indicates -// that an account exists, we make sure that an instance of SignalRecipient exists -// for that recipient id (using mark as registered) and has at least one device. -// When the service indicates that an account does not exist, we remove any devices -// from that SignalRecipient - but do not remove it from the database. -// Note that SignalRecipients without any devices are not considered registered. -//// b) We hang the "known device list" for known signal accounts on this entity. -@interface SignalRecipient : TSYapDatabaseObject - -@property (nonatomic, readonly) NSOrderedSet *devices; - -- (instancetype)init NS_UNAVAILABLE; - -+ (nullable instancetype)registeredRecipientForRecipientId:(NSString *)recipientId - mustHaveDevices:(BOOL)mustHaveDevices - transaction:(YapDatabaseReadTransaction *)transaction; -+ (instancetype)getOrBuildUnsavedRecipientForRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadTransaction *)transaction; - -- (void)updateRegisteredRecipientWithDevicesToAdd:(nullable NSArray *)devicesToAdd - devicesToRemove:(nullable NSArray *)devicesToRemove - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (NSString *)recipientId; - -- (NSComparisonResult)compare:(SignalRecipient *)other; - -+ (BOOL)isRegisteredRecipient:(NSString *)recipientId transaction:(YapDatabaseReadTransaction *)transaction; - -+ (SignalRecipient *)markRecipientAsRegisteredAndGet:(NSString *)recipientId - transaction:(YapDatabaseReadWriteTransaction *)transaction; -+ (void)markRecipientAsRegistered:(NSString *)recipientId - deviceId:(UInt32)deviceId - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -+ (void)markRecipientAsUnregistered:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/SignalRecipient.m b/SignalServiceKit/src/Contacts/SignalRecipient.m deleted file mode 100644 index 96342c9a2..000000000 --- a/SignalServiceKit/src/Contacts/SignalRecipient.m +++ /dev/null @@ -1,283 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "SignalRecipient.h" -#import "OWSDevice.h" -#import "ProfileManagerProtocol.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSSocketManager.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface SignalRecipient () - -@property (nonatomic) NSOrderedSet *devices; - -@end - -#pragma mark - - -@implementation SignalRecipient - -#pragma mark - Dependencies - -- (id)profileManager -{ - return SSKEnvironment.shared.profileManager; -} - -- (id)udManager -{ - return SSKEnvironment.shared.udManager; -} - -- (TSAccountManager *)tsAccountManager -{ - OWSAssertDebug(SSKEnvironment.shared.tsAccountManager); - - return SSKEnvironment.shared.tsAccountManager; -} - -- (TSSocketManager *)socketManager -{ - OWSAssertDebug(SSKEnvironment.shared.socketManager); - - return SSKEnvironment.shared.socketManager; -} - -#pragma mark - - -+ (instancetype)getOrBuildUnsavedRecipientForRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - OWSAssertDebug(recipientId.length > 0); - - SignalRecipient *_Nullable recipient = - [self registeredRecipientForRecipientId:recipientId mustHaveDevices:NO transaction:transaction]; - if (!recipient) { - recipient = [[self alloc] initWithTextSecureIdentifier:recipientId]; - } - return recipient; -} - -- (instancetype)initWithTextSecureIdentifier:(NSString *)textSecureIdentifier -{ - self = [super initWithUniqueId:textSecureIdentifier]; - if (!self) { - return self; - } - - _devices = [NSOrderedSet orderedSetWithObject:@(OWSDevicePrimaryDeviceId)]; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - if (_devices == nil) { - _devices = [NSOrderedSet new]; - } - - // Since we use device count to determine whether a user is registered or not, - // ensure the local user always has at least *this* device. - if (![_devices containsObject:@(OWSDevicePrimaryDeviceId)]) { - if ([self.uniqueId isEqualToString:self.tsAccountManager.localNumber]) { - DDLogInfo(@"Adding primary device to self recipient."); - [self addDevices:[NSSet setWithObject:@(OWSDevicePrimaryDeviceId)]]; - } - } - - return self; -} - -+ (nullable instancetype)registeredRecipientForRecipientId:(NSString *)recipientId - mustHaveDevices:(BOOL)mustHaveDevices - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - OWSAssertDebug(recipientId.length > 0); - - SignalRecipient *_Nullable signalRecipient = [self fetchObjectWithUniqueID:recipientId transaction:transaction]; - if (mustHaveDevices && signalRecipient.devices.count < 1) { - return nil; - } - return signalRecipient; -} - -- (void)addDevices:(NSSet *)devices -{ - OWSAssertDebug(devices.count > 0); - - NSMutableOrderedSet *updatedDevices = [self.devices mutableCopy]; - [updatedDevices unionSet:devices]; - self.devices = [updatedDevices copy]; -} - -- (void)removeDevices:(NSSet *)devices -{ - OWSAssertDebug(devices.count > 0); - - NSMutableOrderedSet *updatedDevices = [self.devices mutableCopy]; - [updatedDevices minusSet:devices]; - self.devices = [updatedDevices copy]; -} - -- (void)updateRegisteredRecipientWithDevicesToAdd:(nullable NSArray *)devicesToAdd - devicesToRemove:(nullable NSArray *)devicesToRemove - transaction:(YapDatabaseReadWriteTransaction *)transaction { - OWSAssertDebug(transaction); - OWSAssertDebug(devicesToAdd.count > 0 || devicesToRemove.count > 0); - - // Add before we remove, since removeDevicesFromRecipient:... - // can markRecipientAsUnregistered:... if the recipient has - // no devices left. - if (devicesToAdd.count > 0) { - [self addDevicesToRegisteredRecipient:[NSSet setWithArray:devicesToAdd] transaction:transaction]; - } - if (devicesToRemove.count > 0) { - [self removeDevicesFromRecipient:[NSSet setWithArray:devicesToRemove] transaction:transaction]; - } - - // Device changes - dispatch_async(dispatch_get_main_queue(), ^{ - // Device changes can affect the UD access mode for a recipient, - // so we need to fetch the profile for this user to update UD access mode. - [self.profileManager fetchProfileForRecipientId:self.recipientId]; - - if ([self.recipientId isEqualToString:self.tsAccountManager.localNumber]) { - [self.socketManager cycleSocket]; - } - }); -} - -- (void)addDevicesToRegisteredRecipient:(NSSet *)devices transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - OWSAssertDebug(devices.count > 0); - OWSLogDebug(@"adding devices: %@, to recipient: %@", devices, self); - - [self reloadWithTransaction:transaction]; - [self addDevices:devices]; - [self saveWithTransaction_internal:transaction]; -} - -- (void)removeDevicesFromRecipient:(NSSet *)devices transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - OWSAssertDebug(devices.count > 0); - - OWSLogDebug(@"removing devices: %@, from registered recipient: %@", devices, self); - [self reloadWithTransaction:transaction ignoreMissing:YES]; - [self removeDevices:devices]; - [self saveWithTransaction_internal:transaction]; -} - -- (NSString *)recipientId -{ - return self.uniqueId; -} - -- (NSComparisonResult)compare:(SignalRecipient *)other -{ - return [self.recipientId compare:other.recipientId]; -} - -- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - // We need to distinguish between "users we know to be unregistered" and - // "users whose registration status is unknown". The former correspond to - // instances of SignalRecipient with no devices. The latter do not - // correspond to an instance of SignalRecipient in the database (although - // they may correspond to an "unsaved" instance of SignalRecipient built - // by getOrBuildUnsavedRecipientForRecipientId. - OWSFailDebug(@"Don't call removeWithTransaction."); - - [super removeWithTransaction:transaction]; -} - -- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - // We only want to mutate the persisted SignalRecipients in the database - // using other methods of this class, e.g. markRecipientAsRegistered... - // to create, addDevices and removeDevices to mutate. We're trying to - // be strict about using persisted SignalRecipients as a cache to - // reflect "last known registration status". Forcing our codebase to - // use those methods helps ensure that we update the cache deliberately. - OWSFailDebug(@"Don't call saveWithTransaction from outside this class."); - - [self saveWithTransaction_internal:transaction]; -} - -- (void)saveWithTransaction_internal:(YapDatabaseReadWriteTransaction *)transaction -{ - [super saveWithTransaction:transaction]; - - OWSLogVerbose(@"saved signal recipient: %@ (%lu)", self.recipientId, (unsigned long) self.devices.count); -} - -+ (BOOL)isRegisteredRecipient:(NSString *)recipientId transaction:(YapDatabaseReadTransaction *)transaction -{ - return nil != [self registeredRecipientForRecipientId:recipientId mustHaveDevices:YES transaction:transaction]; -} - -+ (SignalRecipient *)markRecipientAsRegisteredAndGet:(NSString *)recipientId - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - OWSAssertDebug(recipientId.length > 0); - - SignalRecipient *_Nullable instance = - [self registeredRecipientForRecipientId:recipientId mustHaveDevices:YES transaction:transaction]; - - if (!instance) { - OWSLogDebug(@"creating recipient: %@", recipientId); - - instance = [[self alloc] initWithTextSecureIdentifier:recipientId]; - [instance saveWithTransaction_internal:transaction]; - } - return instance; -} - -+ (void)markRecipientAsRegistered:(NSString *)recipientId - deviceId:(UInt32)deviceId - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - OWSAssertDebug(recipientId.length > 0); - - SignalRecipient *recipient = [self markRecipientAsRegisteredAndGet:recipientId transaction:transaction]; - if (![recipient.devices containsObject:@(deviceId)]) { - OWSLogDebug(@"Adding device %u to existing recipient.", (unsigned int)deviceId); - - [recipient addDevices:[NSSet setWithObject:@(deviceId)]]; - [recipient saveWithTransaction_internal:transaction]; - } -} - -+ (void)markRecipientAsUnregistered:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - OWSAssertDebug(recipientId.length > 0); - - SignalRecipient *instance = [self getOrBuildUnsavedRecipientForRecipientId:recipientId - transaction:transaction]; - OWSLogDebug(@"Marking recipient as not registered: %@", recipientId); - if (instance.devices.count > 0) { - [instance removeDevices:instance.devices.set]; - } - [instance saveWithTransaction_internal:transaction]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/TSThread.h b/SignalServiceKit/src/Contacts/TSThread.h deleted file mode 100644 index 5af5ec9a5..000000000 --- a/SignalServiceKit/src/Contacts/TSThread.h +++ /dev/null @@ -1,182 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSYapDatabaseObject.h" - -NS_ASSUME_NONNULL_BEGIN - -BOOL IsNoteToSelfEnabled(void); - -@class OWSDisappearingMessagesConfiguration; -@class TSInteraction; -@class TSInvalidIdentityKeyReceivingErrorMessage; - -typedef NSString *ConversationColorName NS_STRING_ENUM; - -extern ConversationColorName const ConversationColorNameCrimson; -extern ConversationColorName const ConversationColorNameVermilion; -extern ConversationColorName const ConversationColorNameBurlap; -extern ConversationColorName const ConversationColorNameForest; -extern ConversationColorName const ConversationColorNameWintergreen; -extern ConversationColorName const ConversationColorNameTeal; -extern ConversationColorName const ConversationColorNameBlue; -extern ConversationColorName const ConversationColorNameIndigo; -extern ConversationColorName const ConversationColorNameViolet; -extern ConversationColorName const ConversationColorNamePlum; -extern ConversationColorName const ConversationColorNameTaupe; -extern ConversationColorName const ConversationColorNameSteel; - -extern ConversationColorName const kConversationColorName_Default; - -/** - * TSThread is the superclass of TSContactThread and TSGroupThread - */ -@interface TSThread : TSYapDatabaseObject - -@property (nonatomic) BOOL shouldThreadBeVisible; -@property (nonatomic, readonly) NSDate *creationDate; -@property (nonatomic, readonly) BOOL isArchivedByLegacyTimestampForSorting; -@property (nonatomic, readonly) TSInteraction *lastInteraction; -@property (nonatomic, readonly) BOOL isSlaveThread; - -/** - * Whether the object is a group thread or not. - * - * @return YES if is a group thread, NO otherwise. - */ -- (BOOL)isGroupThread; - -/** - * Returns the name of the thread. - * - * @return The name of the thread. - */ -- (NSString *)name; - -@property (nonatomic, readonly) ConversationColorName conversationColorName; - -- (void)updateConversationColorName:(ConversationColorName)colorName - transaction:(YapDatabaseReadWriteTransaction *)transaction; -+ (ConversationColorName)stableColorNameForNewConversationWithString:(NSString *)colorSeed; -@property (class, nonatomic, readonly) NSArray *conversationColorNames; - -/** - * @returns - * Signal Id (e164) of the contact if it's a contact thread. - */ -- (nullable NSString *)contactIdentifier; - -/** - * @returns recipientId for each recipient in the thread - */ -@property (nonatomic, readonly) NSArray *recipientIdentifiers; - -- (BOOL)isNoteToSelf; - -#pragma mark Interactions - -- (void)enumerateInteractionsWithTransaction:(YapDatabaseReadTransaction *)transaction usingBlock:(void (^)(TSInteraction *interaction, YapDatabaseReadTransaction *transaction))block; - -- (void)enumerateInteractionsUsingBlock:(void (^)(TSInteraction *interaction))block; - -/** - * @return The number of interactions in this thread. - */ -- (NSUInteger)numberOfInteractions; - -/** - * Get all messages in the thread we weren't able to decrypt - */ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -- (NSArray *)receivedMessagesForInvalidKey:(NSData *)key; -#pragma clang diagnostic pop - -- (NSUInteger)unreadMessageCountWithTransaction:(YapDatabaseReadTransaction *)transaction - NS_SWIFT_NAME(unreadMessageCount(transaction:)); - -- (BOOL)hasSafetyNumbers; - -- (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -/** - * Returns the string that will be displayed typically in a conversations view as a preview of the last message - *received in this thread. - * - * @return Thread preview string. - */ -- (NSString *)lastMessageTextWithTransaction:(YapDatabaseReadTransaction *)transaction - NS_SWIFT_NAME(lastMessageText(transaction:)); - -- (nullable TSInteraction *)lastInteractionForInboxWithTransaction:(YapDatabaseReadTransaction *)transaction - NS_SWIFT_NAME(lastInteractionForInbox(transaction:)); - -/** - * Updates the thread's caches of the latest interaction. - * - * @param lastMessage Latest Interaction to take into consideration. - * @param transaction Database transaction. - */ -- (void)updateWithLastMessage:(TSInteraction *)lastMessage transaction:(YapDatabaseReadWriteTransaction *)transaction; - -#pragma mark Archival - -/** - * @return YES if no new messages have been sent or received since the thread was last archived. - */ -- (BOOL)isArchivedWithTransaction:(YapDatabaseReadTransaction *)transaction; - -/** - * Archives a thread - * - * @param transaction Database transaction. - */ -- (void)archiveThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -/** - * Unarchives a thread - * - * @param transaction Database transaction. - */ -- (void)unarchiveThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)removeAllThreadInteractionsWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (TSInteraction *)getLastInteractionWithTransaction:(YapDatabaseReadTransaction *)transaction; - -#pragma mark Disappearing Messages - -- (OWSDisappearingMessagesConfiguration *)disappearingMessagesConfigurationWithTransaction: - (YapDatabaseReadTransaction *)transaction; -- (uint32_t)disappearingMessagesDurationWithTransaction:(YapDatabaseReadTransaction *)transaction; - -#pragma mark Drafts - -/** - * Returns the last known draft for that thread. Always returns a string. Empty string if nil. - * - * @param transaction Database transaction. - * - * @return Last known draft for that thread. - */ -- (NSString *)currentDraftWithTransaction:(YapDatabaseReadTransaction *)transaction; - -/** - * Sets the draft of a thread. Typically called when leaving a conversation view. - * - * @param draftString Draft string to be saved. - * @param transaction Database transaction. - */ -- (void)setDraft:(NSString *)draftString transaction:(YapDatabaseReadWriteTransaction *)transaction; - -@property (atomic, readonly) BOOL isMuted; -@property (atomic, readonly, nullable) NSDate *mutedUntilDate; - -#pragma mark - Update With... Methods - -- (void)updateWithMutedUntilDate:(NSDate *)mutedUntilDate transaction:(YapDatabaseReadWriteTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/TSThread.m b/SignalServiceKit/src/Contacts/TSThread.m deleted file mode 100644 index eeb2a87f0..000000000 --- a/SignalServiceKit/src/Contacts/TSThread.m +++ /dev/null @@ -1,740 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSThread.h" -#import "NSString+SSK.h" -#import "OWSDisappearingMessagesConfiguration.h" -#import "OWSPrimaryStorage.h" -#import "OWSReadTracking.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSDatabaseView.h" -#import "TSIncomingMessage.h" -#import "TSInfoMessage.h" -#import "TSInteraction.h" -#import "TSInvalidIdentityKeyReceivingErrorMessage.h" -#import "TSOutgoingMessage.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -BOOL IsNoteToSelfEnabled(void) -{ - return YES; -} - -ConversationColorName const ConversationColorNameCrimson = @"red"; -ConversationColorName const ConversationColorNameVermilion = @"orange"; -ConversationColorName const ConversationColorNameBurlap = @"brown"; -ConversationColorName const ConversationColorNameForest = @"green"; -ConversationColorName const ConversationColorNameWintergreen = @"light_green"; -ConversationColorName const ConversationColorNameTeal = @"teal"; -ConversationColorName const ConversationColorNameBlue = @"blue"; -ConversationColorName const ConversationColorNameIndigo = @"indigo"; -ConversationColorName const ConversationColorNameViolet = @"purple"; -ConversationColorName const ConversationColorNamePlum = @"pink"; -ConversationColorName const ConversationColorNameTaupe = @"blue_grey"; -ConversationColorName const ConversationColorNameSteel = @"grey"; - -ConversationColorName const kConversationColorName_Default = ConversationColorNameSteel; - -@interface TSThread () - -@property (nonatomic) NSDate *creationDate; -@property (nonatomic) NSString *conversationColorName; -@property (nonatomic, nullable) NSNumber *archivedAsOfMessageSortId; -@property (nonatomic, copy, nullable) NSString *messageDraft; -@property (atomic, nullable) NSDate *mutedUntilDate; - -// DEPRECATED - not used since migrating to sortId -// but keeping these properties around to ease any pain in the back-forth -// migration while testing. Eventually we can safely delete these as they aren't used anywhere. -@property (nonatomic, nullable) NSDate *lastMessageDate DEPRECATED_ATTRIBUTE; -@property (nonatomic, nullable) NSDate *archivalDate DEPRECATED_ATTRIBUTE; - -@end - -#pragma mark - - -@implementation TSThread - -#pragma mark - Dependencies - -- (TSAccountManager *)tsAccountManager -{ - OWSAssertDebug(SSKEnvironment.shared.tsAccountManager); - - return SSKEnvironment.shared.tsAccountManager; -} - -#pragma mark - - -+ (NSString *)collection { - return @"TSThread"; -} - -- (instancetype)initWithUniqueId:(NSString *_Nullable)uniqueId -{ - self = [super initWithUniqueId:uniqueId]; - - if (self) { - _creationDate = [NSDate date]; - _messageDraft = nil; - - NSString *_Nullable contactId = self.contactIdentifier; - if (contactId.length > 0) { - // To be consistent with colors synced to desktop - _conversationColorName = [self.class stableColorNameForNewConversationWithString:contactId]; - } else { - _conversationColorName = [self.class stableColorNameForNewConversationWithString:self.uniqueId]; - } - } - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - // renamed `hasEverHadMessage` -> `shouldThreadBeVisible` - if (!_shouldThreadBeVisible) { - NSNumber *_Nullable legacy_hasEverHadMessage = [coder decodeObjectForKey:@"hasEverHadMessage"]; - - if (legacy_hasEverHadMessage != nil) { - _shouldThreadBeVisible = legacy_hasEverHadMessage.boolValue; - } - } - - if (_conversationColorName.length == 0) { - NSString *_Nullable colorSeed = self.contactIdentifier; - if (colorSeed.length > 0) { - // group threads - colorSeed = self.uniqueId; - } - - // To be consistent with colors synced to desktop - ConversationColorName colorName = [self.class stableColorNameForLegacyConversationWithString:colorSeed]; - OWSAssertDebug(colorName); - - _conversationColorName = colorName; - } else if (![[[self class] conversationColorNames] containsObject:_conversationColorName]) { - // If we'd persisted a non-mapped color name - ConversationColorName _Nullable mappedColorName = self.class.legacyConversationColorMap[_conversationColorName]; - - if (!mappedColorName) { - // We previously used the wrong values for the new colors, it's possible we persited them. - // map them to the proper value - mappedColorName = self.class.legacyFixupConversationColorMap[_conversationColorName]; - } - - if (!mappedColorName) { - OWSFailDebug(@"failure: unexpected unmappable conversationColorName: %@", _conversationColorName); - mappedColorName = kConversationColorName_Default; - } - - _conversationColorName = mappedColorName; - } - - NSDate *_Nullable lastMessageDate = [coder decodeObjectOfClass:NSDate.class forKey:@"lastMessageDate"]; - NSDate *_Nullable archivalDate = [coder decodeObjectOfClass:NSDate.class forKey:@"archivalDate"]; - _isArchivedByLegacyTimestampForSorting = - [self.class legacyIsArchivedWithLastMessageDate:lastMessageDate archivalDate:archivalDate]; - - return self; -} - -- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [super saveWithTransaction:transaction]; - - [SSKPreferences setHasSavedThreadWithValue:YES transaction:transaction]; -} - -- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [self removeAllThreadInteractionsWithTransaction:transaction]; - - [super removeWithTransaction:transaction]; -} - -- (void)removeAllThreadInteractionsWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - // We can't safely delete interactions while enumerating them, so - // we collect and delete separately. - // - // We don't want to instantiate the interactions when collecting them - // or when deleting them. - NSMutableArray *interactionIds = [NSMutableArray new]; - YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName]; - OWSAssertDebug(interactionsByThread); - __block BOOL didDetectCorruption = NO; - [interactionsByThread enumerateKeysInGroup:self.uniqueId - usingBlock:^(NSString *collection, NSString *key, NSUInteger index, BOOL *stop) { - if (![key isKindOfClass:[NSString class]] || key.length < 1) { - OWSFailDebug( - @"invalid key in thread interactions: %@, %@.", key, [key class]); - didDetectCorruption = YES; - return; - } - [interactionIds addObject:key]; - }]; - - if (didDetectCorruption) { - OWSLogWarn(@"incrementing version of: %@", TSMessageDatabaseViewExtensionName); - [OWSPrimaryStorage incrementVersionOfDatabaseExtension:TSMessageDatabaseViewExtensionName]; - } - - for (NSString *interactionId in interactionIds) { - // We need to fetch each interaction, since [TSInteraction removeWithTransaction:] does important work. - TSInteraction *_Nullable interaction = - [TSInteraction fetchObjectWithUniqueID:interactionId transaction:transaction]; - if (!interaction) { - OWSFailDebug(@"couldn't load thread's interaction for deletion."); - continue; - } - [interaction removeWithTransaction:transaction]; - } -} - -- (BOOL)isNoteToSelf -{ - if (!IsNoteToSelfEnabled()) { - return NO; - } - return [LKSessionMetaProtocol isThreadNoteToSelf:self]; -} - -#pragma mark - To be subclassed. - -- (BOOL)isGroupThread { - OWSAbstractMethod(); - - return NO; -} - -// Override in ContactThread -- (nullable NSString *)contactIdentifier -{ - return nil; -} - -- (NSString *)name { - OWSAbstractMethod(); - - return nil; -} - -- (NSArray *)recipientIdentifiers -{ - OWSAbstractMethod(); - - return @[]; -} - -- (BOOL)hasSafetyNumbers -{ - return NO; -} - -#pragma mark - Interactions - -/** - * Iterate over this thread's interactions - */ -- (void)enumerateInteractionsWithTransaction:(YapDatabaseReadTransaction *)transaction - usingBlock:(void (^)(TSInteraction *interaction, - YapDatabaseReadTransaction *transaction))block -{ - YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName]; - [interactionsByThread - enumerateKeysAndObjectsInGroup:self.uniqueId - usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { - TSInteraction *interaction = object; - block(interaction, transaction); - }]; -} - -/** - * Enumerates all the threads interactions. Note this will explode if you try to create a transaction in the block. - * If you need a transaction, use the sister method: `enumerateInteractionsWithTransaction:usingBlock` - */ -- (void)enumerateInteractionsUsingBlock:(void (^)(TSInteraction *interaction))block -{ - [self.dbReadWriteConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - [self enumerateInteractionsWithTransaction:transaction - usingBlock:^( - TSInteraction *interaction, YapDatabaseReadTransaction *transaction) { - - block(interaction); - }]; - }]; -} - -- (TSInteraction *)lastInteraction -{ - __block TSInteraction *interaction; - [self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - interaction = [self getLastInteractionWithTransaction:transaction]; - }]; - return interaction; -} - -- (TSInteraction *)getLastInteractionWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - YapDatabaseViewTransaction *interactions = [transaction ext:TSMessageDatabaseViewExtensionName]; - return [interactions lastObjectInGroup:self.uniqueId]; -} - -/** - * Useful for tests and debugging. In production use an enumeration method. - */ -- (NSArray *)allInteractions -{ - NSMutableArray *interactions = [NSMutableArray new]; - [self enumerateInteractionsUsingBlock:^(TSInteraction *interaction) { - [interactions addObject:interaction]; - }]; - - return [interactions copy]; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -- (NSArray *)receivedMessagesForInvalidKey:(NSData *)key -{ - NSMutableArray *errorMessages = [NSMutableArray new]; - [self enumerateInteractionsUsingBlock:^(TSInteraction *interaction) { - if ([interaction isKindOfClass:[TSInvalidIdentityKeyReceivingErrorMessage class]]) { - TSInvalidIdentityKeyReceivingErrorMessage *error = (TSInvalidIdentityKeyReceivingErrorMessage *)interaction; - @try { - if ([[error throws_newIdentityKey] isEqualToData:key]) { - [errorMessages addObject:(TSInvalidIdentityKeyReceivingErrorMessage *)interaction]; - } - } @catch (NSException *exception) { - OWSFailDebug(@"exception: %@", exception); - } - } - }]; - - return [errorMessages copy]; -} -#pragma clang diagnostic pop - -- (NSUInteger)numberOfInteractions -{ - __block NSUInteger count; - [[self dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *transaction) { - YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName]; - count = [interactionsByThread numberOfItemsInGroup:self.uniqueId]; - }]; - return count; -} - -- (NSArray> *)unseenMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - NSMutableArray> *messages = [NSMutableArray new]; - [[TSDatabaseView unseenDatabaseViewExtension:transaction] - enumerateKeysAndObjectsInGroup:self.uniqueId - usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { - if (![object conformsToProtocol:@protocol(OWSReadTracking)]) { - OWSFailDebug(@"Unexpected object in unseen messages: %@", [object class]); - return; - } - id unread = (id)object; - if (unread.read) { - [LKLogger print:@"Found an already read message in the * unseen * messages list."]; - return; - } - [messages addObject:unread]; - }]; - - return [messages copy]; -} - -- (NSUInteger)unreadMessageCountWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - __block NSUInteger count = 0; - - YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName]; - [unreadMessages enumerateKeysAndObjectsInGroup:self.uniqueId - usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { - if (![object conformsToProtocol:@protocol(OWSReadTracking)]) { - OWSFailDebug(@"Unexpected object in unread messages: %@", [object class]); - return; - } - id unread = (id)object; - if (unread.read) { - [LKLogger print:@"Found an already read message in the * unread * messages list."]; - return; - } - count += 1; - }]; - - return count; -} - -- (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - for (id message in [self unseenMessagesWithTransaction:transaction]) { - [message markAsReadAtTimestamp:[NSDate ows_millisecondTimeStamp] sendReadReceipt:YES transaction:transaction]; - } - - // Just to be defensive, we'll also check for unread messages. - OWSAssertDebug([self unseenMessagesWithTransaction:transaction].count < 1); -} - -- (nullable TSInteraction *)lastInteractionForInboxWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - __block NSUInteger missedCount = 0; - __block TSInteraction *last = nil; - [[transaction ext:TSMessageDatabaseViewExtensionName] - enumerateKeysAndObjectsInGroup:self.uniqueId - withOptions:NSEnumerationReverse - usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { - OWSAssertDebug([object isKindOfClass:[TSInteraction class]]); - - missedCount++; - TSInteraction *interaction = (TSInteraction *)object; - - if ([TSThread shouldInteractionAppearInInbox:interaction]) { - last = interaction; - - // For long ignored threads, with lots of SN changes this can get really slow. - // I see this in development because I have a lot of long forgotten threads with - // members who's test devices are constantly reinstalled. We could add a - // purpose-built DB view, but I think in the real world this is rare to be a - // hotspot. - if (missedCount > 50) { - OWSLogWarn(@"found last interaction for inbox after skipping %lu items", - (unsigned long)missedCount); - } - *stop = YES; - } - }]; - return last; -} - -- (NSString *)lastMessageTextWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - TSInteraction *interaction = [self lastInteractionForInboxWithTransaction:transaction]; - if ([interaction conformsToProtocol:@protocol(OWSPreviewText)]) { - id previewable = (id)interaction; - return [previewable previewTextWithTransaction:transaction].filterStringForDisplay; - } else { - return @""; - } -} - -// Returns YES IFF the interaction should show up in the inbox as the last message. -+ (BOOL)shouldInteractionAppearInInbox:(TSInteraction *)interaction -{ - OWSAssertDebug(interaction); - - if (interaction.isDynamicInteraction) { - return NO; - } - - if ([interaction isKindOfClass:[TSErrorMessage class]]) { - TSErrorMessage *errorMessage = (TSErrorMessage *)interaction; - if (errorMessage.errorType == TSErrorMessageNonBlockingIdentityChange) { - // Otherwise all group threads with the recipient will percolate to the top of the inbox, even though - // there was no meaningful interaction. - return NO; - } - } else if ([interaction isKindOfClass:[TSInfoMessage class]]) { - TSInfoMessage *infoMessage = (TSInfoMessage *)interaction; - if (infoMessage.messageType == TSInfoMessageVerificationStateChange) { - return NO; - } - } - - return YES; -} - -- (void)updateWithLastMessage:(TSInteraction *)lastMessage transaction:(YapDatabaseReadWriteTransaction *)transaction { - OWSAssertDebug(lastMessage); - OWSAssertDebug(transaction); - - if (![self.class shouldInteractionAppearInInbox:lastMessage]) { - return; - } - - if (!self.shouldThreadBeVisible) { - self.shouldThreadBeVisible = YES; - [self saveWithTransaction:transaction]; - } else { - [self touchWithTransaction:transaction]; - } -} - -#pragma mark - Disappearing Messages - -- (OWSDisappearingMessagesConfiguration *)disappearingMessagesConfigurationWithTransaction: - (YapDatabaseReadTransaction *)transaction -{ - return [OWSDisappearingMessagesConfiguration fetchOrBuildDefaultWithThreadId:self.uniqueId transaction:transaction]; -} - -- (uint32_t)disappearingMessagesDurationWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - - OWSDisappearingMessagesConfiguration *config = [self disappearingMessagesConfigurationWithTransaction:transaction]; - - if (!config.isEnabled) { - return 0; - } else { - return config.durationSeconds; - } -} - -#pragma mark - Archival - -- (BOOL)isArchivedWithTransaction:(YapDatabaseReadTransaction *)transaction; -{ - if (!self.archivedAsOfMessageSortId) { - return NO; - } - - TSInteraction *_Nullable latestInteraction = [self lastInteractionForInboxWithTransaction:transaction]; - uint64_t latestSortIdForInbox = latestInteraction ? latestInteraction.sortId : 0; - return self.archivedAsOfMessageSortId.unsignedLongLongValue >= latestSortIdForInbox; -} - -+ (BOOL)legacyIsArchivedWithLastMessageDate:(nullable NSDate *)lastMessageDate - archivalDate:(nullable NSDate *)archivalDate -{ - if (!archivalDate) { - return NO; - } - - if (!lastMessageDate) { - return YES; - } - - return [archivalDate compare:lastMessageDate] != NSOrderedAscending; -} - -- (void)archiveThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSThread *thread) { - uint64_t latestId = [SSKIncrementingIdFinder previousIdWithKey:TSInteraction.collection - transaction:transaction]; - thread.archivedAsOfMessageSortId = @(latestId); - }]; - - [self markAllAsReadWithTransaction:transaction]; -} - -- (void)unarchiveThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSThread *thread) { - thread.archivedAsOfMessageSortId = nil; - }]; -} - -#pragma mark - Drafts - -- (NSString *)currentDraftWithTransaction:(YapDatabaseReadTransaction *)transaction { - TSThread *thread = [TSThread fetchObjectWithUniqueID:self.uniqueId transaction:transaction]; - if (thread.messageDraft) { - return thread.messageDraft; - } else { - return @""; - } -} - -- (void)setDraft:(NSString *)draftString transaction:(YapDatabaseReadWriteTransaction *)transaction { - TSThread *thread = [TSThread fetchObjectWithUniqueID:self.uniqueId transaction:transaction]; - thread.messageDraft = draftString; - [thread saveWithTransaction:transaction]; -} - -#pragma mark - Muted - -- (BOOL)isMuted -{ - NSDate *mutedUntilDate = self.mutedUntilDate; - NSDate *now = [NSDate date]; - return (mutedUntilDate != nil && - [mutedUntilDate timeIntervalSinceDate:now] > 0); -} - -- (void)updateWithMutedUntilDate:(NSDate *)mutedUntilDate transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSThread *thread) { - [thread setMutedUntilDate:mutedUntilDate]; - }]; -} - -#pragma mark - Conversation Color - -- (ConversationColorName)conversationColorName -{ - OWSAssertDebug([self.class.conversationColorNames containsObject:_conversationColorName]); - return _conversationColorName; -} - -+ (NSArray *)colorNamesForNewConversation -{ - // all conversation colors except "steel" - return @[ - ConversationColorNameCrimson, - ConversationColorNameVermilion, - ConversationColorNameBurlap, - ConversationColorNameForest, - ConversationColorNameWintergreen, - ConversationColorNameTeal, - ConversationColorNameBlue, - ConversationColorNameIndigo, - ConversationColorNameViolet, - ConversationColorNamePlum, - ConversationColorNameTaupe, - ]; -} - -+ (NSArray *)conversationColorNames -{ - return [self.colorNamesForNewConversation arrayByAddingObject:kConversationColorName_Default]; -} - -+ (ConversationColorName)stableConversationColorNameForString:(NSString *)colorSeed - colorNames:(NSArray *)colorNames -{ - NSData *contactData = [colorSeed dataUsingEncoding:NSUTF8StringEncoding]; - - unsigned long long hash = 0; - NSUInteger hashingLength = sizeof(hash); - NSData *_Nullable hashData = [Cryptography computeSHA256Digest:contactData truncatedToBytes:hashingLength]; - if (hashData) { - [hashData getBytes:&hash length:hashingLength]; - } else { - OWSFailDebug(@"could not compute hash for color seed."); - } - - NSUInteger index = (hash % colorNames.count); - return [colorNames objectAtIndex:index]; -} - -+ (ConversationColorName)stableColorNameForNewConversationWithString:(NSString *)colorSeed -{ - return [self stableConversationColorNameForString:colorSeed colorNames:self.colorNamesForNewConversation]; -} - -// After introducing new conversation colors, we want to try to maintain as close as possible to the old color for an -// existing thread. -+ (ConversationColorName)stableColorNameForLegacyConversationWithString:(NSString *)colorSeed -{ - NSString *legacyColorName = - [self stableConversationColorNameForString:colorSeed colorNames:self.legacyConversationColorNames]; - ConversationColorName _Nullable mappedColorName = self.class.legacyConversationColorMap[legacyColorName]; - - if (!mappedColorName) { - OWSFailDebug(@"failure: unexpected unmappable legacyColorName: %@", legacyColorName); - return kConversationColorName_Default; - } - - return mappedColorName; -} - -+ (NSArray *)legacyConversationColorNames -{ - return @[ - @"red", - @"pink", - @"purple", - @"indigo", - @"blue", - @"cyan", - @"teal", - @"green", - @"deep_orange", - @"grey" - ]; -} - -+ (NSDictionary *)legacyConversationColorMap -{ - static NSDictionary *colorMap; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - colorMap = @{ - @"red" : ConversationColorNameCrimson, - @"deep_orange" : ConversationColorNameCrimson, - @"orange" : ConversationColorNameVermilion, - @"amber" : ConversationColorNameVermilion, - @"brown" : ConversationColorNameBurlap, - @"yellow" : ConversationColorNameBurlap, - @"pink" : ConversationColorNamePlum, - @"purple" : ConversationColorNameViolet, - @"deep_purple" : ConversationColorNameViolet, - @"indigo" : ConversationColorNameIndigo, - @"blue" : ConversationColorNameBlue, - @"light_blue" : ConversationColorNameBlue, - @"cyan" : ConversationColorNameTeal, - @"teal" : ConversationColorNameTeal, - @"green" : ConversationColorNameForest, - @"light_green" : ConversationColorNameWintergreen, - @"lime" : ConversationColorNameWintergreen, - @"blue_grey" : ConversationColorNameTaupe, - @"grey" : ConversationColorNameSteel, - }; - }); - - return colorMap; -} - -// we temporarily used the wrong value for the new color names. -+ (NSDictionary *)legacyFixupConversationColorMap -{ - static NSDictionary *colorMap; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - colorMap = @{ - @"crimson" : ConversationColorNameCrimson, - @"vermilion" : ConversationColorNameVermilion, - @"burlap" : ConversationColorNameBurlap, - @"forest" : ConversationColorNameForest, - @"wintergreen" : ConversationColorNameWintergreen, - @"teal" : ConversationColorNameTeal, - @"blue" : ConversationColorNameBlue, - @"indigo" : ConversationColorNameIndigo, - @"violet" : ConversationColorNameViolet, - @"plum" : ConversationColorNamePlum, - @"taupe" : ConversationColorNameTaupe, - @"steel" : ConversationColorNameSteel, - }; - }); - - return colorMap; -} - -- (void)updateConversationColorName:(ConversationColorName)colorName - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSThread *thread) { - thread.conversationColorName = colorName; - }]; -} - -- (BOOL)isSlaveThread -{ - return [LKMultiDeviceProtocol isSlaveThread:self]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/Threads/TSContactThread.h b/SignalServiceKit/src/Contacts/Threads/TSContactThread.h deleted file mode 100644 index d8893913a..000000000 --- a/SignalServiceKit/src/Contacts/Threads/TSContactThread.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSThread.h" - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const TSContactThreadPrefix; - -typedef NS_ENUM(NSInteger, LKSessionResetStatus); - -@interface TSContactThread : TSThread - -// Loki: The current session reset status for this thread -@property (atomic) LKSessionResetStatus sessionResetStatus; -@property (atomic, readonly) NSArray *sessionRestoreDevices; - -@property (nonatomic) BOOL hasDismissedOffers; - -- (instancetype)initWithContactId:(NSString *)contactId; - -+ (instancetype)getOrCreateThreadWithContactId:(NSString *)contactId NS_SWIFT_NAME(getOrCreateThread(contactId:)); - -+ (instancetype)getOrCreateThreadWithContactId:(NSString *)contactId - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -// Unlike getOrCreateThreadWithContactId, this will _NOT_ create a thread if one does not already exist. -+ (nullable instancetype)getThreadWithContactId:(NSString *)contactId transaction:(YapDatabaseReadTransaction *)transaction; - -- (NSString *)contactIdentifier; - -+ (NSString *)contactIdFromThreadId:(NSString *)threadId; - -+ (NSString *)threadIdFromContactId:(NSString *)contactId; - -// This method can be used to get the conversation color for a given -// recipient without using a read/write transaction to create a -// contact thread. -+ (NSString *)conversationColorNameForRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadTransaction *)transaction; - -#pragma mark - Loki Session Restore - -- (void)addSessionRestoreDevice:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction; -- (void)removeAllSessionRestoreDevicesWithTransaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/Threads/TSContactThread.m b/SignalServiceKit/src/Contacts/Threads/TSContactThread.m deleted file mode 100644 index 3361fe675..000000000 --- a/SignalServiceKit/src/Contacts/Threads/TSContactThread.m +++ /dev/null @@ -1,143 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSContactThread.h" -#import "ContactsManagerProtocol.h" -#import "ContactsUpdater.h" -#import "NotificationsProtocol.h" -#import "OWSIdentityManager.h" -#import "SSKEnvironment.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const TSContactThreadPrefix = @"c"; - -@implementation TSContactThread - -- (instancetype)initWithContactId:(NSString *)contactId { - NSString *uniqueIdentifier = [[self class] threadIdFromContactId:contactId]; - - OWSAssertDebug(contactId.length > 0); - - self = [super initWithUniqueId:uniqueIdentifier]; - - // No session reset ongoing - _sessionResetStatus = LKSessionResetStatusNone; - _sessionRestoreDevices = @[]; - - return self; -} - -+ (instancetype)getOrCreateThreadWithContactId:(NSString *)contactId - transaction:(YapDatabaseReadWriteTransaction *)transaction { - OWSAssertDebug(contactId.length > 0); - - TSContactThread *thread = - [self fetchObjectWithUniqueID:[self threadIdFromContactId:contactId] transaction:transaction]; - - if (!thread) { - thread = [[TSContactThread alloc] initWithContactId:contactId]; - [thread saveWithTransaction:transaction]; - } - - return thread; -} - -+ (instancetype)getOrCreateThreadWithContactId:(NSString *)contactId -{ - OWSAssertDebug(contactId.length > 0); - - __block TSContactThread *thread; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - thread = [self getOrCreateThreadWithContactId:contactId transaction:transaction]; - }]; - - return thread; -} - -+ (nullable instancetype)getThreadWithContactId:(NSString *)contactId transaction:(YapDatabaseReadTransaction *)transaction; -{ - return [TSContactThread fetchObjectWithUniqueID:[self threadIdFromContactId:contactId] transaction:transaction]; -} - -- (NSString *)contactIdentifier { - return [[self class] contactIdFromThreadId:self.uniqueId]; -} - -- (NSArray *)recipientIdentifiers -{ - return @[ self.contactIdentifier ]; -} - -- (BOOL)isGroupThread { - return false; -} - -- (BOOL)hasSafetyNumbers -{ - return !![[OWSIdentityManager sharedManager] identityKeyForRecipientId:self.contactIdentifier]; -} - -// TODO deprecate this? seems weird to access the displayName in the DB model -- (NSString *)name -{ - return [SSKEnvironment.shared.contactsManager displayNameForPhoneIdentifier:self.contactIdentifier]; -} - -+ (NSString *)threadIdFromContactId:(NSString *)contactId { - return [TSContactThreadPrefix stringByAppendingString:contactId]; -} - -+ (NSString *)contactIdFromThreadId:(NSString *)threadId { - return [threadId substringWithRange:NSMakeRange(1, threadId.length - 1)]; -} - -+ (NSString *)conversationColorNameForRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(recipientId.length > 0); - - TSContactThread *_Nullable contactThread = - [TSContactThread getThreadWithContactId:recipientId transaction:transaction]; - if (contactThread) { - return contactThread.conversationColorName; - } - return [self stableColorNameForNewConversationWithString:recipientId]; -} - -#pragma mark - Loki Session Restore - -- (void)addSessionRestoreDevice:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction -{ - NSMutableSet *set = [[NSMutableSet alloc] initWithArray:_sessionRestoreDevices]; - [set addObject:hexEncodedPublicKey]; - [self setSessionRestoreDevices:set.allObjects transaction:transaction]; -} - -- (void)removeAllSessionRestoreDevicesWithTransaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction -{ - [self setSessionRestoreDevices:@[] transaction:transaction]; -} - -- (void)setSessionRestoreDevices:(NSArray *)sessionRestoreDevices transaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction { - _sessionRestoreDevices = sessionRestoreDevices; - void (^postNotification)() = ^() { - [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.threadSessionRestoreDevicesChanged object:self.uniqueId]; - }; - if (transaction == nil) { - [self save]; - [self.dbReadWriteConnection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:^{ postNotification(); }]; - } else { - [self saveWithTransaction:transaction]; - [transaction.connection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:^{ postNotification(); }]; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/Threads/TSGroupThread.h b/SignalServiceKit/src/Contacts/Threads/TSGroupThread.h deleted file mode 100644 index b6378cda1..000000000 --- a/SignalServiceKit/src/Contacts/Threads/TSGroupThread.h +++ /dev/null @@ -1,67 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSGroupModel.h" -#import "TSThread.h" -#import "LKGroupUtilities.h" - -NS_ASSUME_NONNULL_BEGIN - -@class TSAttachmentStream; -@class YapDatabaseReadWriteTransaction; - -extern NSString *const TSGroupThreadAvatarChangedNotification; -extern NSString *const TSGroupThread_NotificationKey_UniqueId; - -@interface TSGroupThread : TSThread - -@property (nonatomic, strong) TSGroupModel *groupModel; -@property (nonatomic, readonly) BOOL isRSSFeed; -@property (nonatomic, readonly) BOOL isPublicChat; -@property (nonatomic) BOOL usesSharedSenderKeys; - -+ (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel; -+ (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -+ (instancetype)getOrCreateThreadWithGroupId:(NSData *)groupId - groupType:(GroupType) groupType; -+ (instancetype)getOrCreateThreadWithGroupId:(NSData *)groupId - groupType:(GroupType) groupType - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -+ (nullable instancetype)threadWithGroupId:(NSData *)groupId transaction:(YapDatabaseReadTransaction *)transaction; - -+ (NSString *)threadIdFromGroupId:(NSData *)groupId; - -+ (NSString *)defaultGroupName; - -- (BOOL)isLocalUserInGroup; -- (BOOL)isCurrentUserInGroupWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (BOOL)isUserMemberInGroup:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadTransaction *)transaction; -- (BOOL)isUserAdminInGroup:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadTransaction *)transaction; - -// all group threads containing recipient as a member -+ (NSArray *)groupThreadsWithRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)setGroupModel:(TSGroupModel *)newGroupModel withTransaction:(YapDatabaseReadWriteTransaction *)transaction; -- (void)leaveGroupWithSneakyTransaction; -- (void)leaveGroupWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)softDeleteGroupThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -#pragma mark - Avatar - -- (void)updateAvatarWithAttachmentStream:(TSAttachmentStream *)attachmentStream; -- (void)updateAvatarWithAttachmentStream:(TSAttachmentStream *)attachmentStream - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)fireAvatarChangedNotification; - -+ (ConversationColorName)defaultConversationColorNameForGroupId:(NSData *)groupId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/Threads/TSGroupThread.m b/SignalServiceKit/src/Contacts/Threads/TSGroupThread.m deleted file mode 100644 index 081f8cdb2..000000000 --- a/SignalServiceKit/src/Contacts/Threads/TSGroupThread.m +++ /dev/null @@ -1,339 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSGroupThread.h" -#import "TSAttachmentStream.h" -#import -#import -#import -#import -#import -#import "OWSPrimaryStorage.h" - -NS_ASSUME_NONNULL_BEGIN - -NSString *const TSGroupThreadAvatarChangedNotification = @"TSGroupThreadAvatarChangedNotification"; -NSString *const TSGroupThread_NotificationKey_UniqueId = @"TSGroupThread_NotificationKey_UniqueId"; - -@implementation TSGroupThread - -#define TSGroupThreadPrefix @"g" - -- (instancetype)initWithGroupModel:(TSGroupModel *)groupModel -{ - OWSAssertDebug(groupModel); - OWSAssertDebug(groupModel.groupId.length > 0); - OWSAssertDebug(groupModel.groupMemberIds.count > 0); - - for (NSString *recipientId in groupModel.groupMemberIds) { - OWSAssertDebug(recipientId.length > 0); - } - - NSString *uniqueIdentifier = [[self class] threadIdFromGroupId:groupModel.groupId]; - self = [super initWithUniqueId:uniqueIdentifier]; - - if (!self) { - return self; - } - - _groupModel = groupModel; - - return self; -} - -- (instancetype)initWithGroupId:(NSData *)groupId groupType:(GroupType)groupType -{ - OWSAssertDebug(groupId.length > 0); - - NSString *localNumber = [TSAccountManager localNumber]; - OWSAssertDebug(localNumber.length > 0); - - TSGroupModel *groupModel = [[TSGroupModel alloc] initWithTitle:nil - memberIds:@[ localNumber ] - image:nil - groupId:groupId - groupType:groupType - adminIds:@[ localNumber ]]; - - self = [self initWithGroupModel:groupModel]; - - if (!self) { - return self; - } - - return self; -} - -+ (nullable instancetype)threadWithGroupId:(NSData *)groupId transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(groupId.length > 0); - - return [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupId] transaction:transaction]; -} - -+ (instancetype)getOrCreateThreadWithGroupId:(NSData *)groupId - groupType:(GroupType)groupType - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(groupId.length > 0); - OWSAssertDebug(transaction); - - TSGroupThread *thread = [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupId] transaction:transaction]; - - if (!thread) { - thread = [[self alloc] initWithGroupId:groupId groupType:groupType]; - [thread saveWithTransaction:transaction]; - } - - return thread; -} - -+ (instancetype)getOrCreateThreadWithGroupId:(NSData *)groupId groupType:(GroupType)groupType -{ - OWSAssertDebug(groupId.length > 0); - - __block TSGroupThread *thread; - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - thread = [self getOrCreateThreadWithGroupId:groupId groupType:groupType transaction:transaction]; - }]; - - return thread; -} - -+ (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel - transaction:(YapDatabaseReadWriteTransaction *)transaction { - OWSAssertDebug(groupModel); - OWSAssertDebug(groupModel.groupId.length > 0); - OWSAssertDebug(transaction); - - TSGroupThread *thread = - [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupModel.groupId] transaction:transaction]; - - if (!thread) { - thread = [[TSGroupThread alloc] initWithGroupModel:groupModel]; - [thread saveWithTransaction:transaction]; - } - - return thread; -} - -+ (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel -{ - OWSAssertDebug(groupModel); - OWSAssertDebug(groupModel.groupId.length > 0); - - __block TSGroupThread *thread; - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - thread = [self getOrCreateThreadWithGroupModel:groupModel transaction:transaction]; - }]; - - return thread; -} - -+ (NSString *)threadIdFromGroupId:(NSData *)groupId -{ - OWSAssertDebug(groupId.length > 0); - - return [TSGroupThreadPrefix stringByAppendingString:[[LKGroupUtilities getDecodedGroupIDAsData:groupId] base64EncodedString]]; -} - -+ (NSData *)groupIdFromThreadId:(NSString *)threadId -{ - OWSAssertDebug(threadId.length > 0); - - return [NSData dataFromBase64String:[threadId substringWithRange:NSMakeRange(1, threadId.length - 1)]]; -} - -- (NSArray *)recipientIdentifiers -{ - NSMutableArray *groupMemberIds = [self.groupModel.groupMemberIds mutableCopy]; - - if (groupMemberIds == nil) { - return @[]; - } - - [groupMemberIds removeObject:TSAccountManager.localNumber]; - - return [groupMemberIds copy]; -} - -// @returns all threads to which the recipient is a member. -// -// @note If this becomes a hotspot we can extract into a YapDB View. -// As is, the number of groups should be small (dozens, *maybe* hundreds), and we only enumerate them upon SN changes. -+ (NSArray *)groupThreadsWithRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - NSMutableArray *groupThreads = [NSMutableArray new]; - - [self enumerateCollectionObjectsWithTransaction:transaction usingBlock:^(id obj, BOOL *stop) { - if ([obj isKindOfClass:[TSGroupThread class]]) { - TSGroupThread *groupThread = (TSGroupThread *)obj; - if ([groupThread.groupModel.groupMemberIds containsObject:recipientId]) { - [groupThreads addObject:groupThread]; - } - } - }]; - - return [groupThreads copy]; -} - -- (BOOL)isGroupThread -{ - return true; -} - -- (BOOL)isPublicChat -{ - return (self.groupModel.groupType == openGroup); -} - -- (BOOL)isRSSFeed -{ - return (self.groupModel.groupType == rssFeed); -} - -- (BOOL)isContactFriend -{ - return false; -} - -- (BOOL)isLocalUserInGroup -{ - __block BOOL result = NO; - - [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - result = [self isCurrentUserInGroupWithTransaction:transaction]; - }]; - - return result; -} - -- (BOOL)isCurrentUserInGroupWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - NSString *userHexEncodedPublicKey = TSAccountManager.localNumber; - return [self isUserMemberInGroup:userHexEncodedPublicKey transaction:transaction]; -} - -- (BOOL)isUserMemberInGroup:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadTransaction *)transaction -{ - if (hexEncodedPublicKey == nil) { return NO; } - NSSet *linkedDeviceHexEncodedPublicKeys = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:hexEncodedPublicKey in:transaction]; - return [linkedDeviceHexEncodedPublicKeys intersectsSet:[NSSet setWithArray:self.groupModel.groupMemberIds]]; -} - -- (BOOL)isUserAdminInGroup:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadTransaction *)transaction -{ - if (hexEncodedPublicKey == nil) { return NO; } - NSSet *linkedDeviceHexEncodedPublicKeys = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:hexEncodedPublicKey in:transaction]; - return [linkedDeviceHexEncodedPublicKeys intersectsSet:[NSSet setWithArray:self.groupModel.groupAdminIds]]; -} - -- (NSString *)name -{ - // TODO sometimes groupName is set to the empty string. I'm hesitent to change - // the semantics here until we have time to thouroughly test the fallout. - // Instead, see the `groupNameOrDefault` which is appropriate for use when displaying - // text corresponding to a group. - return self.groupModel.groupName ?: self.class.defaultGroupName; -} - -+ (NSString *)defaultGroupName -{ - return NSLocalizedString(@"NEW_GROUP_DEFAULT_TITLE", @""); -} - -- (void)setGroupModel:(TSGroupModel *)newGroupModel withTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - self.groupModel = newGroupModel; - - [self saveWithTransaction:transaction]; - - [transaction addCompletionQueue:dispatch_get_main_queue() completionBlock:^{ - [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.groupThreadUpdated object:self.uniqueId]; - }]; -} - -- (void)leaveGroupWithSneakyTransaction -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [self leaveGroupWithTransaction:transaction]; - }]; -} - -- (void)leaveGroupWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - NSMutableSet *newGroupMemberIDs = [NSMutableSet setWithArray:self.groupModel.groupMemberIds]; - NSString *userPublicKey = TSAccountManager.localNumber; - if (userPublicKey == nil) { return; } - NSSet *userLinkedDevices = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:userPublicKey in:transaction]; - [newGroupMemberIDs minusSet:userLinkedDevices]; - self.groupModel.groupMemberIds = newGroupMemberIDs.allObjects; - [self saveWithTransaction:transaction]; - [transaction addCompletionQueue:dispatch_get_main_queue() completionBlock:^{ - [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.groupThreadUpdated object:self.uniqueId]; - }]; -} - -- (void)softDeleteGroupThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [self removeAllThreadInteractionsWithTransaction:transaction]; - self.shouldThreadBeVisible = NO; - [self saveWithTransaction:transaction]; -} - -#pragma mark - Avatar - -- (void)updateAvatarWithAttachmentStream:(TSAttachmentStream *)attachmentStream -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self updateAvatarWithAttachmentStream:attachmentStream transaction:transaction]; - }]; -} - -- (void)updateAvatarWithAttachmentStream:(TSAttachmentStream *)attachmentStream - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(attachmentStream); - OWSAssertDebug(transaction); - - self.groupModel.groupImage = [attachmentStream thumbnailImageSmallSync]; - [self saveWithTransaction:transaction]; - - [transaction addCompletionQueue:nil - completionBlock:^{ - [self fireAvatarChangedNotification]; - }]; - - // Avatars are stored directly in the database, so there's no need - // to keep the attachment around after assigning the image. - [attachmentStream removeWithTransaction:transaction]; -} - -- (void)fireAvatarChangedNotification -{ - OWSAssertIsOnMainThread(); - - NSDictionary *userInfo = @{ TSGroupThread_NotificationKey_UniqueId : self.uniqueId }; - - [[NSNotificationCenter defaultCenter] postNotificationName:TSGroupThreadAvatarChangedNotification - object:self.uniqueId - userInfo:userInfo]; -} - -+ (ConversationColorName)defaultConversationColorNameForGroupId:(NSData *)groupId -{ - OWSAssertDebug(groupId.length > 0); - - return [self.class stableColorNameForNewConversationWithString:[self threadIdFromGroupId:groupId]]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSBlockedPhoneNumbersMessage.h b/SignalServiceKit/src/Devices/OWSBlockedPhoneNumbersMessage.h deleted file mode 100644 index aefd4f831..000000000 --- a/SignalServiceKit/src/Devices/OWSBlockedPhoneNumbersMessage.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingSyncMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSBlockedPhoneNumbersMessage : OWSOutgoingSyncMessage - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithPhoneNumbers:(NSArray *)phoneNumbers - groupIds:(NSArray *)groupIds NS_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSBlockedPhoneNumbersMessage.m b/SignalServiceKit/src/Devices/OWSBlockedPhoneNumbersMessage.m deleted file mode 100644 index 84bef2d1e..000000000 --- a/SignalServiceKit/src/Devices/OWSBlockedPhoneNumbersMessage.m +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSBlockedPhoneNumbersMessage.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSBlockedPhoneNumbersMessage () - -@property (nonatomic, readonly) NSArray *phoneNumbers; -@property (nonatomic, readonly) NSArray *groupIds; - -@end - -@implementation OWSBlockedPhoneNumbersMessage - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (instancetype)initWithPhoneNumbers:(NSArray *)phoneNumbers groupIds:(NSArray *)groupIds -{ - self = [super init]; - if (!self) { - return self; - } - - _phoneNumbers = [phoneNumbers copy]; - _groupIds = [groupIds copy]; - - return self; -} - -- (nullable SSKProtoSyncMessageBuilder *)syncMessageBuilder -{ - SSKProtoSyncMessageBlockedBuilder *blockedBuilder = [SSKProtoSyncMessageBlocked builder]; - [blockedBuilder setNumbers:_phoneNumbers]; - [blockedBuilder setGroupIds:_groupIds]; - - NSError *error; - SSKProtoSyncMessageBlocked *_Nullable blockedProto = [blockedBuilder buildAndReturnError:&error]; - if (error || !blockedProto) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - - SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; - [syncMessageBuilder setBlocked:blockedProto]; - return syncMessageBuilder; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSChunkedOutputStream.h b/SignalServiceKit/src/Devices/OWSChunkedOutputStream.h deleted file mode 100644 index c02a79864..000000000 --- a/SignalServiceKit/src/Devices/OWSChunkedOutputStream.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSChunkedOutputStream : NSObject - -// Indicates whether any write failed. -@property (nonatomic, readonly) BOOL hasError; - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithOutputStream:(NSOutputStream *)outputStream; - -// Returns NO on error. -- (BOOL)writeData:(NSData *)data; -- (BOOL)writeUInt32:(UInt32)value; -- (BOOL)writeVariableLengthUInt32:(UInt32)value; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSChunkedOutputStream.m b/SignalServiceKit/src/Devices/OWSChunkedOutputStream.m deleted file mode 100644 index c9c335654..000000000 --- a/SignalServiceKit/src/Devices/OWSChunkedOutputStream.m +++ /dev/null @@ -1,96 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSChunkedOutputStream.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSChunkedOutputStream () - -@property (nonatomic, readonly) NSOutputStream *outputStream; -@property (nonatomic) BOOL hasError; - -@end - -#pragma mark - - -@implementation OWSChunkedOutputStream - -- (instancetype)initWithOutputStream:(NSOutputStream *)outputStream -{ - if (self = [super init]) { - OWSAssertDebug(outputStream); - _outputStream = outputStream; - } - - return self; -} - -- (BOOL)writeByte:(uint8_t)value -{ - NSInteger written = [self.outputStream write:&value maxLength:sizeof(value)]; - if (written != sizeof(value)) { - OWSFailDebug(@"could not write to output stream."); - self.hasError = YES; - return NO; - } - return YES; -} - -- (BOOL)writeData:(NSData *)data -{ - OWSAssertDebug(data); - - if (data.length < 1) { - return YES; - } - - while (YES) { - NSInteger written = [self.outputStream write:data.bytes maxLength:data.length]; - if (written < 1) { - OWSFailDebug(@"could not write to output stream."); - self.hasError = YES; - return NO; - } - if (written < data.length) { - data = [data subdataWithRange:NSMakeRange(written, data.length - written)]; - } else { - return YES; - } - } - return YES; -} - -- (BOOL)writeUInt32:(UInt32)value { - NSData *data = [[NSData alloc] initWithBytes:&value length:sizeof(value)]; - // Both Android and desktop seem to like this better - const char *bytes = data.bytes; - char *reversedBytes = malloc(sizeof(char) * data.length); - int i = data.length - 1; - for (int j = 0; j < data.length; j++) { - reversedBytes[i] = bytes[j]; - i = i - 1; - } - NSData *reversedData = [NSData dataWithBytes:reversedBytes length:data.length]; - return [self writeData:reversedData]; -} - -- (BOOL)writeVariableLengthUInt32:(UInt32)value -{ - while (YES) { - if (value <= 0x7F) { - return [self writeByte:value]; - } else { - if (![self writeByte:((value & 0x7F) | 0x80)]) { - return NO; - } - value >>= 7; - } - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSContactsOutputStream.h b/SignalServiceKit/src/Devices/OWSContactsOutputStream.h deleted file mode 100644 index 46fe918fe..000000000 --- a/SignalServiceKit/src/Devices/OWSContactsOutputStream.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSChunkedOutputStream.h" - -NS_ASSUME_NONNULL_BEGIN - -@class OWSDisappearingMessagesConfiguration; -@class OWSRecipientIdentity; -@class SignalAccount; - -@protocol ContactsManagerProtocol; - -@interface OWSContactsOutputStream : OWSChunkedOutputStream - -- (void)writeSignalAccount:(SignalAccount *)signalAccount - recipientIdentity:(nullable OWSRecipientIdentity *)recipientIdentity - profileKeyData:(nullable NSData *)profileKeyData - contactsManager:(id)contactsManager - conversationColorName:(NSString *)conversationColorName -disappearingMessagesConfiguration:(nullable OWSDisappearingMessagesConfiguration *)disappearingMessagesConfiguration; - - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSContactsOutputStream.m b/SignalServiceKit/src/Devices/OWSContactsOutputStream.m deleted file mode 100644 index 4e6403675..000000000 --- a/SignalServiceKit/src/Devices/OWSContactsOutputStream.m +++ /dev/null @@ -1,109 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSContactsOutputStream.h" -#import "Contact.h" -#import "ContactsManagerProtocol.h" -#import "MIMETypeUtil.h" -#import "NSData+keyVersionByte.h" -#import "OWSBlockingManager.h" -#import "OWSDisappearingMessagesConfiguration.h" -#import "OWSRecipientIdentity.h" -#import "SignalAccount.h" -#import "TSContactThread.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSContactsOutputStream - -- (void)writeSignalAccount:(SignalAccount *)signalAccount - recipientIdentity:(nullable OWSRecipientIdentity *)recipientIdentity - profileKeyData:(nullable NSData *)profileKeyData - contactsManager:(id)contactsManager - conversationColorName:(NSString *)conversationColorName -disappearingMessagesConfiguration:(nullable OWSDisappearingMessagesConfiguration *)disappearingMessagesConfiguration -{ - OWSAssertDebug(signalAccount); - OWSAssertDebug(contactsManager); - - SSKProtoContactDetailsBuilder *contactBuilder = - [SSKProtoContactDetails builderWithNumber:signalAccount.recipientId]; - [contactBuilder setName:[LKUserDisplayNameUtilities getPrivateChatDisplayNameFor:signalAccount.recipientId] ?: signalAccount.recipientId]; - [contactBuilder setColor:conversationColorName]; - - if (recipientIdentity != nil) { - SSKProtoVerified *_Nullable verified = BuildVerifiedProtoWithRecipientId(recipientIdentity.recipientId, - [recipientIdentity.identityKey prependKeyType], - recipientIdentity.verificationState, - 0); - if (!verified) { - OWSLogError(@"could not build protobuf."); - return; - } - contactBuilder.verified = verified; - } - - /* - UIImage *_Nullable rawAvatar = [contactsManager avatarImageForCNContactId:signalAccount.contact.cnContactId]; - NSData *_Nullable avatarPng; - if (rawAvatar) { - avatarPng = UIImagePNGRepresentation(rawAvatar); - if (avatarPng) { - SSKProtoContactDetailsAvatarBuilder *avatarBuilder = [SSKProtoContactDetailsAvatar builder]; - [avatarBuilder setContentType:OWSMimeTypeImagePng]; - [avatarBuilder setLength:(uint32_t)avatarPng.length]; - - NSError *error; - SSKProtoContactDetailsAvatar *_Nullable avatar = [avatarBuilder buildAndReturnError:&error]; - if (error || !avatar) { - OWSLogError(@"could not build protobuf: %@", error); - return; - } - [contactBuilder setAvatar:avatar]; - } - } - */ - - if (profileKeyData) { - OWSAssertDebug(profileKeyData.length == kAES256_KeyByteLength); - [contactBuilder setProfileKey:profileKeyData]; - } - - // Always ensure the "expire timer" property is set so that desktop - // can easily distinguish between a modern client declaring "off" vs a - // legacy client "not specifying". - [contactBuilder setExpireTimer:0]; - - if (disappearingMessagesConfiguration && disappearingMessagesConfiguration.isEnabled) { - [contactBuilder setExpireTimer:disappearingMessagesConfiguration.durationSeconds]; - } - - if ([OWSBlockingManager.sharedManager isRecipientIdBlocked:signalAccount.recipientId]) { - [contactBuilder setBlocked:YES]; - } - - NSError *error; - NSData *_Nullable contactData = [contactBuilder buildSerializedDataAndReturnError:&error]; - if (error || !contactData) { - OWSFailDebug(@"could not serialize protobuf: %@", error); - return; - } - - uint32_t contactDataLength = (uint32_t)contactData.length; - [self writeUInt32:contactDataLength]; - [self writeData:contactData]; - - /* - if (avatarPng) { - [self writeData:avatarPng]; - } - */ -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSDevice.h b/SignalServiceKit/src/Devices/OWSDevice.h deleted file mode 100644 index 9804c81ce..000000000 --- a/SignalServiceKit/src/Devices/OWSDevice.h +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSYapDatabaseObject.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -extern uint32_t const OWSDevicePrimaryDeviceId; - -@interface OWSDeviceManager : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -+ (instancetype)sharedManager; - -- (BOOL)mayHaveLinkedDevices:(YapDatabaseConnection *)dbConnection; -- (void)setMayHaveLinkedDevices; -- (void)clearMayHaveLinkedDevices; - -- (BOOL)hasReceivedSyncMessageInLastSeconds:(NSTimeInterval)intervalSeconds; -- (void)setHasReceivedSyncMessage; - -@end - -#pragma mark - - -@interface OWSDevice : TSYapDatabaseObject - -@property (nonatomic, readonly) NSInteger deviceId; -@property (nonatomic, readonly, nullable) NSString *name; -@property (nonatomic, readonly) NSDate *createdAt; -@property (nonatomic, readonly) NSDate *lastSeenAt; - -+ (nullable instancetype)deviceFromJSONDictionary:(NSDictionary *)deviceAttributes error:(NSError **)error; - -+ (NSArray *)currentDevicesWithTransaction:(YapDatabaseReadTransaction *)transaction; - -/** - * Set local database of devices to `devices`. - * - * This will create missing devices, update existing devices, and delete stale devices. - * @param devices Removes any existing devices, replacing them with `devices` - * - * Returns YES if any devices were added or removed. - */ -+ (BOOL)replaceAll:(NSArray *)devices; - -/** - * The id of the device currently running this application - */ -+ (uint32_t)currentDeviceId; - -/** - * - * @param transaction yapTransaction - * @return - * If the user has any linked devices (apart from the device this app is running on). - */ -+ (BOOL)hasSecondaryDevicesWithTransaction:(YapDatabaseReadTransaction *)transaction; - -- (NSString *)displayName; -- (BOOL)isPrimaryDevice; - -/** - * Assign attributes to this device from another. - * - * @param other - * OWSDevice whose attributes to copy to this device - * @return - * YES if any values on self changed, else NO - */ -- (BOOL)updateAttributesWithDevice:(OWSDevice *)other; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSDevice.m b/SignalServiceKit/src/Devices/OWSDevice.m deleted file mode 100644 index f38049231..000000000 --- a/SignalServiceKit/src/Devices/OWSDevice.m +++ /dev/null @@ -1,353 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDevice.h" -#import "OWSError.h" -#import "OWSPrimaryStorage.h" -#import "ProfileManagerProtocol.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "YapDatabaseConnection+OWS.h" -#import "YapDatabaseConnection.h" -#import "YapDatabaseTransaction.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -uint32_t const OWSDevicePrimaryDeviceId = 1; -NSString *const kOWSPrimaryStorage_OWSDeviceCollection = @"kTSStorageManager_OWSDeviceCollection"; -NSString *const kOWSPrimaryStorage_MayHaveLinkedDevices = @"kTSStorageManager_MayHaveLinkedDevices"; - -@interface OWSDeviceManager () - -@property (atomic) NSDate *lastReceivedSyncMessage; - -@end - -#pragma mark - - -@implementation OWSDeviceManager - -+ (instancetype)sharedManager -{ - static OWSDeviceManager *instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[self alloc] initDefault]; - }); - return instance; -} - -- (instancetype)initDefault -{ - return [super init]; -} - -- (BOOL)mayHaveLinkedDevices:(YapDatabaseConnection *)dbConnection -{ - OWSAssertDebug(dbConnection); - - return [dbConnection boolForKey:kOWSPrimaryStorage_MayHaveLinkedDevices - inCollection:kOWSPrimaryStorage_OWSDeviceCollection - defaultValue:YES]; -} - -// In order to avoid skipping necessary sync messages, the default value -// for mayHaveLinkedDevices is YES. Once we've successfully sent a -// sync message with no device messages (e.g. the service has confirmed -// that we have no linked devices), we can set mayHaveLinkedDevices to NO -// to avoid unnecessary message sends for sync messages until we learn -// of a linked device (e.g. through the device linking UI or by receiving -// a sync message, etc.). -- (void)clearMayHaveLinkedDevices -{ - // Note that we write async to avoid opening transactions within transactions. - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [transaction setObject:@(NO) - forKey:kOWSPrimaryStorage_MayHaveLinkedDevices - inCollection:kOWSPrimaryStorage_OWSDeviceCollection]; - }]; -} - -- (void)setMayHaveLinkedDevices -{ - // Note that we write async to avoid opening transactions within transactions. - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [transaction setObject:@(YES) - forKey:kOWSPrimaryStorage_MayHaveLinkedDevices - inCollection:kOWSPrimaryStorage_OWSDeviceCollection]; - }]; -} - -- (BOOL)hasReceivedSyncMessageInLastSeconds:(NSTimeInterval)intervalSeconds -{ - return (self.lastReceivedSyncMessage && fabs(self.lastReceivedSyncMessage.timeIntervalSinceNow) < intervalSeconds); -} - -- (void)setHasReceivedSyncMessage -{ - self.lastReceivedSyncMessage = [NSDate new]; - - [self setMayHaveLinkedDevices]; -} - -@end - -#pragma mark - - -@interface OWSDevice () - -@property (nonatomic) NSInteger deviceId; -@property (nonatomic, nullable) NSString *name; -@property (nonatomic) NSDate *createdAt; -@property (nonatomic) NSDate *lastSeenAt; - -@end - -#pragma mark - - -@implementation OWSDevice - -#pragma mark - Dependencies - -+ (id)profileManager -{ - return SSKEnvironment.shared.profileManager; -} - -+ (id)udManager -{ - return SSKEnvironment.shared.udManager; -} - -+ (TSAccountManager *)tsAccountManager -{ - return TSAccountManager.sharedInstance; -} - -- (OWSIdentityManager *)identityManager -{ - OWSAssertDebug(SSKEnvironment.shared.identityManager); - - return SSKEnvironment.shared.identityManager; -} - -#pragma mark - - -- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [super saveWithTransaction:transaction]; -} - -+ (nullable instancetype)deviceFromJSONDictionary:(NSDictionary *)deviceAttributes error:(NSError **)error -{ - OWSDevice *device = [MTLJSONAdapter modelOfClass:[self class] fromJSONDictionary:deviceAttributes error:error]; - if (device.deviceId < OWSDevicePrimaryDeviceId) { - OWSFailDebug(@"Invalid device id: %lu", (unsigned long)device.deviceId); - *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecodeJson, @"Invalid device id."); - return nil; - } - return device; -} - -+ (NSDictionary *)JSONKeyPathsByPropertyKey -{ - return @{ - @"createdAt": @"created", - @"lastSeenAt": @"lastSeen", - @"deviceId": @"id", - @"name": @"name" - }; -} - -+ (MTLValueTransformer *)createdAtJSONTransformer -{ - return self.millisecondTimestampToDateTransformer; -} - -+ (MTLValueTransformer *)lastSeenAtJSONTransformer -{ - return self.millisecondTimestampToDateTransformer; -} - -+ (NSArray *)currentDevicesWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - NSMutableArray *result = [NSMutableArray new]; - [transaction enumerateKeysAndObjectsInCollection:OWSDevice.collection - usingBlock:^(NSString *key, OWSDevice *object, BOOL *stop) { - if (![object isKindOfClass:[OWSDevice class]]) { - OWSFailDebug(@"Unexpected object in collection: %@", object.class); - return; - } - [result addObject:object]; - }]; - return result; -} - -+ (BOOL)replaceAll:(NSArray *)currentDevices -{ - BOOL didAddOrRemove = NO; - NSMutableArray *existingDevices = [[self allObjectsInCollection] mutableCopy]; - for (OWSDevice *currentDevice in currentDevices) { - NSUInteger existingDeviceIndex = [existingDevices indexOfObject:currentDevice]; - if (existingDeviceIndex == NSNotFound) { - // New Device - OWSLogInfo(@"Adding device: %@", currentDevice); - [currentDevice save]; - didAddOrRemove = YES; - } else { - OWSDevice *existingDevice = existingDevices[existingDeviceIndex]; - if ([existingDevice updateAttributesWithDevice:currentDevice]) { - [existingDevice save]; - } - [existingDevices removeObjectAtIndex:existingDeviceIndex]; - } - } - - // Since we removed existing devices as we went, only stale devices remain - for (OWSDevice *staleDevice in existingDevices) { - OWSLogVerbose(@"Removing device: %@", staleDevice); - [staleDevice remove]; - didAddOrRemove = YES; - } - - if (didAddOrRemove) { - dispatch_async(dispatch_get_main_queue(), ^{ - // Device changes can affect the UD access mode for a recipient, - // so we need to fetch the profile for this user to update UD access mode. - [self.profileManager fetchLocalUsersProfile]; - }); - return YES; - } else { - return NO; - } -} - -+ (MTLValueTransformer *)millisecondTimestampToDateTransformer -{ - static MTLValueTransformer *instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [MTLValueTransformer transformerUsingForwardBlock:^id(id value, BOOL *success, NSError **error) { - if ([value isKindOfClass:[NSNumber class]]) { - NSNumber *number = (NSNumber *)value; - NSDate *result = [NSDate ows_dateWithMillisecondsSince1970:[number longLongValue]]; - if (result) { - *success = YES; - return result; - } - } - *success = NO; - OWSLogError(@"unable to decode date from %@", value); - *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecodeJson, @"Unable to decode date from JSON."); - return nil; - } - reverseBlock:^id(id value, BOOL *success, NSError **error) { - if ([value isKindOfClass:[NSDate class]]) { - NSDate *date = (NSDate *)value; - NSNumber *result = [NSNumber numberWithLongLong:[NSDate ows_millisecondsSince1970ForDate:date]]; - if (result) { - *success = YES; - return result; - } - } - OWSLogError(@"unable to encode date from %@", value); - *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToEncodeJson, @"Unable to encode date to JSON."); - *success = NO; - return nil; - }]; - }); - return instance; -} - -+ (uint32_t)currentDeviceId -{ - // Someday it may be possible to have a non-primary iOS device, but for now - // any iOS device must be the primary device. - return OWSDevicePrimaryDeviceId; -} - -- (BOOL)isPrimaryDevice -{ - return self.deviceId == OWSDevicePrimaryDeviceId; -} - -- (NSString *)displayName -{ - if (self.name) { - ECKeyPair *_Nullable identityKeyPair = self.identityManager.identityKeyPair; - OWSAssertDebug(identityKeyPair); - if (identityKeyPair) { - NSError *error; - NSString *_Nullable decryptedName = - [DeviceNames decryptDeviceNameWithBase64String:self.name identityKeyPair:identityKeyPair error:&error]; - if (error) { - // Not necessarily an error; might be a legacy device name. - OWSLogError(@"Could not decrypt device name: %@", error); - } else if (decryptedName) { - return decryptedName; - } - } - - return self.name; - } - - if (self.deviceId == OWSDevicePrimaryDeviceId) { - return @"This Device"; - } - return NSLocalizedString(@"UNNAMED_DEVICE", @"Label text in device manager for a device with no name"); -} - -- (BOOL)updateAttributesWithDevice:(OWSDevice *)other -{ - BOOL changed = NO; - if (![self.lastSeenAt isEqual:other.lastSeenAt]) { - self.lastSeenAt = other.lastSeenAt; - changed = YES; - } - - if (![self.createdAt isEqual:other.createdAt]) { - self.createdAt = other.createdAt; - changed = YES; - } - - if (![self.name isEqual:other.name]) { - self.name = other.name; - changed = YES; - } - - return changed; -} - -+ (BOOL)hasSecondaryDevicesWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - return [self numberOfKeysInCollectionWithTransaction:transaction] > 1; -} - -- (BOOL)isEqual:(id)object -{ - if (self == object) { - return YES; - } - - if (![object isKindOfClass:[OWSDevice class]]) { - return NO; - } - - return [self isEqualToDevice:(OWSDevice *)object]; -} - -- (BOOL)isEqualToDevice:(OWSDevice *)device -{ - return self.deviceId == device.deviceId; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSDeviceProvisioner.h b/SignalServiceKit/src/Devices/OWSDeviceProvisioner.h deleted file mode 100644 index 04d3beae0..000000000 --- a/SignalServiceKit/src/Devices/OWSDeviceProvisioner.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSDeviceProvisioningCodeService; -@class OWSDeviceProvisioningService; - -@interface OWSDeviceProvisioner : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithMyPublicKey:(NSData *)myPublicKey - myPrivateKey:(NSData *)myPrivateKey - theirPublicKey:(NSData *)theirPublicKey - theirEphemeralDeviceId:(NSString *)ephemeralDeviceId - accountIdentifier:(NSString *)accountIdentifier - profileKey:(NSData *)profileKey - readReceiptsEnabled:(BOOL)areReadReceiptsEnabled - provisioningCodeService:(OWSDeviceProvisioningCodeService *)provisioningCodeService - provisioningService:(OWSDeviceProvisioningService *)provisioningService NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithMyPublicKey:(NSData *)myPublicKey - myPrivateKey:(NSData *)myPrivateKey - theirPublicKey:(NSData *)theirPublicKey - theirEphemeralDeviceId:(NSString *)ephemeralDeviceId - accountIdentifier:(NSString *)accountIdentifier - profileKey:(NSData *)profileKey - readReceiptsEnabled:(BOOL)areReadReceiptsEnabled; - -- (void)provisionWithSuccess:(void (^)(void))successCallback failure:(void (^)(NSError *))failureCallback; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSDeviceProvisioner.m b/SignalServiceKit/src/Devices/OWSDeviceProvisioner.m deleted file mode 100644 index 378150985..000000000 --- a/SignalServiceKit/src/Devices/OWSDeviceProvisioner.m +++ /dev/null @@ -1,122 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDeviceProvisioner.h" -#import "OWSDeviceProvisioningCodeService.h" -#import "OWSDeviceProvisioningService.h" -#import "OWSError.h" -#import "OWSProvisioningMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSDeviceProvisioner () - -@property (nonatomic, readonly) NSData *myPublicKey; -@property (nonatomic, readonly) NSData *myPrivateKey; -@property (nonatomic, readonly) NSData *theirPublicKey; -@property (nonatomic, readonly) NSString *accountIdentifier; -@property (nonatomic, readonly) NSData *profileKey; -@property (nonatomic, nullable) NSString *ephemeralDeviceId; -@property (nonatomic, readonly) BOOL areReadReceiptsEnabled; -@property (nonatomic, readonly) OWSDeviceProvisioningCodeService *provisioningCodeService; -@property (nonatomic, readonly) OWSDeviceProvisioningService *provisioningService; - -@end - -@implementation OWSDeviceProvisioner - -- (instancetype)initWithMyPublicKey:(NSData *)myPublicKey - myPrivateKey:(NSData *)myPrivateKey - theirPublicKey:(NSData *)theirPublicKey - theirEphemeralDeviceId:(NSString *)ephemeralDeviceId - accountIdentifier:(NSString *)accountIdentifier - profileKey:(NSData *)profileKey - readReceiptsEnabled:(BOOL)areReadReceiptsEnabled - provisioningCodeService:(OWSDeviceProvisioningCodeService *)provisioningCodeService - provisioningService:(OWSDeviceProvisioningService *)provisioningService -{ - self = [super init]; - if (!self) { - return self; - } - - _myPublicKey = myPublicKey; - _myPrivateKey = myPrivateKey; - _theirPublicKey = theirPublicKey; - _accountIdentifier = accountIdentifier; - _profileKey = profileKey; - _ephemeralDeviceId = ephemeralDeviceId; - _areReadReceiptsEnabled = areReadReceiptsEnabled; - _provisioningCodeService = provisioningCodeService; - _provisioningService = provisioningService; - - return self; -} - -- (instancetype)initWithMyPublicKey:(NSData *)myPublicKey - myPrivateKey:(NSData *)myPrivateKey - theirPublicKey:(NSData *)theirPublicKey - theirEphemeralDeviceId:(NSString *)ephemeralDeviceId - accountIdentifier:(NSString *)accountIdentifier - profileKey:(NSData *)profileKey - readReceiptsEnabled:(BOOL)areReadReceiptsEnabled -{ - return [self initWithMyPublicKey:myPublicKey - myPrivateKey:myPrivateKey - theirPublicKey:theirPublicKey - theirEphemeralDeviceId:ephemeralDeviceId - accountIdentifier:accountIdentifier - profileKey:profileKey - readReceiptsEnabled:areReadReceiptsEnabled - provisioningCodeService:[OWSDeviceProvisioningCodeService new] - provisioningService:[OWSDeviceProvisioningService new]]; -} - -- (void)provisionWithSuccess:(void (^)(void))successCallback failure:(void (^)(NSError *_Nonnull))failureCallback -{ - [self.provisioningCodeService - requestProvisioningCodeWithSuccess:^(NSString *provisioningCode) { - OWSLogInfo(@"Retrieved provisioning code."); - [self provisionWithCode:provisioningCode success:successCallback failure:failureCallback]; - } - failure:^(NSError *error) { - OWSLogError(@"Failed to get provisioning code with error: %@", error); - failureCallback(error); - }]; -} - -- (void)provisionWithCode:(NSString *)provisioningCode - success:(void (^)(void))successCallback - failure:(void (^)(NSError *_Nonnull))failureCallback -{ - OWSProvisioningMessage *message = [[OWSProvisioningMessage alloc] initWithMyPublicKey:self.myPublicKey - myPrivateKey:self.myPrivateKey - theirPublicKey:self.theirPublicKey - accountIdentifier:self.accountIdentifier - profileKey:self.profileKey - readReceiptsEnabled:self.areReadReceiptsEnabled - provisioningCode:provisioningCode]; - - NSData *_Nullable messageBody = [message buildEncryptedMessageBody]; - if (messageBody == nil) { - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToEncryptMessage, @"Failed building provisioning message"); - failureCallback(error); - return; - } - - [self.provisioningService provisionWithMessageBody:messageBody - ephemeralDeviceId:self.ephemeralDeviceId - success:^{ - OWSLogInfo(@"ProvisioningService SUCCEEDED"); - successCallback(); - } - failure:^(NSError *error) { - OWSLogError(@"ProvisioningService FAILED with error:%@", error); - failureCallback(error); - }]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSGroupsOutputStream.h b/SignalServiceKit/src/Devices/OWSGroupsOutputStream.h deleted file mode 100644 index f45a65bba..000000000 --- a/SignalServiceKit/src/Devices/OWSGroupsOutputStream.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSChunkedOutputStream.h" - -NS_ASSUME_NONNULL_BEGIN - -@class TSGroupThread; -@class YapDatabaseReadTransaction; - -@interface OWSGroupsOutputStream : OWSChunkedOutputStream - -- (void)writeGroup:(TSGroupThread *)groupThread transaction:(YapDatabaseReadTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSGroupsOutputStream.m b/SignalServiceKit/src/Devices/OWSGroupsOutputStream.m deleted file mode 100644 index dd41c0dfd..000000000 --- a/SignalServiceKit/src/Devices/OWSGroupsOutputStream.m +++ /dev/null @@ -1,85 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSGroupsOutputStream.h" -#import "MIMETypeUtil.h" -#import "OWSBlockingManager.h" -#import "OWSDisappearingMessagesConfiguration.h" -#import "TSGroupModel.h" -#import "TSGroupThread.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSGroupsOutputStream - -- (void)writeGroup:(TSGroupThread *)groupThread transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(groupThread); - OWSAssertDebug(transaction); - - TSGroupModel *group = groupThread.groupModel; - OWSAssertDebug(group); - - SSKProtoGroupDetailsBuilder *groupBuilder = [SSKProtoGroupDetails builderWithId:group.groupId]; - [groupBuilder setName:group.groupName]; - [groupBuilder setMembers:group.groupMemberIds]; - [groupBuilder setColor:groupThread.conversationColorName]; - [groupBuilder setAdmins:group.groupAdminIds]; - - if ([OWSBlockingManager.sharedManager isGroupIdBlocked:group.groupId]) { - [groupBuilder setBlocked:YES]; - } - /* - NSData *avatarPng; - if (group.groupImage) { - SSKProtoGroupDetailsAvatarBuilder *avatarBuilder = [SSKProtoGroupDetailsAvatar builder]; - - [avatarBuilder setContentType:OWSMimeTypeImagePng]; - avatarPng = UIImagePNGRepresentation(group.groupImage); - [avatarBuilder setLength:(uint32_t)avatarPng.length]; - - NSError *error; - SSKProtoGroupDetailsAvatar *_Nullable avatarProto = [avatarBuilder buildAndReturnError:&error]; - if (error || !avatarProto) { - OWSFailDebug(@"could not build protobuf: %@", error); - } else { - [groupBuilder setAvatar:avatarProto]; - } - } */ - - OWSDisappearingMessagesConfiguration *_Nullable disappearingMessagesConfiguration = - [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:groupThread.uniqueId transaction:transaction]; - - if (disappearingMessagesConfiguration && disappearingMessagesConfiguration.isEnabled) { - [groupBuilder setExpireTimer:disappearingMessagesConfiguration.durationSeconds]; - } else { - // Rather than *not* set the field, we expicitly set it to 0 so desktop - // can easily distinguish between a modern client declaring "off" vs a - // legacy client "not specifying". - [groupBuilder setExpireTimer:0]; - } - - NSError *error; - NSData *_Nullable groupData = [groupBuilder buildSerializedDataAndReturnError:&error]; - if (error || !groupData) { - OWSFailDebug(@"could not serialize protobuf: %@", error); - return; - } - - uint32_t groupDataLength = (uint32_t)groupData.length; - - [self writeUInt32:groupDataLength]; - [self writeData:groupData]; - - /* - if (avatarPng) { - [self writeData:avatarPng]; - } - */ -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h deleted file mode 100644 index eb5d5f79a..000000000 --- a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSYapDatabaseObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSLinkedDeviceReadReceipt : TSYapDatabaseObject - -@property (nonatomic, readonly) NSString *senderId; -@property (nonatomic, readonly) uint64_t messageIdTimestamp; -@property (nonatomic, readonly) uint64_t readTimestamp; - -- (instancetype)initWithSenderId:(NSString *)senderId - messageIdTimestamp:(uint64_t)messageIdtimestamp - readTimestamp:(uint64_t)readTimestamp; - -+ (nullable OWSLinkedDeviceReadReceipt *)findLinkedDeviceReadReceiptWithSenderId:(NSString *)senderId - messageIdTimestamp:(uint64_t)messageIdTimestamp - transaction: - (YapDatabaseReadTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m deleted file mode 100644 index 79eff24cc..000000000 --- a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m +++ /dev/null @@ -1,76 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSLinkedDeviceReadReceipt.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSLinkedDeviceReadReceipt - -- (instancetype)initWithSenderId:(NSString *)senderId - messageIdTimestamp:(uint64_t)messageIdTimestamp - readTimestamp:(uint64_t)readTimestamp -{ - OWSAssertDebug(senderId.length > 0 && messageIdTimestamp > 0); - - NSString *receiptId = - [OWSLinkedDeviceReadReceipt uniqueIdForSenderId:senderId messageIdTimestamp:messageIdTimestamp]; - self = [super initWithUniqueId:receiptId]; - if (!self) { - return self; - } - - _senderId = senderId; - _messageIdTimestamp = messageIdTimestamp; - _readTimestamp = readTimestamp; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - // renamed timestamp -> messageIdTimestamp - if (!_messageIdTimestamp) { - NSNumber *_Nullable legacyTimestamp = (NSNumber *)[coder decodeObjectForKey:@"timestamp"]; - OWSAssertDebug(legacyTimestamp.unsignedLongLongValue > 0); - _messageIdTimestamp = legacyTimestamp.unsignedLongLongValue; - } - - // For legacy objects, before we were tracking read time, use the original messages "sent" timestamp - // as the local read time. This will always be at least a little bit earlier than the message was - // actually read, which isn't ideal, but safer than persisting a disappearing message too long, especially - // since we know they read it on their linked desktop. - if (_readTimestamp == 0) { - _readTimestamp = _messageIdTimestamp; - } - - return self; -} - -+ (NSString *)uniqueIdForSenderId:(NSString *)senderId messageIdTimestamp:(uint64_t)messageIdTimestamp -{ - OWSAssertDebug(senderId.length > 0 && messageIdTimestamp > 0); - - return [NSString stringWithFormat:@"%@-%llu", senderId, messageIdTimestamp]; -} - -+ (nullable OWSLinkedDeviceReadReceipt *)findLinkedDeviceReadReceiptWithSenderId:(NSString *)senderId - messageIdTimestamp:(uint64_t)messageIdTimestamp - transaction: - (YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - NSString *receiptId = - [OWSLinkedDeviceReadReceipt uniqueIdForSenderId:senderId messageIdTimestamp:messageIdTimestamp]; - return [OWSLinkedDeviceReadReceipt fetchObjectWithUniqueID:receiptId transaction:transaction]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSProvisioningCipher.h b/SignalServiceKit/src/Devices/OWSProvisioningCipher.h deleted file mode 100644 index 0e651aa69..000000000 --- a/SignalServiceKit/src/Devices/OWSProvisioningCipher.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSProvisioningCipher : NSObject - -@property (nonatomic, readonly) NSData *ourPublicKey; - -- (instancetype)initWithTheirPublicKey:(NSData *)theirPublicKey; -- (nullable NSData *)encrypt:(NSData *)plainText; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSProvisioningCipher.m b/SignalServiceKit/src/Devices/OWSProvisioningCipher.m deleted file mode 100644 index 3742158c3..000000000 --- a/SignalServiceKit/src/Devices/OWSProvisioningCipher.m +++ /dev/null @@ -1,157 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSProvisioningCipher.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSProvisioningCipher () - -@property (nonatomic, readonly) NSData *theirPublicKey; -@property (nonatomic, readonly) ECKeyPair *ourKeyPair; -@property (nonatomic, readonly) NSData *initializationVector; - -@end - -#pragma mark - - -@implementation OWSProvisioningCipher - -- (instancetype)initWithTheirPublicKey:(NSData *)theirPublicKey -{ - return [self initWithTheirPublicKey:theirPublicKey - ourKeyPair:[Curve25519 generateKeyPair] - initializationVector:[Cryptography generateRandomBytes:kCCBlockSizeAES128]]; -} - -// Private method which exposes dependencies for testing -- (instancetype)initWithTheirPublicKey:(NSData *)theirPublicKey - ourKeyPair:(ECKeyPair *)ourKeyPair - initializationVector:(NSData *)initializationVector -{ - self = [super init]; - if (!self) { - return self; - } - - _theirPublicKey = theirPublicKey; - _ourKeyPair = ourKeyPair; - _initializationVector = initializationVector; - - return self; -} - -- (NSData *)ourPublicKey -{ - return self.ourKeyPair.publicKey; -} - -- (nullable NSData *)encrypt:(NSData *)dataToEncrypt -{ - @try { - return [self throws_encryptWithData:dataToEncrypt]; - } @catch (NSException *exception) { - OWSFailDebug(@"exception: %@ of type: %@ with reason: %@, user info: %@.", - exception.description, - exception.name, - exception.reason, - exception.userInfo); - return nil; - } -} - -- (nullable NSData *)throws_encryptWithData:(NSData *)dataToEncrypt -{ - NSData *sharedSecret = - [Curve25519 throws_generateSharedSecretFromPublicKey:self.theirPublicKey andKeyPair:self.ourKeyPair]; - - NSData *infoData = [@"TextSecure Provisioning Message" dataUsingEncoding:NSASCIIStringEncoding]; - NSData *nullSalt = [[NSMutableData dataWithLength:32] copy]; - NSData *derivedSecret = [HKDFKit throws_deriveKey:sharedSecret info:infoData salt:nullSalt outputSize:64]; - NSData *cipherKey = [derivedSecret subdataWithRange:NSMakeRange(0, 32)]; - NSData *macKey = [derivedSecret subdataWithRange:NSMakeRange(32, 32)]; - if (cipherKey.length != 32) { - OWSFailDebug(@"Cipher Key must be 32 bytes"); - return nil; - } - if (macKey.length != 32) { - OWSFailDebug(@"Mac Key must be 32 bytes"); - return nil; - } - - u_int8_t versionByte[] = { 0x01 }; - NSMutableData *message = [NSMutableData dataWithBytes:&versionByte length:1]; - - NSData *_Nullable cipherText = [self encrypt:dataToEncrypt withKey:cipherKey]; - if (cipherText == nil) { - OWSFailDebug(@"Provisioning cipher failed."); - return nil; - } - - [message appendData:cipherText]; - - NSData *_Nullable mac = [self macForMessage:message withKey:macKey]; - if (mac == nil) { - OWSFailDebug(@"mac failed."); - return nil; - } - [message appendData:mac]; - - return [message copy]; -} - -- (nullable NSData *)encrypt:(NSData *)dataToEncrypt withKey:(NSData *)cipherKey -{ - NSData *iv = self.initializationVector; - if (iv.length != kCCBlockSizeAES128) { - OWSFailDebug(@"Unexpected length for iv"); - return nil; - } - if (dataToEncrypt.length >= SIZE_MAX - (kCCBlockSizeAES128 + iv.length)) { - OWSFailDebug(@"data is too long to encrypt."); - return nil; - } - - // allow space for message + padding any incomplete block. PKCS7 padding will always add at least one byte. - size_t ciphertextBufferSize = dataToEncrypt.length + kCCBlockSizeAES128; - - NSMutableData *ciphertextData = [[NSMutableData alloc] initWithLength:ciphertextBufferSize]; - - size_t bytesEncrypted = 0; - CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, - kCCAlgorithmAES, - kCCOptionPKCS7Padding, - cipherKey.bytes, - cipherKey.length, - iv.bytes, - dataToEncrypt.bytes, - dataToEncrypt.length, - ciphertextData.mutableBytes, - ciphertextBufferSize, - &bytesEncrypted); - - if (cryptStatus != kCCSuccess) { - OWSFailDebug(@"Encryption failed with status: %d", cryptStatus); - return nil; - } - - // message format is (iv || ciphertext) - NSMutableData *encryptedMessage = [NSMutableData new]; - [encryptedMessage appendData:iv]; - [encryptedMessage appendData:[ciphertextData subdataWithRange:NSMakeRange(0, bytesEncrypted)]]; - return [encryptedMessage copy]; -} - -- (nullable NSData *)macForMessage:(NSData *)message withKey:(NSData *)macKey -{ - return [Cryptography computeSHA256HMAC:message withHMACKey:macKey]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSProvisioningMessage.h b/SignalServiceKit/src/Devices/OWSProvisioningMessage.h deleted file mode 100644 index 652fc4a09..000000000 --- a/SignalServiceKit/src/Devices/OWSProvisioningMessage.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSProvisioningMessage : NSObject - -- (instancetype)initWithMyPublicKey:(NSData *)myPublicKey - myPrivateKey:(NSData *)myPrivateKey - theirPublicKey:(NSData *)theirPublicKey - accountIdentifier:(NSString *)accountIdentifier - profileKey:(NSData *)profileKey - readReceiptsEnabled:(BOOL)areReadReceiptsEnabled - provisioningCode:(NSString *)provisioningCode; - -- (nullable NSData *)buildEncryptedMessageBody; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSProvisioningMessage.m b/SignalServiceKit/src/Devices/OWSProvisioningMessage.m deleted file mode 100644 index ef2f3e68a..000000000 --- a/SignalServiceKit/src/Devices/OWSProvisioningMessage.m +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSProvisioningMessage.h" -#import "OWSProvisioningCipher.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSProvisioningMessage () - -@property (nonatomic, readonly) NSData *myPublicKey; -@property (nonatomic, readonly) NSData *myPrivateKey; -@property (nonatomic, readonly) NSString *accountIdentifier; -@property (nonatomic, readonly) NSData *theirPublicKey; -@property (nonatomic, readonly) NSData *profileKey; -@property (nonatomic, readonly) BOOL areReadReceiptsEnabled; -@property (nonatomic, readonly) NSString *provisioningCode; - -@end - -@implementation OWSProvisioningMessage - -- (instancetype)initWithMyPublicKey:(NSData *)myPublicKey - myPrivateKey:(NSData *)myPrivateKey - theirPublicKey:(NSData *)theirPublicKey - accountIdentifier:(NSString *)accountIdentifier - profileKey:(NSData *)profileKey - readReceiptsEnabled:(BOOL)areReadReceiptsEnabled - provisioningCode:(NSString *)provisioningCode -{ - self = [super init]; - if (!self) { - return self; - } - - _myPublicKey = myPublicKey; - _myPrivateKey = myPrivateKey; - _theirPublicKey = theirPublicKey; - _accountIdentifier = accountIdentifier; - _profileKey = profileKey; - _areReadReceiptsEnabled = areReadReceiptsEnabled; - _provisioningCode = provisioningCode; - - return self; -} - -- (nullable NSData *)buildEncryptedMessageBody -{ - ProvisioningProtoProvisionMessageBuilder *messageBuilder = - [ProvisioningProtoProvisionMessage builderWithIdentityKeyPublic:self.myPublicKey - identityKeyPrivate:self.myPrivateKey - number:self.accountIdentifier - provisioningCode:self.provisioningCode - userAgent:@"OWI" - profileKey:self.profileKey - readReceipts:self.areReadReceiptsEnabled]; - - NSError *error; - NSData *_Nullable plainTextProvisionMessage = [messageBuilder buildSerializedDataAndReturnError:&error]; - if (!plainTextProvisionMessage || error) { - OWSFailDebug(@"could not serialize proto: %@.", error); - return nil; - } - - OWSProvisioningCipher *cipher = [[OWSProvisioningCipher alloc] initWithTheirPublicKey:self.theirPublicKey]; - NSData *_Nullable encryptedProvisionMessage = [cipher encrypt:plainTextProvisionMessage]; - if (encryptedProvisionMessage == nil) { - OWSFailDebug(@"Failed to encrypt provision message"); - return nil; - } - - // Note that this is a one-time-use *cipher* public key, not our Signal *identity* public key - ProvisioningProtoProvisionEnvelopeBuilder *envelopeBuilder = - [ProvisioningProtoProvisionEnvelope builderWithPublicKey:[cipher.ourPublicKey prependKeyType] - body:encryptedProvisionMessage]; - - NSData *_Nullable envelopeData = [envelopeBuilder buildSerializedDataAndReturnError:&error]; - if (!envelopeData || error) { - OWSFailDebug(@"could not serialize proto: %@.", error); - return nil; - } - - return envelopeData; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSReadReceiptsForLinkedDevicesMessage.h b/SignalServiceKit/src/Devices/OWSReadReceiptsForLinkedDevicesMessage.h deleted file mode 100644 index 5e2165be7..000000000 --- a/SignalServiceKit/src/Devices/OWSReadReceiptsForLinkedDevicesMessage.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingSyncMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class OWSLinkedDeviceReadReceipt; - -@interface OWSReadReceiptsForLinkedDevicesMessage : OWSOutgoingSyncMessage - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithReadReceipts:(NSArray *)readReceipts NS_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSReadReceiptsForLinkedDevicesMessage.m b/SignalServiceKit/src/Devices/OWSReadReceiptsForLinkedDevicesMessage.m deleted file mode 100644 index 4f03d7411..000000000 --- a/SignalServiceKit/src/Devices/OWSReadReceiptsForLinkedDevicesMessage.m +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSReadReceiptsForLinkedDevicesMessage.h" -#import "OWSLinkedDeviceReadReceipt.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSReadReceiptsForLinkedDevicesMessage () - -@property (nonatomic, readonly) NSArray *readReceipts; - -@end - -@implementation OWSReadReceiptsForLinkedDevicesMessage - -- (instancetype)initWithReadReceipts:(NSArray *)readReceipts -{ - self = [super init]; - if (!self) { - return self; - } - - _readReceipts = [readReceipts copy]; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (nullable SSKProtoSyncMessageBuilder *)syncMessageBuilder -{ - SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; - for (OWSLinkedDeviceReadReceipt *readReceipt in self.readReceipts) { - SSKProtoSyncMessageReadBuilder *readProtoBuilder = - [SSKProtoSyncMessageRead builderWithSender:readReceipt.senderId timestamp:readReceipt.messageIdTimestamp]; - - NSError *error; - SSKProtoSyncMessageRead *_Nullable readProto = [readProtoBuilder buildAndReturnError:&error]; - if (error || !readProto) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - [syncMessageBuilder addRead:readProto]; - } - return syncMessageBuilder; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSReceiptsForSenderMessage.h b/SignalServiceKit/src/Devices/OWSReceiptsForSenderMessage.h deleted file mode 100644 index df4108af1..000000000 --- a/SignalServiceKit/src/Devices/OWSReceiptsForSenderMessage.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingSyncMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class OWSDeliveryReceipt; - -@interface OWSReceiptsForSenderMessage : TSOutgoingMessage - -- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSMutableArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - isVoiceMessage:(BOOL)isVoiceMessage - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_UNAVAILABLE; - -+ (OWSReceiptsForSenderMessage *)deliveryReceiptsForSenderMessageWithThread:(nullable TSThread *)thread - messageTimestamps:(NSArray *)messageTimestamps; - -+ (OWSReceiptsForSenderMessage *)readReceiptsForSenderMessageWithThread:(nullable TSThread *)thread - messageTimestamps:(NSArray *)messageTimestamps; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSReceiptsForSenderMessage.m b/SignalServiceKit/src/Devices/OWSReceiptsForSenderMessage.m deleted file mode 100644 index 34195103a..000000000 --- a/SignalServiceKit/src/Devices/OWSReceiptsForSenderMessage.m +++ /dev/null @@ -1,139 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSReceiptsForSenderMessage.h" -#import "SignalRecipient.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSReceiptsForSenderMessage () - -@property (nonatomic, readonly) NSArray *messageTimestamps; - -@property (nonatomic, readonly) SSKProtoReceiptMessageType receiptType; - -@end - -#pragma mark - - -@implementation OWSReceiptsForSenderMessage - -+ (OWSReceiptsForSenderMessage *)deliveryReceiptsForSenderMessageWithThread:(nullable TSThread *)thread - messageTimestamps:(NSArray *)messageTimestamps -{ - return [[OWSReceiptsForSenderMessage alloc] initWithThread:thread - messageTimestamps:messageTimestamps - receiptType:SSKProtoReceiptMessageTypeDelivery]; -} - -+ (OWSReceiptsForSenderMessage *)readReceiptsForSenderMessageWithThread:(nullable TSThread *)thread - messageTimestamps:(NSArray *)messageTimestamps -{ - return [[OWSReceiptsForSenderMessage alloc] initWithThread:thread - messageTimestamps:messageTimestamps - receiptType:SSKProtoReceiptMessageTypeRead]; -} - -- (instancetype)initWithThread:(nullable TSThread *)thread - messageTimestamps:(NSArray *)messageTimestamps - receiptType:(SSKProtoReceiptMessageType)receiptType -{ - // MJK TODO - remove senderTimestamp - self = [super initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - if (!self) { - return self; - } - - _messageTimestamps = [messageTimestamps copy]; - _receiptType = receiptType; - - return self; -} - -#pragma mark - TSOutgoingMessage overrides - -- (BOOL)shouldSyncTranscript -{ - return NO; -} - -- (BOOL)isSilent -{ - // Avoid "phantom messages" for "recipient read receipts". - - return YES; -} - -- (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient -{ - OWSAssertDebug(recipient); - - SSKProtoReceiptMessage *_Nullable receiptMessage = [self buildReceiptMessage:recipient.recipientId]; - if (!receiptMessage) { - OWSFailDebug(@"could not build protobuf."); - return nil; - } - - SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; - [contentBuilder setReceiptMessage:receiptMessage]; - - NSError *error; - NSData *_Nullable contentData = [contentBuilder buildSerializedDataAndReturnError:&error]; - if (error || !contentData) { - OWSFailDebug(@"could not serialize protobuf: %@", error); - return nil; - } - return contentData; -} - -- (nullable SSKProtoReceiptMessage *)buildReceiptMessage:(NSString *)recipientId -{ - SSKProtoReceiptMessageBuilder *builder = [SSKProtoReceiptMessage builderWithType:self.receiptType]; - - OWSAssertDebug(self.messageTimestamps.count > 0); - for (NSNumber *messageTimestamp in self.messageTimestamps) { - [builder addTimestamp:[messageTimestamp unsignedLongLongValue]]; - } - - NSError *error; - SSKProtoReceiptMessage *_Nullable receiptMessage = [builder buildAndReturnError:&error]; - if (error || !receiptMessage) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - return receiptMessage; -} - -#pragma mark - TSYapDatabaseObject overrides - -- (BOOL)shouldBeSaved -{ - return NO; -} - -- (NSString *)debugDescription -{ - return [NSString - stringWithFormat:@"%@ with message timestamps: %lu", self.logTag, (unsigned long)self.messageTimestamps.count]; -} - -#pragma mark - Other - -- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeReceipt]; } - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.h b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.h deleted file mode 100644 index 53f383a20..000000000 --- a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSIncomingSentMessageTranscript; -@class SSKProtoSyncMessageSentUpdate; -@class TSAttachmentStream; -@class YapDatabaseReadWriteTransaction; - -// This job is used to process "outgoing message" notifications from linked devices. -@interface OWSRecordTranscriptJob : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -+ (void)processIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)incomingSentMessageTranscript - serverID:(uint64_t)serverID - serverTimestamp:(uint64_t)serverTimestamp - attachmentHandler:(void (^)( - NSArray *attachmentStreams))attachmentHandler - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m deleted file mode 100644 index e0a903a80..000000000 --- a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m +++ /dev/null @@ -1,291 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSRecordTranscriptJob.h" -#import "OWSAttachmentDownloads.h" -#import "OWSDisappearingMessagesJob.h" -#import "OWSIncomingSentMessageTranscript.h" -#import "OWSPrimaryStorage+SessionStore.h" -#import "OWSReadReceiptManager.h" -#import "SSKEnvironment.h" -#import "TSAttachmentPointer.h" -#import "TSGroupThread.h" -#import "TSInfoMessage.h" -#import "TSNetworkManager.h" -#import "TSOutgoingMessage.h" -#import "TSQuotedMessage.h" -#import "TSThread.h" -#import -#import "OWSPrimaryStorage+Loki.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSRecordTranscriptJob - -#pragma mark - Dependencies - -+ (OWSPrimaryStorage *)primaryStorage -{ - OWSAssertDebug(SSKEnvironment.shared.primaryStorage); - - return SSKEnvironment.shared.primaryStorage; -} - -+ (TSNetworkManager *)networkManager -{ - OWSAssertDebug(SSKEnvironment.shared.networkManager); - - return SSKEnvironment.shared.networkManager; -} - -+ (OWSReadReceiptManager *)readReceiptManager -{ - OWSAssert(SSKEnvironment.shared.readReceiptManager); - - return SSKEnvironment.shared.readReceiptManager; -} - -+ (id)contactsManager -{ - OWSAssertDebug(SSKEnvironment.shared.contactsManager); - - return SSKEnvironment.shared.contactsManager; -} - -+ (OWSAttachmentDownloads *)attachmentDownloads -{ - return SSKEnvironment.shared.attachmentDownloads; -} - -#pragma mark - - -+ (void)processIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)transcript - serverID:(uint64_t)serverID - serverTimestamp:(uint64_t)serverTimestamp - attachmentHandler:(void (^)( - NSArray *attachmentStreams))attachmentHandler - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transcript); - OWSAssertDebug(transaction); - - if (transcript.isRecipientUpdate) { - // "Recipient updates" are processed completely separately in order - // to avoid resurrecting threads or messages. - [self processRecipientUpdateWithTranscript:transcript transaction:transaction]; - return; - } - - OWSLogInfo(@"Recording transcript in thread: %@ timestamp: %llu", transcript.thread.uniqueId, transcript.timestamp); - - if (transcript.isEndSessionMessage) { - OWSLogInfo(@"EndSession was sent to recipient: %@.", transcript.recipientId); - [self.primaryStorage deleteAllSessionsForContact:transcript.recipientId protocolContext:transaction]; - - // MJK TODO - we don't use this timestamp, safe to remove - [[[TSInfoMessage alloc] initWithTimestamp:transcript.timestamp - inThread:transcript.thread - messageType:TSInfoMessageTypeSessionDidEnd] saveWithTransaction:transaction]; - - // Don't continue processing lest we print a bubble for the session reset. - return; - } - - TSOutgoingMessage *outgoingMessage = - [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:transcript.timestamp - inThread:transcript.thread - messageBody:transcript.body - attachmentIds:[NSMutableArray new] - expiresInSeconds:transcript.expirationDuration - expireStartedAt:transcript.expirationStartedAt - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:transcript.quotedMessage - contactShare:transcript.contact - linkPreview:transcript.linkPreview]; - - - if (transcript.thread.isGroupThread) { - TSGroupThread *thread = (TSGroupThread *)transcript.thread; - if (thread.isPublicChat) { - [outgoingMessage setServerTimestampToReceivedTimestamp:serverTimestamp]; - } - } - - if (serverID != 0) { - outgoingMessage.openGroupServerMessageID = serverID; - } - - NSArray *attachmentPointers = - [TSAttachmentPointer attachmentPointersFromProtos:transcript.attachmentPointerProtos - albumMessage:outgoingMessage]; - for (TSAttachmentPointer *pointer in attachmentPointers) { - [pointer saveWithTransaction:transaction]; - [outgoingMessage.attachmentIds addObject:pointer.uniqueId]; - } - - TSQuotedMessage *_Nullable quotedMessage = transcript.quotedMessage; - if (quotedMessage && quotedMessage.thumbnailAttachmentPointerId) { - // We weren't able to derive a local thumbnail, so we'll fetch the referenced attachment. - TSAttachmentPointer *attachmentPointer = - [TSAttachmentPointer fetchObjectWithUniqueID:quotedMessage.thumbnailAttachmentPointerId - transaction:transaction]; - - if ([attachmentPointer isKindOfClass:[TSAttachmentPointer class]]) { - OWSLogDebug(@"downloading attachments for transcript: %lu", (unsigned long)transcript.timestamp); - - [self.attachmentDownloads downloadAttachmentPointer:attachmentPointer - success:^(NSArray *attachmentStreams) { - OWSAssertDebug(attachmentStreams.count == 1); - TSAttachmentStream *attachmentStream = attachmentStreams.firstObject; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [outgoingMessage setQuotedMessageThumbnailAttachmentStream:attachmentStream]; - [outgoingMessage saveWithTransaction:transaction]; - if (serverID != 0) { - [OWSPrimaryStorage.sharedManager setIDForMessageWithServerID:serverID to:outgoingMessage.uniqueId in:transaction]; - } - }]; - } - failure:^(NSError *error) { - OWSLogWarn(@"failed to fetch thumbnail for transcript: %lu with error: %@", - (unsigned long)transcript.timestamp, - error); - }]; - } - } - - [[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:outgoingMessage.expiresInSeconds - thread:transcript.thread - createdByRemoteRecipientId:nil - createdInExistingGroup:NO - transaction:transaction]; - - if (transcript.isExpirationTimerUpdate) { - // early return to avoid saving an empty incoming message. - OWSAssertDebug(transcript.body.length == 0); - OWSAssertDebug(outgoingMessage.attachmentIds.count == 0); - - return; - } - - if (outgoingMessage.body.length < 1 && outgoingMessage.attachmentIds.count < 1 && !outgoingMessage.contactShare) { - OWSFailDebug(@"Ignoring message transcript for empty message."); - return; - } - - [outgoingMessage saveWithTransaction:transaction]; - [outgoingMessage updateWithWasSentFromLinkedDeviceWithUDRecipientIds:transcript.udRecipientIds - nonUdRecipientIds:transcript.nonUdRecipientIds - isSentUpdate:NO - transaction:transaction]; - [[OWSDisappearingMessagesJob sharedJob] startAnyExpirationForMessage:outgoingMessage - expirationStartedAt:transcript.expirationStartedAt - transaction:transaction]; - [self.readReceiptManager applyEarlyReadReceiptsForOutgoingMessageFromLinkedDevice:outgoingMessage - transaction:transaction]; - - if (outgoingMessage.hasAttachments) { - [self.attachmentDownloads - downloadAttachmentsForMessage:outgoingMessage - transaction:transaction - success:attachmentHandler - failure:^(NSError *error) { - OWSLogError( - @"failed to fetch transcripts attachments for message: %@", outgoingMessage); - }]; - } -} - -#pragma mark - - -+ (void)processRecipientUpdateWithTranscript:(OWSIncomingSentMessageTranscript *)transcript - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transcript); - OWSAssertDebug(transaction); - - if (!AreRecipientUpdatesEnabled()) { - OWSFailDebug(@"Ignoring 'recipient update' transcript; disabled."); - return; - } - - if (transcript.udRecipientIds.count < 1 && transcript.nonUdRecipientIds.count < 1) { - OWSFailDebug(@"Ignoring empty 'recipient update' transcript."); - return; - } - - uint64_t timestamp = transcript.timestamp; - if (timestamp < 1) { - OWSFailDebug(@"'recipient update' transcript has invalid timestamp."); - return; - } - - if (!transcript.thread.isGroupThread) { - OWSFailDebug(@"'recipient update' has missing or invalid thread."); - return; - } - TSGroupThread *groupThread = (TSGroupThread *)transcript.thread; - NSData *groupId = groupThread.groupModel.groupId; - if (groupId.length < 1) { - OWSFailDebug(@"'recipient update' transcript has invalid groupId."); - return; - } - - NSArray *messages - = (NSArray *)[TSInteraction interactionsWithTimestamp:timestamp - ofClass:[TSOutgoingMessage class] - withTransaction:transaction]; - if (messages.count < 1) { - // This message may have disappeared. - OWSLogError(@"No matching message with timestamp: %llu.", timestamp); - return; - } - - BOOL messageFound = NO; - for (TSOutgoingMessage *message in messages) { - if (!message.isFromLinkedDevice) { - // isFromLinkedDevice isn't always set for very old linked messages, but: - // - // a) We should never receive a "sent update" for a very old message. - // b) It's safe to discard suspicious "sent updates." - continue; - } - TSThread *thread = [message threadWithTransaction:transaction]; - if (!thread.isGroupThread) { - continue; - } - TSGroupThread *groupThread = (TSGroupThread *)thread; - if (![groupThread.groupModel.groupId isEqual:groupId]) { - continue; - } - - if (!message.isFromLinkedDevice) { - OWSFailDebug(@"Ignoring 'recipient update' for message which was sent locally."); - continue; - } - - OWSLogInfo(@"Processing 'recipient update' transcript in thread: %@, timestamp: %llu, nonUdRecipientIds: %d, " - @"udRecipientIds: %d.", - thread.uniqueId, - timestamp, - (int)transcript.nonUdRecipientIds.count, - (int)transcript.udRecipientIds.count); - - [message updateWithWasSentFromLinkedDeviceWithUDRecipientIds:transcript.udRecipientIds - nonUdRecipientIds:transcript.nonUdRecipientIds - isSentUpdate:YES - transaction:transaction]; - - messageFound = YES; - } - - if (!messageFound) { - // This message may have disappeared. - OWSLogError(@"No matching message with timestamp: %llu.", timestamp); - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSVerificationStateSyncMessage.h b/SignalServiceKit/src/Devices/OWSVerificationStateSyncMessage.h deleted file mode 100644 index 0c0ebdc3d..000000000 --- a/SignalServiceKit/src/Devices/OWSVerificationStateSyncMessage.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingSyncMessage.h" -#import "OWSRecipientIdentity.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSVerificationStateSyncMessage : OWSOutgoingSyncMessage - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithVerificationState:(OWSVerificationState)verificationState - identityKey:(NSData *)identityKey - verificationForRecipientId:(NSString *)recipientId NS_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -// This is a clunky name, but we want to differentiate it from `recipientIdentifier` inherited from `TSOutgoingMessage` -@property (nonatomic, readonly) NSString *verificationForRecipientId; - -@property (nonatomic, readonly) size_t paddingBytesLength; -@property (nonatomic, readonly) size_t unpaddedVerifiedLength; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Devices/OWSVerificationStateSyncMessage.m b/SignalServiceKit/src/Devices/OWSVerificationStateSyncMessage.m deleted file mode 100644 index 5e28f6e3b..000000000 --- a/SignalServiceKit/src/Devices/OWSVerificationStateSyncMessage.m +++ /dev/null @@ -1,111 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSVerificationStateSyncMessage.h" -#import "OWSIdentityManager.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#pragma mark - - -@interface OWSVerificationStateSyncMessage () - -@property (nonatomic, readonly) OWSVerificationState verificationState; -@property (nonatomic, readonly) NSData *identityKey; - -@end - -#pragma mark - - -@implementation OWSVerificationStateSyncMessage - -- (instancetype)initWithVerificationState:(OWSVerificationState)verificationState - identityKey:(NSData *)identityKey - verificationForRecipientId:(NSString *)verificationForRecipientId -{ - OWSAssertDebug(identityKey.length == kIdentityKeyLength); - OWSAssertDebug(verificationForRecipientId.length > 0); - - // we only sync user's marking as un/verified. Never sync the conflicted state, the sibling device - // will figure that out on it's own. - OWSAssertDebug(verificationState != OWSVerificationStateNoLongerVerified); - - self = [super init]; - if (!self) { - return self; - } - - _verificationState = verificationState; - _identityKey = identityKey; - _verificationForRecipientId = verificationForRecipientId; - - // This sync message should be 1-512 bytes longer than the corresponding NullMessage - // we store this values so the corresponding NullMessage can subtract it from the total length. - _paddingBytesLength = arc4random_uniform(512) + 1; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (nullable SSKProtoSyncMessageBuilder *)syncMessageBuilder -{ - OWSAssertDebug(self.identityKey.length == kIdentityKeyLength); - OWSAssertDebug(self.verificationForRecipientId.length > 0); - - // we only sync user's marking as un/verified. Never sync the conflicted state, the sibling device - // will figure that out on it's own. - OWSAssertDebug(self.verificationState != OWSVerificationStateNoLongerVerified); - - // We add the same amount of padding in the VerificationStateSync message and it's coresponding NullMessage so that - // the sync message is indistinguishable from an outgoing Sent transcript corresponding to the NullMessage. We pad - // the NullMessage so as to obscure it's content. The sync message (like all sync messages) will be *additionally* - // padded by the superclass while being sent. The end result is we send a NullMessage of a non-distinct size, and a - // verification sync which is ~1-512 bytes larger then that. - OWSAssertDebug(self.paddingBytesLength != 0); - - SSKProtoVerified *_Nullable verifiedProto = BuildVerifiedProtoWithRecipientId( - self.verificationForRecipientId, self.identityKey, self.verificationState, self.paddingBytesLength); - if (!verifiedProto) { - OWSFailDebug(@"could not build protobuf."); - return nil; - } - - SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; - [syncMessageBuilder setVerified:verifiedProto]; - return syncMessageBuilder; -} - -- (size_t)unpaddedVerifiedLength -{ - OWSAssertDebug(self.identityKey.length == kIdentityKeyLength); - OWSAssertDebug(self.verificationForRecipientId.length > 0); - - // we only sync user's marking as un/verified. Never sync the conflicted state, the sibling device - // will figure that out on it's own. - OWSAssertDebug(self.verificationState != OWSVerificationStateNoLongerVerified); - - SSKProtoVerified *_Nullable verifiedProto = BuildVerifiedProtoWithRecipientId( - self.verificationForRecipientId, self.identityKey, self.verificationState, 0); - if (!verifiedProto) { - OWSFailDebug(@"could not build protobuf."); - return 0; - } - NSError *error; - NSData *_Nullable verifiedData = [verifiedProto serializedDataAndReturnError:&error]; - if (error || !verifiedData) { - OWSFailDebug(@"could not serialize protobuf."); - return 0; - } - return verifiedData.length; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Loki/API/Deprecated/FileServerAPI+Deprecated.swift b/SignalServiceKit/src/Loki/API/Deprecated/FileServerAPI+Deprecated.swift deleted file mode 100644 index 1fd608a6f..000000000 --- a/SignalServiceKit/src/Loki/API/Deprecated/FileServerAPI+Deprecated.swift +++ /dev/null @@ -1,148 +0,0 @@ -import PromiseKit - -public extension FileServerAPI { - - /// Gets the device links associated with the given hex encoded public key from the - /// server and stores and returns the valid ones. - /// - /// - Note: Deprecated. - public static func getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> Promise> { - return getDeviceLinks(associatedWith: [ hexEncodedPublicKey ]) - } - - /// Gets the device links associated with the given hex encoded public keys from the - /// server and stores and returns the valid ones. - /// - /// - Note: Deprecated. - public static func getDeviceLinks(associatedWith hexEncodedPublicKeys: Set) -> Promise> { - return Promise.value([]) - /* - let hexEncodedPublicKeysDescription = "[ \(hexEncodedPublicKeys.joined(separator: ", ")) ]" - print("[Loki] Getting device links for: \(hexEncodedPublicKeysDescription).") - return getAuthToken(for: server).then2 { token -> Promise> in - let queryParameters = "ids=\(hexEncodedPublicKeys.map { "@\($0)" }.joined(separator: ","))&include_user_annotations=1" - let url = URL(string: "\(server)/users?\(queryParameters)")! - let request = TSRequest(url: url) - return OnionRequestAPI.sendOnionRequest(request, to: server, using: fileServerPublicKey).map2 { rawResponse -> Set in - guard let data = rawResponse["data"] as? [JSON] else { - print("[Loki] Couldn't parse device links for users: \(hexEncodedPublicKeys) from: \(rawResponse).") - throw DotNetAPIError.parsingFailed - } - return Set(data.flatMap { data -> [DeviceLink] in - guard let annotations = data["annotations"] as? [JSON], !annotations.isEmpty else { return [] } - guard let annotation = annotations.first(where: { $0["type"] as? String == deviceLinkType }), - let value = annotation["value"] as? JSON, let rawDeviceLinks = value["authorisations"] as? [JSON], - let hexEncodedPublicKey = data["username"] as? String else { - print("[Loki] Couldn't parse device links from: \(rawResponse).") - return [] - } - return rawDeviceLinks.compactMap { rawDeviceLink in - guard let masterPublicKey = rawDeviceLink["primaryDevicePubKey"] as? String, let slavePublicKey = rawDeviceLink["secondaryDevicePubKey"] as? String, - let base64EncodedSlaveSignature = rawDeviceLink["requestSignature"] as? String else { - print("[Loki] Couldn't parse device link for user: \(hexEncodedPublicKey) from: \(rawResponse).") - return nil - } - let masterSignature: Data? - if let base64EncodedMasterSignature = rawDeviceLink["grantSignature"] as? String { - masterSignature = Data(base64Encoded: base64EncodedMasterSignature) - } else { - masterSignature = nil - } - let slaveSignature = Data(base64Encoded: base64EncodedSlaveSignature) - let master = DeviceLink.Device(publicKey: masterPublicKey, signature: masterSignature) - let slave = DeviceLink.Device(publicKey: slavePublicKey, signature: slaveSignature) - let deviceLink = DeviceLink(between: master, and: slave) - if let masterSignature = masterSignature { - guard DeviceLinkingUtilities.hasValidMasterSignature(deviceLink) else { - print("[Loki] Received a device link with an invalid master signature.") - return nil - } - } - guard DeviceLinkingUtilities.hasValidSlaveSignature(deviceLink) else { - print("[Loki] Received a device link with an invalid slave signature.") - return nil - } - return deviceLink - } - }) - }.map2 { deviceLinks in - storage.setDeviceLinks(deviceLinks) - return deviceLinks - } - }.handlingInvalidAuthTokenIfNeeded(for: server) - */ - } - - /// - Note: Deprecated. - public static func setDeviceLinks(_ deviceLinks: Set) -> Promise { - return Promise.value(()) - /* - print("[Loki] Updating device links.") - return getAuthToken(for: server).then2 { token -> Promise in - let isMaster = deviceLinks.contains { $0.master.publicKey == getUserHexEncodedPublicKey() } - let deviceLinksAsJSON = deviceLinks.map { $0.toJSON() } - let value = !deviceLinksAsJSON.isEmpty ? [ "isPrimary" : isMaster ? 1 : 0, "authorisations" : deviceLinksAsJSON ] : nil - let annotation: JSON = [ "type" : deviceLinkType, "value" : value ] - let parameters: JSON = [ "annotations" : [ annotation ] ] - let url = URL(string: "\(server)/users/me")! - let request = TSRequest(url: url, method: "PATCH", parameters: parameters) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - return attempt(maxRetryCount: 8, recoveringOn: SnodeAPI.workQueue) { - OnionRequestAPI.sendOnionRequest(request, to: server, using: fileServerPublicKey).map2 { _ in } - }.handlingInvalidAuthTokenIfNeeded(for: server).recover2 { error in - print("[Loki] Couldn't update device links due to error: \(error).") - throw error - } - } - */ - } - - /// Adds the given device link to the user's device mapping on the server. - /// - /// - Note: Deprecated. - public static func addDeviceLink(_ deviceLink: DeviceLink) -> Promise { - return Promise.value(()) - /* - var deviceLinks: Set = [] - storage.dbReadConnection.read { transaction in - deviceLinks = storage.getDeviceLinks(for: getUserHexEncodedPublicKey(), in: transaction) - } - deviceLinks.insert(deviceLink) - return setDeviceLinks(deviceLinks).map2 { _ in - storage.addDeviceLink(deviceLink) - } - */ - } - - /// Removes the given device link from the user's device mapping on the server. - /// - /// - Note: Deprecated. - public static func removeDeviceLink(_ deviceLink: DeviceLink) -> Promise { - return Promise.value(()) - /* - var deviceLinks: Set = [] - storage.dbReadConnection.read { transaction in - deviceLinks = storage.getDeviceLinks(for: getUserHexEncodedPublicKey(), in: transaction) - } - deviceLinks.remove(deviceLink) - return setDeviceLinks(deviceLinks).map2 { _ in - storage.removeDeviceLink(deviceLink) - } - */ - } -} - -@objc public extension FileServerAPI { - - /// - Note: Deprecated. - @objc(getDeviceLinksAssociatedWithHexEncodedPublicKey:) - public static func objc_getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> AnyPromise { - return AnyPromise.from(getDeviceLinks(associatedWith: hexEncodedPublicKey)) - } - - /// - Note: Deprecated. - @objc(getDeviceLinksAssociatedWithHexEncodedPublicKeys:) - public static func objc_getDeviceLinks(associatedWith hexEncodedPublicKeys: Set) -> AnyPromise { - return AnyPromise.from(getDeviceLinks(associatedWith: hexEncodedPublicKeys)) - } -} diff --git a/SignalServiceKit/src/Loki/API/Deprecated/ProofOfWork.swift b/SignalServiceKit/src/Loki/API/Deprecated/ProofOfWork.swift deleted file mode 100644 index cbcfb5e4e..000000000 --- a/SignalServiceKit/src/Loki/API/Deprecated/ProofOfWork.swift +++ /dev/null @@ -1,109 +0,0 @@ -import CryptoSwift - -private extension UInt64 { - - fileprivate init(_ decimal: Decimal) { - self.init(truncating: decimal as NSDecimalNumber) - } - - // Convert a UInt8 array to a UInt64 - fileprivate init(_ bytes: [UInt8]) { - precondition(bytes.count <= MemoryLayout.size) - var value: UInt64 = 0 - for byte in bytes { - value <<= 8 - value |= UInt64(byte) - } - self.init(value) - } -} - -private extension MutableCollection where Element == UInt8, Index == Int { - - /// Increment every element by the given amount - /// - /// - Parameter amount: The amount to increment by - /// - Returns: The incremented collection - fileprivate func increment(by amount: Int) -> Self { - var result = self - var increment = amount - for i in (0.. 0 else { break } - let sum = Int(result[i]) + increment - result[i] = UInt8(sum % 256) - increment = sum / 256 - } - return result - } -} - -/** - * The main proof of work logic. - * - * This was copied from the desktop messenger. - * Ref: libloki/proof-of-work.js - */ -public enum ProofOfWork { - - // If this changes then we also have to use something other than UInt64 to support the new length - private static let nonceLength = 8 - - /// Calculate a proof of work with the given configuration - /// - /// Ref: https://bitmessage.org/wiki/Proof_of_work - /// - /// - Parameters: - /// - data: The message data - /// - pubKey: The message recipient - /// - timestamp: The timestamp - /// - ttl: The message time to live in milliseconds - /// - Returns: A nonce string or `nil` if it failed - public static func calculate(data: String, pubKey: String, timestamp: UInt64, ttl: UInt64) -> String? { - let payload = createPayload(pubKey: pubKey, data: data, timestamp: timestamp, ttl: ttl) - let target = calcTarget(ttl: ttl, payloadLength: payload.count, nonceTrials: Int(SnodeAPI.powDifficulty)) - - // Start with the max value - var trialValue = UInt64.max - - let initialHash = payload.sha512() - var nonce = [UInt8](repeating: 0, count: nonceLength) - - while trialValue > target { - nonce = nonce.increment(by: 1) - - // This is different to the bitmessage PoW - // resultHash = hash(nonce + hash(data)) ==> hash(nonce + initialHash) - let resultHash = (nonce + initialHash).sha512() - let trialValueArray = Array(resultHash[0..<8]) - trialValue = UInt64(trialValueArray) - } - - return nonce.toBase64() - } - - /// Get the proof of work payload - private static func createPayload(pubKey: String, data: String, timestamp: UInt64, ttl: UInt64) -> [UInt8] { - let timestampString = String(timestamp) - let ttlString = String(ttl) - let payloadString = timestampString + ttlString + pubKey + data - return payloadString.bytes - } - - /// Calculate the target we need to reach - private static func calcTarget(ttl: UInt64, payloadLength: Int, nonceTrials: Int) -> UInt64 { - let two16 = UInt64(pow(2, 16) - 1) - let two64 = UInt64(pow(2, 64) - 1) - - // Do all the calculations - let totalLength = UInt64(payloadLength + nonceLength) - let ttlInSeconds = ttl / 1000 - let ttlMult = ttlInSeconds * totalLength - - // UInt64 values - let innerFrac = ttlMult / two16 - let lenPlusInnerFrac = totalLength + innerFrac - let denominator = UInt64(nonceTrials) * lenPlusInnerFrac - - return two64 / denominator - } -} diff --git a/SignalServiceKit/src/Loki/API/DotNetAPI.swift b/SignalServiceKit/src/Loki/API/DotNetAPI.swift deleted file mode 100644 index 7fc906bfd..000000000 --- a/SignalServiceKit/src/Loki/API/DotNetAPI.swift +++ /dev/null @@ -1,225 +0,0 @@ -import PromiseKit -import SessionMetadataKit - -/// Base class for `FileServerAPI` and `PublicChatAPI`. -public class DotNetAPI : NSObject { - - internal static var userKeyPair: ECKeyPair { OWSIdentityManager.shared().identityKeyPair()! } - - // MARK: Settings - private static let attachmentType = "network.loki" - private static let maxRetryCount: UInt = 4 - - // MARK: Error - @objc(LKDotNetAPIError) - public class DotNetAPIError : NSError { // Not called `Error` for Obj-C interoperablity - - @objc public static let generic = DotNetAPIError(domain: "DotNetAPIErrorDomain", code: 1, userInfo: [ NSLocalizedDescriptionKey : "An error occurred." ]) - @objc public static let parsingFailed = DotNetAPIError(domain: "DotNetAPIErrorDomain", code: 2, userInfo: [ NSLocalizedDescriptionKey : "Invalid file server response." ]) - @objc public static let signingFailed = DotNetAPIError(domain: "DotNetAPIErrorDomain", code: 3, userInfo: [ NSLocalizedDescriptionKey : "Couldn't sign message." ]) - @objc public static let encryptionFailed = DotNetAPIError(domain: "DotNetAPIErrorDomain", code: 4, userInfo: [ NSLocalizedDescriptionKey : "Couldn't encrypt file." ]) - @objc public static let decryptionFailed = DotNetAPIError(domain: "DotNetAPIErrorDomain", code: 5, userInfo: [ NSLocalizedDescriptionKey : "Couldn't decrypt file." ]) - @objc public static let maxFileSizeExceeded = DotNetAPIError(domain: "DotNetAPIErrorDomain", code: 6, userInfo: [ NSLocalizedDescriptionKey : "Maximum file size exceeded." ]) - } - - // MARK: Storage - /// To be overridden by subclasses. - internal class var authTokenCollection: String { preconditionFailure("authTokenCollection is abstract and must be overridden.") } - - internal static func getAuthToken(for server: String) -> Promise { - if let token = getAuthTokenFromDatabase(for: server) { - return Promise.value(token) - } else { - return requestNewAuthToken(for: server).then2 { submitAuthToken($0, for: server) }.map2 { token in - Storage.writeSync { transaction in - setAuthToken(for: server, to: token, in: transaction) - } - return token - } - } - } - - private static func getAuthTokenFromDatabase(for server: String) -> String? { - var result: String? = nil - Storage.read { transaction in - result = transaction.object(forKey: server, inCollection: authTokenCollection) as? String - } - return result - } - - private static func setAuthToken(for server: String, to newValue: String, in transaction: YapDatabaseReadWriteTransaction) { - transaction.setObject(newValue, forKey: server, inCollection: authTokenCollection) - } - - public static func removeAuthToken(for server: String) { - Storage.writeSync { transaction in - transaction.removeObject(forKey: server, inCollection: authTokenCollection) - } - } - - // MARK: Lifecycle - override private init() { } - - // MARK: Private API - private static func requestNewAuthToken(for server: String) -> Promise { - print("[Loki] Requesting auth token for server: \(server).") - let queryParameters = "pubKey=\(getUserHexEncodedPublicKey())" - let url = URL(string: "\(server)/loki/v1/get_challenge?\(queryParameters)")! - let request = TSRequest(url: url) - let serverPublicKeyPromise = (server == FileServerAPI.server) ? Promise.value(FileServerAPI.fileServerPublicKey) - : PublicChatAPI.getOpenGroupServerPublicKey(for: server) - return serverPublicKeyPromise.then2 { serverPublicKey in - OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey) - }.map2 { json in - guard let base64EncodedChallenge = json["cipherText64"] as? String, let base64EncodedServerPublicKey = json["serverPubKey64"] as? String, - let challenge = Data(base64Encoded: base64EncodedChallenge), var serverPublicKey = Data(base64Encoded: base64EncodedServerPublicKey) else { - throw DotNetAPIError.parsingFailed - } - // Discard the "05" prefix if needed - if serverPublicKey.count == 33 { - let hexEncodedServerPublicKey = serverPublicKey.toHexString() - serverPublicKey = Data.data(fromHex: hexEncodedServerPublicKey.substring(from: 2))! - } - // The challenge is prefixed by the 16 bit IV - guard let tokenAsData = try? DiffieHellman.decrypt(challenge, publicKey: serverPublicKey, privateKey: userKeyPair.privateKey), - let token = String(bytes: tokenAsData, encoding: .utf8) else { - throw DotNetAPIError.decryptionFailed - } - return token - } - } - - private static func submitAuthToken(_ token: String, for server: String) -> Promise { - print("[Loki] Submitting auth token for server: \(server).") - let url = URL(string: "\(server)/loki/v1/submit_challenge")! - let parameters = [ "pubKey" : getUserHexEncodedPublicKey(), "token" : token ] - let request = TSRequest(url: url, method: "POST", parameters: parameters) - let serverPublicKeyPromise = (server == FileServerAPI.server) ? Promise.value(FileServerAPI.fileServerPublicKey) - : PublicChatAPI.getOpenGroupServerPublicKey(for: server) - return serverPublicKeyPromise.then2 { serverPublicKey in - OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey) - }.map2 { _ in token } - } - - // MARK: Public API - @objc(downloadAttachmentFrom:) - public static func objc_downloadAttachment(from url: String) -> AnyPromise { - return AnyPromise.from(downloadAttachment(from: url)) - } - - public static func downloadAttachment(from url: String) -> Promise { - var error: NSError? - var host = "https://\(URL(string: url)!.host!)" - let sanitizedURL: String - if FileServerAPI.fileStorageBucketURL.contains(host) { - sanitizedURL = url.replacingOccurrences(of: FileServerAPI.fileStorageBucketURL, with: "\(FileServerAPI.server)/loki/v1") - host = FileServerAPI.server - } else { - sanitizedURL = url.replacingOccurrences(of: host, with: "\(host)/loki/v1") - } - let request = AFHTTPRequestSerializer().request(withMethod: "GET", urlString: sanitizedURL, parameters: nil, error: &error) - if let error = error { - print("[Loki] Couldn't download attachment due to error: \(error).") - return Promise(error: error) - } - let serverPublicKeyPromise = FileServerAPI.server.contains(host) ? Promise.value(FileServerAPI.fileServerPublicKey) - : PublicChatAPI.getOpenGroupServerPublicKey(for: host) - return attempt(maxRetryCount: maxRetryCount, recoveringOn: SnodeAPI.workQueue) { - serverPublicKeyPromise.then2 { serverPublicKey in - return OnionRequestAPI.sendOnionRequest(request, to: host, using: serverPublicKey, isJSONRequired: false).map2 { json in - guard let body = json["body"] as? JSON, let data = body["data"] as? [UInt8] else { - print("[Loki] Couldn't parse attachment from: \(json).") - throw DotNetAPIError.parsingFailed - } - return Data(data) - } - } - } - } - - @objc(uploadAttachment:withID:toServer:) - public static func objc_uploadAttachment(_ attachment: TSAttachmentStream, with attachmentID: String, to server: String) -> AnyPromise { - return AnyPromise.from(uploadAttachment(attachment, with: attachmentID, to: server)) - } - - public static func uploadAttachment(_ attachment: TSAttachmentStream, with attachmentID: String, to server: String) -> Promise { - let isEncryptionRequired = (server == FileServerAPI.server) - return Promise() { seal in - func proceed(with token: String) { - // Get the attachment - let data: Data - guard let unencryptedAttachmentData = try? attachment.readDataFromFile() else { - print("[Loki] Couldn't read attachment from disk.") - return seal.reject(DotNetAPIError.generic) - } - // Encrypt the attachment if needed - if isEncryptionRequired { - var encryptionKey = NSData() - var digest = NSData() - guard let encryptedAttachmentData = Cryptography.encryptAttachmentData(unencryptedAttachmentData, outKey: &encryptionKey, outDigest: &digest) else { - print("[Loki] Couldn't encrypt attachment.") - return seal.reject(DotNetAPIError.encryptionFailed) - } - attachment.encryptionKey = encryptionKey as Data - attachment.digest = digest as Data - data = encryptedAttachmentData - } else { - data = unencryptedAttachmentData - } - // Check the file size if needed - print("[Loki] File size: \(data.count) bytes.") - if Double(data.count) > Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier { - return seal.reject(DotNetAPIError.maxFileSizeExceeded) - } - // Create the request - let url = "\(server)/files" - let parameters: JSON = [ "type" : attachmentType, "Content-Type" : "application/binary" ] - var error: NSError? - var request = AFHTTPRequestSerializer().multipartFormRequest(withMethod: "POST", urlString: url, parameters: parameters, constructingBodyWith: { formData in - let uuid = UUID().uuidString - print("[Loki] File UUID: \(uuid).") - formData.appendPart(withFileData: data, name: "content", fileName: uuid, mimeType: "application/binary") - }, error: &error) - request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - if let error = error { - print("[Loki] Couldn't upload attachment due to error: \(error).") - return seal.reject(error) - } - // Send the request - let serverPublicKeyPromise = (server == FileServerAPI.server) ? Promise.value(FileServerAPI.fileServerPublicKey) - : PublicChatAPI.getOpenGroupServerPublicKey(for: server) - attachment.isUploaded = false - attachment.save() - let _ = serverPublicKeyPromise.then2 { serverPublicKey in - OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey) - }.done2 { json in - // Parse the server ID & download URL - guard let data = json["data"] as? JSON, let serverID = data["id"] as? UInt64, let downloadURL = data["url"] as? String else { - print("[Loki] Couldn't parse attachment from: \(json).") - return seal.reject(DotNetAPIError.parsingFailed) - } - // Update the attachment - attachment.serverId = serverID - attachment.isUploaded = true - attachment.downloadURL = downloadURL - attachment.save() - seal.fulfill(()) - }.catch2 { error in - seal.reject(error) - } - } - if server == FileServerAPI.server { - DispatchQueue.global(qos: .userInitiated).async { - proceed(with: "loki") // Uploads to the Loki File Server shouldn't include any personally identifiable information so use a dummy auth token - } - } else { - getAuthToken(for: server).done(on: DispatchQueue.global(qos: .userInitiated)) { token in - proceed(with: token) - }.catch2 { error in - print("[Loki] Couldn't upload attachment due to error: \(error).") - seal.reject(error) - } - } - } - } -} diff --git a/SignalServiceKit/src/Loki/API/FileServerAPI.swift b/SignalServiceKit/src/Loki/API/FileServerAPI.swift deleted file mode 100644 index 003785850..000000000 --- a/SignalServiceKit/src/Loki/API/FileServerAPI.swift +++ /dev/null @@ -1,75 +0,0 @@ -import PromiseKit - -@objc(LKFileServerAPI) -public final class FileServerAPI : DotNetAPI { - - // MARK: Settings - private static let attachmentType = "net.app.core.oembed" - private static let deviceLinkType = "network.loki.messenger.devicemapping" - - internal static let fileServerPublicKey = "62509D59BDEEC404DD0D489C1E15BA8F94FD3D619B01C1BF48A9922BFCB7311C" - - public static let maxFileSize = 10_000_000 // 10 MB - /// The file server has a file size limit of `maxFileSize`, which the Service Nodes try to enforce as well. However, the limit applied by the Service Nodes - /// is on the **HTTP request** and not the actual file size. Because the file server expects the file data to be base 64 encoded, the size of the HTTP - /// request for a given file will be at least `ceil(n / 3) * 4` bytes, where n is the file size in bytes. This is the minimum size because there might also - /// be other parameters in the request. On average the multiplier appears to be about 1.5, so when checking whether the file will exceed the file size limit when - /// uploading a file we just divide the size of the file by this number. The alternative would be to actually check the size of the HTTP request but that's only - /// possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds. - public static let fileSizeORMultiplier: Double = 6 // TODO: It should be possible to set this to 1.5? - - @objc public static let server = "https://file.getsession.org" - @objc public static let fileStorageBucketURL = "https://file-static.lokinet.org" - - // MARK: Storage - override internal class var authTokenCollection: String { return "LokiStorageAuthTokenCollection" } - - // MARK: Profile Pictures - @objc(uploadProfilePicture:) - public static func objc_uploadProfilePicture(_ profilePicture: Data) -> AnyPromise { - return AnyPromise.from(uploadProfilePicture(profilePicture)) - } - - public static func uploadProfilePicture(_ profilePicture: Data) -> Promise { - guard Double(profilePicture.count) < Double(maxFileSize) / fileSizeORMultiplier else { return Promise(error: DotNetAPIError.maxFileSizeExceeded) } - let url = "\(server)/files" - let parameters: JSON = [ "type" : attachmentType, "Content-Type" : "application/binary" ] - var error: NSError? - var request = AFHTTPRequestSerializer().multipartFormRequest(withMethod: "POST", urlString: url, parameters: parameters, constructingBodyWith: { formData in - formData.appendPart(withFileData: profilePicture, name: "content", fileName: UUID().uuidString, mimeType: "application/binary") - }, error: &error) - // Uploads to the Loki File Server shouldn't include any personally identifiable information so use a dummy auth token - request.addValue("Bearer loki", forHTTPHeaderField: "Authorization") - if let error = error { - print("[Loki] Couldn't upload profile picture due to error: \(error).") - return Promise(error: error) - } - return OnionRequestAPI.sendOnionRequest(request, to: server, using: fileServerPublicKey).map2 { json in - guard let data = json["data"] as? JSON, let downloadURL = data["url"] as? String else { - print("[Loki] Couldn't parse profile picture from: \(json).") - throw DotNetAPIError.parsingFailed - } - UserDefaults.standard[.lastProfilePictureUpload] = Date() - return downloadURL - } - } - - // MARK: Open Group Server Public Key - public static func getPublicKey(for openGroupServer: String) -> Promise { - let url = URL(string: "\(server)/loki/v1/getOpenGroupKey/\(URL(string: openGroupServer)!.host!)")! - let request = TSRequest(url: url) - let token = "loki" // Tokenless request; use a dummy token - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - return OnionRequestAPI.sendOnionRequest(request, to: server, using: fileServerPublicKey).map2 { json in - guard let bodyAsString = json["data"] as? String, let bodyAsData = bodyAsString.data(using: .utf8), - let body = try JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { throw HTTP.Error.invalidJSON } - guard let base64EncodedPublicKey = body["data"] as? String else { - print("[Loki] Couldn't parse open group public key from: \(body).") - throw DotNetAPIError.parsingFailed - } - let prefixedPublicKey = Data(base64Encoded: base64EncodedPublicKey)! - let hexEncodedPrefixedPublicKey = prefixedPublicKey.toHexString() - return hexEncodedPrefixedPublicKey.removing05PrefixIfNeeded() - } - } -} diff --git a/SignalServiceKit/src/Loki/API/LokiMessage.swift b/SignalServiceKit/src/Loki/API/LokiMessage.swift deleted file mode 100644 index 562a6870a..000000000 --- a/SignalServiceKit/src/Loki/API/LokiMessage.swift +++ /dev/null @@ -1,75 +0,0 @@ -import PromiseKit - -public struct LokiMessage { - /// The hex encoded public key of the recipient. - let recipientPublicKey: String - /// The content of the message. - let data: LosslessStringConvertible - /// The time to live for the message in milliseconds. - let ttl: UInt64 - /// Whether this message is a ping. - /// - /// - Note: The concept of pinging only applies to P2P messaging. - let isPing: Bool - /// When the proof of work was calculated, if applicable (P2P messages don't require proof of work). - /// - /// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970. - private(set) var timestamp: UInt64? = nil - /// The base 64 encoded proof of work, if applicable (P2P messages don't require proof of work). - private(set) var nonce: String? = nil - - private init(destination: String, data: LosslessStringConvertible, ttl: UInt64, isPing: Bool) { - self.recipientPublicKey = destination - self.data = data - self.ttl = ttl - self.isPing = isPing - } - - /// Construct a `LokiMessage` from a `SignalMessage`. - /// - /// - Note: `timestamp` is the original message timestamp (i.e. `TSOutgoingMessage.timestamp`). - public static func from(signalMessage: SignalMessage) -> LokiMessage? { - // To match the desktop application, we have to wrap the data in an envelope and then wrap that in a websocket object - do { - let wrappedMessage = try MessageWrapper.wrap(message: signalMessage) - let data = wrappedMessage.base64EncodedString() - let destination = signalMessage.recipientPublicKey - var ttl = TTLUtilities.fallbackMessageTTL - if let messageTTL = signalMessage.ttl, messageTTL > 0 { ttl = UInt64(messageTTL) } - let isPing = signalMessage.isPing - return LokiMessage(destination: destination, data: data, ttl: ttl, isPing: isPing) - } catch let error { - print("[Loki] Failed to convert Signal message to Loki message: \(signalMessage).") - return nil - } - } - - /// Calculate the proof of work for this message. - /// - /// - Returns: The promise of a new message with its `timestamp` and `nonce` set. - public func calculatePoW() -> Promise { - return Promise { seal in - DispatchQueue.global(qos: .userInitiated).async { - let now = NSDate.ows_millisecondTimeStamp() - let dataAsString = self.data as! String // Safe because of how from(signalMessage:with:) is implemented - if let nonce = ProofOfWork.calculate(data: dataAsString, pubKey: self.recipientPublicKey, timestamp: now, ttl: self.ttl) { - var result = self - result.timestamp = now - result.nonce = nonce - seal.fulfill(result) - } else { - seal.reject(SnodeAPI.SnodeAPIError.proofOfWorkCalculationFailed) - } - } - } - } - - public func toJSON() -> JSON { - var result = [ "pubKey" : recipientPublicKey, "data" : data.description, "ttl" : String(ttl) ] - if let timestamp = timestamp, let nonce = nonce { - result["timestamp"] = String(timestamp) - result["nonce"] = nonce - } - return result - } -} diff --git a/SignalServiceKit/src/Loki/API/MessageWrapper.swift b/SignalServiceKit/src/Loki/API/MessageWrapper.swift deleted file mode 100644 index f19626906..000000000 --- a/SignalServiceKit/src/Loki/API/MessageWrapper.swift +++ /dev/null @@ -1,72 +0,0 @@ - -public enum MessageWrapper { - - public enum Error : LocalizedError { - case failedToWrapData - case failedToWrapMessageInEnvelope - case failedToWrapEnvelopeInWebSocketMessage - case failedToUnwrapData - - public var errorDescription: String? { - switch self { - case .failedToWrapData: return "Failed to wrap data." - case .failedToWrapMessageInEnvelope: return "Failed to wrap message in envelope." - case .failedToWrapEnvelopeInWebSocketMessage: return "Failed to wrap envelope in web socket message." - case .failedToUnwrapData: return "Failed to unwrap data." - } - } - } - - /// Wraps `message` in an `SSKProtoEnvelope` and then a `WebSocketProtoWebSocketMessage` to match the desktop application. - public static func wrap(message: SignalMessage) throws -> Data { - do { - let envelope = try createEnvelope(around: message) - let webSocketMessage = try createWebSocketMessage(around: envelope) - return try webSocketMessage.serializedData() - } catch let error { - throw error as? Error ?? Error.failedToWrapData - } - } - - private static func createEnvelope(around message: SignalMessage) throws -> SSKProtoEnvelope { - do { - let builder = SSKProtoEnvelope.builder(type: message.type, timestamp: message.timestamp) - builder.setSource(message.senderPublicKey) - builder.setSourceDevice(message.senderDeviceID) - if let content = try Data(base64Encoded: message.content, options: .ignoreUnknownCharacters) { - builder.setContent(content) - } else { - throw Error.failedToWrapMessageInEnvelope - } - return try builder.build() - } catch let error { - print("[Loki] Failed to wrap message in envelope: \(error).") - throw Error.failedToWrapMessageInEnvelope - } - } - - private static func createWebSocketMessage(around envelope: SSKProtoEnvelope) throws -> WebSocketProtoWebSocketMessage { - do { - let requestBuilder = WebSocketProtoWebSocketRequestMessage.builder(verb: "PUT", path: "/api/v1/message", requestID: UInt64.random(in: 1.. SSKProtoEnvelope { - do { - let webSocketMessage = try WebSocketProtoWebSocketMessage.parseData(data) - let envelope = webSocketMessage.request!.body! - return try SSKProtoEnvelope.parseData(envelope) - } catch let error { - print("[Loki] Failed to unwrap data: \(error).") - throw Error.failedToUnwrapData - } - } -} diff --git a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift deleted file mode 100644 index 84a8da122..000000000 --- a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI+Encryption.swift +++ /dev/null @@ -1,72 +0,0 @@ -import CryptoSwift -import PromiseKit - -extension OnionRequestAPI { - - internal static func encode(ciphertext: Data, json: JSON) throws -> Data { - // The encoding of V2 onion requests looks like: | 4 bytes: size N of ciphertext | N bytes: ciphertext | json as utf8 | - guard JSONSerialization.isValidJSONObject(json) else { throw HTTP.Error.invalidJSON } - let jsonAsData = try JSONSerialization.data(withJSONObject: json, options: [ .fragmentsAllowed ]) - let ciphertextSize = Int32(ciphertext.count).littleEndian - let ciphertextSizeAsData = withUnsafePointer(to: ciphertextSize) { Data(bytes: $0, count: MemoryLayout.size) } - return ciphertextSizeAsData + ciphertext + jsonAsData - } - - /// Encrypts `payload` for `destination` and returns the result. Use this to build the core of an onion request. - internal static func encrypt(_ payload: JSON, for destination: Destination) -> Promise { - let (promise, seal) = Promise.pending() - DispatchQueue.global(qos: .userInitiated).async { - do { - guard JSONSerialization.isValidJSONObject(payload) else { return seal.reject(HTTP.Error.invalidJSON) } - // Wrapping isn't needed for file server or open group onion requests - switch destination { - case .snode(let snode): - guard let snodeX25519PublicKey = snode.publicKeySet?.x25519Key else { return seal.reject(Error.snodePublicKeySetMissing) } - let payloadAsData = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ]) - let plaintext = try encode(ciphertext: payloadAsData, json: [ "headers" : "" ]) - let result = try EncryptionUtilities.encrypt(plaintext, using: snodeX25519PublicKey) - seal.fulfill(result) - case .server(_, let serverX25519PublicKey): - let plaintext = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ]) - let result = try EncryptionUtilities.encrypt(plaintext, using: serverX25519PublicKey) - seal.fulfill(result) - } - } catch (let error) { - seal.reject(error) - } - } - return promise - } - - /// Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request. - internal static func encryptHop(from lhs: Destination, to rhs: Destination, using previousEncryptionResult: EncryptionResult) -> Promise { - let (promise, seal) = Promise.pending() - DispatchQueue.global(qos: .userInitiated).async { - var parameters: JSON - switch rhs { - case .snode(let snode): - guard let snodeED25519PublicKey = snode.publicKeySet?.ed25519Key else { return seal.reject(Error.snodePublicKeySetMissing) } - parameters = [ "destination" : snodeED25519PublicKey ] - case .server(let host, _): - parameters = [ "host" : host, "target" : "/loki/v2/lsrpc", "method" : "POST" ] - } - parameters["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString() - let x25519PublicKey: String - switch lhs { - case .snode(let snode): - guard let snodeX25519PublicKey = snode.publicKeySet?.x25519Key else { return seal.reject(Error.snodePublicKeySetMissing) } - x25519PublicKey = snodeX25519PublicKey - case .server(_, let serverX25519PublicKey): - x25519PublicKey = serverX25519PublicKey - } - do { - let plaintext = try encode(ciphertext: previousEncryptionResult.ciphertext, json: parameters) - let result = try EncryptionUtilities.encrypt(plaintext, using: x25519PublicKey) - seal.fulfill(result) - } catch (let error) { - seal.reject(error) - } - } - return promise - } -} diff --git a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift b/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift deleted file mode 100644 index 66432ef61..000000000 --- a/SignalServiceKit/src/Loki/API/Onion Requests/OnionRequestAPI.swift +++ /dev/null @@ -1,435 +0,0 @@ -import CryptoSwift -import PromiseKit - -/// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information. -public enum OnionRequestAPI { - private static var pathFailureCount: [Path:UInt] = [:] - private static var snodeFailureCount: [Snode:UInt] = [:] - public static var guardSnodes: Set = [] - public static var paths: [Path] = [] // Not a set to ensure we consistently show the same path to the user - - // MARK: Settings - /// The number of snodes (including the guard snode) in a path. - private static let pathSize: UInt = 3 - /// The number of times a path can fail before it's replaced. - private static let pathFailureThreshold: UInt = 3 - /// The number of times a snode can fail before it's replaced. - private static let snodeFailureThreshold: UInt = 3 - /// The number of paths to maintain. - public static let targetPathCount: UInt = 2 - - /// The number of guard snodes required to maintain `targetPathCount` paths. - private static var targetGuardSnodeCount: UInt { return targetPathCount } // One per path - - // MARK: Destination - internal enum Destination { - case snode(Snode) - case server(host: String, x25519PublicKey: String) - } - - // MARK: Error - public enum Error : LocalizedError { - case httpRequestFailedAtDestination(statusCode: UInt, json: JSON) - case insufficientSnodes - case invalidURL - case missingSnodeVersion - case snodePublicKeySetMissing - case unsupportedSnodeVersion(String) - - public var errorDescription: String? { - switch self { - case .httpRequestFailedAtDestination(let statusCode): return "HTTP request failed at destination with status code: \(statusCode)." - case .insufficientSnodes: return "Couldn't find enough snodes to build a path." - case .invalidURL: return "Invalid URL" - case .missingSnodeVersion: return "Missing snode version." - case .snodePublicKeySetMissing: return "Missing snode public key set." - case .unsupportedSnodeVersion(let version): return "Unsupported snode version: \(version)." - } - } - } - - // MARK: Path - public typealias Path = [Snode] - - // MARK: Onion Building Result - private typealias OnionBuildingResult = (guardSnode: Snode, finalEncryptionResult: EncryptionResult, destinationSymmetricKey: Data) - - // MARK: Private API - /// Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise. - private static func testSnode(_ snode: Snode) -> Promise { - let (promise, seal) = Promise.pending() - DispatchQueue.global(qos: .userInitiated).async { - let url = "\(snode.address):\(snode.port)/get_stats/v1" - let timeout: TimeInterval = 3 // Use a shorter timeout for testing - HTTP.execute(.get, url, timeout: timeout).done2 { rawResponse in - guard let json = rawResponse as? JSON, let version = json["version"] as? String else { return seal.reject(Error.missingSnodeVersion) } - if version >= "2.0.7" { - seal.fulfill(()) - } else { - print("[Loki] [Onion Request API] Unsupported snode version: \(version).") - seal.reject(Error.unsupportedSnodeVersion(version)) - } - }.catch2 { error in - seal.reject(error) - } - } - return promise - } - - /// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with `Error.insufficientSnodes` - /// if not enough (reliable) snodes are available. - private static func getGuardSnodes(reusing reusableGuardSnodes: [Snode]) -> Promise> { - if guardSnodes.count >= targetGuardSnodeCount { - return Promise> { $0.fulfill(guardSnodes) } - } else { - print("[Loki] [Onion Request API] Populating guard snode cache.") - return SnodeAPI.getRandomSnode().then2 { _ -> Promise> in // Just used to populate the snode pool - var unusedSnodes = SnodeAPI.snodePool.subtracting(reusableGuardSnodes) // Sync on LokiAPI.workQueue - let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count) - guard unusedSnodes.count >= (targetGuardSnodeCount - reusableGuardSnodeCount) else { throw Error.insufficientSnodes } - func getGuardSnode() -> Promise { - // randomElement() uses the system's default random generator, which is cryptographically secure - guard let candidate = unusedSnodes.randomElement() else { return Promise { $0.reject(Error.insufficientSnodes) } } - unusedSnodes.remove(candidate) // All used snodes should be unique - print("[Loki] [Onion Request API] Testing guard snode: \(candidate).") - // Loop until a reliable guard snode is found - return testSnode(candidate).map2 { candidate }.recover(on: DispatchQueue.main) { _ in - withDelay(0.1, completionQueue: SnodeAPI.workQueue) { getGuardSnode() } - } - } - let promises = (0..<(targetGuardSnodeCount - reusableGuardSnodeCount)).map { _ in getGuardSnode() } - return when(fulfilled: promises).map2 { guardSnodes in - let guardSnodesAsSet = Set(guardSnodes + reusableGuardSnodes) - OnionRequestAPI.guardSnodes = guardSnodesAsSet - return guardSnodesAsSet - } - } - } - } - - /// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes` - /// if not enough (reliable) snodes are available. - private static func buildPaths(reusing reusablePaths: [Path]) -> Promise<[Path]> { - print("[Loki] [Onion Request API] Building onion request paths.") - DispatchQueue.main.async { - NotificationCenter.default.post(name: .buildingPaths, object: nil) - } - return SnodeAPI.getRandomSnode().then2 { _ -> Promise<[Path]> in // Just used to populate the snode pool - let reusableGuardSnodes = reusablePaths.map { $0[0] } - return getGuardSnodes(reusing: reusableGuardSnodes).map2 { guardSnodes -> [Path] in - var unusedSnodes = SnodeAPI.snodePool.subtracting(guardSnodes).subtracting(reusablePaths.flatMap { $0 }) - let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count) - let pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount) - guard unusedSnodes.count >= pathSnodeCount else { throw Error.insufficientSnodes } - // Don't test path snodes as this would reveal the user's IP to them - return guardSnodes.subtracting(reusableGuardSnodes).map { guardSnode in - let result = [ guardSnode ] + (0..<(pathSize - 1)).map { _ in - // randomElement() uses the system's default random generator, which is cryptographically secure - let pathSnode = unusedSnodes.randomElement()! // Safe because of the pathSnodeCount check above - unusedSnodes.remove(pathSnode) // All used snodes should be unique - return pathSnode - } - print("[Loki] [Onion Request API] Built new onion request path: \(result.prettifiedDescription).") - return result - } - }.map2 { paths in - OnionRequestAPI.paths = paths + reusablePaths - Storage.writeSync { transaction in - print("[Loki] Persisting onion request paths to database.") - Storage.setOnionRequestPaths(paths, using: transaction) - } - DispatchQueue.main.async { - NotificationCenter.default.post(name: .pathsBuilt, object: nil) - } - return paths - } - } - } - - /// Returns a `Path` to be used for building an onion request. Builds new paths as needed. - private static func getPath(excluding snode: Snode?) -> Promise { - guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") } - var paths = OnionRequestAPI.paths - if paths.isEmpty { - paths = Storage.getOnionRequestPaths() - OnionRequestAPI.paths = paths - if !paths.isEmpty { - guardSnodes.formUnion([ paths[0][0] ]) - if paths.count >= 2 { - guardSnodes.formUnion([ paths[1][0] ]) - } - } - } - // randomElement() uses the system's default random generator, which is cryptographically secure - if paths.count >= targetPathCount { - if let snode = snode { - return Promise { $0.fulfill(paths.filter { !$0.contains(snode) }.randomElement()!) } - } else { - return Promise { $0.fulfill(paths.randomElement()!) } - } - } else if !paths.isEmpty { - if let snode = snode { - if let path = paths.first(where: { !$0.contains(snode) }) { - buildPaths(reusing: paths).retainUntilComplete() // Re-build paths in the background - return Promise { $0.fulfill(path) } - } else { - return buildPaths(reusing: paths).map2 { paths in - return paths.filter { !$0.contains(snode) }.randomElement()! - } - } - } else { - buildPaths(reusing: paths).retainUntilComplete() // Re-build paths in the background - return Promise { $0.fulfill(paths.randomElement()!) } - } - } else { - return buildPaths(reusing: []).map2 { paths in - if let snode = snode { - return paths.filter { !$0.contains(snode) }.randomElement()! - } else { - return paths.randomElement()! - } - } - } - } - - private static func dropGuardSnode(_ snode: Snode) { - guardSnodes = guardSnodes.filter { $0 != snode } - } - - private static func drop(_ snode: Snode) throws { - // We repair the path here because we can do it sync. In the case where we drop a whole - // path we leave the re-building up to getPath(excluding:) because re-building the path - // in that case is async. - OnionRequestAPI.snodeFailureCount[snode] = 0 - var oldPaths = paths - guard let pathIndex = oldPaths.firstIndex(where: { $0.contains(snode) }) else { return } - var path = oldPaths[pathIndex] - guard let snodeIndex = path.firstIndex(of: snode) else { return } - path.remove(at: snodeIndex) - let unusedSnodes = SnodeAPI.snodePool.subtracting(oldPaths.flatMap { $0 }) - guard !unusedSnodes.isEmpty else { throw Error.insufficientSnodes } - // randomElement() uses the system's default random generator, which is cryptographically secure - path.append(unusedSnodes.randomElement()!) - // Don't test the new snode as this would reveal the user's IP - oldPaths.remove(at: pathIndex) - let newPaths = oldPaths + [ path ] - paths = newPaths - Storage.writeSync { transaction in - print("[Loki] Persisting onion request paths to database.") - Storage.setOnionRequestPaths(newPaths, using: transaction) - } - } - - private static func drop(_ path: Path) { - OnionRequestAPI.pathFailureCount[path] = 0 - var paths = OnionRequestAPI.paths - guard let pathIndex = paths.firstIndex(of: path) else { return } - paths.remove(at: pathIndex) - OnionRequestAPI.paths = paths - Storage.writeSync { transaction in - if !paths.isEmpty { - print("[Loki] Persisting onion request paths to database.") - Storage.setOnionRequestPaths(paths, using: transaction) - } else { - Storage.clearOnionRequestPaths(using: transaction) - } - } - } - - /// Builds an onion around `payload` and returns the result. - private static func buildOnion(around payload: JSON, targetedAt destination: Destination) -> Promise { - var guardSnode: Snode! - var targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the destination - var encryptionResult: EncryptionResult! - var snodeToExclude: Snode? - if case .snode(let snode) = destination { snodeToExclude = snode } - return getPath(excluding: snodeToExclude).then2 { path -> Promise in - guardSnode = path.first! - // Encrypt in reverse order, i.e. the destination first - return encrypt(payload, for: destination).then2 { r -> Promise in - targetSnodeSymmetricKey = r.symmetricKey - // Recursively encrypt the layers of the onion (again in reverse order) - encryptionResult = r - var path = path - var rhs = destination - func addLayer() -> Promise { - if path.isEmpty { - return Promise { $0.fulfill(encryptionResult) } - } else { - let lhs = Destination.snode(path.removeLast()) - return OnionRequestAPI.encryptHop(from: lhs, to: rhs, using: encryptionResult).then2 { r -> Promise in - encryptionResult = r - rhs = lhs - return addLayer() - } - } - } - return addLayer() - } - }.map2 { _ in (guardSnode, encryptionResult, targetSnodeSymmetricKey) } - } - - // MARK: Internal API - /// Sends an onion request to `snode`. Builds new paths as needed. - internal static func sendOnionRequest(to snode: Snode, invoking method: Snode.Method, with parameters: JSON, associatedWith publicKey: String) -> Promise { - let payload: JSON = [ "method" : method.rawValue, "params" : parameters ] - return sendOnionRequest(with: payload, to: Destination.snode(snode)).recover2 { error -> Promise in - guard case OnionRequestAPI.Error.httpRequestFailedAtDestination(let statusCode, let json) = error else { throw error } - throw SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode, associatedWith: publicKey) ?? error - } - } - - /// Sends an onion request to `server`. Builds new paths as needed. - internal static func sendOnionRequest(_ request: NSURLRequest, to server: String, using x25519PublicKey: String, isJSONRequired: Bool = true) -> Promise { - var rawHeaders = request.allHTTPHeaderFields ?? [:] - rawHeaders.removeValue(forKey: "User-Agent") - var headers: JSON = rawHeaders.mapValues { value in - switch value.lowercased() { - case "true": return true - case "false": return false - default: return value - } - } - guard let url = request.url?.absoluteString, let host = request.url?.host else { return Promise(error: Error.invalidURL) } - var endpoint = "" - if server.count < url.count { - guard let serverEndIndex = url.range(of: server)?.upperBound else { return Promise(error: Error.invalidURL) } - let endpointStartIndex = url.index(after: serverEndIndex) - endpoint = String(url[endpointStartIndex.. Promise { - let (promise, seal) = Promise.pending() - var guardSnode: Snode! - SnodeAPI.workQueue.async { // Avoid race conditions on `guardSnodes` and `paths` - buildOnion(around: payload, targetedAt: destination).done2 { intermediate in - guardSnode = intermediate.guardSnode - let url = "\(guardSnode.address):\(guardSnode.port)/onion_req/v2" - let finalEncryptionResult = intermediate.finalEncryptionResult - let onion = finalEncryptionResult.ciphertext - if case Destination.server = destination, Double(onion.count) > 0.75 * Double(FileServerAPI.maxFileSize) { - print("[Loki] Approaching request size limit: ~\(onion.count) bytes.") - } - let parameters: JSON = [ - "ephemeral_key" : finalEncryptionResult.ephemeralPublicKey.toHexString() - ] - let body: Data - do { - body = try encode(ciphertext: onion, json: parameters) - } catch { - return seal.reject(error) - } - let destinationSymmetricKey = intermediate.destinationSymmetricKey - HTTP.execute(.post, url, body: body).done2 { rawResponse in - guard let json = rawResponse as? JSON, let base64EncodedIVAndCiphertext = json["result"] as? String, - let ivAndCiphertext = Data(base64Encoded: base64EncodedIVAndCiphertext), ivAndCiphertext.count >= EncryptionUtilities.ivSize else { return seal.reject(HTTP.Error.invalidJSON) } - do { - let data = try DecryptionUtilities.decrypt(ivAndCiphertext, usingAESGCMWithSymmetricKey: destinationSymmetricKey) - guard let json = try JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON, - let statusCode = json["status"] as? Int else { return seal.reject(HTTP.Error.invalidJSON) } - if statusCode == 406 { // Clock out of sync - print("[Loki] The user's clock is out of sync with the service node network.") - seal.reject(SnodeAPI.SnodeAPIError.clockOutOfSync) - } else if let bodyAsString = json["body"] as? String { - let body: JSON - if !isJSONRequired { - body = [ "result" : bodyAsString ] - } else { - guard let bodyAsData = bodyAsString.data(using: .utf8), - let b = try JSONSerialization.jsonObject(with: bodyAsData, options: [ .fragmentsAllowed ]) as? JSON else { return seal.reject(HTTP.Error.invalidJSON) } - body = b - } - guard 200...299 ~= statusCode else { return seal.reject(Error.httpRequestFailedAtDestination(statusCode: UInt(statusCode), json: body)) } - seal.fulfill(body) - } else { - guard 200...299 ~= statusCode else { return seal.reject(Error.httpRequestFailedAtDestination(statusCode: UInt(statusCode), json: json)) } - seal.fulfill(json) - } - } catch { - seal.reject(error) - } - }.catch2 { error in - seal.reject(error) - } - }.catch2 { error in - seal.reject(error) - } - } - promise.catch2 { error in // Must be invoked on LokiAPI.workQueue - guard case HTTP.Error.httpRequestFailed(let statusCode, let json) = error else { return } - let path = paths.first { $0.contains(guardSnode) } - func handleUnspecificError() { - guard let path = path else { return } - var pathFailureCount = OnionRequestAPI.pathFailureCount[path] ?? 0 - pathFailureCount += 1 - if pathFailureCount >= pathFailureThreshold { - dropGuardSnode(guardSnode) - path.forEach { snode in - SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode) // Intentionally don't throw - } - drop(path) - } else { - OnionRequestAPI.pathFailureCount[path] = pathFailureCount - } - } - let prefix = "Next node not found: " - if let message = json?["result"] as? String, message.hasPrefix(prefix) { - let ed25519PublicKey = message.substring(from: prefix.count) - if let path = path, let snode = path.first(where: { $0.publicKeySet?.ed25519Key == ed25519PublicKey }) { - var snodeFailureCount = OnionRequestAPI.snodeFailureCount[snode] ?? 0 - snodeFailureCount += 1 - if snodeFailureCount >= snodeFailureThreshold { - SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode) // Intentionally don't throw - do { - try drop(snode) - } catch { - handleUnspecificError() - } - } else { - OnionRequestAPI.snodeFailureCount[snode] = snodeFailureCount - } - } else { - handleUnspecificError() - } - } else if let message = json?["result"] as? String, message == "Loki Server error" { - // Do nothing - } else { - handleUnspecificError() - } - } - return promise - } -} diff --git a/SignalServiceKit/src/Loki/API/Onion Requests/Storage+OnionRequests.swift b/SignalServiceKit/src/Loki/API/Onion Requests/Storage+OnionRequests.swift deleted file mode 100644 index 3a1e417ba..000000000 --- a/SignalServiceKit/src/Loki/API/Onion Requests/Storage+OnionRequests.swift +++ /dev/null @@ -1,48 +0,0 @@ - -public extension Storage { - - // MARK: Onion Request Paths - internal static let onionRequestPathCollection = "LokiOnionRequestPathCollection" - - internal static func setOnionRequestPaths(_ paths: [OnionRequestAPI.Path], using transaction: YapDatabaseReadWriteTransaction) { - let collection = onionRequestPathCollection - // FIXME: This approach assumes either 1 or 2 paths of length 3 each. We should do better than this. - clearOnionRequestPaths(using: transaction) - guard paths.count >= 1 else { return } - let path0 = paths[0] - guard path0.count == 3 else { return } - transaction.setObject(path0[0], forKey: "0-0", inCollection: collection) - transaction.setObject(path0[1], forKey: "0-1", inCollection: collection) - transaction.setObject(path0[2], forKey: "0-2", inCollection: collection) - guard paths.count >= 2 else { return } - let path1 = paths[1] - guard path1.count == 3 else { return } - transaction.setObject(path1[0], forKey: "1-0", inCollection: collection) - transaction.setObject(path1[1], forKey: "1-1", inCollection: collection) - transaction.setObject(path1[2], forKey: "1-2", inCollection: collection) - } - - public static func getOnionRequestPaths() -> [OnionRequestAPI.Path] { - let collection = onionRequestPathCollection - var result: [OnionRequestAPI.Path] = [] - read { transaction in - if - let path0Snode0 = transaction.object(forKey: "0-0", inCollection: collection) as? Snode, - let path0Snode1 = transaction.object(forKey: "0-1", inCollection: collection) as? Snode, - let path0Snode2 = transaction.object(forKey: "0-2", inCollection: collection) as? Snode { - result.append([ path0Snode0, path0Snode1, path0Snode2 ]) - if - let path1Snode0 = transaction.object(forKey: "1-0", inCollection: collection) as? Snode, - let path1Snode1 = transaction.object(forKey: "1-1", inCollection: collection) as? Snode, - let path1Snode2 = transaction.object(forKey: "1-2", inCollection: collection) as? Snode { - result.append([ path1Snode0, path1Snode1, path1Snode2 ]) - } - } - } - return result - } - - internal static func clearOnionRequestPaths(using transaction: YapDatabaseReadWriteTransaction) { - transaction.removeAllObjects(inCollection: onionRequestPathCollection) - } -} diff --git a/SignalServiceKit/src/Loki/API/Open Groups/PublicChat.swift b/SignalServiceKit/src/Loki/API/Open Groups/PublicChat.swift deleted file mode 100644 index 47905ce5d..000000000 --- a/SignalServiceKit/src/Loki/API/Open Groups/PublicChat.swift +++ /dev/null @@ -1,43 +0,0 @@ - -@objc(LKPublicChat) -public final class PublicChat : NSObject, NSCoding { - @objc public let id: String - @objc public let idAsData: Data - @objc public let channel: UInt64 - @objc public let server: String - @objc public let displayName: String - @objc public let isDeletable: Bool - - @objc public init?(channel: UInt64, server: String, displayName: String, isDeletable: Bool) { - let id = "\(server).\(channel)" - self.id = id - guard let idAsData = id.data(using: .utf8) else { return nil } - self.idAsData = idAsData - self.channel = channel - self.server = server.lowercased() - self.displayName = displayName - self.isDeletable = isDeletable - } - - // MARK: Coding - @objc public init?(coder: NSCoder) { - channel = UInt64(coder.decodeInt64(forKey: "channel")) - server = coder.decodeObject(forKey: "server") as! String - let id = "\(server).\(channel)" - self.id = id - guard let idAsData = id.data(using: .utf8) else { return nil } - self.idAsData = idAsData - displayName = coder.decodeObject(forKey: "displayName") as! String - isDeletable = coder.decodeBool(forKey: "isDeletable") - super.init() - } - - @objc public func encode(with coder: NSCoder) { - coder.encode(Int64(channel), forKey: "channel") - coder.encode(server, forKey: "server") - coder.encode(displayName, forKey: "displayName") - coder.encode(isDeletable, forKey: "isDeletable") - } - - override public var description: String { return "\(displayName) (\(server))" } -} diff --git a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift deleted file mode 100644 index 1661414bc..000000000 --- a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatAPI.swift +++ /dev/null @@ -1,527 +0,0 @@ -import PromiseKit - -@objc(LKPublicChatAPI) -public final class PublicChatAPI : DotNetAPI { - private static var moderators: [String:[UInt64:Set]] = [:] // Server URL to (channel ID to set of moderator IDs) - - @objc public static let defaultChats: [PublicChat] = [] // Currently unused - - public static var displayNameUpdatees: [String:Set] = [:] - - // MARK: Settings - private static let attachmentType = "net.app.core.oembed" - private static let channelInfoType = "net.patter-app.settings" - private static let fallbackBatchCount = 64 - private static let maxRetryCount: UInt = 4 - - public static let profilePictureType = "network.loki.messenger.avatar" - - @objc public static let publicChatMessageType = "network.loki.messenger.publicChat" - - // MARK: Convenience - private static var userDisplayName: String { - let userPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? getUserHexEncodedPublicKey() - return SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: userPublicKey) ?? "Anonymous" - } - - // MARK: Database - override internal class var authTokenCollection: String { "LokiGroupChatAuthTokenCollection" } - - @objc public static let lastMessageServerIDCollection = "LokiGroupChatLastMessageServerIDCollection" - @objc public static let lastDeletionServerIDCollection = "LokiGroupChatLastDeletionServerIDCollection" - - private static func getLastMessageServerID(for group: UInt64, on server: String) -> UInt? { - var result: UInt? = nil - Storage.read { transaction in - result = transaction.object(forKey: "\(server).\(group)", inCollection: lastMessageServerIDCollection) as! UInt? - } - return result - } - - private static func setLastMessageServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: YapDatabaseReadWriteTransaction) { - transaction.setObject(newValue, forKey: "\(server).\(group)", inCollection: lastMessageServerIDCollection) - } - - private static func removeLastMessageServerID(for group: UInt64, on server: String, using transaction: YapDatabaseReadWriteTransaction) { - transaction.removeObject(forKey: "\(server).\(group)", inCollection: lastMessageServerIDCollection) - } - - private static func getLastDeletionServerID(for group: UInt64, on server: String) -> UInt? { - var result: UInt? = nil - Storage.read { transaction in - result = transaction.object(forKey: "\(server).\(group)", inCollection: lastDeletionServerIDCollection) as! UInt? - } - return result - } - - private static func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: YapDatabaseReadWriteTransaction) { - transaction.setObject(newValue, forKey: "\(server).\(group)", inCollection: lastDeletionServerIDCollection) - } - - private static func removeLastDeletionServerID(for group: UInt64, on server: String, using transaction: YapDatabaseReadWriteTransaction) { - transaction.removeObject(forKey: "\(server).\(group)", inCollection: lastDeletionServerIDCollection) - } - - public static func clearCaches(for channel: UInt64, on server: String) { - Storage.writeSync { transaction in - removeLastMessageServerID(for: channel, on: server, using: transaction) - removeLastDeletionServerID(for: channel, on: server, using: transaction) - Storage.removeOpenGroupPublicKey(for: server, using: transaction) - } - } - - // MARK: Open Group Public Key Validation - public static func getOpenGroupServerPublicKey(for server: String) -> Promise { - if let publicKey = Storage.getOpenGroupPublicKey(for: server) { - return Promise.value(publicKey) - } else { - return FileServerAPI.getPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { publicKey -> Promise in - let url = URL(string: server)! - let request = TSRequest(url: url) - return OnionRequestAPI.sendOnionRequest(request, to: server, using: publicKey, isJSONRequired: false).map(on: DispatchQueue.global(qos: .default)) { _ -> String in - Storage.writeSync { transaction in - Storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction) - } - return publicKey - } - } - } - } - - // MARK: Receiving - @objc(getMessagesForGroup:onServer:) - public static func objc_getMessages(for group: UInt64, on server: String) -> AnyPromise { - return AnyPromise.from(getMessages(for: group, on: server)) - } - - public static func getMessages(for channel: UInt64, on server: String) -> Promise<[PublicChatMessage]> { - var queryParameters = "include_annotations=1" - if let lastMessageServerID = getLastMessageServerID(for: channel, on: server) { - queryParameters += "&since_id=\(lastMessageServerID)" - } else { - queryParameters += "&count=\(fallbackBatchCount)&include_deleted=0" - } - return getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<[PublicChatMessage]> in - let url = URL(string: "\(server)/channels/\(channel)/messages?\(queryParameters)")! - let request = TSRequest(url: url) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { json in - guard let rawMessages = json["data"] as? [JSON] else { - print("[Loki] Couldn't parse messages for public chat channel with ID: \(channel) on server: \(server) from: \(json).") - throw DotNetAPIError.parsingFailed - } - return rawMessages.flatMap { message in - let isDeleted = (message["is_deleted"] as? Int == 1) - guard !isDeleted else { return nil } - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" - guard let annotations = message["annotations"] as? [JSON], let annotation = annotations.first(where: { $0["type"] as? String == publicChatMessageType }), let value = annotation["value"] as? JSON, - let serverID = message["id"] as? UInt64, let hexEncodedSignatureData = value["sig"] as? String, let signatureVersion = value["sigver"] as? UInt64, - let body = message["text"] as? String, let user = message["user"] as? JSON, let hexEncodedPublicKey = user["username"] as? String, - let timestamp = value["timestamp"] as? UInt64, let dateAsString = message["created_at"] as? String, let date = dateFormatter.date(from: dateAsString) else { - print("[Loki] Couldn't parse message for public chat channel with ID: \(channel) on server: \(server) from: \(message).") - return nil - } - let serverTimestamp = UInt64(date.timeIntervalSince1970) * 1000 - var profilePicture: PublicChatMessage.ProfilePicture? = nil - let displayName = user["name"] as? String ?? NSLocalizedString("Anonymous", comment: "") - if let userAnnotations = user["annotations"] as? [JSON], let profilePictureAnnotation = userAnnotations.first(where: { $0["type"] as? String == profilePictureType }), - let profilePictureValue = profilePictureAnnotation["value"] as? JSON, let profileKeyString = profilePictureValue["profileKey"] as? String, let profileKey = Data(base64Encoded: profileKeyString), let url = profilePictureValue["url"] as? String { - profilePicture = PublicChatMessage.ProfilePicture(profileKey: profileKey, url: url) - } - let lastMessageServerID = getLastMessageServerID(for: channel, on: server) - if serverID > (lastMessageServerID ?? 0) { - Storage.writeSync { transaction in - setLastMessageServerID(for: channel, on: server, to: serverID, using: transaction) - } - } - let quote: PublicChatMessage.Quote? - if let quoteAsJSON = value["quote"] as? JSON, let quotedMessageTimestamp = quoteAsJSON["id"] as? UInt64, let quoteePublicKey = quoteAsJSON["author"] as? String, - let quotedMessageBody = quoteAsJSON["text"] as? String { - let quotedMessageServerID = message["reply_to"] as? UInt64 - quote = PublicChatMessage.Quote(quotedMessageTimestamp: quotedMessageTimestamp, quoteePublicKey: quoteePublicKey, quotedMessageBody: quotedMessageBody, - quotedMessageServerID: quotedMessageServerID) - } else { - quote = nil - } - let signature = PublicChatMessage.Signature(data: Data(hex: hexEncodedSignatureData), version: signatureVersion) - let attachmentsAsJSON = annotations.filter { $0["type"] as? String == attachmentType } - let attachments: [PublicChatMessage.Attachment] = attachmentsAsJSON.compactMap { attachmentAsJSON in - guard let value = attachmentAsJSON["value"] as? JSON, let kindAsString = value["lokiType"] as? String, let kind = PublicChatMessage.Attachment.Kind(rawValue: kindAsString), - let serverID = value["id"] as? UInt64, let contentType = value["contentType"] as? String, let size = value["size"] as? UInt, let url = value["url"] as? String else { return nil } - let fileName = value["fileName"] as? String ?? UUID().description - let width = value["width"] as? UInt ?? 0 - let height = value["height"] as? UInt ?? 0 - let flags = (value["flags"] as? UInt) ?? 0 - let caption = value["caption"] as? String - let linkPreviewURL = value["linkPreviewUrl"] as? String - let linkPreviewTitle = value["linkPreviewTitle"] as? String - if kind == .linkPreview { - guard linkPreviewURL != nil && linkPreviewTitle != nil else { - print("[Loki] Ignoring public chat message with invalid link preview.") - return nil - } - } - return PublicChatMessage.Attachment(kind: kind, server: server, serverID: serverID, contentType: contentType, size: size, fileName: fileName, flags: flags, - width: width, height: height, caption: caption, url: url, linkPreviewURL: linkPreviewURL, linkPreviewTitle: linkPreviewTitle) - } - let result = PublicChatMessage(serverID: serverID, senderPublicKey: hexEncodedPublicKey, displayName: displayName, profilePicture: profilePicture, - body: body, type: publicChatMessageType, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature, serverTimestamp: serverTimestamp) - guard result.hasValidSignature() else { - print("[Loki] Ignoring public chat message with invalid signature.") - return nil - } - var existingMessageID: String? = nil - Storage.read { transaction in - existingMessageID = OWSPrimaryStorage.shared().getIDForMessage(withServerID: UInt(result.serverID!), in: transaction) - } - guard existingMessageID == nil else { - print("[Loki] Ignoring duplicate public chat message.") - return nil - } - return result - }.sorted { $0.serverTimestamp < $1.serverTimestamp} - } - } - }.handlingInvalidAuthTokenIfNeeded(for: server) - } - - // MARK: Sending - @objc(sendMessage:toGroup:onServer:) - public static func objc_sendMessage(_ message: PublicChatMessage, to group: UInt64, on server: String) -> AnyPromise { - return AnyPromise.from(sendMessage(message, to: group, on: server)) - } - - public static func sendMessage(_ message: PublicChatMessage, to channel: UInt64, on server: String) -> Promise { - print("[Loki] Sending message to public chat channel with ID: \(channel) on server: \(server).") - let (promise, seal) = Promise.pending() - DispatchQueue.global(qos: .userInitiated).async { [privateKey = userKeyPair.privateKey] in - guard let signedMessage = message.sign(with: privateKey) else { return seal.reject(DotNetAPIError.signingFailed) } - attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) { - getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise in - let url = URL(string: "\(server)/channels/\(channel)/messages")! - let parameters = signedMessage.toJSON() - let request = TSRequest(url: url, method: "POST", parameters: parameters) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - let displayName = userDisplayName - return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { json in - // ISO8601DateFormatter doesn't support milliseconds before iOS 11 - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" - guard let messageAsJSON = json["data"] as? JSON, let serverID = messageAsJSON["id"] as? UInt64, let body = messageAsJSON["text"] as? String, - let dateAsString = messageAsJSON["created_at"] as? String, let date = dateFormatter.date(from: dateAsString) else { - print("[Loki] Couldn't parse message for public chat channel with ID: \(channel) on server: \(server) from: \(json).") - throw DotNetAPIError.parsingFailed - } - let timestamp = UInt64(date.timeIntervalSince1970) * 1000 - return PublicChatMessage(serverID: serverID, senderPublicKey: getUserHexEncodedPublicKey(), displayName: displayName, profilePicture: signedMessage.profilePicture, body: body, type: publicChatMessageType, timestamp: timestamp, quote: signedMessage.quote, attachments: signedMessage.attachments, signature: signedMessage.signature, serverTimestamp: timestamp) - } - } - }.handlingInvalidAuthTokenIfNeeded(for: server) - }.done(on: DispatchQueue.global(qos: .default)) { message in - seal.fulfill(message) - }.catch(on: DispatchQueue.global(qos: .default)) { error in - seal.reject(error) - } - } - return promise - } - - // MARK: Deletion - public static func getDeletedMessageServerIDs(for channel: UInt64, on server: String) -> Promise<[UInt64]> { - print("[Loki] Getting deleted messages for public chat channel with ID: \(channel) on server: \(server).") - let queryParameters: String - if let lastDeletionServerID = getLastDeletionServerID(for: channel, on: server) { - queryParameters = "since_id=\(lastDeletionServerID)" - } else { - queryParameters = "count=\(fallbackBatchCount)" - } - return getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise<[UInt64]> in - let url = URL(string: "\(server)/loki/v1/channel/\(channel)/deletes?\(queryParameters)")! - let request = TSRequest(url: url) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { json in - guard let body = json["body"] as? JSON, let deletions = body["data"] as? [JSON] else { - print("[Loki] Couldn't parse deleted messages for public chat channel with ID: \(channel) on server: \(server) from: \(json).") - throw DotNetAPIError.parsingFailed - } - return deletions.flatMap { deletion in - guard let serverID = deletion["id"] as? UInt64, let messageServerID = deletion["message_id"] as? UInt64 else { - print("[Loki] Couldn't parse deleted message for public chat channel with ID: \(channel) on server: \(server) from: \(deletion).") - return nil - } - let lastDeletionServerID = getLastDeletionServerID(for: channel, on: server) - if serverID > (lastDeletionServerID ?? 0) { - Storage.writeSync { transaction in - setLastDeletionServerID(for: channel, on: server, to: serverID, using: transaction) - } - } - return messageServerID - } - } - } - }.handlingInvalidAuthTokenIfNeeded(for: server) - } - - @objc(deleteMessageWithID:forGroup:onServer:isSentByUser:) - public static func objc_deleteMessage(with messageID: UInt, for group: UInt64, on server: String, isSentByUser: Bool) -> AnyPromise { - return AnyPromise.from(deleteMessage(with: messageID, for: group, on: server, isSentByUser: isSentByUser)) - } - - public static func deleteMessage(with messageID: UInt, for channel: UInt64, on server: String, isSentByUser: Bool) -> Promise { - let isModerationRequest = !isSentByUser - print("[Loki] Deleting message with ID: \(messageID) for public chat channel with ID: \(channel) on server: \(server) (isModerationRequest = \(isModerationRequest)).") - let urlAsString = isSentByUser ? "\(server)/channels/\(channel)/messages/\(messageID)" : "\(server)/loki/v1/moderation/message/\(messageID)" - return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) { - getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise in - let url = URL(string: urlAsString)! - let request = TSRequest(url: url, method: "DELETE", parameters: [:]) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey, isJSONRequired: false).done(on: DispatchQueue.global(qos: .default)) { _ -> Void in - print("[Loki] Deleted message with ID: \(messageID) on server: \(server).") - } - } - }.handlingInvalidAuthTokenIfNeeded(for: server) - } - } - - // MARK: Display Name & Profile Picture - public static func getDisplayNames(for channel: UInt64, on server: String) -> Promise { - let publicChatID = "\(server).\(channel)" - guard let publicKeys = displayNameUpdatees[publicChatID] else { return Promise.value(()) } - displayNameUpdatees[publicChatID] = [] - print("[Loki] Getting display names for: \(publicKeys).") - return getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise in - let queryParameters = "ids=\(publicKeys.map { "@\($0)" }.joined(separator: ","))&include_user_annotations=1" - let url = URL(string: "\(server)/users?\(queryParameters)")! - let request = TSRequest(url: url) - return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { json in - guard let data = json["data"] as? [JSON] else { - print("[Loki] Couldn't parse display names for users: \(publicKeys) from: \(json).") - throw DotNetAPIError.parsingFailed - } - Storage.writeSync { transaction in - data.forEach { data in - guard let user = data["user"] as? JSON, let hexEncodedPublicKey = user["username"] as? String, let rawDisplayName = user["name"] as? String else { return } - let endIndex = hexEncodedPublicKey.endIndex - let cutoffIndex = hexEncodedPublicKey.index(endIndex, offsetBy: -8) - let displayName = "\(rawDisplayName) (...\(hexEncodedPublicKey[cutoffIndex.. AnyPromise { - return AnyPromise.from(setDisplayName(to: newDisplayName, on: server)) - } - - public static func setDisplayName(to newDisplayName: String?, on server: String) -> Promise { - print("[Loki] Updating display name on server: \(server).") - let parameters: JSON = [ "name" : (newDisplayName ?? "") ] - return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) { - getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise in - let url = URL(string: "\(server)/users/me")! - let request = TSRequest(url: url, method: "PATCH", parameters: parameters) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { _ in }.recover(on: DispatchQueue.global(qos: .default)) { error in - print("Couldn't update display name due to error: \(error).") - throw error - } - } - }.handlingInvalidAuthTokenIfNeeded(for: server) - } - } - - @objc(setProfilePictureURL:usingProfileKey:on:) - public static func objc_setProfilePicture(to url: String?, using profileKey: Data, on server: String) -> AnyPromise { - return AnyPromise.from(setProfilePictureURL(to: url, using: profileKey, on: server)) - } - - public static func setProfilePictureURL(to url: String?, using profileKey: Data, on server: String) -> Promise { - print("[Loki] Updating profile picture on server: \(server).") - var annotation: JSON = [ "type" : profilePictureType ] - if let url = url { - annotation["value"] = [ "profileKey" : profileKey.base64EncodedString(), "url" : url ] - } - let parameters: JSON = [ "annotations" : [ annotation ] ] - return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) { - getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise in - let url = URL(string: "\(server)/users/me")! - let request = TSRequest(url: url, method: "PATCH", parameters: parameters) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { _ in }.recover(on: DispatchQueue.global(qos: .default)) { error in - print("[Loki] Couldn't update profile picture due to error: \(error).") - throw error - } - } - }.handlingInvalidAuthTokenIfNeeded(for: server) - } - } - - static func updateProfileIfNeeded(for channel: UInt64, on server: String, from info: PublicChatInfo) { - let storage = OWSPrimaryStorage.shared() - let publicChatID = "\(server).\(channel)" - Storage.writeSync { transaction in - // Update user count - storage.setUserCount(info.memberCount, forPublicChatWithID: publicChatID, in: transaction) - let groupThread = TSGroupThread.getOrCreateThread(withGroupId: publicChatID.data(using: .utf8)!, groupType: .openGroup, transaction: transaction) - // Update display name if needed - let groupModel = groupThread.groupModel - if groupModel.groupName != info.displayName { - let newGroupModel = TSGroupModel(title: info.displayName, memberIds: groupModel.groupMemberIds, image: groupModel.groupImage, groupId: groupModel.groupId, groupType: groupModel.groupType, adminIds: groupModel.groupAdminIds) - groupThread.groupModel = newGroupModel - groupThread.save(with: transaction) - } - // Download and update profile picture if needed - let oldProfilePictureURL = storage.getProfilePictureURL(forPublicChatWithID: publicChatID, in: transaction) - if oldProfilePictureURL != info.profilePictureURL || groupModel.groupImage == nil { - storage.setProfilePictureURL(info.profilePictureURL, forPublicChatWithID: publicChatID, in: transaction) - if let profilePictureURL = info.profilePictureURL { - let url = server.hasSuffix("/") ? "\(server)\(profilePictureURL)" : "\(server)/\(profilePictureURL)" - FileServerAPI.downloadAttachment(from: url).map2 { data in - let attachmentStream = TSAttachmentStream(contentType: OWSMimeTypeImageJpeg, byteCount: UInt32(data.count), sourceFilename: nil, caption: nil, albumMessageId: nil) - try attachmentStream.write(data) - groupThread.updateAvatar(with: attachmentStream) - } - } - } - } - } - - // MARK: Joining & Leaving - @objc(getInfoForChannelWithID:onServer:) - public static func objc_getInfo(for channel: UInt64, on server: String) -> AnyPromise { - return AnyPromise.from(getInfo(for: channel, on: server)) - } - - public static func getInfo(for channel: UInt64, on server: String) -> Promise { - return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) { - getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise in - let url = URL(string: "\(server)/channels/\(channel)?include_annotations=1")! - let request = TSRequest(url: url) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { json in - guard let data = json["data"] as? JSON, - let annotations = data["annotations"] as? [JSON], - let annotation = annotations.first, - let info = annotation["value"] as? JSON, - let displayName = info["name"] as? String, - let profilePictureURL = info["avatar"] as? String, - let countInfo = data["counts"] as? JSON, - let memberCount = countInfo["subscribers"] as? Int else { - print("[Loki] Couldn't parse info for public chat channel with ID: \(channel) on server: \(server) from: \(json).") - throw DotNetAPIError.parsingFailed - } - let storage = OWSPrimaryStorage.shared() - Storage.writeSync { transaction in - storage.setUserCount(memberCount, forPublicChatWithID: "\(server).\(channel)", in: transaction) - } - let publicChatInfo = PublicChatInfo(displayName: displayName, profilePictureURL: profilePictureURL, memberCount: memberCount) - updateProfileIfNeeded(for: channel, on: server, from: publicChatInfo) - return publicChatInfo - } - } - }.handlingInvalidAuthTokenIfNeeded(for: server) - } - } - - public static func join(_ channel: UInt64, on server: String) -> Promise { - return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) { - getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise in - let url = URL(string: "\(server)/channels/\(channel)/subscribe")! - let request = TSRequest(url: url, method: "POST", parameters: [:]) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).done(on: DispatchQueue.global(qos: .default)) { _ -> Void in - print("[Loki] Joined channel with ID: \(channel) on server: \(server).") - } - } - }.handlingInvalidAuthTokenIfNeeded(for: server) - } - } - - public static func leave(_ channel: UInt64, on server: String) -> Promise { - return attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global(qos: .default)) { - getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise in - let url = URL(string: "\(server)/channels/\(channel)/subscribe")! - let request = TSRequest(url: url, method: "DELETE", parameters: [:]) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).done(on: DispatchQueue.global(qos: .default)) { _ -> Void in - print("[Loki] Left channel with ID: \(channel) on server: \(server).") - } - } - }.handlingInvalidAuthTokenIfNeeded(for: server) - } - } - - // MARK: Reporting - @objc(reportMessageWithID:inChannel:onServer:) - public static func objc_reportMessageWithID(_ messageID: UInt64, in channel: UInt64, on server: String) -> AnyPromise { - return AnyPromise.from(reportMessageWithID(messageID, in: channel, on: server)) - } - - public static func reportMessageWithID(_ messageID: UInt64, in channel: UInt64, on server: String) -> Promise { - let url = URL(string: "\(server)/loki/v1/channels/\(channel)/messages/\(messageID)/report")! - let request = TSRequest(url: url, method: "POST", parameters: [:]) - // Only used for the Loki Public Chat which doesn't require authentication - return getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { _ in } - } - } - - // MARK: Moderators - public static func getModerators(for channel: UInt64, on server: String) -> Promise> { - return getOpenGroupServerPublicKey(for: server).then(on: DispatchQueue.global(qos: .default)) { serverPublicKey in - getAuthToken(for: server).then(on: DispatchQueue.global(qos: .default)) { token -> Promise> in - let url = URL(string: "\(server)/loki/v1/channel/\(channel)/get_moderators")! - let request = TSRequest(url: url) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - return OnionRequestAPI.sendOnionRequest(request, to: server, using: serverPublicKey).map(on: DispatchQueue.global(qos: .default)) { json in - guard let moderators = json["moderators"] as? [String] else { - print("[Loki] Couldn't parse moderators for public chat channel with ID: \(channel) on server: \(server) from: \(json).") - throw DotNetAPIError.parsingFailed - } - let moderatorsAsSet = Set(moderators); - if self.moderators.keys.contains(server) { - self.moderators[server]![channel] = moderatorsAsSet - } else { - self.moderators[server] = [ channel : moderatorsAsSet ] - } - return moderatorsAsSet - } - } - }.handlingInvalidAuthTokenIfNeeded(for: server) - } - - @objc(isUserModerator:forChannel:onServer:) - public static func isUserModerator(_ hexEncodedPublicString: String, for channel: UInt64, on server: String) -> Bool { - return moderators[server]?[channel]?.contains(hexEncodedPublicString) ?? false - } -} - -// MARK: Error Handling -internal extension Promise { - - internal func handlingInvalidAuthTokenIfNeeded(for server: String) -> Promise { - return recover2 { error -> Promise in - if case OnionRequestAPI.Error.httpRequestFailedAtDestination(let statusCode, _) = error, statusCode == 401 || statusCode == 403 { - print("[Loki] Auth token for: \(server) expired; dropping it.") - PublicChatAPI.removeAuthToken(for: server) - } - throw error - } - } -} diff --git a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatInfo.swift b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatInfo.swift deleted file mode 100644 index 49f97ca31..000000000 --- a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatInfo.swift +++ /dev/null @@ -1,6 +0,0 @@ - -public struct PublicChatInfo { - public let displayName: String - public let profilePictureURL: String? - public let memberCount: Int -} diff --git a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatMessage.swift b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatMessage.swift deleted file mode 100644 index 9c15c618f..000000000 --- a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatMessage.swift +++ /dev/null @@ -1,190 +0,0 @@ -import PromiseKit - -@objc(LKPublicChatMessage) -public final class PublicChatMessage : NSObject { - public let serverID: UInt64? - public let senderPublicKey: String - public let displayName: String - public let profilePicture: ProfilePicture? - public let body: String - /// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970. - public let timestamp: UInt64 - public let type: String - public let quote: Quote? - public var attachments: [Attachment] = [] - public let signature: Signature? - /// - Note: Used for sorting. - public let serverTimestamp: UInt64 - - @objc(serverID) - public var objc_serverID: UInt64 { return serverID ?? 0 } - - // MARK: Settings - private let signatureVersion: UInt64 = 1 - private let attachmentType = "net.app.core.oembed" - - // MARK: Types - public struct ProfilePicture { - public let profileKey: Data - public let url: String - } - - public struct Quote { - public let quotedMessageTimestamp: UInt64 - public let quoteePublicKey: String - public let quotedMessageBody: String - public let quotedMessageServerID: UInt64? - } - - public struct Attachment { - public let kind: Kind - public let server: String - public let serverID: UInt64 - public let contentType: String - public let size: UInt - public let fileName: String - public let flags: UInt - public let width: UInt - public let height: UInt - public let caption: String? - public let url: String - /// Guaranteed to be non-`nil` if `kind` is `linkPreview` - public let linkPreviewURL: String? - /// Guaranteed to be non-`nil` if `kind` is `linkPreview` - public let linkPreviewTitle: String? - - public enum Kind : String { case attachment, linkPreview = "preview" } - - public var dotNETType: String { - if contentType.hasPrefix("image") { - return "photo" - } else if contentType.hasPrefix("video") { - return "video" - } else if contentType.hasPrefix("audio") { - return "audio" - } else { - return "other" - } - } - } - - public struct Signature { - public let data: Data - public let version: UInt64 - } - - // MARK: Initialization - public init(serverID: UInt64?, senderPublicKey: String, displayName: String, profilePicture: ProfilePicture?, body: String, type: String, timestamp: UInt64, quote: Quote?, attachments: [Attachment], signature: Signature?, serverTimestamp: UInt64) { - self.serverID = serverID - self.senderPublicKey = senderPublicKey - self.displayName = displayName - self.profilePicture = profilePicture - self.body = body - self.type = type - self.timestamp = timestamp - self.quote = quote - self.attachments = attachments - self.signature = signature - self.serverTimestamp = serverTimestamp - super.init() - } - - @objc public convenience init(senderPublicKey: String, displayName: String, body: String, type: String, timestamp: UInt64, quotedMessageTimestamp: UInt64, quoteePublicKey: String?, quotedMessageBody: String?, quotedMessageServerID: UInt64, signatureData: Data?, signatureVersion: UInt64, serverTimestamp: UInt64) { - let quote: Quote? - if quotedMessageTimestamp != 0, let quoteeHexEncodedPublicKey = quoteePublicKey, let quotedMessageBody = quotedMessageBody { - let quotedMessageServerID = (quotedMessageServerID != 0) ? quotedMessageServerID : nil - quote = Quote(quotedMessageTimestamp: quotedMessageTimestamp, quoteePublicKey: quoteeHexEncodedPublicKey, quotedMessageBody: quotedMessageBody, quotedMessageServerID: quotedMessageServerID) - } else { - quote = nil - } - let signature: Signature? - if let signatureData = signatureData, signatureVersion != 0 { - signature = Signature(data: signatureData, version: signatureVersion) - } else { - signature = nil - } - self.init(serverID: nil, senderPublicKey: senderPublicKey, displayName: displayName, profilePicture: nil, body: body, type: type, timestamp: timestamp, quote: quote, attachments: [], signature: signature, serverTimestamp: serverTimestamp) - } - - // MARK: Crypto - internal func sign(with privateKey: Data) -> PublicChatMessage? { - guard let data = getValidationData(for: signatureVersion) else { - print("[Loki] Failed to sign public chat message.") - return nil - } - let userKeyPair = OWSIdentityManager.shared().identityKeyPair()! - guard let signatureData = try? Ed25519.sign(data, with: userKeyPair) else { - print("[Loki] Failed to sign public chat message.") - return nil - } - let signature = Signature(data: signatureData, version: signatureVersion) - return PublicChatMessage(serverID: serverID, senderPublicKey: senderPublicKey, displayName: displayName, profilePicture: profilePicture, body: body, type: type, timestamp: timestamp, quote: quote, attachments: attachments, signature: signature, serverTimestamp: serverTimestamp) - } - - internal func hasValidSignature() -> Bool { - guard let signature = signature else { return false } - guard let data = getValidationData(for: signature.version) else { return false } - let publicKey = Data(hex: self.senderPublicKey.removing05PrefixIfNeeded()) - return (try? Ed25519.verifySignature(signature.data, publicKey: publicKey, data: data)) ?? false - } - - // MARK: JSON - internal func toJSON() -> JSON { - var value: JSON = [ "timestamp" : timestamp ] - if let quote = quote { - value["quote"] = [ "id" : quote.quotedMessageTimestamp, "author" : quote.quoteePublicKey, "text" : quote.quotedMessageBody ] - } - if let signature = signature { - value["sig"] = signature.data.toHexString() - value["sigver"] = signature.version - } - if let profilePicture = profilePicture { - value["avatar"] = profilePicture; - } - let annotation: JSON = [ "type" : type, "value" : value ] - let attachmentAnnotations: [JSON] = attachments.map { attachment in - let type: String - var attachmentValue: JSON = [ - // Fields required by the .NET API - "version" : 1, "type" : attachment.dotNETType, - // Custom fields - "lokiType" : attachment.kind.rawValue, "server" : attachment.server, "id" : attachment.serverID, "contentType" : attachment.contentType, "size" : attachment.size, "fileName" : attachment.fileName, "width" : attachment.width, "height" : attachment.height, "url" : attachment.url - ] - if let caption = attachment.caption { - attachmentValue["caption"] = attachment.caption - } - if let linkPreviewURL = attachment.linkPreviewURL { - attachmentValue["linkPreviewUrl"] = linkPreviewURL - } - if let linkPreviewTitle = attachment.linkPreviewTitle { - attachmentValue["linkPreviewTitle"] = linkPreviewTitle - } - return [ "type" : attachmentType, "value" : attachmentValue ] - } - var result: JSON = [ "text" : body, "annotations": [ annotation ] + attachmentAnnotations ] - if let quotedMessageServerID = quote?.quotedMessageServerID { - result["reply_to"] = quotedMessageServerID - } - return result - } - - // MARK: Convenience - @objc public func addAttachment(kind: String, server: String, serverID: UInt64, contentType: String, size: UInt, fileName: String, flags: UInt, width: UInt, height: UInt, caption: String?, url: String, linkPreviewURL: String?, linkPreviewTitle: String?) { - guard let kind = Attachment.Kind(rawValue: kind) else { preconditionFailure() } - let attachment = Attachment(kind: kind, server: server, serverID: serverID, contentType: contentType, size: size, fileName: fileName, flags: flags, width: width, height: height, caption: caption, url: url, linkPreviewURL: linkPreviewURL, linkPreviewTitle: linkPreviewTitle) - attachments.append(attachment) - } - - private func getValidationData(for signatureVersion: UInt64) -> Data? { - var string = "\(body.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines))\(timestamp)" - if let quote = quote { - string += "\(quote.quotedMessageTimestamp)\(quote.quoteePublicKey)\(quote.quotedMessageBody.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines))" - if let quotedMessageServerID = quote.quotedMessageServerID { - string += "\(quotedMessageServerID)" - } - } - string += attachments.sorted { $0.serverID < $1.serverID }.map { "\($0.serverID)" }.joined(separator: "") - string += "\(signatureVersion)" - return string.data(using: String.Encoding.utf8) - } -} diff --git a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatPoller.swift b/SignalServiceKit/src/Loki/API/Open Groups/PublicChatPoller.swift deleted file mode 100644 index 7ec06348f..000000000 --- a/SignalServiceKit/src/Loki/API/Open Groups/PublicChatPoller.swift +++ /dev/null @@ -1,248 +0,0 @@ -import PromiseKit - -@objc(LKPublicChatPoller) -public final class PublicChatPoller : NSObject { - private let publicChat: PublicChat - private var pollForNewMessagesTimer: Timer? = nil - private var pollForDeletedMessagesTimer: Timer? = nil - private var pollForModeratorsTimer: Timer? = nil - private var pollForDisplayNamesTimer: Timer? = nil - private var hasStarted = false - private var isPolling = false - - // MARK: Settings - private let pollForNewMessagesInterval: TimeInterval = 4 - private let pollForDeletedMessagesInterval: TimeInterval = 60 - private let pollForModeratorsInterval: TimeInterval = 10 * 60 - private let pollForDisplayNamesInterval: TimeInterval = 60 - - // MARK: Lifecycle - @objc(initForPublicChat:) - public init(for publicChat: PublicChat) { - self.publicChat = publicChat - super.init() - } - - @objc public func startIfNeeded() { - if hasStarted { return } - DispatchQueue.main.async { [weak self] in // Timers don't do well on background queues - guard let strongSelf = self else { return } - strongSelf.pollForNewMessagesTimer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollForNewMessagesInterval, repeats: true) { _ in self?.pollForNewMessages() } - strongSelf.pollForDeletedMessagesTimer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollForDeletedMessagesInterval, repeats: true) { _ in self?.pollForDeletedMessages() } - strongSelf.pollForModeratorsTimer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollForModeratorsInterval, repeats: true) { _ in self?.pollForModerators() } - strongSelf.pollForDisplayNamesTimer = Timer.scheduledTimer(withTimeInterval: strongSelf.pollForDisplayNamesInterval, repeats: true) { _ in self?.pollForDisplayNames() } - // Perform initial updates - strongSelf.pollForNewMessages() - strongSelf.pollForDeletedMessages() - strongSelf.pollForModerators() - strongSelf.pollForDisplayNames() - strongSelf.hasStarted = true - } - } - - @objc public func stop() { - pollForNewMessagesTimer?.invalidate() - pollForDeletedMessagesTimer?.invalidate() - pollForModeratorsTimer?.invalidate() - pollForDisplayNamesTimer?.invalidate() - hasStarted = false - } - - // MARK: Polling - @objc(pollForNewMessages) - public func objc_pollForNewMessages() -> AnyPromise { - AnyPromise.from(pollForNewMessages()) - } - - public func pollForNewMessages() -> Promise { - guard !self.isPolling else { return Promise.value(()) } - self.isPolling = true - let publicChat = self.publicChat - let userPublicKey = getUserHexEncodedPublicKey() - return PublicChatAPI.getMessages(for: publicChat.channel, on: publicChat.server).done(on: DispatchQueue.global(qos: .default)) { messages in - self.isPolling = false - let uniquePublicKeys = Set(messages.map { $0.senderPublicKey }) - func proceed() { - let storage = OWSPrimaryStorage.shared() - /* - var newDisplayNameUpdatees: Set = [] - storage.dbReadConnection.read { transaction in - newDisplayNameUpdatees = Set(uniquePublicKeys.filter { storage.getMasterHexEncodedPublicKey(for: $0, in: transaction) != $0 }.compactMap { storage.getMasterHexEncodedPublicKey(for: $0, in: transaction) }) - } - if !newDisplayNameUpdatees.isEmpty { - let displayNameUpdatees = PublicChatAPI.displayNameUpdatees[publicChat.id] ?? [] - PublicChatAPI.displayNameUpdatees[publicChat.id] = displayNameUpdatees.union(newDisplayNameUpdatees) - } - */ - // Sorting the messages by timestamp before importing them fixes an issue where messages that quote older messages can't find those older messages - messages.sorted { $0.serverTimestamp < $1.serverTimestamp }.forEach { message in - var wasSentByCurrentUser = false - OWSPrimaryStorage.shared().dbReadConnection.read { transaction in - wasSentByCurrentUser = LokiDatabaseUtilities.isUserLinkedDevice(message.senderPublicKey, transaction: transaction) - } - var masterPublicKey: String? = nil - storage.dbReadConnection.read { transaction in - masterPublicKey = storage.getMasterHexEncodedPublicKey(for: message.senderPublicKey, in: transaction) - } - let senderPublicKey = masterPublicKey ?? message.senderPublicKey - func generateDisplayName(from rawDisplayName: String) -> String { - let endIndex = senderPublicKey.endIndex - let cutoffIndex = senderPublicKey.index(endIndex, offsetBy: -8) - return "\(rawDisplayName) (...\(senderPublicKey[cutoffIndex.. 0) { - SSKEnvironment.shared.profileManager.updateProfileForContact(withID: masterPublicKey!, displayName: message.displayName, with: transaction) - } - SSKEnvironment.shared.profileManager.updateService(withProfileName: message.displayName, avatarURL: profilePicture.url) - SSKEnvironment.shared.profileManager.setProfileKeyData(profilePicture.profileKey, forRecipientId: masterPublicKey!, avatarURL: profilePicture.url) - } - } - } - } - /* - let hexEncodedPublicKeysToUpdate = uniquePublicKeys.filter { hexEncodedPublicKey in - let timeSinceLastUpdate: TimeInterval - if let lastDeviceLinkUpdate = MultiDeviceProtocol.lastDeviceLinkUpdate[hexEncodedPublicKey] { - timeSinceLastUpdate = Date().timeIntervalSince(lastDeviceLinkUpdate) - } else { - timeSinceLastUpdate = .infinity - } - return timeSinceLastUpdate > MultiDeviceProtocol.deviceLinkUpdateInterval - } - if !hexEncodedPublicKeysToUpdate.isEmpty { - FileServerAPI.getDeviceLinks(associatedWith: hexEncodedPublicKeysToUpdate).done(on: DispatchQueue.global(qos: .default)) { _ in - proceed() - hexEncodedPublicKeysToUpdate.forEach { - MultiDeviceProtocol.lastDeviceLinkUpdate[$0] = Date() // TODO: Doing this from a global queue seems a bit iffy - } - }.catch(on: DispatchQueue.global(qos: .default)) { error in - if (error as? DotNetAPI.DotNetAPIError) == DotNetAPI.DotNetAPIError.parsingFailed { - // Don't immediately re-fetch in case of failure due to a parsing error - hexEncodedPublicKeysToUpdate.forEach { - MultiDeviceProtocol.lastDeviceLinkUpdate[$0] = Date() // TODO: Doing this from a global queue seems a bit iffy - } - } - proceed() - } - } else { - */ - DispatchQueue.global(qos: .default).async { - proceed() - } - /* - } - */ - } - } - - private func pollForDeletedMessages() { - let publicChat = self.publicChat - let _ = PublicChatAPI.getDeletedMessageServerIDs(for: publicChat.channel, on: publicChat.server).done(on: DispatchQueue.global(qos: .default)) { deletedMessageServerIDs in - Storage.writeSync { transaction in - let deletedMessageIDs = deletedMessageServerIDs.compactMap { OWSPrimaryStorage.shared().getIDForMessage(withServerID: UInt($0), in: transaction) } - deletedMessageIDs.forEach { messageID in - TSMessage.fetch(uniqueId: messageID)?.remove(with: transaction) - } - } - } - } - - private func pollForModerators() { - let _ = PublicChatAPI.getModerators(for: publicChat.channel, on: publicChat.server) - } - - private func pollForDisplayNames() { - let _ = PublicChatAPI.getDisplayNames(for: publicChat.channel, on: publicChat.server) - } -} diff --git a/SignalServiceKit/src/Loki/API/Open Groups/Storage+PublicChats.swift b/SignalServiceKit/src/Loki/API/Open Groups/Storage+PublicChats.swift deleted file mode 100644 index 204588abf..000000000 --- a/SignalServiceKit/src/Loki/API/Open Groups/Storage+PublicChats.swift +++ /dev/null @@ -1,22 +0,0 @@ - -public extension Storage { - - // MARK: Open Group Public Keys - internal static let openGroupPublicKeyCollection = "LokiOpenGroupPublicKeyCollection" - - internal static func getOpenGroupPublicKey(for server: String) -> String? { - var result: String? = nil - read { transaction in - result = transaction.object(forKey: server, inCollection: openGroupPublicKeyCollection) as? String - } - return result - } - - internal static func setOpenGroupPublicKey(for server: String, to publicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - transaction.setObject(publicKey, forKey: server, inCollection: openGroupPublicKeyCollection) - } - - internal static func removeOpenGroupPublicKey(for server: String, using transaction: YapDatabaseReadWriteTransaction) { - transaction.removeObject(forKey: server, inCollection: openGroupPublicKeyCollection) - } -} diff --git a/SignalServiceKit/src/Loki/API/Open Groups/To Do/PublicChatManager.swift b/SignalServiceKit/src/Loki/API/Open Groups/To Do/PublicChatManager.swift deleted file mode 100644 index 1814c6646..000000000 --- a/SignalServiceKit/src/Loki/API/Open Groups/To Do/PublicChatManager.swift +++ /dev/null @@ -1,131 +0,0 @@ -import PromiseKit - -// TODO: Clean - -@objc(LKPublicChatManager) -public final class PublicChatManager : NSObject { - private let storage = OWSPrimaryStorage.shared() - @objc public var chats: [String:PublicChat] = [:] - private var pollers: [String:PublicChatPoller] = [:] - private var isPolling = false - - private var userHexEncodedPublicKey: String? { - return OWSIdentityManager.shared().identityKeyPair()?.hexEncodedPublicKey - } - - public enum Error : Swift.Error { - case chatCreationFailed - case userPublicKeyNotFound - } - - @objc public static let shared = PublicChatManager() - - private override init() { - super.init() - NotificationCenter.default.addObserver(self, selector: #selector(onThreadDeleted(_:)), name: .threadDeleted, object: nil) - refreshChatsAndPollers() - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - @objc public func startPollersIfNeeded() { - for (threadID, publicChat) in chats { - if let poller = pollers[threadID] { - poller.startIfNeeded() - } else { - let poller = PublicChatPoller(for: publicChat) - poller.startIfNeeded() - pollers[threadID] = poller - } - } - isPolling = true - } - - @objc public func stopPollers() { - for poller in pollers.values { poller.stop() } - isPolling = false - } - - public func addChat(server: String, channel: UInt64) -> Promise { - if let existingChat = getChat(server: server, channel: channel) { - if let newChat = self.addChat(server: server, channel: channel, name: existingChat.displayName) { - return Promise.value(newChat) - } else { - return Promise(error: Error.chatCreationFailed) - } - } - return PublicChatAPI.getInfo(for: channel, on: server).map2 { channelInfo -> PublicChat in - guard let chat = self.addChat(server: server, channel: channel, name: channelInfo.displayName) else { throw Error.chatCreationFailed } - return chat - } - } - - @discardableResult - @objc(addChatWithServer:channel:name:) - public func addChat(server: String, channel: UInt64, name: String) -> PublicChat? { - guard let chat = PublicChat(channel: channel, server: server, displayName: name, isDeletable: true) else { return nil } - let model = TSGroupModel(title: chat.displayName, memberIds: [userHexEncodedPublicKey!, chat.server], image: nil, groupId: LKGroupUtilities.getEncodedOpenGroupIDAsData(chat.id), groupType: .openGroup, adminIds: []) - - // Store the group chat mapping - Storage.writeSync { transaction in - let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction) - - // Save the group chat - LokiDatabaseUtilities.setPublicChat(chat, for: thread.uniqueId!, in: transaction) - } - - // Update chats and pollers - self.refreshChatsAndPollers() - - return chat - } - - @objc(addChatWithServer:channel:) - public func objc_addChat(server: String, channel: UInt64) -> AnyPromise { - return AnyPromise.from(addChat(server: server, channel: channel)) - } - - @objc func refreshChatsAndPollers() { - storage.dbReadConnection.read { transaction in - let newChats = LokiDatabaseUtilities.getAllPublicChats(in: transaction) - - // Remove any chats that don't exist in the database - let removedChatThreadIds = self.chats.keys.filter { !newChats.keys.contains($0) } - removedChatThreadIds.forEach { threadID in - let poller = self.pollers.removeValue(forKey: threadID) - poller?.stop() - } - - // Only append to chats if we have a thread for the chat - self.chats = newChats.filter { (threadID, group) in - return TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) != nil - } - } - - if (isPolling) { startPollersIfNeeded() } - } - - @objc private func onThreadDeleted(_ notification: Notification) { - guard let threadId = notification.userInfo?["threadId"] as? String else { return } - - // Reset the last message cache - if let chat = self.chats[threadId] { - PublicChatAPI.clearCaches(for: chat.channel, on: chat.server) - } - - // Remove the chat from the db - Storage.writeSync { transaction in - LokiDatabaseUtilities.removePublicChat(for: threadId, in: transaction) - } - - refreshChatsAndPollers() - } - - public func getChat(server: String, channel: UInt64) -> PublicChat? { - return chats.values.first { chat in - return chat.server == server && chat.channel == channel - } - } -} diff --git a/SignalServiceKit/src/Loki/API/Poller.swift b/SignalServiceKit/src/Loki/API/Poller.swift deleted file mode 100644 index 38b9f1286..000000000 --- a/SignalServiceKit/src/Loki/API/Poller.swift +++ /dev/null @@ -1,114 +0,0 @@ -import PromiseKit - -@objc(LKPoller) -public final class Poller : NSObject { - private let storage = OWSPrimaryStorage.shared() - private var isPolling = false - private var usedSnodes = Set() - private var pollCount = 0 - - // MARK: Settings - private static let pollInterval: TimeInterval = 1 - private static let retryInterval: TimeInterval = 0.25 - /// After polling a given snode this many times we always switch to a new one. - /// - /// The reason for doing this is that sometimes a snode will be giving us successful responses while - /// it isn't actually getting messages from other snodes. - private static let maxPollCount: UInt = 6 - - // MARK: Error - private enum Error : LocalizedError { - case pollLimitReached - - var localizedDescription: String { - switch self { - case .pollLimitReached: return "Poll limit reached for current snode." - } - } - } - - // MARK: Public API - @objc public func startIfNeeded() { - guard !isPolling else { return } - print("[Loki] Started polling.") - isPolling = true - setUpPolling() - } - - @objc public func stop() { - print("[Loki] Stopped polling.") - isPolling = false - usedSnodes.removeAll() - } - - // MARK: Private API - private func setUpPolling() { - guard isPolling else { return } - SnodeAPI.getSwarm(for: getUserHexEncodedPublicKey(), isForcedReload: true).then2 { [weak self] _ -> Promise in - guard let strongSelf = self else { return Promise { $0.fulfill(()) } } - strongSelf.usedSnodes.removeAll() - let (promise, seal) = Promise.pending() - strongSelf.pollNextSnode(seal: seal) - return promise - }.ensure(on: DispatchQueue.main) { [weak self] in // Timers don't do well on background queues - guard let strongSelf = self, strongSelf.isPolling else { return } - Timer.scheduledTimer(withTimeInterval: Poller.retryInterval, repeats: false) { _ in - guard let strongSelf = self else { return } - strongSelf.setUpPolling() - } - } - } - - private func pollNextSnode(seal: Resolver) { - let userPublicKey = getUserHexEncodedPublicKey() - let swarm = SnodeAPI.swarmCache[userPublicKey] ?? [] - let unusedSnodes = Set(swarm).subtracting(usedSnodes) - if !unusedSnodes.isEmpty { - // randomElement() uses the system's default random generator, which is cryptographically secure - let nextSnode = unusedSnodes.randomElement()! - usedSnodes.insert(nextSnode) - poll(nextSnode, seal: seal).done2 { - seal.fulfill(()) - }.catch2 { [weak self] error in - if let error = error as? Error, error == .pollLimitReached { - self?.pollCount = 0 - } else { - print("[Loki] Polling \(nextSnode) failed; dropping it and switching to next snode.") - SnodeAPI.dropSnodeFromSwarmIfNeeded(nextSnode, publicKey: userPublicKey) - } - self?.pollNextSnode(seal: seal) - } - } else { - seal.fulfill(()) - } - } - - private func poll(_ snode: Snode, seal longTermSeal: Resolver) -> Promise { - guard isPolling else { return Promise { $0.fulfill(()) } } - let userPublicKey = getUserHexEncodedPublicKey() - return SnodeAPI.getRawMessages(from: snode, associatedWith: userPublicKey).then(on: DispatchQueue.main) { [weak self] rawResponse -> Promise in - guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } } - let messages = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: userPublicKey) - if !messages.isEmpty { - print("[Loki] Received \(messages.count) new message(s).") - } - messages.forEach { message in - do { - let data = try message.serializedData() - SSKEnvironment.shared.messageReceiver.handleReceivedEnvelopeData(data) - } catch { - print("[Loki] Failed to deserialize envelope due to error: \(error).") - } - } - strongSelf.pollCount += 1 - if strongSelf.pollCount == Poller.maxPollCount { - throw Error.pollLimitReached - } else { - return withDelay(Poller.pollInterval, completionQueue: SnodeAPI.workQueue) { - guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } } - return strongSelf.poll(snode, seal: longTermSeal) - } - } - } - } -} diff --git a/SignalServiceKit/src/Loki/API/SignalMessage.swift b/SignalServiceKit/src/Loki/API/SignalMessage.swift deleted file mode 100644 index c4cb88249..000000000 --- a/SignalServiceKit/src/Loki/API/SignalMessage.swift +++ /dev/null @@ -1,28 +0,0 @@ - -@objc(LKSignalMessage) -public final class SignalMessage : NSObject { - @objc public let type: SSKProtoEnvelope.SSKProtoEnvelopeType - @objc public let timestamp: UInt64 - @objc public let senderPublicKey: String - @objc public let senderDeviceID: UInt32 - @objc public let content: String - @objc public let recipientPublicKey: String - @objc(ttl) - public let objc_ttl: UInt64 - @objc public let isPing: Bool - - public var ttl: UInt64? { return objc_ttl != 0 ? objc_ttl : nil } - - @objc public init(type: SSKProtoEnvelope.SSKProtoEnvelopeType, timestamp: UInt64, senderID: String, senderDeviceID: UInt32, - content: String, recipientID: String, ttl: UInt64, isPing: Bool) { - self.type = type - self.timestamp = timestamp - self.senderPublicKey = senderID - self.senderDeviceID = senderDeviceID - self.content = content - self.recipientPublicKey = recipientID - self.objc_ttl = ttl - self.isPing = isPing - super.init() - } -} diff --git a/SignalServiceKit/src/Loki/API/Snode.swift b/SignalServiceKit/src/Loki/API/Snode.swift deleted file mode 100644 index 1469a2291..000000000 --- a/SignalServiceKit/src/Loki/API/Snode.swift +++ /dev/null @@ -1,66 +0,0 @@ - -public final class Snode : NSObject, NSCoding { - public let address: String - public let port: UInt16 - internal let publicKeySet: KeySet? - - public var ip: String { - String(address[address.index(address.startIndex, offsetBy: 8).. Bool { - guard let other = other as? Snode else { return false } - return address == other.address && port == other.port - } - - // MARK: Hashing - override public var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:) - return address.hashValue ^ port.hashValue - } - - // MARK: Description - override public var description: String { return "\(address):\(port)" } -} diff --git a/SignalServiceKit/src/Loki/API/SnodeAPI.swift b/SignalServiceKit/src/Loki/API/SnodeAPI.swift deleted file mode 100644 index aa365a499..000000000 --- a/SignalServiceKit/src/Loki/API/SnodeAPI.swift +++ /dev/null @@ -1,352 +0,0 @@ -import PromiseKit - -@objc(LKSnodeAPI) -public final class SnodeAPI : NSObject { - internal static let workQueue = DispatchQueue(label: "SnodeAPI.workQueue", qos: .userInitiated) // It's important that this is a serial queue - - /// - Note: Should only be accessed from `LokiAPI.workQueue` to avoid race conditions. - internal static var snodeFailureCount: [Snode:UInt] = [:] - /// - Note: Should only be accessed from `LokiAPI.workQueue` to avoid race conditions. - internal static var snodePool: Set = [] - /// - Note: Should only be accessed from `LokiAPI.workQueue` to avoid race conditions. - internal static var swarmCache: [String:[Snode]] = [:] - - internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } - - // MARK: Settings - private static let maxRetryCount: UInt = 4 - private static let minimumSnodePoolCount = 64 - private static let minimumSwarmSnodeCount = 2 - private static let seedNodePool: Set = [ "https://storage.seed1.loki.network", "https://storage.seed3.loki.network", "https://public.loki.foundation" ] - private static let snodeFailureThreshold = 4 - private static let targetSwarmSnodeCount = 2 - - internal static var powDifficulty: UInt = 1 - /// - Note: Changing this on the fly is not recommended. - internal static var useOnionRequests = true - - // MARK: Error - @objc(LKSnodeAPIError) - public class SnodeAPIError : NSError { // Not called `Error` for Obj-C interoperablity - - @objc public static let proofOfWorkCalculationFailed = SnodeAPIError(domain: "LokiAPIErrorDomain", code: 1, userInfo: [ NSLocalizedDescriptionKey : "Failed to calculate proof of work." ]) - @objc public static let messageConversionFailed = SnodeAPIError(domain: "LokiAPIErrorDomain", code: 2, userInfo: [ NSLocalizedDescriptionKey : "Failed to construct message." ]) - @objc public static let clockOutOfSync = SnodeAPIError(domain: "LokiAPIErrorDomain", code: 3, userInfo: [ NSLocalizedDescriptionKey : "Your clock is out of sync with the service node network." ]) - @objc public static let randomSnodePoolUpdatingFailed = SnodeAPIError(domain: "LokiAPIErrorDomain", code: 4, userInfo: [ NSLocalizedDescriptionKey : "Failed to update random service node pool." ]) - @objc public static let missingSnodeVersion = SnodeAPIError(domain: "LokiAPIErrorDomain", code: 5, userInfo: [ NSLocalizedDescriptionKey : "Missing service node version." ]) - } - - // MARK: Type Aliases - public typealias MessageListPromise = Promise<[SSKProtoEnvelope]> - public typealias RawResponse = Any - public typealias RawResponsePromise = Promise - - // MARK: Lifecycle - override private init() { } - - // MARK: Core - internal static func invoke(_ method: Snode.Method, on snode: Snode, associatedWith publicKey: String, parameters: JSON) -> RawResponsePromise { - if useOnionRequests { - return OnionRequestAPI.sendOnionRequest(to: snode, invoking: method, with: parameters, associatedWith: publicKey).map2 { $0 as Any } - } else { - let url = "\(snode.address):\(snode.port)/storage_rpc/v1" - return HTTP.execute(.post, url, parameters: parameters).map2 { $0 as Any }.recover2 { error -> Promise in - guard case HTTP.Error.httpRequestFailed(let statusCode, let json) = error else { throw error } - throw SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode, associatedWith: publicKey) ?? error - } - } - } - - internal static func getRandomSnode() -> Promise { - if snodePool.count < minimumSnodePoolCount { - storage.dbReadConnection.read { transaction in - snodePool = storage.getSnodePool(in: transaction) - } - } - if snodePool.count < minimumSnodePoolCount { - let target = seedNodePool.randomElement()! - let url = "\(target)/json_rpc" - let parameters: JSON = [ - "method" : "get_n_service_nodes", - "params" : [ - "active_only" : true, - "fields" : [ - "public_ip" : true, "storage_port" : true, "pubkey_ed25519" : true, "pubkey_x25519" : true - ] - ] - ] - print("[Loki] Populating snode pool using: \(target).") - let (promise, seal) = Promise.pending() - attempt(maxRetryCount: 4, recoveringOn: SnodeAPI.workQueue) { - HTTP.execute(.post, url, parameters: parameters, useSeedNodeURLSession: true).map2 { json -> Snode in - guard let intermediate = json["result"] as? JSON, let rawSnodes = intermediate["service_node_states"] as? [JSON] else { throw SnodeAPIError.randomSnodePoolUpdatingFailed } - snodePool = try Set(rawSnodes.flatMap { rawSnode in - guard let address = rawSnode["public_ip"] as? String, let port = rawSnode["storage_port"] as? Int, - let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else { - print("[Loki] Failed to parse target from: \(rawSnode).") - return nil - } - return Snode(address: "https://\(address)", port: UInt16(port), publicKeySet: Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey)) - }) - // randomElement() uses the system's default random generator, which is cryptographically secure - if !snodePool.isEmpty { - return snodePool.randomElement()! - } else { - throw SnodeAPIError.randomSnodePoolUpdatingFailed - } - } - }.done2 { snode in - seal.fulfill(snode) - Storage.writeSync { transaction in - print("[Loki] Persisting snode pool to database.") - storage.setSnodePool(SnodeAPI.snodePool, in: transaction) - } - }.catch2 { error in - print("[Loki] Failed to contact seed node at: \(target).") - seal.reject(error) - } - return promise - } else { - return Promise { seal in - // randomElement() uses the system's default random generator, which is cryptographically secure - seal.fulfill(snodePool.randomElement()!) - } - } - } - - internal static func getSwarm(for publicKey: String, isForcedReload: Bool = false) -> Promise<[Snode]> { - if swarmCache[publicKey] == nil { - storage.dbReadConnection.read { transaction in - swarmCache[publicKey] = storage.getSwarm(for: publicKey, in: transaction) - } - } - if let cachedSwarm = swarmCache[publicKey], cachedSwarm.count >= minimumSwarmSnodeCount && !isForcedReload { - return Promise<[Snode]> { $0.fulfill(cachedSwarm) } - } else { - print("[Loki] Getting swarm for: \(publicKey == getUserHexEncodedPublicKey() ? "self" : publicKey).") - let parameters: [String:Any] = [ "pubKey" : publicKey ] - return getRandomSnode().then2 { snode in - attempt(maxRetryCount: 4, recoveringOn: SnodeAPI.workQueue) { - invoke(.getSwarm, on: snode, associatedWith: publicKey, parameters: parameters) - } - }.map2 { rawSnodes in - let swarm = parseSnodes(from: rawSnodes) - swarmCache[publicKey] = swarm - Storage.writeSync { transaction in - storage.setSwarm(swarm, for: publicKey, in: transaction) - } - return swarm - } - } - } - - internal static func getTargetSnodes(for publicKey: String) -> Promise<[Snode]> { - // shuffled() uses the system's default random generator, which is cryptographically secure - return getSwarm(for: publicKey).map2 { Array($0.shuffled().prefix(targetSwarmSnodeCount)) } - } - - internal static func dropSnodeFromSnodePool(_ snode: Snode) { - SnodeAPI.snodePool.remove(snode) - Storage.writeSync { transaction in - storage.dropSnodeFromSnodePool(snode, in: transaction) - } - } - - @objc public static func clearSnodePool() { - snodePool.removeAll() - Storage.writeSync { transaction in - storage.clearSnodePool(in: transaction) - } - } - - internal static func dropSnodeFromSwarmIfNeeded(_ snode: Snode, publicKey: String) { - let swarm = SnodeAPI.swarmCache[publicKey] - if var swarm = swarm, let index = swarm.firstIndex(of: snode) { - swarm.remove(at: index) - SnodeAPI.swarmCache[publicKey] = swarm - Storage.writeSync { transaction in - storage.setSwarm(swarm, for: publicKey, in: transaction) - } - } - } - - // MARK: Receiving - internal static func getRawMessages(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { - Storage.writeSync { transaction in - Storage.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey, using: transaction) - } - let lastHash = Storage.getLastMessageHash(for: snode, associatedWith: publicKey) ?? "" - let parameters = [ "pubKey" : publicKey, "lastHash" : lastHash ] - return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) - } - - public static func getMessages(for publicKey: String) -> Promise> { - return attempt(maxRetryCount: maxRetryCount, recoveringOn: SnodeAPI.workQueue) { - getTargetSnodes(for: publicKey).mapValues2 { targetSnode in - getRawMessages(from: targetSnode, associatedWith: publicKey).map2 { - parseRawMessagesResponse($0, from: targetSnode, associatedWith: publicKey) - } - }.map2 { Set($0) } - } - } - - // MARK: Sending - @objc(sendSignalMessage:) - public static func objc_sendSignalMessage(_ signalMessage: SignalMessage) -> AnyPromise { - let promise = sendSignalMessage(signalMessage).mapValues2 { AnyPromise.from($0) }.map2 { Set($0) } - return AnyPromise.from(promise) - } - - public static func sendSignalMessage(_ signalMessage: SignalMessage) -> Promise> { - // Convert the message to a Loki message - guard let lokiMessage = LokiMessage.from(signalMessage: signalMessage) else { return Promise(error: SnodeAPIError.messageConversionFailed) } - let publicKey = lokiMessage.recipientPublicKey - let notificationCenter = NotificationCenter.default - notificationCenter.post(name: .calculatingPoW, object: NSNumber(value: signalMessage.timestamp)) - // Calculate proof of work - return lokiMessage.calculatePoW().then2 { lokiMessageWithPoW -> Promise> in - notificationCenter.post(name: .routing, object: NSNumber(value: signalMessage.timestamp)) - // Get the target snodes - return getTargetSnodes(for: publicKey).map2 { snodes in - notificationCenter.post(name: .messageSending, object: NSNumber(value: signalMessage.timestamp)) - let parameters = lokiMessageWithPoW.toJSON() - return Set(snodes.map { snode in - // Send the message to the target snode - return attempt(maxRetryCount: maxRetryCount, recoveringOn: SnodeAPI.workQueue) { - invoke(.sendMessage, on: snode, associatedWith: publicKey, parameters: parameters) - }.map2 { rawResponse in - if let json = rawResponse as? JSON, let powDifficulty = json["difficulty"] as? Int { - guard powDifficulty != SnodeAPI.powDifficulty, powDifficulty < 100 else { return rawResponse } - print("[Loki] Setting proof of work difficulty to \(powDifficulty).") - SnodeAPI.powDifficulty = UInt(powDifficulty) - } else { - print("[Loki] Failed to update proof of work difficulty from: \(rawResponse).") - } - return rawResponse - } - }) - } - } - } - - // MARK: Parsing - - // The parsing utilities below use a best attempt approach to parsing; they warn for parsing failures but don't throw exceptions. - - private static func parseSnodes(from rawResponse: Any) -> [Snode] { - guard let json = rawResponse as? JSON, let rawSnodes = json["snodes"] as? [JSON] else { - print("[Loki] Failed to parse targets from: \(rawResponse).") - return [] - } - return rawSnodes.flatMap { rawSnode in - guard let address = rawSnode["ip"] as? String, let portAsString = rawSnode["port"] as? String, let port = UInt16(portAsString), let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else { - print("[Loki] Failed to parse target from: \(rawSnode).") - return nil - } - return Snode(address: "https://\(address)", port: port, publicKeySet: Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey)) - } - } - - internal static func parseRawMessagesResponse(_ rawResponse: Any, from snode: Snode, associatedWith publicKey: String) -> [SSKProtoEnvelope] { - guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return [] } - updateLastMessageHashValueIfPossible(for: snode, associatedWith: publicKey, from: rawMessages) - let rawNewMessages = removeDuplicates(from: rawMessages, associatedWith: publicKey) - let newMessages = parseProtoEnvelopes(from: rawNewMessages) - return newMessages - } - - private static func updateLastMessageHashValueIfPossible(for snode: Snode, associatedWith publicKey: String, from rawMessages: [JSON]) { - if let lastMessage = rawMessages.last, let lastHash = lastMessage["hash"] as? String, let expirationDate = lastMessage["expiration"] as? UInt64 { - Storage.writeSync { transaction in - Storage.setLastMessageHashInfo(for: snode, associatedWith: publicKey, to: [ "hash" : lastHash, "expirationDate" : NSNumber(value: expirationDate) ], using: transaction) - } - } else if (!rawMessages.isEmpty) { - print("[Loki] Failed to update last message hash value from: \(rawMessages).") - } - } - - private static func removeDuplicates(from rawMessages: [JSON], associatedWith publicKey: String) -> [JSON] { - var receivedMessages = Storage.getReceivedMessages(for: publicKey) ?? [] - return rawMessages.filter { rawMessage in - guard let hash = rawMessage["hash"] as? String else { - print("[Loki] Missing hash value for message: \(rawMessage).") - return false - } - let isDuplicate = receivedMessages.contains(hash) - receivedMessages.insert(hash) - Storage.writeSync { transaction in - Storage.setReceivedMessages(to: receivedMessages, for: publicKey, using: transaction) - } - return !isDuplicate - } - } - - private static func parseProtoEnvelopes(from rawMessages: [JSON]) -> [SSKProtoEnvelope] { - return rawMessages.compactMap { rawMessage in - guard let base64EncodedData = rawMessage["data"] as? String, let data = Data(base64Encoded: base64EncodedData) else { - print("[Loki] Failed to decode data for message: \(rawMessage).") - return nil - } - guard let envelope = try? MessageWrapper.unwrap(data: data) else { - print("[Loki] Failed to unwrap data for message: \(rawMessage).") - return nil - } - return envelope - } - } - - // MARK: Error Handling - /// - Note: Should only be invoked from `LokiAPI.workQueue` to avoid race conditions. - internal static func handleError(withStatusCode statusCode: UInt, json: JSON?, forSnode snode: Snode, associatedWith publicKey: String? = nil) -> Error? { - #if DEBUG - assertOnQueue(SnodeAPI.workQueue) - #endif - func handleBadSnode() { - let oldFailureCount = SnodeAPI.snodeFailureCount[snode] ?? 0 - let newFailureCount = oldFailureCount + 1 - SnodeAPI.snodeFailureCount[snode] = newFailureCount - print("[Loki] Couldn't reach snode at: \(snode); setting failure count to \(newFailureCount).") - if newFailureCount >= SnodeAPI.snodeFailureThreshold { - print("[Loki] Failure threshold reached for: \(snode); dropping it.") - if let publicKey = publicKey { - SnodeAPI.dropSnodeFromSwarmIfNeeded(snode, publicKey: publicKey) - } - SnodeAPI.dropSnodeFromSnodePool(snode) - print("[Loki] Snode pool count: \(snodePool.count).") - SnodeAPI.snodeFailureCount[snode] = 0 - } - } - switch statusCode { - case 0, 400, 500, 503: - // The snode is unreachable - handleBadSnode() - case 406: - print("[Loki] The user's clock is out of sync with the service node network.") - return SnodeAPI.SnodeAPIError.clockOutOfSync - case 421: - // The snode isn't associated with the given public key anymore - if let publicKey = publicKey { - print("[Loki] Invalidating swarm for: \(publicKey).") - SnodeAPI.dropSnodeFromSwarmIfNeeded(snode, publicKey: publicKey) - } else { - print("[Loki] Got a 421 without an associated public key.") - } - case 432: - // The proof of work difficulty is too low - if let powDifficulty = json?["difficulty"] as? UInt { - if powDifficulty < 100 { - print("[Loki] Setting proof of work difficulty to \(powDifficulty).") - SnodeAPI.powDifficulty = UInt(powDifficulty) - } else { - handleBadSnode() - } - } else { - print("[Loki] Failed to update proof of work difficulty.") - } - default: - handleBadSnode() - print("[Loki] Unhandled response code: \(statusCode).") - } - return nil - } -} diff --git a/SignalServiceKit/src/Loki/API/Storage+SnodeAPI.swift b/SignalServiceKit/src/Loki/API/Storage+SnodeAPI.swift deleted file mode 100644 index b7e6aa29e..000000000 --- a/SignalServiceKit/src/Loki/API/Storage+SnodeAPI.swift +++ /dev/null @@ -1,58 +0,0 @@ - -internal extension Storage { - - // MARK: Last Message Hash - private static let lastMessageHashCollection = "LokiLastMessageHashCollection" - - internal static func getLastMessageHashInfo(for snode: Snode, associatedWith publicKey: String) -> JSON? { - let key = "\(snode.address):\(snode.port).\(publicKey)" - var result: JSON? - read { transaction in - result = transaction.object(forKey: key, inCollection: lastMessageHashCollection) as? JSON - } - if let result = result { - guard result["hash"] as? String != nil else { return nil } - guard result["expirationDate"] as? NSNumber != nil else { return nil } - } - return result - } - - internal static func pruneLastMessageHashInfoIfExpired(for snode: Snode, associatedWith publicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - guard let lastMessageHashInfo = getLastMessageHashInfo(for: snode, associatedWith: publicKey), - let hash = lastMessageHashInfo["hash"] as? String, let expirationDate = (lastMessageHashInfo["expirationDate"] as? NSNumber)?.uint64Value else { return } - let now = NSDate.ows_millisecondTimeStamp() - if now >= expirationDate { - removeLastMessageHashInfo(for: snode, associatedWith: publicKey, using: transaction) - } - } - - internal static func getLastMessageHash(for snode: Snode, associatedWith publicKey: String) -> String? { - return getLastMessageHashInfo(for: snode, associatedWith: publicKey)?["hash"] as? String - } - - internal static func removeLastMessageHashInfo(for snode: Snode, associatedWith publicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - let key = "\(snode.address):\(snode.port).\(publicKey)" - transaction.removeObject(forKey: key, inCollection: lastMessageHashCollection) - } - - internal static func setLastMessageHashInfo(for snode: Snode, associatedWith publicKey: String, to lastMessageHashInfo: JSON, using transaction: YapDatabaseReadWriteTransaction) { - let key = "\(snode.address):\(snode.port).\(publicKey)" - guard lastMessageHashInfo.count == 2 && lastMessageHashInfo["hash"] as? String != nil && lastMessageHashInfo["expirationDate"] as? NSNumber != nil else { return } - transaction.setObject(lastMessageHashInfo, forKey: key, inCollection: lastMessageHashCollection) - } - - // MARK: Received Messages - private static let receivedMessagesCollection = "LokiReceivedMessagesCollection" - - internal static func getReceivedMessages(for publicKey: String) -> Set? { - var result: Set? - read { transaction in - result = transaction.object(forKey: publicKey, inCollection: receivedMessagesCollection) as? Set - } - return result - } - - internal static func setReceivedMessages(to receivedMessages: Set, for publicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - transaction.setObject(receivedMessages, forKey: publicKey, inCollection: receivedMessagesCollection) - } -} diff --git a/SignalServiceKit/src/Loki/API/Utilities/DecryptionUtilities.swift b/SignalServiceKit/src/Loki/API/Utilities/DecryptionUtilities.swift deleted file mode 100644 index 974451235..000000000 --- a/SignalServiceKit/src/Loki/API/Utilities/DecryptionUtilities.swift +++ /dev/null @@ -1,18 +0,0 @@ -import CryptoSwift - -enum DecryptionUtilities { - - /// - Note: Sync. Don't call from the main thread. - internal static func decrypt(_ ivAndCiphertext: Data, usingAESGCMWithSymmetricKey symmetricKey: Data) throws -> Data { - if Thread.isMainThread { - #if DEBUG - preconditionFailure("It's illegal to call decrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") - #endif - } - let iv = ivAndCiphertext[0.. Data { - if Thread.isMainThread { - #if DEBUG - preconditionFailure("It's illegal to call encrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") - #endif - } - let iv = Data.getSecureRandomData(ofSize: ivSize)! - let gcm = GCM(iv: iv.bytes, tagLength: Int(gcmTagSize), mode: .combined) - let aes = try AES(key: symmetricKey.bytes, blockMode: gcm, padding: .noPadding) - let ciphertext = try aes.encrypt(plaintext.bytes) - return iv + Data(bytes: ciphertext) - } - - /// - Note: Sync. Don't call from the main thread. - internal static func encrypt(_ plaintext: Data, using hexEncodedX25519PublicKey: String) throws -> EncryptionResult { - if Thread.isMainThread { - #if DEBUG - preconditionFailure("It's illegal to call encrypt(_:forSnode:) from the main thread.") - #endif - } - let x25519PublicKey = Data(hex: hexEncodedX25519PublicKey) - let ephemeralKeyPair = Curve25519.generateKeyPair() - let ephemeralSharedSecret = try Curve25519.generateSharedSecret(fromPublicKey: x25519PublicKey, privateKey: ephemeralKeyPair.privateKey) - let salt = "LOKI" - let symmetricKey = try HMAC(key: salt.bytes, variant: .sha256).authenticate(ephemeralSharedSecret.bytes) - let ciphertext = try encrypt(plaintext, usingAESGCMWithSymmetricKey: Data(bytes: symmetricKey)) - return (ciphertext, Data(bytes: symmetricKey), ephemeralKeyPair.publicKey) - } -} diff --git a/SignalServiceKit/src/Loki/API/Utilities/HTTP.swift b/SignalServiceKit/src/Loki/API/Utilities/HTTP.swift deleted file mode 100644 index a6c908714..000000000 --- a/SignalServiceKit/src/Loki/API/Utilities/HTTP.swift +++ /dev/null @@ -1,107 +0,0 @@ -import PromiseKit - -public enum HTTP { - private static let seedNodeURLSession = URLSession(configuration: .ephemeral) - private static let defaultURLSession = URLSession(configuration: .ephemeral, delegate: defaultURLSessionDelegate, delegateQueue: nil) - private static let defaultURLSessionDelegate = DefaultURLSessionDelegateImplementation() - - // MARK: Settings - public static let timeout: TimeInterval = 10 - - // MARK: URL Session Delegate Implementation - private final class DefaultURLSessionDelegateImplementation : NSObject, URLSessionDelegate { - - func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - // Snode to snode communication uses self-signed certificates but clients can safely ignore this - completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!)) - } - } - - // MARK: Verb - public enum Verb : String { - case get = "GET" - case put = "PUT" - case post = "POST" - case delete = "DELETE" - } - - // MARK: Error - public enum Error : LocalizedError { - case generic - case httpRequestFailed(statusCode: UInt, json: JSON?) - case invalidJSON - - public var errorDescription: String? { - switch self { - case .generic: return "An error occurred." - case .httpRequestFailed(let statusCode, _): return "HTTP request failed with status code: \(statusCode)." - case .invalidJSON: return "Invalid JSON." - } - } - } - - // MARK: Main - public static func execute(_ verb: Verb, _ url: String, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise { - return execute(verb, url, body: nil, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession) - } - - public static func execute(_ verb: Verb, _ url: String, parameters: JSON?, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise { - if let parameters = parameters { - do { - guard JSONSerialization.isValidJSONObject(parameters) else { return Promise(error: Error.invalidJSON) } - let body = try JSONSerialization.data(withJSONObject: parameters, options: [ .fragmentsAllowed ]) - return execute(verb, url, body: body, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession) - } catch (let error) { - return Promise(error: error) - } - } else { - return execute(verb, url, body: nil, timeout: timeout, useSeedNodeURLSession: useSeedNodeURLSession) - } - } - - public static func execute(_ verb: Verb, _ url: String, body: Data?, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise { - var request = URLRequest(url: URL(string: url)!) - request.httpMethod = verb.rawValue - request.httpBody = body - request.timeoutInterval = timeout - request.allHTTPHeaderFields?.removeValue(forKey: "User-Agent") - let (promise, seal) = Promise.pending() - let urlSession = useSeedNodeURLSession ? seedNodeURLSession : defaultURLSession - let task = urlSession.dataTask(with: request) { data, response, error in - guard let data = data, let response = response as? HTTPURLResponse else { - if let error = error { - print("[Loki] \(verb.rawValue) request to \(url) failed due to error: \(error).") - } else { - print("[Loki] \(verb.rawValue) request to \(url) failed.") - } - // Override the actual error so that we can correctly catch failed requests in sendOnionRequest(invoking:on:with:) - return seal.reject(Error.httpRequestFailed(statusCode: 0, json: nil)) - } - if let error = error { - print("[Loki] \(verb.rawValue) request to \(url) failed due to error: \(error).") - // Override the actual error so that we can correctly catch failed requests in sendOnionRequest(invoking:on:with:) - return seal.reject(Error.httpRequestFailed(statusCode: 0, json: nil)) - } - let statusCode = UInt(response.statusCode) - var json: JSON? = nil - if let j = try? JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON { - json = j - } else if let result = String(data: data, encoding: .utf8) { - json = [ "result" : result ] - } - guard 200...299 ~= statusCode else { - let jsonDescription = json?.prettifiedDescription ?? "no debugging info provided" - print("[Loki] \(verb.rawValue) request to \(url) failed with status code: \(statusCode) (\(jsonDescription)).") - return seal.reject(Error.httpRequestFailed(statusCode: statusCode, json: json)) - } - if let json = json { - seal.fulfill(json) - } else { - print("[Loki] Couldn't parse JSON returned by \(verb.rawValue) request to \(url).") - return seal.reject(Error.invalidJSON) - } - } - task.resume() - return promise - } -} diff --git a/SignalServiceKit/src/Loki/Architecture Overview.md b/SignalServiceKit/src/Loki/Architecture Overview.md deleted file mode 100644 index 05d058976..000000000 --- a/SignalServiceKit/src/Loki/Architecture Overview.md +++ /dev/null @@ -1,37 +0,0 @@ -This document outlines the main abstractions (layers) in the Session app. These layers should be as independent from eachother as possible. - -# Service Node / File Server / Open Group Communication Layer - -* Onion requests -* Service Node RPC calls -* Message sending & receiving (in a message agnostic manner, i.e. without any knowledge of what the message is) -* Swarm and Service Node management (e.g. error handling) -* File server API -* Open group API - -# Session Protocol Layer - -* Customized session handling protocol on top of Signal's implementation (i.e. session reset and things like that) -* Multi device protocol -* Customized closed groups protocol on top of Signal's implementation -* Customized profile management protocol on top of Signal's implementation -* Friend request protocol -* Customized sync messages protocol on top of Signal's implementation -* Customized transcripts, receipts & typing indicators protocol on top of Signal's implementation - - -# Signal Protocol Layer - -Don't touch this. Ever. - -# Push Notifications Layer - -Only applicable to mobile. Speaks for itself. - -# Database Layer - -Built on top of Signal's implementation. Responsible for storing state used by the Session protocol layer and the communication layer. - -# UI Layer - -Should be as independent from Signal as possible. Ideally re-built from the ground up. diff --git a/SignalServiceKit/src/Loki/Crypto/Mnemonic.swift b/SignalServiceKit/src/Loki/Crypto/Mnemonic.swift deleted file mode 100644 index d7b01aa7c..000000000 --- a/SignalServiceKit/src/Loki/Crypto/Mnemonic.swift +++ /dev/null @@ -1,162 +0,0 @@ -import CryptoSwift - -/// Based on [mnemonic.js](https://github.com/loki-project/loki-messenger/blob/development/libloki/modules/mnemonic.js) . -public enum Mnemonic { - - public struct Language : Hashable { - fileprivate let filename: String - fileprivate let prefixLength: UInt - - public static let english = Language(filename: "english", prefixLength: 3) - public static let japanese = Language(filename: "japanese", prefixLength: 3) - public static let portuguese = Language(filename: "portuguese", prefixLength: 4) - public static let spanish = Language(filename: "spanish", prefixLength: 4) - - private static var wordSetCache: [Language:[String]] = [:] - private static var truncatedWordSetCache: [Language:[String]] = [:] - - private init(filename: String, prefixLength: UInt) { - self.filename = filename - self.prefixLength = prefixLength - } - - fileprivate func loadWordSet() -> [String] { - if let cachedResult = Language.wordSetCache[self] { - return cachedResult - } else { - let bundleID = "org.cocoapods.SessionServiceKit" - let url = Bundle(identifier: bundleID)!.url(forResource: filename, withExtension: "txt")! - let contents = try! String(contentsOf: url) - let result = contents.split(separator: ",").map { String($0) } - Language.wordSetCache[self] = result - return result - } - } - - fileprivate func loadTruncatedWordSet() -> [String] { - if let cachedResult = Language.truncatedWordSetCache[self] { - return cachedResult - } else { - let result = loadWordSet().map { $0.prefix(length: prefixLength) } - Language.truncatedWordSetCache[self] = result - return result - } - } - } - - public enum DecodingError : LocalizedError { - case generic, inputTooShort, missingLastWord, invalidWord, verificationFailed - - public var errorDescription: String? { - switch self { - case .generic: return NSLocalizedString("Something went wrong. Please check your recovery phrase and try again.", comment: "") - case .inputTooShort: return NSLocalizedString("Looks like you didn't enter enough words. Please check your recovery phrase and try again.", comment: "") - case .missingLastWord: return NSLocalizedString("You seem to be missing the last word of your recovery phrase. Please check what you entered and try again.", comment: "") - case .invalidWord: return NSLocalizedString("There appears to be an invalid word in your recovery phrase. Please check what you entered and try again.", comment: "") - case .verificationFailed: return NSLocalizedString("Your recovery phrase couldn't be verified. Please check what you entered and try again.", comment: "") - } - } - } - - public static func hash(hexEncodedString string: String, language: Language = .english) -> String { - return encode(hexEncodedString: string).split(separator: " ")[0..<3].joined(separator: " ") - } - - public static func encode(hexEncodedString string: String, language: Language = .english) -> String { - var string = string - let wordSet = language.loadWordSet() - let prefixLength = language.prefixLength - var result: [String] = [] - let n = wordSet.count - let characterCount = string.indices.count // Safe for this particular case - for chunkStartIndexAsInt in stride(from: 0, to: characterCount, by: 8) { - let chunkStartIndex = string.index(string.startIndex, offsetBy: chunkStartIndexAsInt) - let chunkEndIndex = string.index(chunkStartIndex, offsetBy: 8) - let p1 = string[string.startIndex.. String { - var words = mnemonic.split(separator: " ").map { String($0) } - let truncatedWordSet = language.loadTruncatedWordSet() - let prefixLength = language.prefixLength - var result = "" - let n = truncatedWordSet.count - // Check preconditions - guard words.count >= 12 else { throw DecodingError.inputTooShort } - guard !words.count.isMultiple(of: 3) else { throw DecodingError.missingLastWord } - // Get checksum word - let checksumWord = words.popLast()! - // Decode - for chunkStartIndex in stride(from: 0, to: words.count, by: 3) { - guard let w1 = truncatedWordSet.firstIndex(of: words[chunkStartIndex].prefix(length: prefixLength)), - let w2 = truncatedWordSet.firstIndex(of: words[chunkStartIndex + 1].prefix(length: prefixLength)), - let w3 = truncatedWordSet.firstIndex(of: words[chunkStartIndex + 2].prefix(length: prefixLength)) else { throw DecodingError.invalidWord } - let x = w1 + n * ((n - w1 + w2) % n) + n * n * ((n - w2 + w3) % n) - guard x % n == w1 else { throw DecodingError.generic } - let string = "0000000" + String(x, radix: 16) - result += swap(String(string[string.index(string.endIndex, offsetBy: -8).. String { - func toStringIndex(_ indexAsInt: Int) -> String.Index { - return x.index(x.startIndex, offsetBy: indexAsInt) - } - let p1 = x[toStringIndex(6).. Int { - let checksum = Array(x.map { $0.prefix(length: prefixLength) }.joined().utf8).crc32() - return Int(checksum) % x.count - } -} - -private extension String { - - func prefix(length: UInt) -> String { - return String(self[startIndex.. String { - return Mnemonic.hash(hexEncodedString: string) - } - - @objc(encodeHexEncodedString:) - public static func encode(hexEncodedString string: String) -> String { - return Mnemonic.encode(hexEncodedString: string) - } -} diff --git a/SignalServiceKit/src/Loki/Database/Deprecated/LokiDatabaseUtilities.swift b/SignalServiceKit/src/Loki/Database/Deprecated/LokiDatabaseUtilities.swift deleted file mode 100644 index fa2b4df07..000000000 --- a/SignalServiceKit/src/Loki/Database/Deprecated/LokiDatabaseUtilities.swift +++ /dev/null @@ -1,98 +0,0 @@ - -@objc(LKDatabaseUtilities) -public final class LokiDatabaseUtilities : NSObject { - - private override init() { } - - // MARK: - Quotes - @objc(getServerIDForQuoteWithID:quoteeHexEncodedPublicKey:threadID:transaction:) - public static func getServerID(quoteID: UInt64, quoteeHexEncodedPublicKey: String, threadID: String, transaction: YapDatabaseReadTransaction) -> UInt64 { - guard let message = TSInteraction.interactions(withTimestamp: quoteID, filter: { interaction in - let senderHexEncodedPublicKey: String - if let message = interaction as? TSIncomingMessage { - senderHexEncodedPublicKey = message.authorId - } else if let message = interaction as? TSOutgoingMessage { - senderHexEncodedPublicKey = getUserHexEncodedPublicKey() - } else { - return false - } - return (senderHexEncodedPublicKey == quoteeHexEncodedPublicKey) && (interaction.uniqueThreadId == threadID) - }, with: transaction).first as! TSMessage? else { return 0 } - return message.openGroupServerMessageID - } - - - - // MARK: - Device Links - @objc(getLinkedDeviceHexEncodedPublicKeysFor:in:) - public static func getLinkedDeviceHexEncodedPublicKeys(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> Set { - return [ hexEncodedPublicKey ] - /* - let storage = OWSPrimaryStorage.shared() - let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey - var result = Set(storage.getDeviceLinks(for: masterHexEncodedPublicKey, in: transaction).flatMap { deviceLink in - return [ deviceLink.master.publicKey, deviceLink.slave.publicKey ] - }) - result.insert(hexEncodedPublicKey) - return result - */ - } - - @objc(getLinkedDeviceThreadsFor:in:) - public static func getLinkedDeviceThreads(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> Set { - return Set([ TSContactThread.getWithContactId(hexEncodedPublicKey, transaction: transaction) ].compactMap { $0 }) -// return Set(getLinkedDeviceHexEncodedPublicKeys(for: hexEncodedPublicKey, in: transaction).compactMap { TSContactThread.getWithContactId($0, transaction: transaction) }) - } - - @objc(isUserLinkedDevice:in:) - public static func isUserLinkedDevice(_ hexEncodedPublicKey: String, transaction: YapDatabaseReadTransaction) -> Bool { - return hexEncodedPublicKey == getUserHexEncodedPublicKey() - /* - let userHexEncodedPublicKey = getUserHexEncodedPublicKey() - let userLinkedDeviceHexEncodedPublicKeys = getLinkedDeviceHexEncodedPublicKeys(for: userHexEncodedPublicKey, in: transaction) - return userLinkedDeviceHexEncodedPublicKeys.contains(hexEncodedPublicKey) - */ - } - - @objc(getMasterHexEncodedPublicKeyFor:in:) - public static func objc_getMasterHexEncodedPublicKey(for slaveHexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> String? { - return nil -// return OWSPrimaryStorage.shared().getMasterHexEncodedPublicKey(for: slaveHexEncodedPublicKey, in: transaction) - } - - @objc(getDeviceLinksFor:in:) - public static func objc_getDeviceLinks(for masterHexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> Set { - return [] -// return OWSPrimaryStorage.shared().getDeviceLinks(for: masterHexEncodedPublicKey, in: transaction) - } - - - - // MARK: - Open Groups - private static let publicChatCollection = "LokiPublicChatCollection" - - @objc(getAllPublicChats:) - public static func getAllPublicChats(in transaction: YapDatabaseReadTransaction) -> [String:PublicChat] { - var result = [String:PublicChat]() - transaction.enumerateKeysAndObjects(inCollection: publicChatCollection) { threadID, object, _ in - guard let publicChat = object as? PublicChat else { return } - result[threadID] = publicChat - } - return result - } - - @objc(getPublicChatForThreadID:transaction:) - public static func getPublicChat(for threadID: String, in transaction: YapDatabaseReadTransaction) -> PublicChat? { - return transaction.object(forKey: threadID, inCollection: publicChatCollection) as? PublicChat - } - - @objc(setPublicChat:threadID:transaction:) - public static func setPublicChat(_ publicChat: PublicChat, for threadID: String, in transaction: YapDatabaseReadWriteTransaction) { - transaction.setObject(publicChat, forKey: threadID, inCollection: publicChatCollection) - } - - @objc(removePublicChatForThreadID:transaction:) - public static func removePublicChat(for threadID: String, in transaction: YapDatabaseReadWriteTransaction) { - transaction.removeObject(forKey: threadID, inCollection: publicChatCollection) - } -} diff --git a/SignalServiceKit/src/Loki/Database/Deprecated/OWSPrimaryStorage+Loki.h b/SignalServiceKit/src/Loki/Database/Deprecated/OWSPrimaryStorage+Loki.h deleted file mode 100644 index 8cb00ac97..000000000 --- a/SignalServiceKit/src/Loki/Database/Deprecated/OWSPrimaryStorage+Loki.h +++ /dev/null @@ -1,50 +0,0 @@ -#import "OWSPrimaryStorage.h" - -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSPrimaryStorage (Loki) - -# pragma mark - Pre Key Record Management - -- (BOOL)hasPreKeyRecordForContact:(NSString *)hexEncodedPublicKey; -- (PreKeyRecord *_Nullable)getPreKeyRecordForContact:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadTransaction *)transaction; -- (PreKeyRecord *)getOrCreatePreKeyRecordForContact:(NSString *)hexEncodedPublicKey; - -# pragma mark - Pre Key Bundle Management - -/** - * Generates a pre key bundle for the given contact. Doesn't store the pre key bundle (pre key bundles are supposed to be sent without ever being stored). - */ -- (PreKeyBundle *)generatePreKeyBundleForContact:(NSString *)hexEncodedPublicKey; -- (PreKeyBundle *_Nullable)getPreKeyBundleForContact:(NSString *)hexEncodedPublicKey; -- (void)setPreKeyBundle:(PreKeyBundle *)bundle forContact:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction; -- (void)removePreKeyBundleForContact:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction; - -# pragma mark - Last Message Hash - -/** - * Gets the last message hash and removes it if its `expiresAt` has already passed. - */ -- (NSString *_Nullable)getLastMessageHashForSnode:(NSString *)snode transaction:(YapDatabaseReadWriteTransaction *)transaction; -- (void)setLastMessageHashForSnode:(NSString *)snode hash:(NSString *)hash expiresAt:(u_int64_t)expiresAt transaction:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(setLastMessageHash(forSnode:hash:expiresAt:transaction:)); - -# pragma mark - Open Groups - -- (void)setIDForMessageWithServerID:(NSUInteger)serverID to:(NSString *)messageID in:(YapDatabaseReadWriteTransaction *)transaction; -- (NSString *_Nullable)getIDForMessageWithServerID:(NSUInteger)serverID in:(YapDatabaseReadTransaction *)transaction; -- (void)updateMessageIDCollectionByPruningMessagesWithIDs:(NSSet *)targetMessageIDs in:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(updateMessageIDCollectionByPruningMessagesWithIDs(_:in:)); - -# pragma mark - Restoration from Seed - -- (void)setRestorationTime:(NSTimeInterval)time; -- (NSTimeInterval)getRestorationTime; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Loki/Database/Deprecated/OWSPrimaryStorage+Loki.m b/SignalServiceKit/src/Loki/Database/Deprecated/OWSPrimaryStorage+Loki.m deleted file mode 100644 index 251a63f0b..000000000 --- a/SignalServiceKit/src/Loki/Database/Deprecated/OWSPrimaryStorage+Loki.m +++ /dev/null @@ -1,194 +0,0 @@ -#import "OWSPrimaryStorage+Loki.h" -#import "OWSPrimaryStorage+PreKeyStore.h" -#import "OWSPrimaryStorage+SignedPreKeyStore.h" -#import "OWSPrimaryStorage+keyFromIntLong.h" -#import "OWSDevice.h" -#import "OWSIdentityManager.h" -#import "NSDate+OWS.h" -#import "TSAccountManager.h" -#import "TSPreKeyManager.h" -#import "YapDatabaseConnection+OWS.h" -#import "YapDatabaseTransaction+OWS.h" -#import -#import "NSObject+Casting.h" -#import - -@implementation OWSPrimaryStorage (Loki) - -# pragma mark - Convenience - -- (OWSIdentityManager *)identityManager { - return OWSIdentityManager.sharedManager; -} - -- (TSAccountManager *)accountManager { - return TSAccountManager.sharedInstance; -} - -# pragma mark - Pre Key Record Management - -#define LKPreKeyContactCollection @"LKPreKeyContactCollection" -#define OWSPrimaryStoragePreKeyStoreCollection @"TSStorageManagerPreKeyStoreCollection" - -- (BOOL)hasPreKeyRecordForContact:(NSString *)hexEncodedPublicKey { - int preKeyId = [self.dbReadWriteConnection intForKey:hexEncodedPublicKey inCollection:LKPreKeyContactCollection]; - return preKeyId > 0; -} - -- (PreKeyRecord *_Nullable)getPreKeyRecordForContact:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadTransaction *)transaction { - OWSAssertDebug(hexEncodedPublicKey.length > 0); - int preKeyID = [transaction intForKey:hexEncodedPublicKey inCollection:LKPreKeyContactCollection]; - - if (preKeyID <= 0) { return nil; } - - // throws_loadPreKey doesn't allow us to pass transaction - // FIXME: This seems like it could be a pretty big issue? - return [transaction preKeyRecordForKey:[self keyFromInt:preKeyID] inCollection:OWSPrimaryStoragePreKeyStoreCollection]; -} - -- (PreKeyRecord *)getOrCreatePreKeyRecordForContact:(NSString *)hexEncodedPublicKey { - OWSAssertDebug(hexEncodedPublicKey.length > 0); - int preKeyID = [self.dbReadWriteConnection intForKey:hexEncodedPublicKey inCollection:LKPreKeyContactCollection]; - - // If we don't have an ID then generate and store a new one - if (preKeyID <= 0) { - return [self generateAndStorePreKeyRecordForContact:hexEncodedPublicKey]; - } - - // Load existing pre key record if possible; generate a new one otherwise - @try { - return [self throws_loadPreKey:preKeyID]; - } @catch (NSException *exception) { - return [self generateAndStorePreKeyRecordForContact:hexEncodedPublicKey]; - } -} - -- (PreKeyRecord *)generateAndStorePreKeyRecordForContact:(NSString *)hexEncodedPublicKey { - [LKLogger print:[NSString stringWithFormat:@"[Loki] Generating new pre key record for: %@.", hexEncodedPublicKey]]; - OWSAssertDebug(hexEncodedPublicKey.length > 0); - - NSArray *records = [self generatePreKeyRecords:1]; - OWSAssertDebug(records.count > 0); - [self storePreKeyRecords:records]; - - PreKeyRecord *record = records.firstObject; - [self.dbReadWriteConnection setInt:record.Id forKey:hexEncodedPublicKey inCollection:LKPreKeyContactCollection]; - - return record; -} - -# pragma mark - Pre Key Bundle Management - -#define LKPreKeyBundleCollection @"LKPreKeyBundleCollection" - -- (PreKeyBundle *)generatePreKeyBundleForContact:(NSString *)hexEncodedPublicKey forceClean:(BOOL)forceClean { - // Refresh signed pre key if needed - [TSPreKeyManager checkPreKeys]; - - ECKeyPair *_Nullable keyPair = self.identityManager.identityKeyPair; - OWSAssertDebug(keyPair); - - // Refresh signed pre key if needed - if (self.currentSignedPreKey == nil || forceClean) { // TODO: Is the self.currentSignedPreKey == nil check needed? - SignedPreKeyRecord *signedPreKeyRecord = [self generateRandomSignedRecord]; - [signedPreKeyRecord markAsAcceptedByService]; - [self storeSignedPreKey:signedPreKeyRecord.Id signedPreKeyRecord:signedPreKeyRecord]; - [self setCurrentSignedPrekeyId:signedPreKeyRecord.Id]; - [LKLogger print:@"[Loki] Signed pre key refreshed successfully."]; - } - - SignedPreKeyRecord *_Nullable signedPreKey = self.currentSignedPreKey; - if (signedPreKey == nil) { - OWSFailDebug(@"Signed pre key is nil."); - } - - PreKeyRecord *preKey = [self getOrCreatePreKeyRecordForContact:hexEncodedPublicKey]; - uint32_t registrationID = [self.accountManager getOrGenerateRegistrationId]; - - PreKeyBundle *bundle = [[PreKeyBundle alloc] initWithRegistrationId:registrationID - deviceId:OWSDevicePrimaryDeviceId - preKeyId:preKey.Id - preKeyPublic:preKey.keyPair.publicKey.prependKeyType - signedPreKeyPublic:signedPreKey.keyPair.publicKey.prependKeyType - signedPreKeyId:signedPreKey.Id - signedPreKeySignature:signedPreKey.signature - identityKey:keyPair.publicKey.prependKeyType]; - return bundle; -} - -- (PreKeyBundle *)generatePreKeyBundleForContact:(NSString *)hexEncodedPublicKey { - NSInteger failureCount = 0; - BOOL forceClean = NO; - while (failureCount < 3) { - @try { - PreKeyBundle *preKeyBundle = [self generatePreKeyBundleForContact:hexEncodedPublicKey forceClean:forceClean]; - if (![Ed25519 throws_verifySignature:preKeyBundle.signedPreKeySignature - publicKey:preKeyBundle.identityKey.throws_removeKeyType - data:preKeyBundle.signedPreKeyPublic]) { - @throw [NSException exceptionWithName:InvalidKeyException reason:@"KeyIsNotValidlySigned" userInfo:nil]; - } - [LKLogger print:[NSString stringWithFormat:@"[Loki] Generated a new pre key bundle for: %@.", hexEncodedPublicKey]]; - return preKeyBundle; - } @catch (NSException *exception) { - failureCount += 1; - forceClean = YES; - } - } - [LKLogger print:[NSString stringWithFormat:@"[Loki] Failed to generate a valid pre key bundle for: %@.", hexEncodedPublicKey]]; - return nil; -} - -- (PreKeyBundle *_Nullable)getPreKeyBundleForContact:(NSString *)hexEncodedPublicKey { - return [self.dbReadConnection preKeyBundleForKey:hexEncodedPublicKey inCollection:LKPreKeyBundleCollection]; -} - -- (void)setPreKeyBundle:(PreKeyBundle *)bundle forContact:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction { - [transaction setObject:bundle forKey:hexEncodedPublicKey inCollection:LKPreKeyBundleCollection]; - [LKLogger print:[NSString stringWithFormat:@"[Loki] Stored pre key bundle from: %@.", hexEncodedPublicKey]]; - // FIXME: I don't think the line below is good for anything - [transaction.connection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:^{ }]; -} - -- (void)removePreKeyBundleForContact:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction { - [transaction removeObjectForKey:hexEncodedPublicKey inCollection:LKPreKeyBundleCollection]; - [LKLogger print:[NSString stringWithFormat:@"[Loki] Removed pre key bundle from: %@.", hexEncodedPublicKey]]; -} - -# pragma mark - Open Groups - -#define LKMessageIDCollection @"LKMessageIDCollection" - -- (void)setIDForMessageWithServerID:(NSUInteger)serverID to:(NSString *)messageID in:(YapDatabaseReadWriteTransaction *)transaction { - NSString *key = [NSString stringWithFormat:@"%@", @(serverID)]; - [transaction setObject:messageID forKey:key inCollection:LKMessageIDCollection]; -} - -- (NSString *_Nullable)getIDForMessageWithServerID:(NSUInteger)serverID in:(YapDatabaseReadTransaction *)transaction { - NSString *key = [NSString stringWithFormat:@"%@", @(serverID)]; - return [transaction objectForKey:key inCollection:LKMessageIDCollection]; -} - -- (void)updateMessageIDCollectionByPruningMessagesWithIDs:(NSSet *)targetMessageIDs in:(YapDatabaseReadWriteTransaction *)transaction { - NSMutableArray *serverIDs = [NSMutableArray new]; - [transaction enumerateRowsInCollection:LKMessageIDCollection usingBlock:^(NSString *key, id object, id metadata, BOOL *stop) { - if (![object isKindOfClass:NSString.class]) { return; } - NSString *messageID = (NSString *)object; - if (![targetMessageIDs containsObject:messageID]) { return; } - [serverIDs addObject:key]; - }]; - [transaction removeObjectsForKeys:serverIDs inCollection:LKMessageIDCollection]; -} - -# pragma mark - Restoration from Seed - -#define LKGeneralCollection @"Loki" - -- (void)setRestorationTime:(NSTimeInterval)time { - [self.dbReadWriteConnection setDouble:time forKey:@"restoration_time" inCollection:LKGeneralCollection]; -} - -- (NSTimeInterval)getRestorationTime { - return [self.dbReadConnection doubleForKey:@"restoration_time" inCollection:LKGeneralCollection defaultValue:0]; -} - -@end diff --git a/SignalServiceKit/src/Loki/Database/Deprecated/OWSPrimaryStorage+Loki.swift b/SignalServiceKit/src/Loki/Database/Deprecated/OWSPrimaryStorage+Loki.swift deleted file mode 100644 index e9bddf298..000000000 --- a/SignalServiceKit/src/Loki/Database/Deprecated/OWSPrimaryStorage+Loki.swift +++ /dev/null @@ -1,89 +0,0 @@ - -// TODO: Make this strongly typed like LKUserDefaults - -public extension OWSPrimaryStorage { - - // MARK: Snode Pool - public func setSnodePool(_ snodePool: Set, in transaction: YapDatabaseReadWriteTransaction) { - clearSnodePool(in: transaction) - snodePool.forEach { snode in - transaction.setObject(snode, forKey: snode.description, inCollection: Storage.snodePoolCollection) - } - } - - public func clearSnodePool(in transaction: YapDatabaseReadWriteTransaction) { - transaction.removeAllObjects(inCollection: Storage.snodePoolCollection) - } - - public func getSnodePool(in transaction: YapDatabaseReadTransaction) -> Set { - var result: Set = [] - transaction.enumerateKeysAndObjects(inCollection: Storage.snodePoolCollection) { _, object, _ in - guard let snode = object as? Snode else { return } - result.insert(snode) - } - return result - } - - public func dropSnodeFromSnodePool(_ snode: Snode, in transaction: YapDatabaseReadWriteTransaction) { - transaction.removeObject(forKey: snode.description, inCollection: Storage.snodePoolCollection) - } - - // MARK: Swarm - public func setSwarm(_ swarm: [Snode], for publicKey: String, in transaction: YapDatabaseReadWriteTransaction) { - print("[Loki] Caching swarm for: \(publicKey == getUserHexEncodedPublicKey() ? "self" : publicKey).") - clearSwarm(for: publicKey, in: transaction) - let collection = Storage.getSwarmCollection(for: publicKey) - swarm.forEach { snode in - transaction.setObject(snode, forKey: snode.description, inCollection: collection) - } - } - - public func clearSwarm(for publicKey: String, in transaction: YapDatabaseReadWriteTransaction) { - let collection = Storage.getSwarmCollection(for: publicKey) - transaction.removeAllObjects(inCollection: collection) - } - - public func getSwarm(for publicKey: String, in transaction: YapDatabaseReadTransaction) -> [Snode] { - var result: [Snode] = [] - let collection = Storage.getSwarmCollection(for: publicKey) - transaction.enumerateKeysAndObjects(inCollection: collection) { _, object, _ in - guard let snode = object as? Snode else { return } - result.append(snode) - } - return result - } - - // MARK: Session Requests - public func setSessionRequestTimestamp(for publicKey: String, to timestamp: Date, in transaction: YapDatabaseReadWriteTransaction) { - transaction.setDate(timestamp, forKey: publicKey, inCollection: Storage.sessionRequestTimestampCollection) - } - - public func getSessionRequestTimestamp(for publicKey: String, in transaction: YapDatabaseReadTransaction) -> Date? { - transaction.date(forKey: publicKey, inCollection: Storage.sessionRequestTimestampCollection) - } - - // MARK: Multi Device - public func setDeviceLinks(_ deviceLinks: Set) { } - public func addDeviceLink(_ deviceLink: DeviceLink) { } - public func removeDeviceLink(_ deviceLink: DeviceLink) { } - public func getDeviceLinks(for masterHexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> Set { return [] } - public func getDeviceLink(for slaveHexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> DeviceLink? { return nil } - public func getMasterHexEncodedPublicKey(for slaveHexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> String? { return nil } - - // MARK: Open Groups - public func getUserCount(for publicChat: PublicChat, in transaction: YapDatabaseReadTransaction) -> Int? { - return transaction.object(forKey: publicChat.id, inCollection: Storage.openGroupUserCountCollection) as? Int - } - - public func setUserCount(_ userCount: Int, forPublicChatWithID publicChatID: String, in transaction: YapDatabaseReadWriteTransaction) { - transaction.setObject(userCount, forKey: publicChatID, inCollection: Storage.openGroupUserCountCollection) - } - - public func getProfilePictureURL(forPublicChatWithID publicChatID: String, in transaction: YapDatabaseReadTransaction) -> String? { - return transaction.object(forKey: publicChatID, inCollection: Storage.openGroupProfilePictureURLCollection) as? String - } - - public func setProfilePictureURL(_ profilePictureURL: String?, forPublicChatWithID publicChatID: String, in transaction: YapDatabaseReadWriteTransaction) { - transaction.setObject(profilePictureURL, forKey: publicChatID, inCollection: Storage.openGroupProfilePictureURLCollection) - } -} diff --git a/SignalServiceKit/src/Loki/Database/Deprecated/Storage+Collections.swift b/SignalServiceKit/src/Loki/Database/Deprecated/Storage+Collections.swift deleted file mode 100644 index d017cf89d..000000000 --- a/SignalServiceKit/src/Loki/Database/Deprecated/Storage+Collections.swift +++ /dev/null @@ -1,21 +0,0 @@ - -// TODO: Create an extension for each category, e.g. Storage+OpenGroups, Storage+SnodePool, etc. - -@objc public extension Storage { - - // TODO: Add remaining collections - - @objc func getDeviceLinkCollection(for masterPublicKey: String) -> String { - return "LokiDeviceLinkCollection-\(masterPublicKey)" - } - - @objc public static func getSwarmCollection(for publicKey: String) -> String { - return "LokiSwarmCollection-\(publicKey)" - } - - @objc public static let openGroupCollection = "LokiPublicChatCollection" - @objc public static let openGroupProfilePictureURLCollection = "LokiPublicChatAvatarURLCollection" - @objc public static let openGroupUserCountCollection = "LokiPublicChatUserCountCollection" - @objc public static let sessionRequestTimestampCollection = "LokiSessionRequestTimestampCollection" - @objc public static let snodePoolCollection = "LokiSnodePoolCollection" -} diff --git a/SignalServiceKit/src/Loki/Database/Storage.swift b/SignalServiceKit/src/Loki/Database/Storage.swift deleted file mode 100644 index add543757..000000000 --- a/SignalServiceKit/src/Loki/Database/Storage.swift +++ /dev/null @@ -1,76 +0,0 @@ -import PromiseKit - -// Some important notes about YapDatabase: -// -// • Connections are thread-safe. -// • Executing a write transaction from within a write transaction is NOT allowed. - -@objc(LKStorage) -public final class Storage : NSObject { - public static let serialQueue = DispatchQueue(label: "Storage.serialQueue", qos: .userInitiated) - - private static var owsStorage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } - - // MARK: Reading - - // Some important points regarding reading from the database: - // - // • Background threads should use `OWSPrimaryStorage`'s `dbReadConnection`, whereas the main thread should use `OWSPrimaryStorage`'s `uiDatabaseConnection` (see the `YapDatabaseConnectionPool` documentation for more information). - // • Multiple read transactions can safely be executed at the same time. - - @objc(readWithBlock:) - public static func read(with block: @escaping (YapDatabaseReadTransaction) -> Void) { - // FIXME: For some reason the code below appears to be causing crashes even though it *should* - // be in line with the YapDatabase docs - /* - let isMainThread = Thread.current.isMainThread - let connection = isMainThread ? owsStorage.uiDatabaseConnection : owsStorage.dbReadConnection - connection.read(block) - */ - owsStorage.dbReadConnection.read(block) - } - - // MARK: Writing - - // Some important points regarding writing to the database: - // - // • There can only be a single write transaction per database at any one time, so all write transactions must use `OWSPrimaryStorage`'s `dbReadWriteConnection`. - // • Executing a write transaction from within a write transaction causes a deadlock and must be avoided. - - @discardableResult - @objc(writeWithBlock:) - public static func objc_write(with block: @escaping (YapDatabaseReadWriteTransaction) -> Void) -> AnyPromise { - return AnyPromise.from(write(with: block) { }) - } - - @discardableResult - public static func write(with block: @escaping (YapDatabaseReadWriteTransaction) -> Void) -> Promise { - return write(with: block) { } - } - - @discardableResult - @objc(writeWithBlock:completion:) - public static func objc_write(with block: @escaping (YapDatabaseReadWriteTransaction) -> Void, completion: @escaping () -> Void) -> AnyPromise { - return AnyPromise.from(write(with: block, completion: completion)) - } - - @discardableResult - public static func write(with block: @escaping (YapDatabaseReadWriteTransaction) -> Void, completion: @escaping () -> Void) -> Promise { - let (promise, seal) = Promise.pending() - serialQueue.async { - owsStorage.dbReadWriteConnection.readWrite { transaction in - transaction.addCompletionQueue(DispatchQueue.main, completionBlock: completion) - block(transaction) - } - seal.fulfill(()) - } - return promise - } - - /// Blocks the calling thread until the write has finished. - @discardableResult - @objc(writeSyncWithBlock:) - public static func writeSync(with block: @escaping (YapDatabaseReadWriteTransaction) -> Void) { - try! write(with: block, completion: { }).wait() // The promise returned by write(with:completion:) never rejects - } -} diff --git a/SignalServiceKit/src/Loki/Documentation/SessionReset.md b/SignalServiceKit/src/Loki/Documentation/SessionReset.md deleted file mode 100644 index 3ae8ddab8..000000000 --- a/SignalServiceKit/src/Loki/Documentation/SessionReset.md +++ /dev/null @@ -1,56 +0,0 @@ -# Loki Session Reset - -## Signal -Since Signal uses a centralised server, creating sessions is easy as the prekeys can be easily fetched. - -The process is as follows: - -1. `A` deletes all their sessions and sends `End Session` to `B` - - `A` contacts the server and creates a new session -2. `B` Gets this message and deletes all sessions. -3. `B` Sends a message with a newly created session - - `B` contacted server and established this -4. `A` and `B` now have the same sessions so they can delete any archived ones. - -## Loki -Loki doesn't have a centralised server and thus we need to change the process above with something similar. - -We have to introduce a session reset state `sessionState` which can take the following states: -- `none`: No session reset is in progress -- `initiated`: We have initiated the session reset -- `received`: We have received a session reset from the other user - -The new process is as follows: - -1. `A` Sends `End Session` with a `PreKeyBundle` and archives its own session. - - `sessionState = initiated` - - The session is archived as we could get a message from `B` using the archived session, so we still want to be able to decrypt that. - - We can show `Session reset in progress` -2. `B` Gets this message and saves the `PreKeyBundle` and archives its own sessions. - - `sessionState = received` - - `B` sends an empty message, which will trigger a new session to be created. - - `B` deletes the `PreKeyBundle` once session is created. - - We can show `Session reset in progress` -3. `A` and `B` both do the routine below when receiving messages. - -### Upon receiving message (Only applies to PreKey and Cipher messages) - -- Store the current active session `PS` -- Decrypt the message - - Decrypting a message can cause the active session to change -- If `sessionState == none` then it means that we haven't started session reset and we can abort. -- Get the current session `CS` -- If `PS` is `nil` then abort as we didn't have a session before. -- If `CS != PS` then sessions were changed. - - If `sessionState == received` then it means that the sender used an old session to contact us. We need to wait for them to use the new one. - - Archive `CS` and set the session to `PS` - - If `sessionState == initiated` then it means that the sender acknowledged our session reset and sent a message with a new session - - Delete all session except `CS` - - `sessionState = none` - - Send an empty message to confirm session adoption - - We can show `Session reset done` -- If `CS == PS` then sessions were the same. - - If `sessionState == received` then it means that the new session we created is the one the sender used for sending message. We have successfully adopted the new session. - - Delete all sessions except `PS` - - `sessionState = none` - - We can show `Session reset done` diff --git a/SignalServiceKit/src/Loki/Mnemonic/english.txt b/SignalServiceKit/src/Loki/Mnemonic/english.txt deleted file mode 100644 index c7f3a75da..000000000 --- a/SignalServiceKit/src/Loki/Mnemonic/english.txt +++ /dev/null @@ -1 +0,0 @@ -abbey,abducts,ability,ablaze,abnormal,abort,abrasive,absorb,abyss,academy,aces,aching,acidic,acoustic,acquire,across,actress,acumen,adapt,addicted,adept,adhesive,adjust,adopt,adrenalin,adult,adventure,aerial,afar,affair,afield,afloat,afoot,afraid,after,against,agenda,aggravate,agile,aglow,agnostic,agony,agreed,ahead,aided,ailments,aimless,airport,aisle,ajar,akin,alarms,album,alchemy,alerts,algebra,alkaline,alley,almost,aloof,alpine,already,also,altitude,alumni,always,amaze,ambush,amended,amidst,ammo,amnesty,among,amply,amused,anchor,android,anecdote,angled,ankle,annoyed,answers,antics,anvil,anxiety,anybody,apart,apex,aphid,aplomb,apology,apply,apricot,aptitude,aquarium,arbitrary,archer,ardent,arena,argue,arises,army,around,arrow,arsenic,artistic,ascend,ashtray,aside,asked,asleep,aspire,assorted,asylum,athlete,atlas,atom,atrium,attire,auburn,auctions,audio,august,aunt,austere,autumn,avatar,avidly,avoid,awakened,awesome,awful,awkward,awning,awoken,axes,axis,axle,aztec,azure,baby,bacon,badge,baffles,bagpipe,bailed,bakery,balding,bamboo,banjo,baptism,basin,batch,bawled,bays,because,beer,befit,begun,behind,being,below,bemused,benches,berries,bested,betting,bevel,beware,beyond,bias,bicycle,bids,bifocals,biggest,bikini,bimonthly,binocular,biology,biplane,birth,biscuit,bite,biweekly,blender,blip,bluntly,boat,bobsled,bodies,bogeys,boil,boldly,bomb,border,boss,both,bounced,bovine,bowling,boxes,boyfriend,broken,brunt,bubble,buckets,budget,buffet,bugs,building,bulb,bumper,bunch,business,butter,buying,buzzer,bygones,byline,bypass,cabin,cactus,cadets,cafe,cage,cajun,cake,calamity,camp,candy,casket,catch,cause,cavernous,cease,cedar,ceiling,cell,cement,cent,certain,chlorine,chrome,cider,cigar,cinema,circle,cistern,citadel,civilian,claim,click,clue,coal,cobra,cocoa,code,coexist,coffee,cogs,cohesive,coils,colony,comb,cool,copy,corrode,costume,cottage,cousin,cowl,criminal,cube,cucumber,cuddled,cuffs,cuisine,cunning,cupcake,custom,cycling,cylinder,cynical,dabbing,dads,daft,dagger,daily,damp,dangerous,dapper,darted,dash,dating,dauntless,dawn,daytime,dazed,debut,decay,dedicated,deepest,deftly,degrees,dehydrate,deity,dejected,delayed,demonstrate,dented,deodorant,depth,desk,devoid,dewdrop,dexterity,dialect,dice,diet,different,digit,dilute,dime,dinner,diode,diplomat,directed,distance,ditch,divers,dizzy,doctor,dodge,does,dogs,doing,dolphin,domestic,donuts,doorway,dormant,dosage,dotted,double,dove,down,dozen,dreams,drinks,drowning,drunk,drying,dual,dubbed,duckling,dude,duets,duke,dullness,dummy,dunes,duplex,duration,dusted,duties,dwarf,dwelt,dwindling,dying,dynamite,dyslexic,each,eagle,earth,easy,eating,eavesdrop,eccentric,echo,eclipse,economics,ecstatic,eden,edgy,edited,educated,eels,efficient,eggs,egotistic,eight,either,eject,elapse,elbow,eldest,eleven,elite,elope,else,eluded,emails,ember,emerge,emit,emotion,empty,emulate,energy,enforce,enhanced,enigma,enjoy,enlist,enmity,enough,enraged,ensign,entrance,envy,epoxy,equip,erase,erected,erosion,error,eskimos,espionage,essential,estate,etched,eternal,ethics,etiquette,evaluate,evenings,evicted,evolved,examine,excess,exhale,exit,exotic,exquisite,extra,exult,fabrics,factual,fading,fainted,faked,fall,family,fancy,farming,fatal,faulty,fawns,faxed,fazed,feast,february,federal,feel,feline,females,fences,ferry,festival,fetches,fever,fewest,fiat,fibula,fictional,fidget,fierce,fifteen,fight,films,firm,fishing,fitting,five,fixate,fizzle,fleet,flippant,flying,foamy,focus,foes,foggy,foiled,folding,fonts,foolish,fossil,fountain,fowls,foxes,foyer,framed,friendly,frown,fruit,frying,fudge,fuel,fugitive,fully,fuming,fungal,furnished,fuselage,future,fuzzy,gables,gadget,gags,gained,galaxy,gambit,gang,gasp,gather,gauze,gave,gawk,gaze,gearbox,gecko,geek,gels,gemstone,general,geometry,germs,gesture,getting,geyser,ghetto,ghost,giant,giddy,gifts,gigantic,gills,gimmick,ginger,girth,giving,glass,gleeful,glide,gnaw,gnome,goat,goblet,godfather,goes,goggles,going,goldfish,gone,goodbye,gopher,gorilla,gossip,gotten,gourmet,governing,gown,greater,grunt,guarded,guest,guide,gulp,gumball,guru,gusts,gutter,guys,gymnast,gypsy,gyrate,habitat,hacksaw,haggled,hairy,hamburger,happens,hashing,hatchet,haunted,having,hawk,haystack,hazard,hectare,hedgehog,heels,hefty,height,hemlock,hence,heron,hesitate,hexagon,hickory,hiding,highway,hijack,hiker,hills,himself,hinder,hippo,hire,history,hitched,hive,hoax,hobby,hockey,hoisting,hold,honked,hookup,hope,hornet,hospital,hotel,hounded,hover,howls,hubcaps,huddle,huge,hull,humid,hunter,hurried,husband,huts,hybrid,hydrogen,hyper,iceberg,icing,icon,identity,idiom,idled,idols,igloo,ignore,iguana,illness,imagine,imbalance,imitate,impel,inactive,inbound,incur,industrial,inexact,inflamed,ingested,initiate,injury,inkling,inline,inmate,innocent,inorganic,input,inquest,inroads,insult,intended,inundate,invoke,inwardly,ionic,irate,iris,irony,irritate,island,isolated,issued,italics,itches,items,itinerary,itself,ivory,jabbed,jackets,jaded,jagged,jailed,jamming,january,jargon,jaunt,javelin,jaws,jazz,jeans,jeers,jellyfish,jeopardy,jerseys,jester,jetting,jewels,jigsaw,jingle,jittery,jive,jobs,jockey,jogger,joining,joking,jolted,jostle,journal,joyous,jubilee,judge,juggled,juicy,jukebox,july,jump,junk,jury,justice,juvenile,kangaroo,karate,keep,kennel,kept,kernels,kettle,keyboard,kickoff,kidneys,king,kiosk,kisses,kitchens,kiwi,knapsack,knee,knife,knowledge,knuckle,koala,laboratory,ladder,lagoon,lair,lakes,lamb,language,laptop,large,last,later,launching,lava,lawsuit,layout,lazy,lectures,ledge,leech,left,legion,leisure,lemon,lending,leopard,lesson,lettuce,lexicon,liar,library,licks,lids,lied,lifestyle,light,likewise,lilac,limits,linen,lion,lipstick,liquid,listen,lively,loaded,lobster,locker,lodge,lofty,logic,loincloth,long,looking,lopped,lordship,losing,lottery,loudly,love,lower,loyal,lucky,luggage,lukewarm,lullaby,lumber,lunar,lurk,lush,luxury,lymph,lynx,lyrics,macro,madness,magically,mailed,major,makeup,malady,mammal,maps,masterful,match,maul,maverick,maximum,mayor,maze,meant,mechanic,medicate,meeting,megabyte,melting,memoir,menu,merger,mesh,metro,mews,mice,midst,mighty,mime,mirror,misery,mittens,mixture,moat,mobile,mocked,mohawk,moisture,molten,moment,money,moon,mops,morsel,mostly,motherly,mouth,movement,mowing,much,muddy,muffin,mugged,mullet,mumble,mundane,muppet,mural,musical,muzzle,myriad,mystery,myth,nabbing,nagged,nail,names,nanny,napkin,narrate,nasty,natural,nautical,navy,nearby,necklace,needed,negative,neither,neon,nephew,nerves,nestle,network,neutral,never,newt,nexus,nibs,niche,niece,nifty,nightly,nimbly,nineteen,nirvana,nitrogen,nobody,nocturnal,nodes,noises,nomad,noodles,northern,nostril,noted,nouns,novelty,nowhere,nozzle,nuance,nucleus,nudged,nugget,nuisance,null,number,nuns,nurse,nutshell,nylon,oaks,oars,oasis,oatmeal,obedient,object,obliged,obnoxious,observant,obtains,obvious,occur,ocean,october,odds,odometer,offend,often,oilfield,ointment,okay,older,olive,olympics,omega,omission,omnibus,onboard,oncoming,oneself,ongoing,onion,online,onslaught,onto,onward,oozed,opacity,opened,opposite,optical,opus,orange,orbit,orchid,orders,organs,origin,ornament,orphans,oscar,ostrich,otherwise,otter,ouch,ought,ounce,ourselves,oust,outbreak,oval,oven,owed,owls,owner,oxidant,oxygen,oyster,ozone,pact,paddles,pager,pairing,palace,pamphlet,pancakes,paper,paradise,pastry,patio,pause,pavements,pawnshop,payment,peaches,pebbles,peculiar,pedantic,peeled,pegs,pelican,pencil,people,pepper,perfect,pests,petals,phase,pheasants,phone,phrases,physics,piano,picked,pierce,pigment,piloted,pimple,pinched,pioneer,pipeline,pirate,pistons,pitched,pivot,pixels,pizza,playful,pledge,pliers,plotting,plus,plywood,poaching,pockets,podcast,poetry,point,poker,polar,ponies,pool,popular,portents,possible,potato,pouch,poverty,powder,pram,present,pride,problems,pruned,prying,psychic,public,puck,puddle,puffin,pulp,pumpkins,punch,puppy,purged,push,putty,puzzled,pylons,pyramid,python,queen,quick,quote,rabbits,racetrack,radar,rafts,rage,railway,raking,rally,ramped,randomly,rapid,rarest,rash,rated,ravine,rays,razor,react,rebel,recipe,reduce,reef,refer,regular,reheat,reinvest,rejoices,rekindle,relic,remedy,renting,reorder,repent,request,reruns,rest,return,reunion,revamp,rewind,rhino,rhythm,ribbon,richly,ridges,rift,rigid,rims,ringing,riots,ripped,rising,ritual,river,roared,robot,rockets,rodent,rogue,roles,romance,roomy,roped,roster,rotate,rounded,rover,rowboat,royal,ruby,rudely,ruffled,rugged,ruined,ruling,rumble,runway,rural,rustled,ruthless,sabotage,sack,sadness,safety,saga,sailor,sake,salads,sample,sanity,sapling,sarcasm,sash,satin,saucepan,saved,sawmill,saxophone,sayings,scamper,scenic,school,science,scoop,scrub,scuba,seasons,second,sedan,seeded,segments,seismic,selfish,semifinal,sensible,september,sequence,serving,session,setup,seventh,sewage,shackles,shelter,shipped,shocking,shrugged,shuffled,shyness,siblings,sickness,sidekick,sieve,sifting,sighting,silk,simplest,sincerely,sipped,siren,situated,sixteen,sizes,skater,skew,skirting,skulls,skydive,slackens,sleepless,slid,slower,slug,smash,smelting,smidgen,smog,smuggled,snake,sneeze,sniff,snout,snug,soapy,sober,soccer,soda,software,soggy,soil,solved,somewhere,sonic,soothe,soprano,sorry,southern,sovereign,sowed,soya,space,speedy,sphere,spiders,splendid,spout,sprig,spud,spying,square,stacking,stellar,stick,stockpile,strained,stunning,stylishly,subtly,succeed,suddenly,suede,suffice,sugar,suitcase,sulking,summon,sunken,superior,surfer,sushi,suture,swagger,swept,swiftly,sword,swung,syllabus,symptoms,syndrome,syringe,system,taboo,tacit,tadpoles,tagged,tail,taken,talent,tamper,tanks,tapestry,tarnished,tasked,tattoo,taunts,tavern,tawny,taxi,teardrop,technical,tedious,teeming,tell,template,tender,tepid,tequila,terminal,testing,tether,textbook,thaw,theatrics,thirsty,thorn,threaten,thumbs,thwart,ticket,tidy,tiers,tiger,tilt,timber,tinted,tipsy,tirade,tissue,titans,toaster,tobacco,today,toenail,toffee,together,toilet,token,tolerant,tomorrow,tonic,toolbox,topic,torch,tossed,total,touchy,towel,toxic,toyed,trash,trendy,tribal,trolling,truth,trying,tsunami,tubes,tucks,tudor,tuesday,tufts,tugs,tuition,tulips,tumbling,tunnel,turnip,tusks,tutor,tuxedo,twang,tweezers,twice,twofold,tycoon,typist,tyrant,ugly,ulcers,ultimate,umbrella,umpire,unafraid,unbending,uncle,under,uneven,unfit,ungainly,unhappy,union,unjustly,unknown,unlikely,unmask,unnoticed,unopened,unplugs,unquoted,unrest,unsafe,until,unusual,unveil,unwind,unzip,upbeat,upcoming,update,upgrade,uphill,upkeep,upload,upon,upper,upright,upstairs,uptight,upwards,urban,urchins,urgent,usage,useful,usher,using,usual,utensils,utility,utmost,utopia,uttered,vacation,vague,vain,value,vampire,vane,vapidly,vary,vastness,vats,vaults,vector,veered,vegan,vehicle,vein,velvet,venomous,verification,vessel,veteran,vexed,vials,vibrate,victim,video,viewpoint,vigilant,viking,village,vinegar,violin,vipers,virtual,visited,vitals,vivid,vixen,vocal,vogue,voice,volcano,vortex,voted,voucher,vowels,voyage,vulture,wade,waffle,wagtail,waist,waking,wallets,wanted,warped,washing,water,waveform,waxing,wayside,weavers,website,wedge,weekday,weird,welders,went,wept,were,western,wetsuit,whale,when,whipped,whole,wickets,width,wield,wife,wiggle,wildly,winter,wipeout,wiring,wise,withdrawn,wives,wizard,wobbly,woes,woken,wolf,womanly,wonders,woozy,worry,wounded,woven,wrap,wrist,wrong,yacht,yahoo,yanks,yard,yawning,yearbook,yellow,yesterday,yeti,yields,yodel,yoga,younger,yoyo,zapped,zeal,zebra,zero,zesty,zigzags,zinger,zippers,zodiac,zombie,zones,zoom diff --git a/SignalServiceKit/src/Loki/Mnemonic/japanese.txt b/SignalServiceKit/src/Loki/Mnemonic/japanese.txt deleted file mode 100644 index 5080d65a3..000000000 --- a/SignalServiceKit/src/Loki/Mnemonic/japanese.txt +++ /dev/null @@ -1 +0,0 @@ -あいこくしん,あいさつ,あいだ,あおぞら,あかちゃん,あきる,あけがた,あける,あこがれる,あさい,あさひ,あしあと,あじわう,あずかる,あずき,あそぶ,あたえる,あたためる,あたりまえ,あたる,あつい,あつかう,あっしゅく,あつまり,あつめる,あてな,あてはまる,あひる,あぶら,あぶる,あふれる,あまい,あまど,あまやかす,あまり,あみもの,あめりか,あやまる,あゆむ,あらいぐま,あらし,あらすじ,あらためる,あらゆる,あらわす,ありがとう,あわせる,あわてる,あんい,あんがい,あんこ,あんぜん,あんてい,あんない,あんまり,いいだす,いおん,いがい,いがく,いきおい,いきなり,いきもの,いきる,いくじ,いくぶん,いけばな,いけん,いこう,いこく,いこつ,いさましい,いさん,いしき,いじゅう,いじょう,いじわる,いずみ,いずれ,いせい,いせえび,いせかい,いせき,いぜん,いそうろう,いそがしい,いだい,いだく,いたずら,いたみ,いたりあ,いちおう,いちじ,いちど,いちば,いちぶ,いちりゅう,いつか,いっしゅん,いっせい,いっそう,いったん,いっち,いってい,いっぽう,いてざ,いてん,いどう,いとこ,いない,いなか,いねむり,いのち,いのる,いはつ,いばる,いはん,いびき,いひん,いふく,いへん,いほう,いみん,いもうと,いもたれ,いもり,いやがる,いやす,いよかん,いよく,いらい,いらすと,いりぐち,いりょう,いれい,いれもの,いれる,いろえんぴつ,いわい,いわう,いわかん,いわば,いわゆる,いんげんまめ,いんさつ,いんしょう,いんよう,うえき,うえる,うおざ,うがい,うかぶ,うかべる,うきわ,うくらいな,うくれれ,うけたまわる,うけつけ,うけとる,うけもつ,うける,うごかす,うごく,うこん,うさぎ,うしなう,うしろがみ,うすい,うすぎ,うすぐらい,うすめる,うせつ,うちあわせ,うちがわ,うちき,うちゅう,うっかり,うつくしい,うったえる,うつる,うどん,うなぎ,うなじ,うなずく,うなる,うねる,うのう,うぶげ,うぶごえ,うまれる,うめる,うもう,うやまう,うよく,うらがえす,うらぐち,うらない,うりあげ,うりきれ,うるさい,うれしい,うれゆき,うれる,うろこ,うわき,うわさ,うんこう,うんちん,うんてん,うんどう,えいえん,えいが,えいきょう,えいご,えいせい,えいぶん,えいよう,えいわ,えおり,えがお,えがく,えきたい,えくせる,えしゃく,えすて,えつらん,えのぐ,えほうまき,えほん,えまき,えもじ,えもの,えらい,えらぶ,えりあ,えんえん,えんかい,えんぎ,えんげき,えんしゅう,えんぜつ,えんそく,えんちょう,えんとつ,おいかける,おいこす,おいしい,おいつく,おうえん,おうさま,おうじ,おうせつ,おうたい,おうふく,おうべい,おうよう,おえる,おおい,おおう,おおどおり,おおや,おおよそ,おかえり,おかず,おがむ,おかわり,おぎなう,おきる,おくさま,おくじょう,おくりがな,おくる,おくれる,おこす,おこなう,おこる,おさえる,おさない,おさめる,おしいれ,おしえる,おじぎ,おじさん,おしゃれ,おそらく,おそわる,おたがい,おたく,おだやか,おちつく,おっと,おつり,おでかけ,おとしもの,おとなしい,おどり,おどろかす,おばさん,おまいり,おめでとう,おもいで,おもう,おもたい,おもちゃ,おやつ,おやゆび,およぼす,おらんだ,おろす,おんがく,おんけい,おんしゃ,おんせん,おんだん,おんちゅう,おんどけい,かあつ,かいが,がいき,がいけん,がいこう,かいさつ,かいしゃ,かいすいよく,かいぜん,かいぞうど,かいつう,かいてん,かいとう,かいふく,がいへき,かいほう,かいよう,がいらい,かいわ,かえる,かおり,かかえる,かがく,かがし,かがみ,かくご,かくとく,かざる,がぞう,かたい,かたち,がちょう,がっきゅう,がっこう,がっさん,がっしょう,かなざわし,かのう,がはく,かぶか,かほう,かほご,かまう,かまぼこ,かめれおん,かゆい,かようび,からい,かるい,かろう,かわく,かわら,がんか,かんけい,かんこう,かんしゃ,かんそう,かんたん,かんち,がんばる,きあい,きあつ,きいろ,ぎいん,きうい,きうん,きえる,きおう,きおく,きおち,きおん,きかい,きかく,きかんしゃ,ききて,きくばり,きくらげ,きけんせい,きこう,きこえる,きこく,きさい,きさく,きさま,きさらぎ,ぎじかがく,ぎしき,ぎじたいけん,ぎじにってい,ぎじゅつしゃ,きすう,きせい,きせき,きせつ,きそう,きぞく,きぞん,きたえる,きちょう,きつえん,ぎっちり,きつつき,きつね,きてい,きどう,きどく,きない,きなが,きなこ,きぬごし,きねん,きのう,きのした,きはく,きびしい,きひん,きふく,きぶん,きぼう,きほん,きまる,きみつ,きむずかしい,きめる,きもだめし,きもち,きもの,きゃく,きやく,ぎゅうにく,きよう,きょうりゅう,きらい,きらく,きりん,きれい,きれつ,きろく,ぎろん,きわめる,ぎんいろ,きんかくじ,きんじょ,きんようび,ぐあい,くいず,くうかん,くうき,くうぐん,くうこう,ぐうせい,くうそう,ぐうたら,くうふく,くうぼ,くかん,くきょう,くげん,ぐこう,くさい,くさき,くさばな,くさる,くしゃみ,くしょう,くすのき,くすりゆび,くせげ,くせん,ぐたいてき,くださる,くたびれる,くちこみ,くちさき,くつした,ぐっすり,くつろぐ,くとうてん,くどく,くなん,くねくね,くのう,くふう,くみあわせ,くみたてる,くめる,くやくしょ,くらす,くらべる,くるま,くれる,くろう,くわしい,ぐんかん,ぐんしょく,ぐんたい,ぐんて,けあな,けいかく,けいけん,けいこ,けいさつ,げいじゅつ,けいたい,げいのうじん,けいれき,けいろ,けおとす,けおりもの,げきか,げきげん,げきだん,げきちん,げきとつ,げきは,げきやく,げこう,げこくじょう,げざい,けさき,げざん,けしき,けしごむ,けしょう,げすと,けたば,けちゃっぷ,けちらす,けつあつ,けつい,けつえき,けっこん,けつじょ,けっせき,けってい,けつまつ,げつようび,げつれい,けつろん,げどく,けとばす,けとる,けなげ,けなす,けなみ,けぬき,げねつ,けねん,けはい,げひん,けぶかい,げぼく,けまり,けみかる,けむし,けむり,けもの,けらい,けろけろ,けわしい,けんい,けんえつ,けんお,けんか,げんき,けんげん,けんこう,けんさく,けんしゅう,けんすう,げんそう,けんちく,けんてい,けんとう,けんない,けんにん,げんぶつ,けんま,けんみん,けんめい,けんらん,けんり,こあくま,こいぬ,こいびと,ごうい,こうえん,こうおん,こうかん,ごうきゅう,ごうけい,こうこう,こうさい,こうじ,こうすい,ごうせい,こうそく,こうたい,こうちゃ,こうつう,こうてい,こうどう,こうない,こうはい,ごうほう,ごうまん,こうもく,こうりつ,こえる,こおり,ごかい,ごがつ,ごかん,こくご,こくさい,こくとう,こくない,こくはく,こぐま,こけい,こける,ここのか,こころ,こさめ,こしつ,こすう,こせい,こせき,こぜん,こそだて,こたい,こたえる,こたつ,こちょう,こっか,こつこつ,こつばん,こつぶ,こてい,こてん,ことがら,ことし,ことば,ことり,こなごな,こねこね,このまま,このみ,このよ,ごはん,こひつじ,こふう,こふん,こぼれる,ごまあぶら,こまかい,ごますり,こまつな,こまる,こむぎこ,こもじ,こもち,こもの,こもん,こやく,こやま,こゆう,こゆび,こよい,こよう,こりる,これくしょん,ころっけ,こわもて,こわれる,こんいん,こんかい,こんき,こんしゅう,こんすい,こんだて,こんとん,こんなん,こんびに,こんぽん,こんまけ,こんや,こんれい,こんわく,ざいえき,さいかい,さいきん,ざいげん,ざいこ,さいしょ,さいせい,ざいたく,ざいちゅう,さいてき,ざいりょう,さうな,さかいし,さがす,さかな,さかみち,さがる,さぎょう,さくし,さくひん,さくら,さこく,さこつ,さずかる,ざせき,さたん,さつえい,ざつおん,ざっか,ざつがく,さっきょく,ざっし,さつじん,ざっそう,さつたば,さつまいも,さてい,さといも,さとう,さとおや,さとし,さとる,さのう,さばく,さびしい,さべつ,さほう,さほど,さます,さみしい,さみだれ,さむけ,さめる,さやえんどう,さゆう,さよう,さよく,さらだ,ざるそば,さわやか,さわる,さんいん,さんか,さんきゃく,さんこう,さんさい,ざんしょ,さんすう,さんせい,さんそ,さんち,さんま,さんみ,さんらん,しあい,しあげ,しあさって,しあわせ,しいく,しいん,しうち,しえい,しおけ,しかい,しかく,じかん,しごと,しすう,じだい,したうけ,したぎ,したて,したみ,しちょう,しちりん,しっかり,しつじ,しつもん,してい,してき,してつ,じてん,じどう,しなぎれ,しなもの,しなん,しねま,しねん,しのぐ,しのぶ,しはい,しばかり,しはつ,しはらい,しはん,しひょう,しふく,じぶん,しへい,しほう,しほん,しまう,しまる,しみん,しむける,じむしょ,しめい,しめる,しもん,しゃいん,しゃうん,しゃおん,じゃがいも,しやくしょ,しゃくほう,しゃけん,しゃこ,しゃざい,しゃしん,しゃせん,しゃそう,しゃたい,しゃちょう,しゃっきん,じゃま,しゃりん,しゃれい,じゆう,じゅうしょ,しゅくはく,じゅしん,しゅっせき,しゅみ,しゅらば,じゅんばん,しょうかい,しょくたく,しょっけん,しょどう,しょもつ,しらせる,しらべる,しんか,しんこう,じんじゃ,しんせいじ,しんちく,しんりん,すあげ,すあし,すあな,ずあん,すいえい,すいか,すいとう,ずいぶん,すいようび,すうがく,すうじつ,すうせん,すおどり,すきま,すくう,すくない,すける,すごい,すこし,ずさん,すずしい,すすむ,すすめる,すっかり,ずっしり,ずっと,すてき,すてる,すねる,すのこ,すはだ,すばらしい,ずひょう,ずぶぬれ,すぶり,すふれ,すべて,すべる,ずほう,すぼん,すまい,すめし,すもう,すやき,すらすら,するめ,すれちがう,すろっと,すわる,すんぜん,すんぽう,せあぶら,せいかつ,せいげん,せいじ,せいよう,せおう,せかいかん,せきにん,せきむ,せきゆ,せきらんうん,せけん,せこう,せすじ,せたい,せたけ,せっかく,せっきゃく,ぜっく,せっけん,せっこつ,せっさたくま,せつぞく,せつだん,せつでん,せっぱん,せつび,せつぶん,せつめい,せつりつ,せなか,せのび,せはば,せびろ,せぼね,せまい,せまる,せめる,せもたれ,せりふ,ぜんあく,せんい,せんえい,せんか,せんきょ,せんく,せんげん,ぜんご,せんさい,せんしゅ,せんすい,せんせい,せんぞ,せんたく,せんちょう,せんてい,せんとう,せんぬき,せんねん,せんぱい,ぜんぶ,ぜんぽう,せんむ,せんめんじょ,せんもん,せんやく,せんゆう,せんよう,ぜんら,ぜんりゃく,せんれい,せんろ,そあく,そいとげる,そいね,そうがんきょう,そうき,そうご,そうしん,そうだん,そうなん,そうび,そうめん,そうり,そえもの,そえん,そがい,そげき,そこう,そこそこ,そざい,そしな,そせい,そせん,そそぐ,そだてる,そつう,そつえん,そっかん,そつぎょう,そっけつ,そっこう,そっせん,そっと,そとがわ,そとづら,そなえる,そなた,そふぼ,そぼく,そぼろ,そまつ,そまる,そむく,そむりえ,そめる,そもそも,そよかぜ,そらまめ,そろう,そんかい,そんけい,そんざい,そんしつ,そんぞく,そんちょう,ぞんび,ぞんぶん,そんみん,たあい,たいいん,たいうん,たいえき,たいおう,だいがく,たいき,たいぐう,たいけん,たいこ,たいざい,だいじょうぶ,だいすき,たいせつ,たいそう,だいたい,たいちょう,たいてい,だいどころ,たいない,たいねつ,たいのう,たいはん,だいひょう,たいふう,たいへん,たいほ,たいまつばな,たいみんぐ,たいむ,たいめん,たいやき,たいよう,たいら,たいりょく,たいる,たいわん,たうえ,たえる,たおす,たおる,たおれる,たかい,たかね,たきび,たくさん,たこく,たこやき,たさい,たしざん,だじゃれ,たすける,たずさわる,たそがれ,たたかう,たたく,ただしい,たたみ,たちばな,だっかい,だっきゃく,だっこ,だっしゅつ,だったい,たてる,たとえる,たなばた,たにん,たぬき,たのしみ,たはつ,たぶん,たべる,たぼう,たまご,たまる,だむる,ためいき,ためす,ためる,たもつ,たやすい,たよる,たらす,たりきほんがん,たりょう,たりる,たると,たれる,たれんと,たろっと,たわむれる,だんあつ,たんい,たんおん,たんか,たんき,たんけん,たんご,たんさん,たんじょうび,だんせい,たんそく,たんたい,だんち,たんてい,たんとう,だんな,たんにん,だんねつ,たんのう,たんぴん,だんぼう,たんまつ,たんめい,だんれつ,だんろ,だんわ,ちあい,ちあん,ちいき,ちいさい,ちえん,ちかい,ちから,ちきゅう,ちきん,ちけいず,ちけん,ちこく,ちさい,ちしき,ちしりょう,ちせい,ちそう,ちたい,ちたん,ちちおや,ちつじょ,ちてき,ちてん,ちぬき,ちぬり,ちのう,ちひょう,ちへいせん,ちほう,ちまた,ちみつ,ちみどろ,ちめいど,ちゃんこなべ,ちゅうい,ちゆりょく,ちょうし,ちょさくけん,ちらし,ちらみ,ちりがみ,ちりょう,ちるど,ちわわ,ちんたい,ちんもく,ついか,ついたち,つうか,つうじょう,つうはん,つうわ,つかう,つかれる,つくね,つくる,つけね,つける,つごう,つたえる,つづく,つつじ,つつむ,つとめる,つながる,つなみ,つねづね,つのる,つぶす,つまらない,つまる,つみき,つめたい,つもり,つもる,つよい,つるぼ,つるみく,つわもの,つわり,てあし,てあて,てあみ,ていおん,ていか,ていき,ていけい,ていこく,ていさつ,ていし,ていせい,ていたい,ていど,ていねい,ていひょう,ていへん,ていぼう,てうち,ておくれ,てきとう,てくび,でこぼこ,てさぎょう,てさげ,てすり,てそう,てちがい,てちょう,てつがく,てつづき,でっぱ,てつぼう,てつや,でぬかえ,てぬき,てぬぐい,てのひら,てはい,てぶくろ,てふだ,てほどき,てほん,てまえ,てまきずし,てみじか,てみやげ,てらす,てれび,てわけ,てわたし,でんあつ,てんいん,てんかい,てんき,てんぐ,てんけん,てんごく,てんさい,てんし,てんすう,でんち,てんてき,てんとう,てんない,てんぷら,てんぼうだい,てんめつ,てんらんかい,でんりょく,でんわ,どあい,といれ,どうかん,とうきゅう,どうぐ,とうし,とうむぎ,とおい,とおか,とおく,とおす,とおる,とかい,とかす,ときおり,ときどき,とくい,とくしゅう,とくてん,とくに,とくべつ,とけい,とける,とこや,とさか,としょかん,とそう,とたん,とちゅう,とっきゅう,とっくん,とつぜん,とつにゅう,とどける,ととのえる,とない,となえる,となり,とのさま,とばす,どぶがわ,とほう,とまる,とめる,ともだち,ともる,どようび,とらえる,とんかつ,どんぶり,ないかく,ないこう,ないしょ,ないす,ないせん,ないそう,なおす,ながい,なくす,なげる,なこうど,なさけ,なたでここ,なっとう,なつやすみ,ななおし,なにごと,なにもの,なにわ,なのか,なふだ,なまいき,なまえ,なまみ,なみだ,なめらか,なめる,なやむ,ならう,ならび,ならぶ,なれる,なわとび,なわばり,にあう,にいがた,にうけ,におい,にかい,にがて,にきび,にくしみ,にくまん,にげる,にさんかたんそ,にしき,にせもの,にちじょう,にちようび,にっか,にっき,にっけい,にっこう,にっさん,にっしょく,にっすう,にっせき,にってい,になう,にほん,にまめ,にもつ,にやり,にゅういん,にりんしゃ,にわとり,にんい,にんか,にんき,にんげん,にんしき,にんずう,にんそう,にんたい,にんち,にんてい,にんにく,にんぷ,にんまり,にんむ,にんめい,にんよう,ぬいくぎ,ぬかす,ぬぐいとる,ぬぐう,ぬくもり,ぬすむ,ぬまえび,ぬめり,ぬらす,ぬんちゃく,ねあげ,ねいき,ねいる,ねいろ,ねぐせ,ねくたい,ねくら,ねこぜ,ねこむ,ねさげ,ねすごす,ねそべる,ねだん,ねつい,ねっしん,ねつぞう,ねったいぎょ,ねぶそく,ねふだ,ねぼう,ねほりはほり,ねまき,ねまわし,ねみみ,ねむい,ねむたい,ねもと,ねらう,ねわざ,ねんいり,ねんおし,ねんかん,ねんきん,ねんぐ,ねんざ,ねんし,ねんちゃく,ねんど,ねんぴ,ねんぶつ,ねんまつ,ねんりょう,ねんれい,のいず,のおづま,のがす,のきなみ,のこぎり,のこす,のこる,のせる,のぞく,のぞむ,のたまう,のちほど,のっく,のばす,のはら,のべる,のぼる,のみもの,のやま,のらいぬ,のらねこ,のりもの,のりゆき,のれん,のんき,ばあい,はあく,ばあさん,ばいか,ばいく,はいけん,はいご,はいしん,はいすい,はいせん,はいそう,はいち,ばいばい,はいれつ,はえる,はおる,はかい,ばかり,はかる,はくしゅ,はけん,はこぶ,はさみ,はさん,はしご,ばしょ,はしる,はせる,ぱそこん,はそん,はたん,はちみつ,はつおん,はっかく,はづき,はっきり,はっくつ,はっけん,はっこう,はっさん,はっしん,はったつ,はっちゅう,はってん,はっぴょう,はっぽう,はなす,はなび,はにかむ,はぶらし,はみがき,はむかう,はめつ,はやい,はやし,はらう,はろうぃん,はわい,はんい,はんえい,はんおん,はんかく,はんきょう,ばんぐみ,はんこ,はんしゃ,はんすう,はんだん,ぱんち,ぱんつ,はんてい,はんとし,はんのう,はんぱ,はんぶん,はんぺん,はんぼうき,はんめい,はんらん,はんろん,ひいき,ひうん,ひえる,ひかく,ひかり,ひかる,ひかん,ひくい,ひけつ,ひこうき,ひこく,ひさい,ひさしぶり,ひさん,びじゅつかん,ひしょ diff --git a/SignalServiceKit/src/Loki/Mnemonic/portuguese.txt b/SignalServiceKit/src/Loki/Mnemonic/portuguese.txt deleted file mode 100644 index e224a094b..000000000 --- a/SignalServiceKit/src/Loki/Mnemonic/portuguese.txt +++ /dev/null @@ -1 +0,0 @@ -abaular,abdominal,abeto,abissinio,abjeto,ablucao,abnegar,abotoar,abrutalhar,absurdo,abutre,acautelar,accessorios,acetona,achocolatado,acirrar,acne,acovardar,acrostico,actinomicete,acustico,adaptavel,adeus,adivinho,adjunto,admoestar,adnominal,adotivo,adquirir,adriatico,adsorcao,adutora,advogar,aerossol,afazeres,afetuoso,afixo,afluir,afortunar,afrouxar,aftosa,afunilar,agentes,agito,aglutinar,aiatola,aimore,aino,aipo,airoso,ajeitar,ajoelhar,ajudante,ajuste,alazao,albumina,alcunha,alegria,alexandre,alforriar,alguns,alhures,alivio,almoxarife,alotropico,alpiste,alquimista,alsaciano,altura,aluviao,alvura,amazonico,ambulatorio,ametodico,amizades,amniotico,amovivel,amurada,anatomico,ancorar,anexo,anfora,aniversario,anjo,anotar,ansioso,anturio,anuviar,anverso,anzol,aonde,apaziguar,apito,aplicavel,apoteotico,aprimorar,aprumo,apto,apuros,aquoso,arauto,arbusto,arduo,aresta,arfar,arguto,aritmetico,arlequim,armisticio,aromatizar,arpoar,arquivo,arrumar,arsenio,arturiano,aruaque,arvores,asbesto,ascorbico,aspirina,asqueroso,assustar,astuto,atazanar,ativo,atletismo,atmosferico,atormentar,atroz,aturdir,audivel,auferir,augusto,aula,aumento,aurora,autuar,avatar,avexar,avizinhar,avolumar,avulso,axiomatico,azerbaijano,azimute,azoto,azulejo,bacteriologista,badulaque,baforada,baixote,bajular,balzaquiana,bambuzal,banzo,baoba,baqueta,barulho,bastonete,batuta,bauxita,bavaro,bazuca,bcrepuscular,beato,beduino,begonia,behaviorista,beisebol,belzebu,bemol,benzido,beocio,bequer,berro,besuntar,betume,bexiga,bezerro,biatlon,biboca,bicuspide,bidirecional,bienio,bifurcar,bigorna,bijuteria,bimotor,binormal,bioxido,bipolarizacao,biquini,birutice,bisturi,bituca,biunivoco,bivalve,bizarro,blasfemo,blenorreia,blindar,bloqueio,blusao,boazuda,bofete,bojudo,bolso,bombordo,bonzo,botina,boquiaberto,bostoniano,botulismo,bourbon,bovino,boximane,bravura,brevidade,britar,broxar,bruno,bruxuleio,bubonico,bucolico,buda,budista,bueiro,buffer,bugre,bujao,bumerangue,burundines,busto,butique,buzios,caatinga,cabuqui,cacunda,cafuzo,cajueiro,camurca,canudo,caquizeiro,carvoeiro,casulo,catuaba,cauterizar,cebolinha,cedula,ceifeiro,celulose,cerzir,cesto,cetro,ceus,cevar,chavena,cheroqui,chita,chovido,chuvoso,ciatico,cibernetico,cicuta,cidreira,cientistas,cifrar,cigarro,cilio,cimo,cinzento,cioso,cipriota,cirurgico,cisto,citrico,ciumento,civismo,clavicula,clero,clitoris,cluster,coaxial,cobrir,cocota,codorniz,coexistir,cogumelo,coito,colusao,compaixao,comutativo,contentamento,convulsivo,coordenativa,coquetel,correto,corvo,costureiro,cotovia,covil,cozinheiro,cretino,cristo,crivo,crotalo,cruzes,cubo,cucuia,cueiro,cuidar,cujo,cultural,cunilingua,cupula,curvo,custoso,cutucar,czarismo,dablio,dacota,dados,daguerreotipo,daiquiri,daltonismo,damista,dantesco,daquilo,darwinista,dasein,dativo,deao,debutantes,decurso,deduzir,defunto,degustar,dejeto,deltoide,demover,denunciar,deputado,deque,dervixe,desvirtuar,deturpar,deuteronomio,devoto,dextrose,dezoito,diatribe,dicotomico,didatico,dietista,difuso,digressao,diluvio,diminuto,dinheiro,dinossauro,dioxido,diplomatico,dique,dirimivel,disturbio,diurno,divulgar,dizivel,doar,dobro,docura,dodoi,doer,dogue,doloso,domo,donzela,doping,dorsal,dossie,dote,doutro,doze,dravidico,dreno,driver,dropes,druso,dubnio,ducto,dueto,dulija,dundum,duodeno,duquesa,durou,duvidoso,duzia,ebano,ebrio,eburneo,echarpe,eclusa,ecossistema,ectoplasma,ecumenismo,eczema,eden,editorial,edredom,edulcorar,efetuar,efigie,efluvio,egiptologo,egresso,egua,einsteiniano,eira,eivar,eixos,ejetar,elastomero,eldorado,elixir,elmo,eloquente,elucidativo,emaranhar,embutir,emerito,emfa,emitir,emotivo,empuxo,emulsao,enamorar,encurvar,enduro,enevoar,enfurnar,enguico,enho,enigmista,enlutar,enormidade,enpreendimento,enquanto,enriquecer,enrugar,entusiastico,enunciar,envolvimento,enxuto,enzimatico,eolico,epiteto,epoxi,epura,equivoco,erario,erbio,ereto,erguido,erisipela,ermo,erotizar,erros,erupcao,ervilha,esburacar,escutar,esfuziante,esguio,esloveno,esmurrar,esoterismo,esperanca,espirito,espurio,essencialmente,esturricar,esvoacar,etario,eterno,etiquetar,etnologo,etos,etrusco,euclidiano,euforico,eugenico,eunuco,europio,eustaquio,eutanasia,evasivo,eventualidade,evitavel,evoluir,exaustor,excursionista,exercito,exfoliado,exito,exotico,expurgo,exsudar,extrusora,exumar,fabuloso,facultativo,fado,fagulha,faixas,fajuto,faltoso,famoso,fanzine,fapesp,faquir,fartura,fastio,faturista,fausto,favorito,faxineira,fazer,fealdade,febril,fecundo,fedorento,feerico,feixe,felicidade,felpudo,feltro,femur,fenotipo,fervura,festivo,feto,feudo,fevereiro,fezinha,fiasco,fibra,ficticio,fiduciario,fiesp,fifa,figurino,fijiano,filtro,finura,fiorde,fiquei,firula,fissurar,fitoteca,fivela,fixo,flavio,flexor,flibusteiro,flotilha,fluxograma,fobos,foco,fofura,foguista,foie,foliculo,fominha,fonte,forum,fosso,fotossintese,foxtrote,fraudulento,frevo,frivolo,frouxo,frutose,fuba,fucsia,fugitivo,fuinha,fujao,fulustreco,fumo,funileiro,furunculo,fustigar,futurologo,fuxico,fuzue,gabriel,gado,gaelico,gafieira,gaguejo,gaivota,gajo,galvanoplastico,gamo,ganso,garrucha,gastronomo,gatuno,gaussiano,gaviao,gaxeta,gazeteiro,gear,geiser,geminiano,generoso,genuino,geossinclinal,gerundio,gestual,getulista,gibi,gigolo,gilete,ginseng,giroscopio,glaucio,glacial,gleba,glifo,glote,glutonia,gnostico,goela,gogo,goitaca,golpista,gomo,gonzo,gorro,gostou,goticula,gourmet,governo,gozo,graxo,grevista,grito,grotesco,gruta,guaxinim,gude,gueto,guizo,guloso,gume,guru,gustativo,grelhado,gutural,habitue,haitiano,halterofilista,hamburguer,hanseniase,happening,harpista,hastear,haveres,hebreu,hectometro,hedonista,hegira,helena,helminto,hemorroidas,henrique,heptassilabo,hertziano,hesitar,heterossexual,heuristico,hexagono,hiato,hibrido,hidrostatico,hieroglifo,hifenizar,higienizar,hilario,himen,hino,hippie,hirsuto,historiografia,hitlerista,hodometro,hoje,holograma,homus,honroso,hoquei,horto,hostilizar,hotentote,huguenote,humilde,huno,hurra,hutu,iaia,ialorixa,iambico,iansa,iaque,iara,iatista,iberico,ibis,icar,iceberg,icosagono,idade,ideologo,idiotice,idoso,iemenita,iene,igarape,iglu,ignorar,igreja,iguaria,iidiche,ilativo,iletrado,ilharga,ilimitado,ilogismo,ilustrissimo,imaturo,imbuzeiro,imerso,imitavel,imovel,imputar,imutavel,inaveriguavel,incutir,induzir,inextricavel,infusao,ingua,inhame,iniquo,injusto,inning,inoxidavel,inquisitorial,insustentavel,intumescimento,inutilizavel,invulneravel,inzoneiro,iodo,iogurte,ioio,ionosfera,ioruba,iota,ipsilon,irascivel,iris,irlandes,irmaos,iroques,irrupcao,isca,isento,islandes,isotopo,isqueiro,israelita,isso,isto,iterbio,itinerario,itrio,iuane,iugoslavo,jabuticabeira,jacutinga,jade,jagunco,jainista,jaleco,jambo,jantarada,japones,jaqueta,jarro,jasmim,jato,jaula,javel,jazz,jegue,jeitoso,jejum,jenipapo,jeova,jequitiba,jersei,jesus,jetom,jiboia,jihad,jilo,jingle,jipe,jocoso,joelho,joguete,joio,jojoba,jorro,jota,joule,joviano,jubiloso,judoca,jugular,juizo,jujuba,juliano,jumento,junto,jururu,justo,juta,juventude,labutar,laguna,laico,lajota,lanterninha,lapso,laquear,lastro,lauto,lavrar,laxativo,lazer,leasing,lebre,lecionar,ledo,leguminoso,leitura,lele,lemure,lento,leonardo,leopardo,lepton,leque,leste,letreiro,leucocito,levitico,lexicologo,lhama,lhufas,liame,licoroso,lidocaina,liliputiano,limusine,linotipo,lipoproteina,liquidos,lirismo,lisura,liturgico,livros,lixo,lobulo,locutor,lodo,logro,lojista,lombriga,lontra,loop,loquaz,lorota,losango,lotus,louvor,luar,lubrificavel,lucros,lugubre,luis,luminoso,luneta,lustroso,luto,luvas,luxuriante,luzeiro,maduro,maestro,mafioso,magro,maiuscula,majoritario,malvisto,mamute,manutencao,mapoteca,maquinista,marzipa,masturbar,matuto,mausoleu,mavioso,maxixe,mazurca,meandro,mecha,medusa,mefistofelico,megera,meirinho,melro,memorizar,menu,mequetrefe,mertiolate,mestria,metroviario,mexilhao,mezanino,miau,microssegundo,midia,migratorio,mimosa,minuto,miosotis,mirtilo,misturar,mitzvah,miudos,mixuruca,mnemonico,moagem,mobilizar,modulo,moer,mofo,mogno,moita,molusco,monumento,moqueca,morubixaba,mostruario,motriz,mouse,movivel,mozarela,muarra,muculmano,mudo,mugir,muitos,mumunha,munir,muon,muquira,murros,musselina,nacoes,nado,naftalina,nago,naipe,naja,nalgum,namoro,nanquim,napolitano,naquilo,nascimento,nautilo,navios,nazista,nebuloso,nectarina,nefrologo,negus,nelore,nenufar,nepotismo,nervura,neste,netuno,neutron,nevoeiro,newtoniano,nexo,nhenhenhem,nhoque,nigeriano,niilista,ninho,niobio,niponico,niquelar,nirvana,nisto,nitroglicerina,nivoso,nobreza,nocivo,noel,nogueira,noivo,nojo,nominativo,nonuplo,noruegues,nostalgico,noturno,nouveau,nuanca,nublar,nucleotideo,nudista,nulo,numismatico,nunquinha,nupcias,nutritivo,nuvens,oasis,obcecar,obeso,obituario,objetos,oblongo,obnoxio,obrigatorio,obstruir,obtuso,obus,obvio,ocaso,occipital,oceanografo,ocioso,oclusivo,ocorrer,ocre,octogono,odalisca,odisseia,odorifico,oersted,oeste,ofertar,ofidio,oftalmologo,ogiva,ogum,oigale,oitavo,oitocentos,ojeriza,olaria,oleoso,olfato,olhos,oliveira,olmo,olor,olvidavel,ombudsman,omeleteira,omitir,omoplata,onanismo,ondular,oneroso,onomatopeico,ontologico,onus,onze,opalescente,opcional,operistico,opio,oposto,oprobrio,optometrista,opusculo,oratorio,orbital,orcar,orfao,orixa,orla,ornitologo,orquidea,ortorrombico,orvalho,osculo,osmotico,ossudo,ostrogodo,otario,otite,ouro,ousar,outubro,ouvir,ovario,overnight,oviparo,ovni,ovoviviparo,ovulo,oxala,oxente,oxiuro,oxossi,ozonizar,paciente,pactuar,padronizar,paete,pagodeiro,paixao,pajem,paludismo,pampas,panturrilha,papudo,paquistanes,pastoso,patua,paulo,pauzinhos,pavoroso,paxa,pazes,peao,pecuniario,pedunculo,pegaso,peixinho,pejorativo,pelvis,penuria,pequno,petunia,pezada,piauiense,pictorico,pierro,pigmeu,pijama,pilulas,pimpolho,pintura,piorar,pipocar,piqueteiro,pirulito,pistoleiro,pituitaria,pivotar,pixote,pizzaria,plistoceno,plotar,pluviometrico,pneumonico,poco,podridao,poetisa,pogrom,pois,polvorosa,pomposo,ponderado,pontudo,populoso,poquer,porvir,posudo,potro,pouso,povoar,prazo,prezar,privilegios,proximo,prussiano,pseudopode,psoriase,pterossauros,ptialina,ptolemaico,pudor,pueril,pufe,pugilista,puir,pujante,pulverizar,pumba,punk,purulento,pustula,putsch,puxe,quatrocentos,quetzal,quixotesco,quotizavel,rabujice,racista,radonio,rafia,ragu,rajado,ralo,rampeiro,ranzinza,raptor,raquitismo,raro,rasurar,ratoeira,ravioli,razoavel,reavivar,rebuscar,recusavel,reduzivel,reexposicao,refutavel,regurgitar,reivindicavel,rejuvenescimento,relva,remuneravel,renunciar,reorientar,repuxo,requisito,resumo,returno,reutilizar,revolvido,rezonear,riacho,ribossomo,ricota,ridiculo,rifle,rigoroso,rijo,rimel,rins,rios,riqueza,respeito,rissole,ritualistico,rivalizar,rixa,robusto,rococo,rodoviario,roer,rogo,rojao,rolo,rompimento,ronronar,roqueiro,rorqual,rosto,rotundo,rouxinol,roxo,royal,ruas,rucula,rudimentos,ruela,rufo,rugoso,ruivo,rule,rumoroso,runico,ruptura,rural,rustico,rutilar,saariano,sabujo,sacudir,sadomasoquista,safra,sagui,sais,samurai,santuario,sapo,saquear,sartriano,saturno,saude,sauva,saveiro,saxofonista,sazonal,scherzo,script,seara,seborreia,secura,seduzir,sefardim,seguro,seja,selvas,sempre,senzala,sepultura,sequoia,sestercio,setuplo,seus,seviciar,sezonismo,shalom,siames,sibilante,sicrano,sidra,sifilitico,signos,silvo,simultaneo,sinusite,sionista,sirio,sisudo,situar,sivan,slide,slogan,soar,sobrio,socratico,sodomizar,soerguer,software,sogro,soja,solver,somente,sonso,sopro,soquete,sorveteiro,sossego,soturno,sousafone,sovinice,sozinho,suavizar,subverter,sucursal,sudoriparo,sufragio,sugestoes,suite,sujo,sultao,sumula,suntuoso,suor,supurar,suruba,susto,suturar,suvenir,tabuleta,taco,tadjique,tafeta,tagarelice,taitiano,talvez,tampouco,tanzaniano,taoista,tapume,taquion,tarugo,tascar,tatuar,tautologico,tavola,taxionomista,tchecoslovaco,teatrologo,tectonismo,tedioso,teflon,tegumento,teixo,telurio,temporas,tenue,teosofico,tepido,tequila,terrorista,testosterona,tetrico,teutonico,teve,texugo,tiara,tibia,tiete,tifoide,tigresa,tijolo,tilintar,timpano,tintureiro,tiquete,tiroteio,tisico,titulos,tive,toar,toboga,tofu,togoles,toicinho,tolueno,tomografo,tontura,toponimo,toquio,torvelinho,tostar,toto,touro,toxina,trazer,trezentos,trivialidade,trovoar,truta,tuaregue,tubular,tucano,tudo,tufo,tuiste,tulipa,tumultuoso,tunisino,tupiniquim,turvo,tutu,ucraniano,udenista,ufanista,ufologo,ugaritico,uiste,uivo,ulceroso,ulema,ultravioleta,umbilical,umero,umido,umlaut,unanimidade,unesco,ungulado,unheiro,univoco,untuoso,urano,urbano,urdir,uretra,urgente,urinol,urna,urologo,urro,ursulina,urtiga,urupe,usavel,usbeque,usei,usineiro,usurpar,utero,utilizar,utopico,uvular,uxoricidio,vacuo,vadio,vaguear,vaivem,valvula,vampiro,vantajoso,vaporoso,vaquinha,varziano,vasto,vaticinio,vaudeville,vazio,veado,vedico,veemente,vegetativo,veio,veja,veludo,venusiano,verdade,verve,vestuario,vetusto,vexatorio,vezes,viavel,vibratorio,victor,vicunha,vidros,vietnamita,vigoroso,vilipendiar,vime,vintem,violoncelo,viquingue,virus,visualizar,vituperio,viuvo,vivo,vizir,voar,vociferar,vodu,vogar,voile,volver,vomito,vontade,vortice,vosso,voto,vovozinha,voyeuse,vozes,vulva,vupt,western,xadrez,xale,xampu,xango,xarope,xaual,xavante,xaxim,xenonio,xepa,xerox,xicara,xifopago,xiita,xilogravura,xinxim,xistoso,xixi,xodo,xogum,xucro,zabumba,zagueiro,zambiano,zanzar,zarpar,zebu,zefiro,zeloso,zenite,zumbi diff --git a/SignalServiceKit/src/Loki/Mnemonic/spanish.txt b/SignalServiceKit/src/Loki/Mnemonic/spanish.txt deleted file mode 100644 index 0513668c6..000000000 --- a/SignalServiceKit/src/Loki/Mnemonic/spanish.txt +++ /dev/null @@ -1 +0,0 @@ -ábaco,abdomen,abeja,abierto,abogado,abono,aborto,abrazo,abrir,abuelo,abuso,acabar,academia,acceso,acción,aceite,acelga,acento,aceptar,ácido,aclarar,acné,acoger,acoso,activo,acto,actriz,actuar,acudir,acuerdo,acusar,adicto,admitir,adoptar,adorno,aduana,adulto,aéreo,afectar,afición,afinar,afirmar,ágil,agitar,agonía,agosto,agotar,agregar,agrio,agua,agudo,águila,aguja,ahogo,ahorro,aire,aislar,ajedrez,ajeno,ajuste,alacrán,alambre,alarma,alba,álbum,alcalde,aldea,alegre,alejar,alerta,aleta,alfiler,alga,algodón,aliado,aliento,alivio,alma,almeja,almíbar,altar,alteza,altivo,alto,altura,alumno,alzar,amable,amante,amapola,amargo,amasar,ámbar,ámbito,ameno,amigo,amistad,amor,amparo,amplio,ancho,anciano,ancla,andar,andén,anemia,ángulo,anillo,ánimo,anís,anotar,antena,antiguo,antojo,anual,anular,anuncio,añadir,añejo,año,apagar,aparato,apetito,apio,aplicar,apodo,aporte,apoyo,aprender,aprobar,apuesta,apuro,arado,araña,arar,árbitro,árbol,arbusto,archivo,arco,arder,ardilla,arduo,área,árido,aries,armonía,arnés,aroma,arpa,arpón,arreglo,arroz,arruga,arte,artista,asa,asado,asalto,ascenso,asegurar,aseo,asesor,asiento,asilo,asistir,asno,asombro,áspero,astilla,astro,astuto,asumir,asunto,atajo,ataque,atar,atento,ateo,ático,atleta,átomo,atraer,atroz,atún,audaz,audio,auge,aula,aumento,ausente,autor,aval,avance,avaro,ave,avellana,avena,avestruz,avión,aviso,ayer,ayuda,ayuno,azafrán,azar,azote,azúcar,azufre,azul,baba,babor,bache,bahía,baile,bajar,balanza,balcón,balde,bambú,banco,banda,baño,barba,barco,barniz,barro,báscula,bastón,basura,batalla,batería,batir,batuta,baúl,bazar,bebé,bebida,bello,besar,beso,bestia,bicho,bien,bingo,blanco,bloque,blusa,boa,bobina,bobo,boca,bocina,boda,bodega,boina,bola,bolero,bolsa,bomba,bondad,bonito,bono,bonsái,borde,borrar,bosque,bote,botín,bóveda,bozal,bravo,brazo,brecha,breve,brillo,brinco,brisa,broca,broma,bronce,brote,bruja,brusco,bruto,buceo,bucle,bueno,buey,bufanda,bufón,búho,buitre,bulto,burbuja,burla,burro,buscar,butaca,buzón,caballo,cabeza,cabina,cabra,cacao,cadáver,cadena,caer,café,caída,caimán,caja,cajón,cal,calamar,calcio,caldo,calidad,calle,calma,calor,calvo,cama,cambio,camello,camino,campo,cáncer,candil,canela,canguro,canica,canto,caña,cañón,caoba,caos,capaz,capitán,capote,captar,capucha,cara,carbón,cárcel,careta,carga,cariño,carne,carpeta,carro,carta,casa,casco,casero,caspa,castor,catorce,catre,caudal,causa,cazo,cebolla,ceder,cedro,celda,célebre,celoso,célula,cemento,ceniza,centro,cerca,cerdo,cereza,cero,cerrar,certeza,césped,cetro,chacal,chaleco,champú,chancla,chapa,charla,chico,chiste,chivo,choque,choza,chuleta,chupar,ciclón,ciego,cielo,cien,cierto,cifra,cigarro,cima,cinco,cine,cinta,ciprés,circo,ciruela,cisne,cita,ciudad,clamor,clan,claro,clase,clave,cliente,clima,clínica,cobre,cocción,cochino,cocina,coco,código,codo,cofre,coger,cohete,cojín,cojo,cola,colcha,colegio,colgar,colina,collar,colmo,columna,combate,comer,comida,cómodo,compra,conde,conejo,conga,conocer,consejo,contar,copa,copia,corazón,corbata,corcho,cordón,corona,correr,coser,cosmos,costa,cráneo,cráter,crear,crecer,creído,crema,cría,crimen,cripta,crisis,cromo,crónica,croqueta,crudo,cruz,cuadro,cuarto,cuatro,cubo,cubrir,cuchara,cuello,cuento,cuerda,cuesta,cueva,cuidar,culebra,culpa,culto,cumbre,cumplir,cuna,cuneta,cuota,cupón,cúpula,curar,curioso,curso,curva,cutis,dama,danza,dar,dardo,dátil,deber,débil,década,decir,dedo,defensa,definir,dejar,delfín,delgado,delito,demora,denso,dental,deporte,derecho,derrota,desayuno,deseo,desfile,desnudo,destino,desvío,detalle,detener,deuda,día,diablo,diadema,diamante,diana,diario,dibujo,dictar,diente,dieta,diez,difícil,digno,dilema,diluir,dinero,directo,dirigir,disco,diseño,disfraz,diva,divino,doble,doce,dolor,domingo,don,donar,dorado,dormir,dorso,dos,dosis,dragón,droga,ducha,duda,duelo,dueño,dulce,dúo,duque,durar,dureza,duro,ébano,ebrio,echar,eco,ecuador,edad,edición,edificio,editor,educar,efecto,eficaz,eje,ejemplo,elefante,elegir,elemento,elevar,elipse,élite,elixir,elogio,eludir,embudo,emitir,emoción,empate,empeño,empleo,empresa,enano,encargo,enchufe,encía,enemigo,enero,enfado,enfermo,engaño,enigma,enlace,enorme,enredo,ensayo,enseñar,entero,entrar,envase,envío,época,equipo,erizo,escala,escena,escolar,escribir,escudo,esencia,esfera,esfuerzo,espada,espejo,espía,esposa,espuma,esquí,estar,este,estilo,estufa,etapa,eterno,ética,etnia,evadir,evaluar,evento,evitar,exacto,examen,exceso,excusa,exento,exigir,exilio,existir,éxito,experto,explicar,exponer,extremo,fábrica,fábula,fachada,fácil,factor,faena,faja,falda,fallo,falso,faltar,fama,familia,famoso,faraón,farmacia,farol,farsa,fase,fatiga,fauna,favor,fax,febrero,fecha,feliz,feo,feria,feroz,fértil,fervor,festín,fiable,fianza,fiar,fibra,ficción,ficha,fideo,fiebre,fiel,fiera,fiesta,figura,fijar,fijo,fila,filete,filial,filtro,fin,finca,fingir,finito,firma,flaco,flauta,flecha,flor,flota,fluir,flujo,flúor,fobia,foca,fogata,fogón,folio,folleto,fondo,forma,forro,fortuna,forzar,fosa,foto,fracaso,frágil,franja,frase,fraude,freír,freno,fresa,frío,frito,fruta,fuego,fuente,fuerza,fuga,fumar,función,funda,furgón,furia,fusil,fútbol,futuro,gacela,gafas,gaita,gajo,gala,galería,gallo,gamba,ganar,gancho,ganga,ganso,garaje,garza,gasolina,gastar,gato,gavilán,gemelo,gemir,gen,género,genio,gente,geranio,gerente,germen,gesto,gigante,gimnasio,girar,giro,glaciar,globo,gloria,gol,golfo,goloso,golpe,goma,gordo,gorila,gorra,gota,goteo,gozar,grada,gráfico,grano,grasa,gratis,grave,grieta,grillo,gripe,gris,grito,grosor,grúa,grueso,grumo,grupo,guante,guapo,guardia,guerra,guía,guiño,guion,guiso,guitarra,gusano,gustar,haber,hábil,hablar,hacer,hacha,hada,hallar,hamaca,harina,haz,hazaña,hebilla,hebra,hecho,helado,helio,hembra,herir,hermano,héroe,hervir,hielo,hierro,hígado,higiene,hijo,himno,historia,hocico,hogar,hoguera,hoja,hombre,hongo,honor,honra,hora,hormiga,horno,hostil,hoyo,hueco,huelga,huerta,hueso,huevo,huida,huir,humano,húmedo,humilde,humo,hundir,huracán,hurto,icono,ideal,idioma,ídolo,iglesia,iglú,igual,ilegal,ilusión,imagen,imán,imitar,impar,imperio,imponer,impulso,incapaz,índice,inerte,infiel,informe,ingenio,inicio,inmenso,inmune,innato,insecto,instante,interés,íntimo,intuir,inútil,invierno,ira,iris,ironía,isla,islote,jabalí,jabón,jamón,jarabe,jardín,jarra,jaula,jazmín,jefe,jeringa,jinete,jornada,joroba,joven,joya,juerga,jueves,juez,jugador,jugo,juguete,juicio,junco,jungla,junio,juntar,júpiter,jurar,justo,juvenil,juzgar,kilo,koala,labio,lacio,lacra,lado,ladrón,lagarto,lágrima,laguna,laico,lamer,lámina,lámpara,lana,lancha,langosta,lanza,lápiz,largo,larva,lástima,lata,látex,latir,laurel,lavar,lazo,leal,lección,leche,lector,leer,legión,legumbre,lejano,lengua,lento,leña,león,leopardo,lesión,letal,letra,leve,leyenda,libertad,libro,licor,líder,lidiar,lienzo,liga,ligero,lima,límite,limón,limpio,lince,lindo,línea,lingote,lino,linterna,líquido,liso,lista,litera,litio,litro,llaga,llama,llanto,llave,llegar,llenar,llevar,llorar,llover,lluvia,lobo,loción,loco,locura,lógica,logro,lombriz,lomo,lonja,lote,lucha,lucir,lugar,lujo,luna,lunes,lupa,lustro,luto,luz,maceta,macho,madera,madre,maduro,maestro,mafia,magia,mago,maíz,maldad,maleta,malla,malo,mamá,mambo,mamut,manco,mando,manejar,manga,maniquí,manjar,mano,manso,manta,mañana,mapa,máquina,mar,marco,marea,marfil,margen,marido,mármol,marrón,martes,marzo,masa,máscara,masivo,matar,materia,matiz,matriz,máximo,mayor,mazorca,mecha,medalla,medio,médula,mejilla,mejor,melena,melón,memoria,menor,mensaje,mente,menú,mercado,merengue,mérito,mes,mesón,meta,meter,método,metro,mezcla,miedo,miel,miembro,miga,mil,milagro,militar,millón,mimo,mina,minero,mínimo,minuto,miope,mirar,misa,miseria,misil,mismo,mitad,mito,mochila,moción,moda,modelo,moho,mojar,molde,moler,molino,momento,momia,monarca,moneda,monja,monto,moño,morada,morder,moreno,morir,morro,morsa,mortal,mosca,mostrar,motivo,mover,móvil,mozo,mucho,mudar,mueble,muela,muerte,muestra,mugre,mujer,mula,muleta,multa,mundo,muñeca,mural,muro,músculo,museo,musgo,música,muslo,nácar,nación,nadar,naipe,naranja,nariz,narrar,nasal,natal,nativo,natural,náusea,naval,nave,navidad,necio,néctar,negar,negocio,negro,neón,nervio,neto,neutro,nevar,nevera,nicho,nido,niebla,nieto,niñez,niño,nítido,nivel,nobleza,noche,nómina,noria,norma,norte,nota,noticia,novato,novela,novio,nube,nuca,núcleo,nudillo,nudo,nuera,nueve,nuez,nulo,número,nutria,oasis,obeso,obispo,objeto,obra,obrero,observar,obtener,obvio,oca,ocaso,océano,ochenta,ocho,ocio,ocre,octavo,octubre,oculto,ocupar,ocurrir,odiar,odio,odisea,oeste,ofensa,oferta,oficio,ofrecer,ogro,oído,oír,ojo,ola,oleada,olfato,olivo,olla,olmo,olor,olvido,ombligo,onda,onza,opaco,opción,ópera,opinar,oponer,optar,óptica,opuesto,oración,orador,oral,órbita,orca,orden,oreja,órgano,orgía,orgullo,oriente,origen,orilla,oro,orquesta,oruga,osadía,oscuro,osezno,oso,ostra,otoño,otro,oveja,óvulo,óxido,oxígeno,oyente,ozono,pacto,padre,paella,página,pago,país,pájaro,palabra,palco,paleta,pálido,palma,paloma,palpar,pan,panal,pánico,pantera,pañuelo,papá,papel,papilla,paquete,parar,parcela,pared,parir,paro,párpado,parque,párrafo,parte,pasar,paseo,pasión,paso,pasta,pata,patio,patria,pausa,pauta,pavo,payaso,peatón,pecado,pecera,pecho,pedal,pedir,pegar,peine,pelar,peldaño,pelea,peligro,pellejo,pelo,peluca,pena,pensar,peñón,peón,peor,pepino,pequeño,pera,percha,perder,pereza,perfil,perico,perla,permiso,perro,persona,pesa,pesca,pésimo,pestaña,pétalo,petróleo,pez,pezuña,picar,pichón,pie,piedra,pierna,pieza,pijama,pilar,piloto,pimienta,pino,pintor,pinza,piña,piojo,pipa,pirata,pisar,piscina,piso,pista,pitón,pizca,placa,plan,plata,playa,plaza,pleito,pleno,plomo,pluma,plural,pobre,poco,poder,podio,poema,poesía,poeta,polen,policía,pollo,polvo,pomada,pomelo,pomo,pompa,poner,porción,portal,posada,poseer,posible,poste,potencia,potro,pozo,prado,precoz,pregunta,premio,prensa,preso,previo,primo,príncipe,prisión,privar,proa,probar,proceso,producto,proeza,profesor,programa,prole,promesa,pronto,propio,próximo,prueba,público,puchero,pudor,pueblo,puerta,puesto,pulga,pulir,pulmón,pulpo,pulso,puma,punto,puñal,puño,pupa,pupila,puré,quedar,queja,quemar,querer,queso,quieto,química,quince,quitar,rábano,rabia,rabo,ración,radical,raíz,rama,rampa,rancho,rango,rapaz,rápido,rapto,rasgo,raspa,rato,rayo,raza,razón,reacción,realidad,rebaño,rebote,recaer,receta,rechazo,recoger,recreo,recto,recurso,red,redondo,reducir,reflejo,reforma,refrán,refugio,regalo,regir,regla,regreso,rehén,reino,reír,reja,relato,relevo,relieve,relleno,reloj,remar,remedio,remo,rencor,rendir,renta,reparto,repetir,reposo,reptil,res,rescate,resina,respeto,resto,resumen,retiro,retorno,retrato,reunir,revés,revista,rey,rezar,rico,riego,rienda,riesgo,rifa,rígido,rigor,rincón,riñón,río,riqueza,risa,ritmo,rito diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupPoller.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupPoller.swift deleted file mode 100644 index 3db7d48eb..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupPoller.swift +++ /dev/null @@ -1,78 +0,0 @@ -import PromiseKit - -@objc(LKClosedGroupPoller) -public final class ClosedGroupPoller : NSObject { - private var isPolling = false - private var timer: Timer? - - // MARK: Settings - private static let pollInterval: TimeInterval = 2 - - // MARK: Error - private enum Error : LocalizedError { - case insufficientSnodes - case pollingCanceled - - internal var errorDescription: String? { - switch self { - case .insufficientSnodes: return "No snodes left to poll." - case .pollingCanceled: return "Polling canceled." - } - } - } - - // MARK: Public API - @objc public func startIfNeeded() { - AssertIsOnMainThread() // Timers don't do well on background queues - guard !isPolling else { return } - isPolling = true - timer = Timer.scheduledTimer(withTimeInterval: ClosedGroupPoller.pollInterval, repeats: true) { [weak self] _ in - self?.poll() - } - } - - public func pollOnce() -> [Promise] { - guard !isPolling else { return [] } - isPolling = true - return poll() - } - - @objc public func stop() { - isPolling = false - timer?.invalidate() - } - - // MARK: Private API - private func poll() -> [Promise] { - guard isPolling else { return [] } - let publicKeys = Storage.getUserClosedGroupPublicKeys() - return publicKeys.map { publicKey in - let promise = SnodeAPI.getSwarm(for: publicKey).then2 { [weak self] swarm -> Promise<[SSKProtoEnvelope]> in - // randomElement() uses the system's default random generator, which is cryptographically secure - guard let snode = swarm.randomElement() else { return Promise(error: Error.insufficientSnodes) } - guard let self = self, self.isPolling else { return Promise(error: Error.pollingCanceled) } - return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).map2 { - SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: publicKey) - } - } - promise.done2 { [weak self] messages in - guard let self = self, self.isPolling else { return } - if !messages.isEmpty { - print("[Loki] Received \(messages.count) new message(s) in closed group with public key: \(publicKey).") - } - messages.forEach { message in - do { - let data = try message.serializedData() - SSKEnvironment.shared.messageReceiver.handleReceivedEnvelopeData(data) - } catch { - print("[Loki] Failed to deserialize envelope due to error: \(error).") - } - } - } - promise.catch2 { error in - print("[Loki] Polling failed for closed group with public key: \(publicKey) due to error: \(error).") - } - return promise.map { _ in } - } - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupRatchet.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupRatchet.swift deleted file mode 100644 index 07fbf3ede..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupRatchet.swift +++ /dev/null @@ -1,44 +0,0 @@ - -public final class ClosedGroupRatchet : NSObject, NSCoding { - public let chainKey: String - public let keyIndex: UInt - public let messageKeys: [String] - - // MARK: Initialization - public init(chainKey: String, keyIndex: UInt, messageKeys: [String]) { - self.chainKey = chainKey - self.keyIndex = keyIndex - self.messageKeys = messageKeys - } - - // MARK: Coding - public init?(coder: NSCoder) { - guard let chainKey = coder.decodeObject(forKey: "chainKey") as? String, - let keyIndex = coder.decodeObject(forKey: "keyIndex") as? UInt, - let messageKeys = coder.decodeObject(forKey: "messageKeys") as? [String] else { return nil } - self.chainKey = chainKey - self.keyIndex = UInt(keyIndex) - self.messageKeys = messageKeys - super.init() - } - - public func encode(with coder: NSCoder) { - coder.encode(chainKey, forKey: "chainKey") - coder.encode(keyIndex, forKey: "keyIndex") - coder.encode(messageKeys, forKey: "messageKeys") - } - - // MARK: Equality - override public func isEqual(_ other: Any?) -> Bool { - guard let other = other as? ClosedGroupRatchet else { return false } - return chainKey == other.chainKey && keyIndex == other.keyIndex && messageKeys == other.messageKeys - } - - // MARK: Hashing - override public var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:) - return chainKey.hashValue ^ keyIndex.hashValue ^ messageKeys.hashValue - } - - // MARK: Description - override public var description: String { return "[ chainKey : \(chainKey), keyIndex : \(keyIndex), messageKeys : \(messageKeys.prettifiedDescription) ]" } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift deleted file mode 100644 index e29fda01e..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift +++ /dev/null @@ -1,51 +0,0 @@ - -internal final class ClosedGroupSenderKey : NSObject, NSCoding { - internal let chainKey: Data - internal let keyIndex: UInt - internal let publicKey: Data - - // MARK: Initialization - init(chainKey: Data, keyIndex: UInt, publicKey: Data) { - self.chainKey = chainKey - self.keyIndex = keyIndex - self.publicKey = publicKey - } - - // MARK: Coding - public init?(coder: NSCoder) { - guard let chainKey = coder.decodeObject(forKey: "chainKey") as? Data, - let keyIndex = coder.decodeObject(forKey: "keyIndex") as? UInt, - let publicKey = coder.decodeObject(forKey: "publicKey") as? Data else { return nil } - self.chainKey = chainKey - self.keyIndex = UInt(keyIndex) - self.publicKey = publicKey - super.init() - } - - public func encode(with coder: NSCoder) { - coder.encode(chainKey, forKey: "chainKey") - coder.encode(keyIndex, forKey: "keyIndex") - coder.encode(publicKey, forKey: "publicKey") - } - - // MARK: Proto Conversion - internal func toProto() throws -> SSKProtoDataMessageClosedGroupUpdateSenderKey { - return try SSKProtoDataMessageClosedGroupUpdateSenderKey.builder(chainKey: chainKey, keyIndex: UInt32(keyIndex), publicKey: publicKey).build() - } - - // MARK: Equality - override public func isEqual(_ other: Any?) -> Bool { - guard let other = other as? ClosedGroupSenderKey else { return false } - return chainKey == other.chainKey && keyIndex == other.keyIndex && publicKey == other.publicKey - } - - // MARK: Hashing - override public var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:) - return chainKey.hashValue ^ keyIndex.hashValue ^ publicKey.hashValue - } - - // MARK: Description - override public var description: String { - return "[ chainKey : \(chainKey), keyIndex : \(keyIndex), publicKey: \(publicKey.toHexString()) ]" - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift deleted file mode 100644 index dfa11d0c1..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift +++ /dev/null @@ -1,125 +0,0 @@ - -@objc(LKClosedGroupUpdateMessage) -internal final class ClosedGroupUpdateMessage : TSOutgoingMessage { - private let kind: Kind - - // MARK: Settings - @objc internal override var ttl: UInt32 { return UInt32(TTLUtilities.getTTL(for: .closedGroupUpdate)) } - - @objc internal override func shouldBeSaved() -> Bool { return false } - @objc internal override func shouldSyncTranscript() -> Bool { return false } - - // MARK: Kind - internal enum Kind { - case new(groupPublicKey: Data, name: String, groupPrivateKey: Data, senderKeys: [ClosedGroupSenderKey], members: [Data], admins: [Data]) - case info(groupPublicKey: Data, name: String, senderKeys: [ClosedGroupSenderKey], members: [Data], admins: [Data]) - case senderKeyRequest(groupPublicKey: Data) - case senderKey(groupPublicKey: Data, senderKey: ClosedGroupSenderKey) - } - - // MARK: Initialization - internal init(thread: TSThread, kind: Kind) { - self.kind = kind - super.init(outgoingMessageWithTimestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageBody: "", - attachmentIds: NSMutableArray(), expiresInSeconds: 0, expireStartedAt: 0, isVoiceMessage: false, - groupMetaMessage: .unspecified, quotedMessage: nil, contactShare: nil, linkPreview: nil) - } - - required init(dictionary: [String:Any]) throws { - preconditionFailure("Use init(thread:kind:) instead.") - } - - // MARK: Coding - internal required init?(coder: NSCoder) { - guard let thread = coder.decodeObject(forKey: "thread") as? TSThread, - let timestamp = coder.decodeObject(forKey: "timestamp") as? UInt64, - let groupPublicKey = coder.decodeObject(forKey: "groupPublicKey") as? Data, - let rawKind = coder.decodeObject(forKey: "kind") as? String else { return nil } - switch rawKind { - case "new": - guard let name = coder.decodeObject(forKey: "name") as? String, - let groupPrivateKey = coder.decodeObject(forKey: "groupPrivateKey") as? Data, - let senderKeys = coder.decodeObject(forKey: "senderKeys") as? [ClosedGroupSenderKey], - let members = coder.decodeObject(forKey: "members") as? [Data], - let admins = coder.decodeObject(forKey: "admins") as? [Data] else { return nil } - self.kind = .new(groupPublicKey: groupPublicKey, name: name, groupPrivateKey: groupPrivateKey, senderKeys: senderKeys, members: members, admins: admins) - case "info": - guard let name = coder.decodeObject(forKey: "name") as? String, - let senderKeys = coder.decodeObject(forKey: "senderKeys") as? [ClosedGroupSenderKey], - let members = coder.decodeObject(forKey: "members") as? [Data], - let admins = coder.decodeObject(forKey: "admins") as? [Data] else { return nil } - self.kind = .info(groupPublicKey: groupPublicKey, name: name, senderKeys: senderKeys, members: members, admins: admins) - case "senderKeyRequest": - self.kind = .senderKeyRequest(groupPublicKey: groupPublicKey) - case "senderKey": - guard let senderKeys = coder.decodeObject(forKey: "senderKeys") as? [ClosedGroupSenderKey], - let senderKey = senderKeys.first else { return nil } - self.kind = .senderKey(groupPublicKey: groupPublicKey, senderKey: senderKey) - default: return nil - } - super.init(outgoingMessageWithTimestamp: timestamp, in: thread, messageBody: "", - attachmentIds: NSMutableArray(), expiresInSeconds: 0, expireStartedAt: 0, isVoiceMessage: false, - groupMetaMessage: .unspecified, quotedMessage: nil, contactShare: nil, linkPreview: nil) - } - - internal override func encode(with coder: NSCoder) { - coder.encode(thread, forKey: "thread") - coder.encode(timestamp, forKey: "timestamp") - switch kind { - case .new(let groupPublicKey, let name, let groupPrivateKey, let senderKeys, let members, let admins): - coder.encode("new", forKey: "kind") - coder.encode(groupPublicKey, forKey: "groupPublicKey") - coder.encode(name, forKey: "name") - coder.encode(groupPrivateKey, forKey: "groupPrivateKey") - coder.encode(senderKeys, forKey: "senderKeys") - coder.encode(members, forKey: "members") - coder.encode(admins, forKey: "admins") - case .info(let groupPublicKey, let name, let senderKeys, let members, let admins): - coder.encode("info", forKey: "kind") - coder.encode(groupPublicKey, forKey: "groupPublicKey") - coder.encode(name, forKey: "name") - coder.encode(senderKeys, forKey: "senderKeys") - coder.encode(members, forKey: "members") - coder.encode(admins, forKey: "admins") - case .senderKeyRequest(let groupPublicKey): - coder.encode(groupPublicKey, forKey: "groupPublicKey") - case .senderKey(let groupPublicKey, let senderKey): - coder.encode("senderKey", forKey: "kind") - coder.encode(groupPublicKey, forKey: "groupPublicKey") - coder.encode([ senderKey ], forKey: "senderKeys") - } - } - - // MARK: Building - @objc internal override func dataMessageBuilder() -> Any? { - guard let builder = super.dataMessageBuilder() as? SSKProtoDataMessage.SSKProtoDataMessageBuilder else { return nil } - do { - let closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate.SSKProtoDataMessageClosedGroupUpdateBuilder - switch kind { - case .new(let groupPublicKey, let name, let groupPrivateKey, let senderKeys, let members, let admins): - closedGroupUpdate = SSKProtoDataMessageClosedGroupUpdate.builder(groupPublicKey: groupPublicKey, type: .new) - closedGroupUpdate.setName(name) - closedGroupUpdate.setGroupPrivateKey(groupPrivateKey) - closedGroupUpdate.setSenderKeys(try senderKeys.map { try $0.toProto() }) - closedGroupUpdate.setMembers(members) - closedGroupUpdate.setAdmins(admins) - case .info(let groupPublicKey, let name, let senderKeys, let members, let admins): - closedGroupUpdate = SSKProtoDataMessageClosedGroupUpdate.builder(groupPublicKey: groupPublicKey, type: .info) - closedGroupUpdate.setName(name) - closedGroupUpdate.setSenderKeys(try senderKeys.map { try $0.toProto() }) - closedGroupUpdate.setMembers(members) - closedGroupUpdate.setAdmins(admins) - case .senderKeyRequest(let groupPublicKey): - closedGroupUpdate = SSKProtoDataMessageClosedGroupUpdate.builder(groupPublicKey: groupPublicKey, type: .senderKeyRequest) - case .senderKey(let groupPublicKey, let senderKey): - closedGroupUpdate = SSKProtoDataMessageClosedGroupUpdate.builder(groupPublicKey: groupPublicKey, type: .senderKey) - closedGroupUpdate.setSenderKeys([ try senderKey.toProto() ]) - } - builder.setClosedGroupUpdate(try closedGroupUpdate.build()) - } catch { - owsFailDebug("Failed to build closed group update due to error: \(error).") - return nil - } - return builder - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUtilities.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUtilities.swift deleted file mode 100644 index 4fb7e8bbf..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUtilities.swift +++ /dev/null @@ -1,70 +0,0 @@ -import CryptoSwift -import SessionMetadataKit - -@objc(LKClosedGroupUtilities) -public final class ClosedGroupUtilities : NSObject { - - @objc(LKSSKDecryptionError) - public class SSKDecryptionError : NSError { // Not called `Error` for Obj-C interoperablity - - @objc public static let invalidGroupPublicKey = SSKDecryptionError(domain: "SSKErrorDomain", code: 1, userInfo: [ NSLocalizedDescriptionKey : "Invalid group public key." ]) - @objc public static let noData = SSKDecryptionError(domain: "SSKErrorDomain", code: 2, userInfo: [ NSLocalizedDescriptionKey : "Received an empty envelope." ]) - @objc public static let noGroupPrivateKey = SSKDecryptionError(domain: "SSKErrorDomain", code: 3, userInfo: [ NSLocalizedDescriptionKey : "Missing group private key." ]) - @objc public static let selfSend = SSKDecryptionError(domain: "SSKErrorDomain", code: 4, userInfo: [ NSLocalizedDescriptionKey : "Message addressed at self." ]) - } - - @objc(encryptData:usingGroupPublicKey:transaction:error:) - public static func encrypt(data: Data, groupPublicKey: String, transaction: YapDatabaseReadWriteTransaction) throws -> Data { - // 1. ) Encrypt the data with the user's sender key - guard let userPublicKey = OWSIdentityManager.shared().identityKeyPair()?.hexEncodedPublicKey else { - throw SMKError.assertionError(description: "[Loki] Couldn't find user key pair.") - } - let ciphertextAndKeyIndex = try SharedSenderKeysImplementation.shared.encrypt(data, forGroupWithPublicKey: groupPublicKey, - senderPublicKey: userPublicKey, protocolContext: transaction) - let ivAndCiphertext = ciphertextAndKeyIndex[0] as! Data - let keyIndex = ciphertextAndKeyIndex[1] as! UInt - let encryptedMessage = ClosedGroupCiphertextMessage(_throws_withIVAndCiphertext: ivAndCiphertext, senderPublicKey: Data(hex: userPublicKey), keyIndex: UInt32(keyIndex)) - // 2. ) Encrypt the result for the group's public key to hide the sender public key and key index - let (ciphertext, _, ephemeralPublicKey) = try EncryptionUtilities.encrypt(encryptedMessage.serialized, using: groupPublicKey.removing05PrefixIfNeeded()) - // 3. ) Wrap the result - return try SSKProtoClosedGroupCiphertextMessageWrapper.builder(ciphertext: ciphertext, ephemeralPublicKey: ephemeralPublicKey).build().serializedData() - } - - @objc(decryptEnvelope:transaction:error:) - public static func decrypt(envelope: SSKProtoEnvelope, transaction: YapDatabaseReadWriteTransaction) throws -> [Any] { - let (plaintext, senderPublicKey) = try decrypt(envelope: envelope, transaction: transaction) - return [ plaintext, senderPublicKey ] - } - - public static func decrypt(envelope: SSKProtoEnvelope, transaction: YapDatabaseReadWriteTransaction) throws -> (plaintext: Data, senderPublicKey: String) { - // 1. ) Check preconditions - guard let groupPublicKey = envelope.source, SharedSenderKeysImplementation.shared.isClosedGroup(groupPublicKey) else { - throw SSKDecryptionError.invalidGroupPublicKey - } - guard let data = envelope.content else { - throw SSKDecryptionError.noData - } - guard let hexEncodedGroupPrivateKey = Storage.getClosedGroupPrivateKey(for: groupPublicKey) else { - throw SSKDecryptionError.noGroupPrivateKey - } - let groupPrivateKey = Data(hex: hexEncodedGroupPrivateKey) - // 2. ) Parse the wrapper - let wrapper = try SSKProtoClosedGroupCiphertextMessageWrapper.parseData(data) - let ivAndCiphertext = wrapper.ciphertext - let ephemeralPublicKey = wrapper.ephemeralPublicKey - // 3. ) Decrypt the data inside - let ephemeralSharedSecret = try Curve25519.generateSharedSecret(fromPublicKey: ephemeralPublicKey, privateKey: groupPrivateKey) - let salt = "LOKI" - let symmetricKey = try HMAC(key: salt.bytes, variant: .sha256).authenticate(ephemeralSharedSecret.bytes) - let closedGroupCiphertextMessageAsData = try DecryptionUtilities.decrypt(ivAndCiphertext, usingAESGCMWithSymmetricKey: Data(symmetricKey)) - // 4. ) Parse the closed group ciphertext message - let closedGroupCiphertextMessage = ClosedGroupCiphertextMessage(_throws_with: closedGroupCiphertextMessageAsData) - let senderPublicKey = closedGroupCiphertextMessage.senderPublicKey.toHexString() - guard senderPublicKey != getUserHexEncodedPublicKey() else { throw SSKDecryptionError.selfSend } - // 5. ) Use the info inside the closed group ciphertext message to decrypt the actual message content - let plaintext = try SharedSenderKeysImplementation.shared.decrypt(closedGroupCiphertextMessage.ivAndCiphertext, forGroupWithPublicKey: groupPublicKey, - senderPublicKey: senderPublicKey, keyIndex: UInt(closedGroupCiphertextMessage.keyIndex), protocolContext: transaction) - // 6. ) Return - return (plaintext, senderPublicKey) - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift deleted file mode 100644 index 0d62b1750..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift +++ /dev/null @@ -1,486 +0,0 @@ -import PromiseKit - -// A few notes about making changes in this file: -// -// • Don't use a database transaction if you can avoid it. -// • If you do need to use a database transaction, use a read transaction if possible. -// • For write transactions, consider making it the caller's responsibility to manage the database transaction (this helps avoid unnecessary transactions). -// • Think carefully about adding a function; there might already be one for what you need. -// • Document the expected cases in which a function will be used. -// • Express those cases in tests. - -/// See [the documentation](https://github.com/loki-project/session-protocol-docs/wiki/Medium-Size-Groups) for more information. -@objc(LKClosedGroupsProtocol) -public final class ClosedGroupsProtocol : NSObject { - public static let isSharedSenderKeysEnabled = true - public static let groupSizeLimit = 20 - public static let maxNameSize = 64 - - public enum Error : LocalizedError { - case noThread - case noPrivateKey - case invalidUpdate - - public var errorDescription: String? { - switch self { - case .noThread: return "Couldn't find a thread associated with the given group public key." - case .noPrivateKey: return "Couldn't find a private key associated with the given group public key." - case .invalidUpdate: return "Invalid group update." - } - } - } - - // MARK: - Sending - - /// - Note: It's recommended to batch fetch the device links for the given set of members before invoking this, to avoid the message sending pipeline - /// making a request for each member. - public static func createClosedGroup(name: String, members: Set, transaction: YapDatabaseReadWriteTransaction) -> Promise { - // Prepare - var members = members - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - let userPublicKey = getUserHexEncodedPublicKey() - // Generate a key pair for the group - let groupKeyPair = Curve25519.generateKeyPair() - let groupPublicKey = groupKeyPair.hexEncodedPublicKey // Includes the "05" prefix - // Ensure the current user's master device is the one that's included in the member list - members.remove(userPublicKey) - members.insert(UserDefaults.standard[.masterHexEncodedPublicKey] ?? userPublicKey) - let membersAsData = members.map { Data(hex: $0) } - // Create ratchets for all members (and their linked devices) - var membersAndLinkedDevices: Set = members - for member in members { - let deviceLinks = OWSPrimaryStorage.shared().getDeviceLinks(for: member, in: transaction) - membersAndLinkedDevices.formUnion(deviceLinks.flatMap { [ $0.master.publicKey, $0.slave.publicKey ] }) - } - let senderKeys: [ClosedGroupSenderKey] = membersAndLinkedDevices.map { publicKey in - let ratchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: publicKey, using: transaction) - return ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: Data(hex: publicKey)) - } - // Create the group - let admins = [ UserDefaults.standard[.masterHexEncodedPublicKey] ?? userPublicKey ] - let adminsAsData = admins.map { Data(hex: $0) } - let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) - let group = TSGroupModel(title: name, memberIds: [String](members), image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins) - let thread = TSGroupThread.getOrCreateThread(with: group, transaction: transaction) - thread.usesSharedSenderKeys = true - thread.save(with: transaction) - SSKEnvironment.shared.profileManager.addThread(toProfileWhitelist: thread) - // Establish sessions if needed - establishSessionsIfNeeded(with: [String](members), using: transaction) // Not `membersAndLinkedDevices` as this internally takes care of multi device already - // Send a closed group update message to all members (and their linked devices) using established channels - var promises: [Promise] = [] - for member in members { // Not `membersAndLinkedDevices` as this internally takes care of multi device already - guard member != userPublicKey else { continue } - let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) - thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, - groupPrivateKey: groupKeyPair.privateKey, senderKeys: senderKeys, members: membersAsData, admins: adminsAsData) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - promises.append(SSKEnvironment.shared.messageSender.sendPromise(message: closedGroupUpdateMessage)) - } - // Add the group to the user's set of public keys to poll for - Storage.setClosedGroupPrivateKey(groupKeyPair.privateKey.toHexString(), for: groupPublicKey, using: transaction) - // Notify the PN server - promises.append(LokiPushNotificationManager.performOperation(.subscribe, for: groupPublicKey, publicKey: userPublicKey)) - // Notify the user - let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate) - infoMessage.save(with: transaction) - // Return - return when(fulfilled: promises).map2 { thread } - } - - /// - Note: The returned promise is only relevant for group leaving. - public static func update(_ groupPublicKey: String, with members: Set, name: String, transaction: YapDatabaseReadWriteTransaction) -> Promise { - let (promise, seal) = Promise.pending() - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - let userPublicKey = getUserHexEncodedPublicKey() - let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) - guard let thread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else { - print("[Loki] Can't update nonexistent closed group.") - return Promise(error: Error.noThread) - } - let group = thread.groupModel - let oldMembers = Set(group.groupMemberIds) - let newMembers = members.subtracting(oldMembers) - let membersAsData = members.map { Data(hex: $0) } - let admins = group.groupAdminIds - let adminsAsData = admins.map { Data(hex: $0) } - guard let groupPrivateKey = Storage.getClosedGroupPrivateKey(for: groupPublicKey) else { - print("[Loki] Couldn't get private key for closed group.") - return Promise(error: Error.noPrivateKey) - } - let wasAnyUserRemoved = Set(members).intersection(oldMembers) != oldMembers - let removedMembers = oldMembers.subtracting(members) - let isUserLeaving = removedMembers.contains(userPublicKey) - var newSenderKeys: [ClosedGroupSenderKey] = [] - if wasAnyUserRemoved { - if isUserLeaving && removedMembers.count != 1 { - print("[Loki] Can't remove self and others simultaneously.") - return Promise(error: Error.invalidUpdate) - } - // Establish sessions if needed - establishSessionsIfNeeded(with: [String](members), using: transaction) - // Send the update to the existing members using established channels (don't include new ratchets as everyone should regenerate new ratchets individually) - let promises: [Promise] = oldMembers.map { member in - let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) - thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: [], - members: membersAsData, admins: adminsAsData) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - return SSKEnvironment.shared.messageSender.sendPromise(message: closedGroupUpdateMessage) - } - when(resolved: promises).done2 { _ in seal.fulfill(()) }.catch2 { seal.reject($0) } - promise.done { - Storage.writeSync { transaction in - let allOldRatchets = Storage.getAllClosedGroupRatchets(for: groupPublicKey) - for (senderPublicKey, oldRatchet) in allOldRatchets { - let collection = Storage.ClosedGroupRatchetCollectionType.old - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: oldRatchet, in: collection, using: transaction) - } - // Delete all ratchets (it's important that this happens * after * sending out the update) - Storage.removeAllClosedGroupRatchets(for: groupPublicKey, using: transaction) - // Remove the group from the user's set of public keys to poll for if the user is leaving. Otherwise generate a new ratchet and - // send it out to all members (minus the removed ones) using established channels. - if isUserLeaving { - Storage.removeClosedGroupPrivateKey(for: groupPublicKey, using: transaction) - // Notify the PN server - LokiPushNotificationManager.performOperation(.unsubscribe, for: groupPublicKey, publicKey: userPublicKey) - } else { - // Send closed group update messages to any new members using established channels - for member in newMembers { - let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) - thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, - groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: [], members: membersAsData, admins: adminsAsData) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) - } - // Send out the user's new ratchet to all members (minus the removed ones) using established channels - let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) - let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) - for member in members { - guard member != userPublicKey else { continue } - let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) - thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) - } - } - } - } - } else if !newMembers.isEmpty { - seal.fulfill(()) - // Generate ratchets for any new members - newSenderKeys = newMembers.map { publicKey in - let ratchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: publicKey, using: transaction) - return ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: Data(hex: publicKey)) - } - // Send a closed group update message to the existing members with the new members' ratchets (this message is aimed at the group) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: newSenderKeys, - members: membersAsData, admins: adminsAsData) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) - // Establish sessions if needed - establishSessionsIfNeeded(with: [String](newMembers), using: transaction) - // Send closed group update messages to the new members using established channels - var allSenderKeys = Storage.getAllClosedGroupSenderKeys(for: groupPublicKey) - allSenderKeys.formUnion(newSenderKeys) - for member in newMembers { - let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) - thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, - groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: [ClosedGroupSenderKey](allSenderKeys), members: membersAsData, admins: adminsAsData) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) - } - } else { - seal.fulfill(()) - let allSenderKeys = Storage.getAllClosedGroupSenderKeys(for: groupPublicKey) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, - senderKeys: [ClosedGroupSenderKey](allSenderKeys), members: membersAsData, admins: adminsAsData) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) - } - // Update the group - let newGroupModel = TSGroupModel(title: name, memberIds: [String](members), image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins) - thread.setGroupModel(newGroupModel, with: transaction) - // Notify the user - let updateInfo = group.getInfoStringAboutUpdate(to: newGroupModel, contactsManager: SSKEnvironment.shared.contactsManager) - let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate, customMessage: updateInfo) - infoMessage.save(with: transaction) - // Return - return promise - } - - /// The returned promise is fulfilled when the group update message has been sent. It doesn't wait for the user's new ratchet to be distributed. - @objc(leaveGroupWithPublicKey:transaction:) - public static func objc_leave(_ groupPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) -> AnyPromise { - return AnyPromise.from(leave(groupPublicKey, using: transaction)) - } - - /// The returned promise is fulfilled when the group update message has been sent. It doesn't wait for the user's new ratchet to be distributed. - public static func leave(_ groupPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise { - let userPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? getUserHexEncodedPublicKey() - let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) - guard let thread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else { - print("[Loki] Can't leave nonexistent closed group.") - return Promise(error: Error.noThread) - } - let group = thread.groupModel - var newMembers = Set(group.groupMemberIds) - newMembers.remove(userPublicKey) - return update(groupPublicKey, with: newMembers, name: group.groupName!, transaction: transaction) - } - - public static func requestSenderKey(for groupPublicKey: String, senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - print("[Loki] Requesting sender key for group public key: \(groupPublicKey), sender public key: \(senderPublicKey).") - // Establish session if needed - SessionManagementProtocol.sendSessionRequestIfNeeded(to: senderPublicKey, using: transaction) - // Send the request - let thread = TSContactThread.getOrCreateThread(withContactId: senderPublicKey, transaction: transaction) - thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKeyRequest(groupPublicKey: Data(hex: groupPublicKey)) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) - } - - // MARK: - Receiving - - @objc(handleSharedSenderKeysUpdateIfNeeded:from:transaction:) - public static func handleSharedSenderKeysUpdateIfNeeded(_ dataMessage: SSKProtoDataMessage, from publicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - // Note that `publicKey` is either the public key of the group or the public key of the - // sender, depending on how the message was sent - guard let closedGroupUpdate = dataMessage.closedGroupUpdate, isValid(closedGroupUpdate) else { return } - switch closedGroupUpdate.type { - case .new: handleNewGroupMessage(closedGroupUpdate, using: transaction) - case .info: handleInfoMessage(closedGroupUpdate, from: publicKey, using: transaction) - case .senderKeyRequest: handleSenderKeyRequestMessage(closedGroupUpdate, from: publicKey, using: transaction) - case .senderKey: handleSenderKeyMessage(closedGroupUpdate, from: publicKey, using: transaction) - } - } - - private static func isValid(_ closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate) -> Bool { - guard !closedGroupUpdate.groupPublicKey.isEmpty else { return false } - switch closedGroupUpdate.type { - case .new: return !(closedGroupUpdate.name ?? "").isEmpty && !(closedGroupUpdate.groupPrivateKey ?? Data()).isEmpty && !closedGroupUpdate.members.isEmpty - && !closedGroupUpdate.admins.isEmpty // senderKeys may be empty - case .info: return !(closedGroupUpdate.name ?? "").isEmpty && !closedGroupUpdate.members.isEmpty && !closedGroupUpdate.admins.isEmpty // senderKeys may be empty - case .senderKeyRequest: return true - case .senderKey: return !closedGroupUpdate.senderKeys.isEmpty - } - } - - private static func handleNewGroupMessage(_ closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate, using transaction: YapDatabaseReadWriteTransaction) { - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - // Unwrap the message - let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() - let name = closedGroupUpdate.name - let groupPrivateKey = closedGroupUpdate.groupPrivateKey! - let senderKeys = closedGroupUpdate.senderKeys - let members = closedGroupUpdate.members.map { $0.toHexString() } - let admins = closedGroupUpdate.admins.map { $0.toHexString() } - // Persist the ratchets - senderKeys.forEach { senderKey in - guard members.contains(senderKey.publicKey.toHexString()) else { return } // TODO: This currently doesn't take into account multi device - let ratchet = ClosedGroupRatchet(chainKey: senderKey.chainKey.toHexString(), keyIndex: UInt(senderKey.keyIndex), messageKeys: []) - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderKey.publicKey.toHexString(), ratchet: ratchet, using: transaction) - } - // Sort out any discrepancies between the provided sender keys and what's required - let missingSenderKeys = Set(members).subtracting(senderKeys.map { $0.publicKey.toHexString() }) - let userPublicKey = getUserHexEncodedPublicKey() - if missingSenderKeys.contains(userPublicKey) { - establishSessionsIfNeeded(with: [String](members), using: transaction) - let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) - let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) - for member in members { - guard member != userPublicKey else { continue } - let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) - thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) - } - } - for publicKey in missingSenderKeys.subtracting([ userPublicKey ]) { - requestSenderKey(for: groupPublicKey, senderPublicKey: publicKey, using: transaction) - } - // Create the group - let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) - let group = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins) - let thread: TSGroupThread - if let t = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) { - thread = t - thread.setGroupModel(group, with: transaction) - } else { - thread = TSGroupThread.getOrCreateThread(with: group, transaction: transaction) - thread.usesSharedSenderKeys = true - thread.save(with: transaction) - } - SSKEnvironment.shared.profileManager.addThread(toProfileWhitelist: thread) - // Add the group to the user's set of public keys to poll for - Storage.setClosedGroupPrivateKey(groupPrivateKey.toHexString(), for: groupPublicKey, using: transaction) - // Notify the PN server - LokiPushNotificationManager.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey()) - // Notify the user - let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate) - infoMessage.save(with: transaction) - // Establish sessions if needed - establishSessionsIfNeeded(with: members, using: transaction) // This internally takes care of multi device - } - - /// Invoked upon receiving a group update. A group update is sent out when a group's name is changed, when new users are added, when users leave or are - /// kicked, or if the group admins are changed. - private static func handleInfoMessage(_ closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate, from senderPublicKey: String, - using transaction: YapDatabaseReadWriteTransaction) { - // Unwrap the message - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() - let name = closedGroupUpdate.name - let senderKeys = closedGroupUpdate.senderKeys - let members = closedGroupUpdate.members.map { $0.toHexString() } - let admins = closedGroupUpdate.admins.map { $0.toHexString() } - // Get the group - let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) - guard let thread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else { - return print("[Loki] Ignoring closed group info message for nonexistent group.") - } - let group = thread.groupModel - // Check that the sender is a member of the group (before the update) - var membersAndLinkedDevices: Set = Set(group.groupMemberIds) - for member in group.groupMemberIds { - let deviceLinks = OWSPrimaryStorage.shared().getDeviceLinks(for: member, in: transaction) - membersAndLinkedDevices.formUnion(deviceLinks.flatMap { [ $0.master.publicKey, $0.slave.publicKey ] }) - } - guard membersAndLinkedDevices.contains(senderPublicKey) else { - return print("[Loki] Ignoring closed group info message from non-member.") - } - // Store the ratchets for any new members (it's important that this happens before the code below) - senderKeys.forEach { senderKey in - let ratchet = ClosedGroupRatchet(chainKey: senderKey.chainKey.toHexString(), keyIndex: UInt(senderKey.keyIndex), messageKeys: []) - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderKey.publicKey.toHexString(), ratchet: ratchet, using: transaction) - } - // Delete all ratchets and either: - // • Send out the user's new ratchet using established channels if other members of the group left or were removed - // • Remove the group from the user's set of public keys to poll for if the current user was among the members that were removed - let oldMembers = group.groupMemberIds - let userPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? getUserHexEncodedPublicKey() - let wasUserRemoved = !members.contains(userPublicKey) - if Set(members).intersection(oldMembers) != Set(oldMembers) { - let allOldRatchets = Storage.getAllClosedGroupRatchets(for: groupPublicKey) - for (senderPublicKey, oldRatchet) in allOldRatchets { - let collection = Storage.ClosedGroupRatchetCollectionType.old - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: oldRatchet, in: collection, using: transaction) - } - Storage.removeAllClosedGroupRatchets(for: groupPublicKey, using: transaction) - if wasUserRemoved { - Storage.removeClosedGroupPrivateKey(for: groupPublicKey, using: transaction) - // Notify the PN server - LokiPushNotificationManager.performOperation(.unsubscribe, for: groupPublicKey, publicKey: userPublicKey) - } else { - establishSessionsIfNeeded(with: members, using: transaction) // This internally takes care of multi device - let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) - let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) - for member in members { - guard member != userPublicKey else { continue } - let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) - thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // This internally takes care of multi device - } - } - } - // Update the group - let newGroupModel = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins) - thread.setGroupModel(newGroupModel, with: transaction) - // Notify the user if needed (don't notify them if the message just contained linked device sender keys) - if Set(members) != Set(oldMembers) || Set(admins) != Set(group.groupAdminIds) || name != group.groupName { - let infoMessageType: TSInfoMessageType = wasUserRemoved ? .typeGroupQuit : .typeGroupUpdate - let updateInfo = group.getInfoStringAboutUpdate(to: newGroupModel, contactsManager: SSKEnvironment.shared.contactsManager) - let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: infoMessageType, customMessage: updateInfo) - infoMessage.save(with: transaction) - } - } - - private static func handleSenderKeyRequestMessage(_ closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate, from senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - // Prepare - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - let userPublicKey = getUserHexEncodedPublicKey() - let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() - let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) - guard let groupThread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else { - return print("[Loki] Ignoring closed group sender key request for nonexistent group.") - } - let group = groupThread.groupModel - // Check that the requesting user is a member of the group - var membersAndLinkedDevices: Set = Set(group.groupMemberIds) - for member in group.groupMemberIds { - let deviceLinks = OWSPrimaryStorage.shared().getDeviceLinks(for: member, in: transaction) - membersAndLinkedDevices.formUnion(deviceLinks.flatMap { [ $0.master.publicKey, $0.slave.publicKey ] }) - } - guard membersAndLinkedDevices.contains(senderPublicKey) else { - return print("[Loki] Ignoring closed group sender key request from non-member.") - } - // Respond to the request - print("[Loki] Responding to sender key request from: \(senderPublicKey).") - SessionManagementProtocol.sendSessionRequestIfNeeded(to: senderPublicKey, using: transaction) // This internally takes care of multi device - let userRatchet = Storage.getClosedGroupRatchet(for: groupPublicKey, senderPublicKey: userPublicKey) - ?? SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) - let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) - let thread = TSContactThread.getOrCreateThread(withContactId: senderPublicKey, transaction: transaction) - thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // This internally takes care of multi device - } - - /// Invoked upon receiving a sender key from another user. - private static func handleSenderKeyMessage(_ closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate, from senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - // Prepare - let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() - guard let senderKey = closedGroupUpdate.senderKeys.first else { - return print("[Loki] Ignoring invalid closed group sender key.") - } - guard senderKey.publicKey.toHexString() == senderPublicKey else { - return print("[Loki] Ignoring invalid closed group sender key.") - } - // Store the sender key - print("[Loki] Received a sender key from: \(senderPublicKey).") - let ratchet = ClosedGroupRatchet(chainKey: senderKey.chainKey.toHexString(), keyIndex: UInt(senderKey.keyIndex), messageKeys: []) - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: ratchet, using: transaction) - } - - // MARK: - General - - @objc(establishSessionsIfNeededWithClosedGroupMembers:transaction:) - public static func establishSessionsIfNeeded(with closedGroupMembers: [String], using transaction: YapDatabaseReadWriteTransaction) { - closedGroupMembers.forEach { publicKey in - SessionManagementProtocol.sendSessionRequestIfNeeded(to: publicKey, using: transaction) - } - } - - @objc(shouldIgnoreClosedGroupMessage:inThread:wrappedIn:) - public static func shouldIgnoreClosedGroupMessage(_ dataMessage: SSKProtoDataMessage, in thread: TSGroupThread, wrappedIn envelope: SSKProtoEnvelope) -> Bool { - guard thread.groupModel.groupType == .closedGroup else { return true } - let publicKey = envelope.source! // Set during UD decryption - var result = false - Storage.read { transaction in - result = !thread.isUserMember(inGroup: publicKey, transaction: transaction) - } - return result - } - - /// - Note: Deprecated. - @objc(shouldIgnoreClosedGroupUpdateMessage:inThread:wrappedIn:) - public static func shouldIgnoreClosedGroupUpdateMessage(_ dataMessage: SSKProtoDataMessage, in thread: TSGroupThread, wrappedIn envelope: SSKProtoEnvelope) -> Bool { - guard thread.groupModel.groupType == .closedGroup else { return true } - let publicKey = envelope.source! // Set during UD decryption - var result = false - Storage.read { transaction in - result = !thread.isUserAdmin(inGroup: publicKey, transaction: transaction) - } - return result - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift deleted file mode 100644 index 21724bf07..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift +++ /dev/null @@ -1,220 +0,0 @@ -import CryptoSwift -import PromiseKit -import SessionMetadataKit - -@objc(LKSharedSenderKeysImplementation) -public final class SharedSenderKeysImplementation : NSObject { - private static let gcmTagSize: UInt = 16 - private static let ivSize: UInt = 12 - - // MARK: Documentation - // A quick overview of how shared sender key based closed groups work: - // - // • When a user creates a group, they generate a key pair for the group along with a ratchet for - // every member of the group. They bundle this together with some other group info such as the group - // name in a `ClosedGroupUpdateMessage` and send that using established channels to every member of - // the group. Note that because a user can only pick from their existing contacts when selecting - // the group members they shouldn't need to establish sessions before being able to send the - // `ClosedGroupUpdateMessage`. Another way to optimize the performance of the group creation process - // is to batch fetch the device links of all members involved ahead of time, rather than letting - // the sending pipeline do it separately for every user the `ClosedGroupUpdateMessage` is sent to. - // • After the group is created, every user polls for the public key associated with the group. - // • Upon receiving a `ClosedGroupUpdateMessage` of type `.new`, a user sends session requests to all - // other members of the group they don't yet have a session with for reasons outlined below. - // • When a user sends a message they step their ratchet and use the resulting message key to encrypt - // the message. - // • When another user receives that message, they step the ratchet associated with the sender and - // use the resulting message key to decrypt the message. - // • When a user leaves or is kicked from a group, all members must generate new ratchets to ensure that - // removed users can't decrypt messages going forward. To this end every user deletes all ratchets - // associated with the group in question upon receiving a group update message that indicates that - // a user left. They then generate a new ratchet for themselves and send it out to all members of - // the group (again fetching device links ahead of time). The user should already have established - // sessions with all other members at this point because of the behavior outlined a few points above. - // • When a user adds a new member to the group, they generate a ratchet for that new member and - // send that bundled in a `ClosedGroupUpdateMessage` to the group. They send a - // `ClosedGroupUpdateMessage` with the newly generated ratchet but also the existing ratchets of - // every other member of the group to the user that joined. - - // MARK: Ratcheting Error - public enum RatchetingError : LocalizedError { - case loadingFailed(groupPublicKey: String, senderPublicKey: String) - case messageKeyMissing(targetKeyIndex: UInt, groupPublicKey: String, senderPublicKey: String) - case generic - - public var errorDescription: String? { - switch self { - case .loadingFailed(let groupPublicKey, let senderPublicKey): return "Couldn't get ratchet for closed group with public key: \(groupPublicKey), sender public key: \(senderPublicKey)." - case .messageKeyMissing(let targetKeyIndex, let groupPublicKey, let senderPublicKey): return "Couldn't find message key for old key index: \(targetKeyIndex), public key: \(groupPublicKey), sender public key: \(senderPublicKey)." - case .generic: return "An error occurred" - } - } - } - - // MARK: Initialization - @objc public static let shared = SharedSenderKeysImplementation() - - private override init() { } - - // MARK: Private/Internal API - internal func generateRatchet(for groupPublicKey: String, senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) -> ClosedGroupRatchet { - let rootChainKey = Data.getSecureRandomData(ofSize: 32)!.toHexString() - let ratchet = ClosedGroupRatchet(chainKey: rootChainKey, keyIndex: 0, messageKeys: []) - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: ratchet, using: transaction) - return ratchet - } - - private func step(_ ratchet: ClosedGroupRatchet) throws -> ClosedGroupRatchet { - let nextMessageKey = try HMAC(key: Data(hex: ratchet.chainKey).bytes, variant: .sha256).authenticate([ UInt8(1) ]) - let nextChainKey = try HMAC(key: Data(hex: ratchet.chainKey).bytes, variant: .sha256).authenticate([ UInt8(2) ]) - let nextKeyIndex = ratchet.keyIndex + 1 - let messageKeys = ratchet.messageKeys + [ nextMessageKey.toHexString() ] - return ClosedGroupRatchet(chainKey: nextChainKey.toHexString(), keyIndex: nextKeyIndex, messageKeys: messageKeys) - } - - /// - Note: Sync. Don't call from the main thread. - private func stepRatchetOnce(for groupPublicKey: String, senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) throws -> ClosedGroupRatchet { - #if DEBUG - assert(!Thread.isMainThread) - #endif - guard let ratchet = Storage.getClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey) else { - let error = RatchetingError.loadingFailed(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) - print("[Loki] \(error.errorDescription!)") - throw error - } - do { - let result = try step(ratchet) - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: result, using: transaction) - return result - } catch { - print("[Loki] Couldn't step ratchet due to error: \(error).") - throw error - } - } - - /// - Note: Sync. Don't call from the main thread. - private func stepRatchet(for groupPublicKey: String, senderPublicKey: String, until targetKeyIndex: UInt, using transaction: YapDatabaseReadWriteTransaction, isRetry: Bool = false) throws -> ClosedGroupRatchet { - #if DEBUG - assert(!Thread.isMainThread) - #endif - let collection: Storage.ClosedGroupRatchetCollectionType = (isRetry) ? .old : .current - guard let ratchet = Storage.getClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, from: collection) else { - let error = RatchetingError.loadingFailed(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) - print("[Loki] \(error.errorDescription!)") - throw error - } - if targetKeyIndex < ratchet.keyIndex { - // There's no need to advance the ratchet if this is invoked for an old key index - guard ratchet.messageKeys.count > targetKeyIndex else { - let error = RatchetingError.messageKeyMissing(targetKeyIndex: targetKeyIndex, groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) - print("[Loki] \(error.errorDescription!)") - throw error - } - return ratchet - } else { - var currentKeyIndex = ratchet.keyIndex - var result = ratchet - while currentKeyIndex < targetKeyIndex { - do { - result = try step(result) - currentKeyIndex = result.keyIndex - } catch { - print("[Loki] Couldn't step ratchet due to error: \(error).") - throw error - } - } - let collection: Storage.ClosedGroupRatchetCollectionType = (isRetry) ? .old : .current - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: result, in: collection, using: transaction) - return result - } - } - - // MARK: Public API - @objc(encrypt:forGroupWithPublicKey:senderPublicKey:protocolContext:error:) - public func encrypt(_ plaintext: Data, forGroupWithPublicKey groupPublicKey: String, senderPublicKey: String, protocolContext: Any) throws -> [Any] { - let transaction = protocolContext as! YapDatabaseReadWriteTransaction - let (ivAndCiphertext, keyIndex) = try encrypt(plaintext, for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction) - return [ ivAndCiphertext, NSNumber(value: keyIndex) ] - } - - public func encrypt(_ plaintext: Data, for groupPublicKey: String, senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) throws -> (ivAndCiphertext: Data, keyIndex: UInt) { - let ratchet: ClosedGroupRatchet - do { - ratchet = try stepRatchetOnce(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction) - } catch { - // FIXME: It'd be cleaner to handle this in OWSMessageDecrypter (where all the other decryption errors are handled), but this was a lot more - // convenient because there's an easy way to get the sender public key from here. - if case RatchetingError.loadingFailed(_, _) = error { - ClosedGroupsProtocol.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction) - } - throw error - } - let iv = Data.getSecureRandomData(ofSize: SharedSenderKeysImplementation.ivSize)! - let gcm = GCM(iv: iv.bytes, tagLength: Int(SharedSenderKeysImplementation.gcmTagSize), mode: .combined) - let messageKey = ratchet.messageKeys.last! - let aes = try AES(key: Data(hex: messageKey).bytes, blockMode: gcm, padding: .noPadding) - let ciphertext = try aes.encrypt(plaintext.bytes) - return (ivAndCiphertext: iv + Data(bytes: ciphertext), ratchet.keyIndex) - } - - @objc(decrypt:forGroupWithPublicKey:senderPublicKey:keyIndex:protocolContext:error:) - public func decrypt(_ ivAndCiphertext: Data, forGroupWithPublicKey groupPublicKey: String, senderPublicKey: String, keyIndex: UInt, protocolContext: Any) throws -> Data { - let transaction = protocolContext as! YapDatabaseReadWriteTransaction - return try decrypt(ivAndCiphertext, for: groupPublicKey, senderPublicKey: senderPublicKey, keyIndex: keyIndex, using: transaction) - } - - public func decrypt(_ ivAndCiphertext: Data, for groupPublicKey: String, senderPublicKey: String, keyIndex: UInt, using transaction: YapDatabaseReadWriteTransaction, isRetry: Bool = false) throws -> Data { - let ratchet: ClosedGroupRatchet - do { - ratchet = try stepRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, until: keyIndex, using: transaction, isRetry: isRetry) - } catch { - if !isRetry { - return try decrypt(ivAndCiphertext, for: groupPublicKey, senderPublicKey: senderPublicKey, keyIndex: keyIndex, using: transaction, isRetry: true) - } else { - // FIXME: It'd be cleaner to handle this in OWSMessageDecrypter (where all the other decryption errors are handled), but this was a lot more - // convenient because there's an easy way to get the sender public key from here. - if case RatchetingError.loadingFailed(_, _) = error { - ClosedGroupsProtocol.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction) - } - throw error - } - } - let iv = ivAndCiphertext[0.. 16 { // Pick an arbitrary number of message keys to try; this helps resolve issues caused by messages arriving out of order - lastNMessageKeys = [String](messageKeys[messageKeys.index(messageKeys.endIndex, offsetBy: -16).. Bool { - return Storage.getUserClosedGroupPublicKeys().contains(publicKey) - } - - public func getKeyPair(forGroupWithPublicKey groupPublicKey: String) -> ECKeyPair { - let privateKey = Storage.getClosedGroupPrivateKey(for: groupPublicKey)! - return ECKeyPair(publicKey: Data(hex: groupPublicKey.removing05PrefixIfNeeded()), privateKey: Data(hex: privateKey))! - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/Storage+ClosedGroups.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/Storage+ClosedGroups.swift deleted file mode 100644 index f4e031dd3..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/Storage+ClosedGroups.swift +++ /dev/null @@ -1,83 +0,0 @@ - -public extension Storage { - - internal enum ClosedGroupRatchetCollectionType { - case old, current - } - - // MARK: Ratchets - internal static func getClosedGroupRatchetCollection(_ collection: ClosedGroupRatchetCollectionType, for groupPublicKey: String) -> String { - switch collection { - case .old: return "LokiOldClosedGroupRatchetCollection.\(groupPublicKey)" - case .current: return "LokiClosedGroupRatchetCollection.\(groupPublicKey)" - } - } - - internal static func getClosedGroupRatchet(for groupPublicKey: String, senderPublicKey: String, from collection: ClosedGroupRatchetCollectionType = .current) -> ClosedGroupRatchet? { - let collection = getClosedGroupRatchetCollection(collection, for: groupPublicKey) - var result: ClosedGroupRatchet? - read { transaction in - result = transaction.object(forKey: senderPublicKey, inCollection: collection) as? ClosedGroupRatchet - } - return result - } - - internal static func setClosedGroupRatchet(for groupPublicKey: String, senderPublicKey: String, ratchet: ClosedGroupRatchet, in collection: ClosedGroupRatchetCollectionType = .current, using transaction: YapDatabaseReadWriteTransaction) { - let collection = getClosedGroupRatchetCollection(collection, for: groupPublicKey) - transaction.setObject(ratchet, forKey: senderPublicKey, inCollection: collection) - } - - internal static func getAllClosedGroupRatchets(for groupPublicKey: String, from collection: ClosedGroupRatchetCollectionType = .current) -> [(senderPublicKey: String, ratchet: ClosedGroupRatchet)] { - let collection = getClosedGroupRatchetCollection(collection, for: groupPublicKey) - var result: [(senderPublicKey: String, ratchet: ClosedGroupRatchet)] = [] - read { transaction in - transaction.enumerateRows(inCollection: collection) { key, object, _, _ in - guard let senderPublicKey = key as? String, let ratchet = object as? ClosedGroupRatchet else { return } - result.append((senderPublicKey: senderPublicKey, ratchet: ratchet)) - } - } - return result - } - - internal static func getAllClosedGroupSenderKeys(for groupPublicKey: String, from collection: ClosedGroupRatchetCollectionType = .current) -> Set { - return Set(getAllClosedGroupRatchets(for: groupPublicKey, from: collection).map { senderPublicKey, ratchet in - ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: Data(hex: senderPublicKey)) - }) - } - - internal static func removeAllClosedGroupRatchets(for groupPublicKey: String, from collection: ClosedGroupRatchetCollectionType = .current, using transaction: YapDatabaseReadWriteTransaction) { - let collection = getClosedGroupRatchetCollection(collection, for: groupPublicKey) - transaction.removeAllObjects(inCollection: collection) - } -} - -@objc public extension Storage { - - // MARK: Private Keys - internal static let closedGroupPrivateKeyCollection = "LokiClosedGroupPrivateKeyCollection" - - public static func getUserClosedGroupPublicKeys() -> Set { - var result: Set = [] - read { transaction in - result = Set(transaction.allKeys(inCollection: closedGroupPrivateKeyCollection)) - } - return result - } - - @objc(getPrivateKeyForClosedGroupWithPublicKey:) - internal static func getClosedGroupPrivateKey(for publicKey: String) -> String? { - var result: String? - read { transaction in - result = transaction.object(forKey: publicKey, inCollection: closedGroupPrivateKeyCollection) as? String - } - return result - } - - internal static func setClosedGroupPrivateKey(_ privateKey: String, for publicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - transaction.setObject(privateKey, forKey: publicKey, inCollection: closedGroupPrivateKeyCollection) - } - - internal static func removeClosedGroupPrivateKey(for publicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - transaction.removeObject(forKey: publicKey, inCollection: closedGroupPrivateKeyCollection) - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Mentions/Mention.swift b/SignalServiceKit/src/Loki/Protocol/Mentions/Mention.swift deleted file mode 100644 index c064a5122..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Mentions/Mention.swift +++ /dev/null @@ -1,15 +0,0 @@ - -@objc(LKMention) -public final class Mention : NSObject { - @objc public let publicKey: String - @objc public let displayName: String - - @objc public init(publicKey: String, displayName: String) { - self.publicKey = publicKey - self.displayName = displayName - } - - @objc public func isContained(in string: String) -> Bool { - return string.contains(displayName) - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Mentions/MentionsManager.swift b/SignalServiceKit/src/Loki/Protocol/Mentions/MentionsManager.swift deleted file mode 100644 index 29beb779d..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Mentions/MentionsManager.swift +++ /dev/null @@ -1,92 +0,0 @@ -import PromiseKit - -@objc(LKMentionsManager) -public final class MentionsManager : NSObject { - - /// A mapping from thread ID to set of user hex encoded public keys. - /// - /// - Note: Should only be accessed from the main queue to avoid race conditions. - @objc public static var userPublicKeyCache: [String:Set] = [:] - - internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } - - // MARK: Settings - private static var userIDScanLimit: UInt = 4096 - - // MARK: Initialization - private override init() { } - - // MARK: Implementation - @objc public static func cache(_ publicKey: String, for threadID: String) { - if let cache = userPublicKeyCache[threadID] { - userPublicKeyCache[threadID] = cache.union([ publicKey ]) - } else { - userPublicKeyCache[threadID] = [ publicKey ] - } - } - - @objc public static func getMentionCandidates(for query: String, in threadID: String) -> [Mention] { - // Prepare - guard let cache = userPublicKeyCache[threadID] else { return [] } - var candidates: [Mention] = [] - // Gather candidates - var publicChat: PublicChat? - storage.dbReadConnection.read { transaction in - publicChat = LokiDatabaseUtilities.getPublicChat(for: threadID, in: transaction) - } - storage.dbReadConnection.read { transaction in - candidates = cache.flatMap { publicKey in - let uncheckedDisplayName: String? - if let publicChat = publicChat { - uncheckedDisplayName = UserDisplayNameUtilities.getPublicChatDisplayName(for: publicKey, in: publicChat.channel, on: publicChat.server) - } else { - uncheckedDisplayName = UserDisplayNameUtilities.getPrivateChatDisplayName(for: publicKey) - } - guard let displayName = uncheckedDisplayName else { return nil } - guard !displayName.hasPrefix("Anonymous") else { return nil } - return Mention(publicKey: publicKey, displayName: displayName) - } - } - candidates = candidates.filter { $0.publicKey != getUserHexEncodedPublicKey() } - // Sort alphabetically first - candidates.sort { $0.displayName < $1.displayName } - if query.count >= 2 { - // Filter out any non-matching candidates - candidates = candidates.filter { $0.displayName.lowercased().contains(query.lowercased()) } - // Sort based on where in the candidate the query occurs - candidates.sort { - $0.displayName.lowercased().range(of: query.lowercased())!.lowerBound < $1.displayName.lowercased().range(of: query.lowercased())!.lowerBound - } - } - // Return - return candidates - } - - @objc public static func populateUserPublicKeyCacheIfNeeded(for threadID: String, in transaction: YapDatabaseReadTransaction? = nil) { - var result: Set = [] - func populate(in transaction: YapDatabaseReadTransaction) { - guard let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return } - if let groupThread = thread as? TSGroupThread, groupThread.groupModel.groupType == .closedGroup { - result = result.union(groupThread.groupModel.groupMemberIds).subtracting([ getUserHexEncodedPublicKey() ]) - } else { - guard userPublicKeyCache[threadID] == nil else { return } - let interactions = transaction.ext(TSMessageDatabaseViewExtensionName) as! YapDatabaseViewTransaction - interactions.enumerateKeysAndObjects(inGroup: threadID) { _, _, object, index, _ in - guard let message = object as? TSIncomingMessage, index < userIDScanLimit else { return } - result.insert(message.authorId) - } - } - result.insert(getUserHexEncodedPublicKey()) - } - if let transaction = transaction { - populate(in: transaction) - } else { - storage.dbReadConnection.read { transaction in - populate(in: transaction) - } - } - if !result.isEmpty { - userPublicKeyCache[threadID] = result - } - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Meta/SessionMetaProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Meta/SessionMetaProtocol.swift deleted file mode 100644 index dde938992..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Meta/SessionMetaProtocol.swift +++ /dev/null @@ -1,134 +0,0 @@ -import PromiseKit - -// A few notes about making changes in this file: -// -// • Don't use a database transaction if you can avoid it. -// • If you do need to use a database transaction, use a read transaction if possible. -// • For write transactions, consider making it the caller's responsibility to manage the database transaction (this helps avoid unnecessary transactions). -// • Think carefully about adding a function; there might already be one for what you need. -// • Document the expected cases in which a function will be used -// • Express those cases in tests. - -/// See [Receipts, Transcripts & Typing Indicators](https://github.com/loki-project/session-protocol-docs/wiki/Receipts,-Transcripts-&-Typing-Indicators) for more information. -@objc(LKSessionMetaProtocol) -public final class SessionMetaProtocol : NSObject { - - internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } - - // MARK: - Sending - - // MARK: Message Destination(s) - @objc public static func getDestinationsForOutgoingSyncMessage() -> NSMutableSet { - return NSMutableSet(set: [ getUserHexEncodedPublicKey() ]) // return NSMutableSet(set: MultiDeviceProtocol.getUserLinkedDevices()) - } - - @objc(getDestinationsForOutgoingGroupMessage:inThread:) - public static func getDestinations(for outgoingGroupMessage: TSOutgoingMessage, in thread: TSThread) -> NSMutableSet { - guard let thread = thread as? TSGroupThread else { preconditionFailure("Can't get destinations for group message in non-group thread.") } - var result: Set = [] - if thread.isPublicChat { - storage.dbReadConnection.read { transaction in - if let openGroup = LokiDatabaseUtilities.getPublicChat(for: thread.uniqueId!, in: transaction) { - result = [ openGroup.server ] // Aim the message at the open group server - } else { - // Should never occur - } - } - } else { - if let groupThread = thread as? TSGroupThread, groupThread.usesSharedSenderKeys { - let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupThread.groupModel.groupId) - result = [ groupPublicKey ] - } else { - result = Set(outgoingGroupMessage.sendingRecipientIds()) - .intersection(thread.groupModel.groupMemberIds) - .subtracting([ getUserHexEncodedPublicKey() ]) // .subtracting(MultiDeviceProtocol.getUserLinkedDevices()) - } - } - return NSMutableSet(set: result) - } - - // MARK: Note to Self - @objc(isThreadNoteToSelf:) - public static func isThreadNoteToSelf(_ thread: TSThread) -> Bool { - guard let thread = thread as? TSContactThread else { return false } - return thread.contactIdentifier() == getUserHexEncodedPublicKey() - /* - var isNoteToSelf = false - storage.dbReadConnection.read { transaction in - isNoteToSelf = LokiDatabaseUtilities.isUserLinkedDevice(thread.contactIdentifier(), transaction: transaction) - } - return isNoteToSelf - */ - } - - // MARK: Transcripts - @objc(shouldSendTranscriptForMessage:inThread:) - public static func shouldSendTranscript(for message: TSOutgoingMessage, in thread: TSThread) -> Bool { - guard message.shouldSyncTranscript() else { return false } - let isOpenGroupMessage = (thread as? TSGroupThread)?.isPublicChat == true - let wouldSignalRequireTranscript = (AreRecipientUpdatesEnabled() || !message.hasSyncedTranscript) - guard wouldSignalRequireTranscript && !isOpenGroupMessage else { return false } - return false - /* - var usesMultiDevice = false - storage.dbReadConnection.read { transaction in - usesMultiDevice = !storage.getDeviceLinks(for: getUserHexEncodedPublicKey(), in: transaction).isEmpty - || UserDefaults.standard[.masterHexEncodedPublicKey] != nil - } - return usesMultiDevice - */ - } - - // MARK: Typing Indicators - /// Invoked only if typing indicators are enabled in the settings. Provides an opportunity - /// to avoid sending them if certain conditions are met. - @objc(shouldSendTypingIndicatorInThread:) - public static func shouldSendTypingIndicator(in thread: TSThread) -> Bool { - return !thread.isGroupThread() && thread.numberOfInteractions() > 0 - } - - // MARK: Receipts - @objc(shouldSendReceiptInThread:) - public static func shouldSendReceipt(in thread: TSThread) -> Bool { - return !thread.isGroupThread() - } - - // MARK: - Receiving - - @objc(isErrorMessageFromBeforeRestoration:) - public static func isErrorMessageFromBeforeRestoration(_ errorMessage: TSErrorMessage) -> Bool { - let restorationTimeInMs = UInt64(storage.getRestorationTime() * 1000) - return errorMessage.timestamp < restorationTimeInMs - } - - @objc(shouldSkipMessageDecryptResult:wrappedIn:) - public static func shouldSkipMessageDecryptResult(_ result: OWSMessageDecryptResult, wrappedIn envelope: SSKProtoEnvelope) -> Bool { - return result.source == getUserHexEncodedPublicKey() - /* - if result.source == getUserHexEncodedPublicKey() { return true } - var isLinkedDevice = false - Storage.read { transaction in - isLinkedDevice = LokiDatabaseUtilities.isUserLinkedDevice(result.source, transaction: transaction) - } - return isLinkedDevice && envelope.type == .closedGroupCiphertext - */ - } - - @objc(updateDisplayNameIfNeededForPublicKey:using:transaction:) - public static func updateDisplayNameIfNeeded(for publicKey: String, using dataMessage: SSKProtoDataMessage, in transaction: YapDatabaseReadWriteTransaction) { - guard let profile = dataMessage.profile, let displayName = profile.displayName, !displayName.isEmpty else { return } - let profileManager = SSKEnvironment.shared.profileManager - profileManager.updateProfileForContact(withID: publicKey, displayName: displayName, with: transaction) - } - - @objc(updateProfileKeyIfNeededForPublicKey:using:) - public static func updateProfileKeyIfNeeded(for publicKey: String, using dataMessage: SSKProtoDataMessage) { - guard dataMessage.hasProfileKey, let profileKey = dataMessage.profileKey else { return } - guard profileKey.count == kAES256_KeyByteLength else { - return print("[Loki] Unexpected profile key size: \(profileKey.count).") - } - let profilePictureURL = dataMessage.profile?.profilePicture - let profileManager = SSKEnvironment.shared.profileManager - profileManager.setProfileKeyData(profileKey, forRecipientId: publicKey, avatarURL: profilePictureURL) - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Session Management/LokiSessionResetImplementation.swift b/SignalServiceKit/src/Loki/Protocol/Session Management/LokiSessionResetImplementation.swift deleted file mode 100644 index eb5c97b88..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Session Management/LokiSessionResetImplementation.swift +++ /dev/null @@ -1,57 +0,0 @@ -import SessionMetadataKit - -@objc(LKSessionResetImplementation) -public class LokiSessionResetImplementation : NSObject, SessionResetProtocol { - - private var storage: OWSPrimaryStorage { - return OWSPrimaryStorage.shared() - } - - enum Error : Swift.Error { - case invalidPreKey - case preKeyIDsDontMatch - } - - public func validatePreKeyWhisperMessage(for recipientID: String, whisperMessage: CipherMessage, protocolContext: Any?) throws { - guard let transaction = protocolContext as? YapDatabaseReadTransaction else { - print("[Loki] Invalid transaction.") - return - } - guard let preKeyMessage = whisperMessage as? PreKeyWhisperMessage else { return } - guard let storedPreKey = storage.getPreKeyRecord(forContact: recipientID, transaction: transaction) else { - print("[Loki] Missing pre key bundle.") - throw Error.invalidPreKey - } - guard storedPreKey.id == preKeyMessage.prekeyID else { - print("[Loki] Received a `PreKeyWhisperMessage` from an unknown source.") - throw Error.preKeyIDsDontMatch - } - } - - public func getSessionResetStatus(for recipientID: String, protocolContext: Any?) -> SessionResetStatus { - guard let transaction = protocolContext as? YapDatabaseReadTransaction else { - print("[Loki] Couldn't get session reset status for \(recipientID) because an invalid transaction was provided.") - return .none - } - guard let thread = TSContactThread.getWithContactId(recipientID, transaction: transaction) else { return .none } - return thread.sessionResetStatus - } - - public func onNewSessionAdopted(for recipientID: String, protocolContext: Any?) { - guard let transaction = protocolContext as? YapDatabaseReadWriteTransaction else { - Logger.warn("[Loki] Cannot handle new session adoption because an invalid transaction was provided.") - return - } - guard !recipientID.isEmpty else { return } - guard let thread = TSContactThread.getWithContactId(recipientID, transaction: transaction) else { - Logger.debug("[Loki] A new session was adopted but the thread couldn't be found for: \(recipientID).") - return - } - // Notify the user - let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeLokiSessionResetDone) - infoMessage.save(with: transaction) - // Update the session reset status - thread.sessionResetStatus = .none - thread.save(with: transaction) - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Session Management/SSKProtoPrekeyBundleMessage+Loki.swift b/SignalServiceKit/src/Loki/Protocol/Session Management/SSKProtoPrekeyBundleMessage+Loki.swift deleted file mode 100644 index 387bf4415..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Session Management/SSKProtoPrekeyBundleMessage+Loki.swift +++ /dev/null @@ -1,23 +0,0 @@ - -@objc public extension SSKProtoPrekeyBundleMessage { - - @objc(builderFromPreKeyBundle:) - public static func builder(from preKeyBundle: PreKeyBundle) -> SSKProtoPrekeyBundleMessageBuilder { - let builder = self.builder() - builder.setIdentityKey(preKeyBundle.identityKey) - builder.setDeviceID(UInt32(preKeyBundle.deviceId)) - builder.setPrekeyID(UInt32(preKeyBundle.preKeyId)) - builder.setPrekey(preKeyBundle.preKeyPublic) - builder.setSignedKeyID(UInt32(preKeyBundle.signedPreKeyId)) - builder.setSignedKey(preKeyBundle.signedPreKeyPublic) - builder.setSignature(preKeyBundle.signedPreKeySignature) - return builder - } - - @objc(getPreKeyBundleWithTransaction:) - public func getPreKeyBundle(with transaction: YapDatabaseReadWriteTransaction) -> PreKeyBundle? { - let registrationId = TSAccountManager.sharedInstance().getOrGenerateRegistrationId(transaction) - return PreKeyBundle(registrationId: Int32(registrationId), deviceId: Int32(deviceID), preKeyId: Int32(prekeyID), preKeyPublic: prekey, - signedPreKeyPublic: signedKey, signedPreKeyId: Int32(signedKeyID), signedPreKeySignature: signature, identityKey: identityKey) - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift deleted file mode 100644 index 93e5a2f37..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift +++ /dev/null @@ -1,223 +0,0 @@ -import PromiseKit - -// A few notes about making changes in this file: -// -// • Don't use a database transaction if you can avoid it. -// • If you do need to use a database transaction, use a read transaction if possible. -// • For write transactions, consider making it the caller's responsibility to manage the database transaction (this helps avoid unnecessary transactions). -// • Think carefully about adding a function; there might already be one for what you need. -// • Document the expected cases in which a function will be used -// • Express those cases in tests. - -@objc(LKSessionManagementProtocol) -public final class SessionManagementProtocol : NSObject { - - internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } - - // MARK: - General - - @objc(createPreKeys) - public static func createPreKeys() { - // We don't generate new pre keys here like Signal does. - // This is because we need the records to be linked to a contact since we don't have a central server. - // It's done automatically when we generate a pre key bundle to send to a contact (generatePreKeyBundleForContact:). - // You can use getOrCreatePreKeyForContact: to generate one if needed. - let signedPreKeyRecord = storage.generateRandomSignedRecord() - signedPreKeyRecord.markAsAcceptedByService() - storage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) - storage.setCurrentSignedPrekeyId(signedPreKeyRecord.id) - print("[Loki] Pre keys created successfully.") - } - - @objc(refreshSignedPreKey) - public static func refreshSignedPreKey() { - // We don't generate new pre keys here like Signal does. - // This is because we need the records to be linked to a contact since we don't have a central server. - // It's done automatically when we generate a pre key bundle to send to a contact (generatePreKeyBundleForContact:). - // You can use getOrCreatePreKeyForContact: to generate one if needed. - guard storage.currentSignedPrekeyId() == nil else { return } - let signedPreKeyRecord = storage.generateRandomSignedRecord() - signedPreKeyRecord.markAsAcceptedByService() - storage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) - storage.setCurrentSignedPrekeyId(signedPreKeyRecord.id) - TSPreKeyManager.clearPreKeyUpdateFailureCount() - TSPreKeyManager.clearSignedPreKeyRecords() - print("[Loki] Signed pre key refreshed successfully.") - } - - @objc(rotateSignedPreKey) - public static func rotateSignedPreKey() { - // This is identical to what Signal does, except that it doesn't upload the signed pre key - // to a server. - let signedPreKeyRecord = storage.generateRandomSignedRecord() - signedPreKeyRecord.markAsAcceptedByService() - storage.storeSignedPreKey(signedPreKeyRecord.id, signedPreKeyRecord: signedPreKeyRecord) - storage.setCurrentSignedPrekeyId(signedPreKeyRecord.id) - TSPreKeyManager.clearPreKeyUpdateFailureCount() - TSPreKeyManager.clearSignedPreKeyRecords() - print("[Loki] Signed pre key rotated successfully.") - } - - // MARK: - Sending - - @objc(isSessionRequiredForMessage:recipientID:transaction:) - public static func isSessionRequired(for message: TSOutgoingMessage, recipientID: String, transaction: YapDatabaseReadWriteTransaction) -> Bool { - if SharedSenderKeysImplementation.shared.isClosedGroup(recipientID) { - return false - } else { - return !shouldUseFallbackEncryption(for: message, recipientID: recipientID, transaction: transaction) - } - } - - @objc(shouldUseFallbackEncryptionForMessage:recipientID:transaction:) - public static func shouldUseFallbackEncryption(for message: TSOutgoingMessage, recipientID: String, transaction: YapDatabaseReadWriteTransaction) -> Bool { - if SharedSenderKeysImplementation.shared.isClosedGroup(recipientID) { return false } - else if message is SessionRequestMessage { return true } - else if message is EndSessionMessage { return true } - else if let message = message as? DeviceLinkMessage, message.kind == .request { return true } - else if message is OWSOutgoingNullMessage { return false } - return !storage.containsSession(recipientID, deviceId: Int32(OWSDevicePrimaryDeviceId), protocolContext: transaction) - } - - private static func hasSentSessionRequestExpired(for publicKey: String) -> Bool { - let timestamp = Storage.getSessionRequestSentTimestamp(for: publicKey) - let expiration = timestamp + TTLUtilities.getTTL(for: .sessionRequest) - return NSDate.ows_millisecondTimeStamp() > expiration - } - - @objc(sendSessionRequestIfNeededToPublicKey:transaction:) - public static func sendSessionRequestIfNeeded(to publicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - // It's never necessary to establish a session with self - guard publicKey != getUserHexEncodedPublicKey() else { return } - // Check that we don't already have a session - let hasSession = storage.containsSession(publicKey, deviceId: Int32(OWSDevicePrimaryDeviceId), protocolContext: transaction) - guard !hasSession else { return } - // Check that we didn't already send a session request - let hasSentSessionRequest = (Storage.getSessionRequestSentTimestamp(for: publicKey) > 0) - let hasSentSessionRequestExpired = SessionManagementProtocol.hasSentSessionRequestExpired(for: publicKey) - if hasSentSessionRequestExpired { - Storage.setSessionRequestSentTimestamp(for: publicKey, to: 0, using: transaction) - } - guard !hasSentSessionRequest || hasSentSessionRequestExpired else { return } - // Create the thread if needed - let thread = TSContactThread.getOrCreateThread(withContactId: publicKey, transaction: transaction) - thread.save(with: transaction) - // Send the session request - print("[Loki] Sending session request to: \(publicKey).") - Storage.setSessionRequestSentTimestamp(for: publicKey, to: NSDate.ows_millisecondTimeStamp(), using: transaction) - let sessionRequestMessage = SessionRequestMessage(thread: thread) - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - messageSenderJobQueue.add(message: sessionRequestMessage, transaction: transaction) - } - - @objc(sendNullMessageToPublicKey:transaction:) - public static func sendNullMessage(to publicKey: String, in transaction: YapDatabaseReadWriteTransaction) { - let thread = TSContactThread.getOrCreateThread(withContactId: publicKey, transaction: transaction) - thread.save(with: transaction) - let nullMessage = OWSOutgoingNullMessage(outgoingMessageWithTimestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageBody: nil, - attachmentIds: [], expiresInSeconds: 0, expireStartedAt: 0, isVoiceMessage: false, groupMetaMessage: .unspecified, quotedMessage: nil, - contactShare: nil, linkPreview: nil) - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - messageSenderJobQueue.add(message: nullMessage, transaction: transaction) - } - - /// - Note: Deprecated. - /// - /// Only relevant for closed groups that don't use shared sender keys. - @objc(shouldIgnoreMissingPreKeyBundleExceptionForMessage:to:) - public static func shouldIgnoreMissingPreKeyBundleException(for message: TSOutgoingMessage, to hexEncodedPublicKey: String) -> Bool { - // When a closed group is created, members try to establish sessions with eachother in the background through - // session requests. Until ALL users those session requests were sent to have come online, stored the pre key - // bundles contained in the session requests and replied with background messages to finalize the session - // creation, a given user won't be able to successfully send a message to all members of a group. This check - // is so that until we can do better on this front the user at least won't see this as an error in the UI. - guard let groupThread = message.thread as? TSGroupThread else { return false } - return groupThread.groupModel.groupType == .closedGroup && !groupThread.usesSharedSenderKeys - } - - @objc(startSessionResetInThread:transaction:) - public static func startSessionReset(in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) { - // Check preconditions - guard let thread = thread as? TSContactThread else { - return print("[Loki] Can't restore session for non contact thread.") - } - // Send end session messages to the devices requiring session restoration - let devices = thread.sessionRestoreDevices // TODO: Rename this to something that reads better - for device in devices { - guard ECKeyPair.isValidHexEncodedPublicKey(candidate: device) else { continue } - let thread = TSContactThread.getOrCreateThread(withContactId: device, transaction: transaction) - thread.save(with: transaction) - let endSessionMessage = EndSessionMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread) - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - messageSenderJobQueue.add(message: endSessionMessage, transaction: transaction) - } - thread.removeAllSessionRestoreDevices(with: transaction) - // Notify the user - let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeLokiSessionResetInProgress) - infoMessage.save(with: transaction) - // Update the session reset status - thread.sessionResetStatus = .initiated - thread.save(with: transaction) - } - - // MARK: - Receiving - - @objc(handleDecryptionError:forPublicKey:transaction:) - public static func handleDecryptionError(_ errorMessage: TSErrorMessage, for publicKey: String, using transaction: YapDatabaseReadWriteTransaction) { - let type = errorMessage.errorType - let masterPublicKey = storage.getMasterHexEncodedPublicKey(for: publicKey, in: transaction) ?? publicKey - let thread = TSContactThread.getOrCreateThread(withContactId: masterPublicKey, transaction: transaction) - let restorationTimeInMs = UInt64(storage.getRestorationTime() * 1000) - // Show the session reset prompt upon certain errors - switch type { - case .noSession, .invalidMessage, .invalidKeyException: - if restorationTimeInMs > errorMessage.timestamp { - // Automatically rebuild session after restoration - sendSessionRequestIfNeeded(to: publicKey, using: transaction) - } else { - // Store the source device's public key in case it was a secondary device - thread.addSessionRestoreDevice(publicKey, transaction: transaction) - } - default: break - } - } - - private static func shouldProcessSessionRequest(from publicKey: String, at timestamp: UInt64) -> Bool { - let sentTimestamp = Storage.getSessionRequestSentTimestamp(for: publicKey) - let processedTimestamp = Storage.getSessionRequestProcessedTimestamp(for: publicKey) - let restorationTimestamp = UInt64(storage.getRestorationTime() * 1000) - return timestamp > sentTimestamp && timestamp > processedTimestamp && timestamp > restorationTimestamp - } - - @objc(handlePreKeyBundleMessageIfNeeded:wrappedIn:transaction:) - public static func handlePreKeyBundleMessageIfNeeded(_ protoContent: SSKProtoContent, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { - let publicKey = envelope.source! // Set during UD decryption - guard let preKeyBundleMessage = protoContent.prekeyBundleMessage else { return } - print("[Loki] Received a pre key bundle message from: \(publicKey).") - guard let preKeyBundle = preKeyBundleMessage.getPreKeyBundle(with: transaction) else { - return print("[Loki] Couldn't parse pre key bundle received from: \(publicKey).") - } - if !shouldProcessSessionRequest(from: publicKey, at: envelope.timestamp) { - return print("[Loki] Ignoring session request from: \(publicKey).") - } - storage.setPreKeyBundle(preKeyBundle, forContact: publicKey, transaction: transaction) - Storage.setSessionRequestProcessedTimestamp(for: publicKey, to: NSDate.ows_millisecondTimeStamp(), using: transaction) - sendNullMessage(to: publicKey, in: transaction) - } - - @objc(handleEndSessionMessageReceivedInThread:using:) - public static func handleEndSessionMessageReceived(in thread: TSContactThread, using transaction: YapDatabaseReadWriteTransaction) { - let publicKey = thread.contactIdentifier() - print("[Loki] End session message received from: \(publicKey).") - // Notify the user - let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeLokiSessionResetInProgress) - infoMessage.save(with: transaction) - // Archive all sessions - storage.archiveAllSessions(forContact: publicKey, protocolContext: transaction) - // Update the session reset status - thread.sessionResetStatus = .requestReceived - thread.save(with: transaction) - // Send a null message - sendNullMessage(to: publicKey, in: transaction) - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionRequestMessage.swift b/SignalServiceKit/src/Loki/Protocol/Session Management/SessionRequestMessage.swift deleted file mode 100644 index b612b1608..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionRequestMessage.swift +++ /dev/null @@ -1,51 +0,0 @@ - -@objc(LKSessionRequestMessage) -internal final class SessionRequestMessage : TSOutgoingMessage { - - @objc internal override var ttl: UInt32 { return UInt32(TTLUtilities.getTTL(for: .sessionRequest)) } - - @objc internal override func shouldBeSaved() -> Bool { return false } - @objc internal override func shouldSyncTranscript() -> Bool { return false } - - @objc internal init(thread: TSThread) { - super.init(outgoingMessageWithTimestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageBody: "", - attachmentIds: NSMutableArray(), expiresInSeconds: 0, expireStartedAt: 0, isVoiceMessage: false, - groupMetaMessage: .unspecified, quotedMessage: nil, contactShare: nil, linkPreview: nil) - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - } - - required init(dictionary: [String:Any]) throws { - try super.init(dictionary: dictionary) - } - - override func prepareCustomContentBuilder(_ recipient: SignalRecipient) -> Any? { - guard let contentBuilder = super.prepareCustomContentBuilder(recipient) as? SSKProtoContent.SSKProtoContentBuilder else { return nil } - // Attach a null message - let nullMessageBuilder = SSKProtoNullMessage.builder() - let paddingSize = UInt.random(in: 0..<512) // random(in:) uses the system's default random generator, which is cryptographically secure - let padding = Cryptography.generateRandomBytes(paddingSize) - nullMessageBuilder.setPadding(padding) - do { - let nullMessage = try nullMessageBuilder.build() - contentBuilder.setNullMessage(nullMessage) - } catch { - owsFailDebug("Failed to build session request message for: \(recipient.recipientId()) due to error: \(error).") - return nil - } - // Generate a pre key bundle for the recipient and attach it - let preKeyBundle = OWSPrimaryStorage.shared().generatePreKeyBundle(forContact: recipient.recipientId()) - let preKeyBundleMessageBuilder = SSKProtoPrekeyBundleMessage.builder(from: preKeyBundle) - do { - let preKeyBundleMessage = try preKeyBundleMessageBuilder.build() - contentBuilder.setPrekeyBundleMessage(preKeyBundleMessage) - } catch { - owsFailDebug("Failed to build session request message for: \(recipient.recipientId()) due to error: \(error).") - return nil - } - // Return - return contentBuilder - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Session Management/Storage+SessionManagement.swift b/SignalServiceKit/src/Loki/Protocol/Session Management/Storage+SessionManagement.swift deleted file mode 100644 index 7284a9601..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Session Management/Storage+SessionManagement.swift +++ /dev/null @@ -1,31 +0,0 @@ - -public extension Storage { - - // MARK: Session Request Timestamps - internal static let sessionRequestSentTimestampCollection = "LokiSessionRequestSentTimestampCollection" - internal static let sessionRequestProcessedTimestampCollection = "LokiSessionRequestProcessedTimestampCollection" - - internal static func getSessionRequestSentTimestamp(for publicKey: String) -> UInt64 { - var result: UInt64? - read { transaction in - result = transaction.object(forKey: publicKey, inCollection: sessionRequestSentTimestampCollection) as? UInt64 - } - return result ?? 0 - } - - internal static func setSessionRequestSentTimestamp(for publicKey: String, to timestamp: UInt64, using transaction: YapDatabaseReadWriteTransaction) { - transaction.setObject(timestamp, forKey: publicKey, inCollection: sessionRequestSentTimestampCollection) - } - - internal static func getSessionRequestProcessedTimestamp(for publicKey: String) -> UInt64 { - var result: UInt64? - read { transaction in - result = transaction.object(forKey: publicKey, inCollection: sessionRequestProcessedTimestampCollection) as? UInt64 - } - return result ?? 0 - } - - internal static func setSessionRequestProcessedTimestamp(for publicKey: String, to timestamp: UInt64, using transaction: YapDatabaseReadWriteTransaction) { - transaction.setObject(timestamp, forKey: publicKey, inCollection: sessionRequestProcessedTimestampCollection) - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLink.swift b/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLink.swift deleted file mode 100644 index 7f9b79a8e..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLink.swift +++ /dev/null @@ -1,96 +0,0 @@ - -@objc(LKDeviceLink) -public final class DeviceLink : NSObject, NSCoding { - @objc public let master: Device - @objc public let slave: Device - - @objc public var isAuthorized: Bool { return master.signature != nil } - - @objc public var other: Device { - let userPublicKey = getUserHexEncodedPublicKey() - return (userPublicKey == master.publicKey) ? slave : master - } - - // MARK: Device - @objc(LKDevice) - public final class Device : NSObject, NSCoding { - @objc public let publicKey: String - @objc public let signature: Data? - - @objc public var displayName: String { - if let customDisplayName = UserDefaults.standard[.slaveDeviceName(publicKey)] { - return customDisplayName - } else { - return NSLocalizedString("Unnamed Device", comment: "") - } - } - - @objc public init(publicKey: String, signature: Data? = nil) { - self.publicKey = publicKey - self.signature = signature - } - - @objc public init?(coder: NSCoder) { - publicKey = coder.decodeObject(forKey: "hexEncodedPublicKey") as! String - signature = coder.decodeObject(forKey: "signature") as! Data? - } - - @objc public func encode(with coder: NSCoder) { - coder.encode(publicKey, forKey: "hexEncodedPublicKey") - if let signature = signature { coder.encode(signature, forKey: "signature") } - } - - @objc public override func isEqual(_ other: Any?) -> Bool { - guard let other = other as? Device else { return false } - return publicKey == other.publicKey && signature == other.signature - } - - @objc override public var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:) - var result = publicKey.hashValue - if let signature = signature { result = result ^ signature.hashValue } - return result - } - - @objc override public var description: String { return publicKey } - } - - // MARK: Lifecycle - @objc public init(between master: Device, and slave: Device) { - self.master = master - self.slave = slave - } - - // MARK: Coding - @objc public init?(coder: NSCoder) { - master = coder.decodeObject(forKey: "master") as! Device - slave = coder.decodeObject(forKey: "slave") as! Device - super.init() - } - - @objc public func encode(with coder: NSCoder) { - coder.encode(master, forKey: "master") - coder.encode(slave, forKey: "slave") - } - - // MARK: JSON - public func toJSON() -> JSON { - var result = [ "primaryDevicePubKey" : master.publicKey, "secondaryDevicePubKey" : slave.publicKey ] - if let masterSignature = master.signature { result["grantSignature"] = masterSignature.base64EncodedString() } - if let slaveSignature = slave.signature { result["requestSignature"] = slaveSignature.base64EncodedString() } - return result - } - - // MARK: Equality - @objc override public func isEqual(_ other: Any?) -> Bool { - guard let other = other as? DeviceLink else { return false } - return master == other.master && slave == other.slave - } - - // MARK: Hashing - @objc override public var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:) - return master.hash ^ slave.hash - } - - // MARK: Description - @objc override public var description: String { return "\(master) - \(slave)" } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkIndex.swift b/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkIndex.swift deleted file mode 100644 index 15c3fd0ec..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkIndex.swift +++ /dev/null @@ -1,43 +0,0 @@ - -@objc(LKDeviceLinkIndex) -public final class DeviceLinkIndex : NSObject { - - private static let name = "loki_device_link_index" - - @objc public static let masterPublicKey = "master_hex_encoded_public_key" - @objc public static let slavePublicKey = "slave_hex_encoded_public_key" - @objc public static let isAuthorized = "is_authorized" - - @objc public static let indexDatabaseExtension: YapDatabaseSecondaryIndex = { - let setup = YapDatabaseSecondaryIndexSetup() - setup.addColumn(masterPublicKey, with: .text) - setup.addColumn(slavePublicKey, with: .text) - setup.addColumn(isAuthorized, with: .integer) - let handler = YapDatabaseSecondaryIndexHandler.withObjectBlock { _, map, _, _, object in - guard let deviceLink = object as? DeviceLink else { return } - map[masterPublicKey] = deviceLink.master.publicKey - map[slavePublicKey] = deviceLink.slave.publicKey - map[isAuthorized] = deviceLink.isAuthorized - } - return YapDatabaseSecondaryIndex(setup: setup, handler: handler) - }() - - @objc public static let databaseExtensionName: String = name - - @objc public static func asyncRegisterDatabaseExtensions(_ storage: OWSStorage) { - storage.asyncRegister(indexDatabaseExtension, withName: name) - } - - @objc public static func getDeviceLinks(for query: YapDatabaseQuery, in transaction: YapDatabaseReadTransaction) -> [DeviceLink] { - guard let ext = transaction.ext(DeviceLinkIndex.name) as? YapDatabaseSecondaryIndexTransaction else { - print("[Loki] Couldn't get device link index database extension.") - return [] - } - var result: [DeviceLink] = [] - ext.enumerateKeysAndObjects(matching: query) { _, _, object, _ in - guard let deviceLink = object as? DeviceLink else { return } - result.append(deviceLink) - } - return result - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkingSession.swift b/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkingSession.swift deleted file mode 100644 index 7b28f4b96..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkingSession.swift +++ /dev/null @@ -1,69 +0,0 @@ -import SessionCurve25519Kit -import PromiseKit - -@objc (LKDeviceLinkingSession) -public final class DeviceLinkingSession : NSObject { - private let delegate: DeviceLinkingSessionDelegate - @objc public var isListeningForLinkingRequests = false - @objc public var isProcessingLinkingRequest = false - @objc public var isListeningForLinkingAuthorization = false - - // MARK: Lifecycle - @objc public static var current: DeviceLinkingSession? - - private init(delegate: DeviceLinkingSessionDelegate) { - self.delegate = delegate - } - - // MARK: Public API - public static func startListeningForLinkingRequests(with delegate: DeviceLinkingSessionDelegate) -> DeviceLinkingSession { - let session = DeviceLinkingSession(delegate: delegate) - session.isListeningForLinkingRequests = true - DeviceLinkingSession.current = session - return session - } - - public static func startListeningForLinkingAuthorization(with delegate: DeviceLinkingSessionDelegate) -> DeviceLinkingSession { - let session = DeviceLinkingSession(delegate: delegate) - session.isListeningForLinkingAuthorization = true - DeviceLinkingSession.current = session - return session - } - - @objc public func processLinkingRequest(from slavePublicKey: String, to masterPublicKey: String, with slaveSignature: Data) { - guard isListeningForLinkingRequests, !isProcessingLinkingRequest, masterPublicKey == getUserHexEncodedPublicKey() else { return } - let master = DeviceLink.Device(publicKey: masterPublicKey) - let slave = DeviceLink.Device(publicKey: slavePublicKey, signature: slaveSignature) - let deviceLink = DeviceLink(between: master, and: slave) - guard DeviceLinkingUtilities.hasValidSlaveSignature(deviceLink) else { return } - isProcessingLinkingRequest = true - DispatchQueue.main.async { - self.delegate.requestUserAuthorization(for: deviceLink) - } - } - - @objc public func processLinkingAuthorization(from masterPublicKey: String, for slavePublicKey: String, masterSignature: Data, slaveSignature: Data) { - guard isListeningForLinkingAuthorization, slavePublicKey == getUserHexEncodedPublicKey() else { return } - let master = DeviceLink.Device(publicKey: masterPublicKey, signature: masterSignature) - let slave = DeviceLink.Device(publicKey: slavePublicKey, signature: slaveSignature) - let deviceLink = DeviceLink(between: master, and: slave) - guard DeviceLinkingUtilities.hasValidSlaveSignature(deviceLink) && DeviceLinkingUtilities.hasValidMasterSignature(deviceLink) else { return } - DispatchQueue.main.async { - self.delegate.handleDeviceLinkAuthorized(deviceLink) - } - } - - public func stopListeningForLinkingRequests() { - DeviceLinkingSession.current = nil - isListeningForLinkingRequests = false - } - - public func stopListeningForLinkingAuthorization() { - DeviceLinkingSession.current = nil - isListeningForLinkingAuthorization = false - } - - public func markLinkingRequestAsProcessed() { - isProcessingLinkingRequest = false - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkingSessionDelegate.swift b/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkingSessionDelegate.swift deleted file mode 100644 index ab806251c..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkingSessionDelegate.swift +++ /dev/null @@ -1,6 +0,0 @@ - -public protocol DeviceLinkingSessionDelegate { - - func requestUserAuthorization(for deviceLink: DeviceLink) - func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink) -} diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkingUtilities.swift b/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkingUtilities.swift deleted file mode 100644 index 8893a1106..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/DeviceLinkingUtilities.swift +++ /dev/null @@ -1,57 +0,0 @@ - -@objc(LKDeviceLinkingUtilities) -public final class DeviceLinkingUtilities : NSObject { - private static var lastUnexpectedDeviceLinkRequestDate: Date? = nil - - private override init() { } - - @objc public static var shouldShowUnexpectedDeviceLinkRequestReceivedAlert: Bool { - let now = Date() - if let lastUnexpectedDeviceLinkRequestDate = lastUnexpectedDeviceLinkRequestDate { - if now.timeIntervalSince(lastUnexpectedDeviceLinkRequestDate) < 30 { return false } - } - lastUnexpectedDeviceLinkRequestDate = now - return true - } - - // When requesting a device link, the slave device signs the master device's public key. When authorizing - // a device link, the master device signs the slave device's public key. - - public static func getLinkingRequestMessage(for masterPublicKey: String) -> DeviceLinkMessage { - let slaveKeyPair = OWSIdentityManager.shared().identityKeyPair()! - let slavePublicKey = slaveKeyPair.hexEncodedPublicKey - var kind = UInt8(LKDeviceLinkMessageKind.request.rawValue) - let data = Data(hex: masterPublicKey) + Data(bytes: &kind, count: MemoryLayout.size(ofValue: kind)) - let slaveSignature = try! Ed25519.sign(data, with: slaveKeyPair) - let thread = TSContactThread.getOrCreateThread(contactId: masterPublicKey) - return DeviceLinkMessage(in: thread, masterPublicKey: masterPublicKey, slavePublicKey: slavePublicKey, masterSignature: nil, slaveSignature: slaveSignature) - } - - public static func getLinkingAuthorizationMessage(for deviceLink: DeviceLink) -> DeviceLinkMessage { - let masterKeyPair = OWSIdentityManager.shared().identityKeyPair()! - let masterPublicKey = masterKeyPair.hexEncodedPublicKey - let slavePublicKey = deviceLink.slave.publicKey - var kind = UInt8(LKDeviceLinkMessageKind.authorization.rawValue) - let data = Data(hex: slavePublicKey) + Data(bytes: &kind, count: MemoryLayout.size(ofValue: kind)) - let masterSignature = try! Ed25519.sign(data, with: masterKeyPair) - let slaveSignature = deviceLink.slave.signature! - let thread = TSContactThread.getOrCreateThread(contactId: slavePublicKey) - return DeviceLinkMessage(in: thread, masterPublicKey: masterPublicKey, slavePublicKey: slavePublicKey, masterSignature: masterSignature, slaveSignature: slaveSignature) - } - - public static func hasValidSlaveSignature(_ deviceLink: DeviceLink) -> Bool { - guard let slaveSignature = deviceLink.slave.signature else { return false } - let slavePublicKey = Data(hex: deviceLink.slave.publicKey.removing05PrefixIfNeeded()) - var kind = UInt8(LKDeviceLinkMessageKind.request.rawValue) - let data = Data(hex: deviceLink.master.publicKey) + Data(bytes: &kind, count: MemoryLayout.size(ofValue: kind)) - return (try? Ed25519.verifySignature(slaveSignature, publicKey: slavePublicKey, data: data)) ?? false - } - - public static func hasValidMasterSignature(_ deviceLink: DeviceLink) -> Bool { - guard let masterSignature = deviceLink.master.signature else { return false } - let masterPublicKey = Data(hex: deviceLink.master.publicKey.removing05PrefixIfNeeded()) - var kind = UInt8(LKDeviceLinkMessageKind.authorization.rawValue) - let data = Data(hex: deviceLink.slave.publicKey) + Data(bytes: &kind, count: MemoryLayout.size(ofValue: kind)) - return (try? Ed25519.verifySignature(masterSignature, publicKey: masterPublicKey, data: data)) ?? false - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKDeviceLinkMessage.h b/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKDeviceLinkMessage.h deleted file mode 100644 index 783078a42..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKDeviceLinkMessage.h +++ /dev/null @@ -1,19 +0,0 @@ -#import "TSOutgoingMessage.h" - -typedef NS_ENUM(NSUInteger, LKDeviceLinkMessageKind) { - LKDeviceLinkMessageKindRequest = 1, - LKDeviceLinkMessageKindAuthorization = 2, -}; - -NS_SWIFT_NAME(DeviceLinkMessage) -@interface LKDeviceLinkMessage : TSOutgoingMessage - -@property (nonatomic, readonly) NSString *masterPublicKey; -@property (nonatomic, readonly) NSString *slavePublicKey; -@property (nonatomic, readonly) NSData *masterSignature; // nil for device linking requests -@property (nonatomic, readonly) NSData *slaveSignature; -@property (nonatomic, readonly) LKDeviceLinkMessageKind kind; - -- (instancetype)initInThread:(TSThread *)thread masterPublicKey:(NSString *)masterHexEncodedPublicKey slavePublicKey:(NSString *)slaveHexEncodedPublicKey masterSignature:(NSData * _Nullable)masterSignature slaveSignature:(NSData *)slaveSignature; - -@end diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKDeviceLinkMessage.m b/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKDeviceLinkMessage.m deleted file mode 100644 index ddd4bf14f..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKDeviceLinkMessage.m +++ /dev/null @@ -1,89 +0,0 @@ -#import "LKDeviceLinkMessage.h" -#import "OWSIdentityManager.h" -#import "OWSPrimaryStorage+Loki.h" -#import "ProfileManagerProtocol.h" -#import "ProtoUtils.h" -#import "SSKEnvironment.h" -#import "SignalRecipient.h" -#import -#import -#import - -@implementation LKDeviceLinkMessage - -#pragma mark Convenience -- (LKDeviceLinkMessageKind)kind { - if (self.masterSignature != nil) { - return LKDeviceLinkMessageKindAuthorization; - } else { - return LKDeviceLinkMessageKindRequest; - } -} - -#pragma mark Initialization -- (instancetype)initInThread:(TSThread *)thread masterPublicKey:(NSString *)masterHexEncodedPublicKey slavePublicKey:(NSString *)slaveHexEncodedPublicKey masterSignature:(NSData * _Nullable)masterSignature slaveSignature:(NSData *)slaveSignature { - self = [self initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"" attachmentIds:[NSMutableArray new] - expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil]; - if (self) { - _masterPublicKey = masterHexEncodedPublicKey; - _slavePublicKey = slaveHexEncodedPublicKey; - _masterSignature = masterSignature; - _slaveSignature = slaveSignature; - } - return self; -} - -#pragma mark Building -- (nullable id)prepareCustomContentBuilder:(SignalRecipient *)recipient { - SSKProtoContentBuilder *contentBuilder = [super prepareCustomContentBuilder:recipient]; - NSError *error; - if (self.kind == LKDeviceLinkMessageKindRequest) { - // The slave device attaches a pre key bundle with the request it sends so that a - // session can be established with the master device. - PreKeyBundle *preKeyBundle = [OWSPrimaryStorage.sharedManager generatePreKeyBundleForContact:recipient.recipientId]; - SSKProtoPrekeyBundleMessageBuilder *preKeyBundleMessageBuilder = [SSKProtoPrekeyBundleMessage builderFromPreKeyBundle:preKeyBundle]; - SSKProtoPrekeyBundleMessage *preKeyBundleMessage = [preKeyBundleMessageBuilder buildAndReturnError:&error]; - if (error || preKeyBundleMessage == nil) { - OWSFailDebug(@"Failed to build pre key bundle message for: %@ due to error: %@.", recipient.recipientId, error); - return nil; - } else { - [contentBuilder setPrekeyBundleMessage:preKeyBundleMessage]; - } - } else { - // The master device attaches its display name and profile picture URL to the device link - // authorization message so that the slave device is in sync with these things as soon - // as possible. - id profileManager = SSKEnvironment.shared.profileManager; - NSString *displayName = profileManager.localProfileName; - NSString *profilePictureURL = profileManager.profilePictureURL; - SSKProtoDataMessageLokiProfileBuilder *profileBuilder = [SSKProtoDataMessageLokiProfile builder]; - [profileBuilder setDisplayName:displayName]; - [profileBuilder setProfilePicture:profilePictureURL ?: @""]; - SSKProtoDataMessageBuilder *messageBuilder = [SSKProtoDataMessage builder]; - [messageBuilder setProfile:[profileBuilder buildAndReturnError:nil]]; - [ProtoUtils addLocalProfileKeyToDataMessageBuilder:messageBuilder]; - [contentBuilder setDataMessage:messageBuilder]; - } - // Build the device link message - SSKProtoLokiDeviceLinkMessageBuilder *deviceLinkMessageBuilder = [SSKProtoLokiDeviceLinkMessage builder]; - [deviceLinkMessageBuilder setMasterPublicKey:self.masterPublicKey]; - [deviceLinkMessageBuilder setSlavePublicKey:self.slavePublicKey]; - if (self.masterSignature != nil) { [deviceLinkMessageBuilder setMasterSignature:self.masterSignature]; } - [deviceLinkMessageBuilder setSlaveSignature:self.slaveSignature]; - SSKProtoLokiDeviceLinkMessage *deviceLinkMessage = [deviceLinkMessageBuilder buildAndReturnError:&error]; - if (error || deviceLinkMessage == nil) { - OWSFailDebug(@"Failed to build device link message for: %@ due to error: %@.", recipient.recipientId, error); - return nil; - } else { - [contentBuilder setLokiDeviceLinkMessage:deviceLinkMessage]; - } - // Return - return contentBuilder; -} - -#pragma mark Settings -- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeLinkDevice]; } -- (BOOL)shouldSyncTranscript { return NO; } -- (BOOL)shouldBeSaved { return NO; } - -@end diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKUnlinkDeviceMessage.h b/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKUnlinkDeviceMessage.h deleted file mode 100644 index b9b9f9fbb..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKUnlinkDeviceMessage.h +++ /dev/null @@ -1,12 +0,0 @@ -#import "TSOutgoingMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -NS_SWIFT_NAME(UnlinkDeviceMessage) -@interface LKUnlinkDeviceMessage : TSOutgoingMessage - -- (instancetype)initWithThread:(TSThread *)thread; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKUnlinkDeviceMessage.m b/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKUnlinkDeviceMessage.m deleted file mode 100644 index 81a60f6c4..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/LKUnlinkDeviceMessage.m +++ /dev/null @@ -1,27 +0,0 @@ -#import "LKUnlinkDeviceMessage.h" -#import -#import - -@implementation LKUnlinkDeviceMessage - -#pragma mark Initialization -- (instancetype)initWithThread:(TSThread *)thread { - return [self initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"" attachmentIds:[NSMutableArray new] - expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil]; -} - -#pragma mark Building -- (nullable id)dataMessageBuilder -{ - SSKProtoDataMessageBuilder *builder = super.dataMessageBuilder; - if (builder == nil) { return nil; } - [builder setFlags:SSKProtoDataMessageFlagsUnlinkDevice]; - return builder; -} - -#pragma mark Settings -- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeUnlinkDevice]; } -- (BOOL)shouldSyncTranscript { return NO; } -- (BOOL)shouldBeSaved { return NO; } - -@end diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/MultiDeviceProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/MultiDeviceProtocol.swift deleted file mode 100644 index 9bb881cf6..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Multi Device/MultiDeviceProtocol.swift +++ /dev/null @@ -1,274 +0,0 @@ -import PromiseKit - -// A few notes about making changes in this file: -// -// • Don't use a database transaction if you can avoid it. -// • If you do need to use a database transaction, use a read transaction if possible. -// • For write transactions, consider making it the caller's responsibility to manage the database transaction (this helps avoid unnecessary transactions). -// • Think carefully about adding a function; there might already be one for what you need. -// • Document the expected cases in which a function will be used -// • Express those cases in tests. - -@objc(LKMultiDeviceProtocol) -public final class MultiDeviceProtocol : NSObject { - - /// A mapping from hex encoded public key to date updated. - /// - /// - Note: Should only be accessed from `LokiAPI.workQueue` to avoid race conditions. - public static var lastDeviceLinkUpdate: [String:Date] = [:] - - internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } - - // MARK: Settings - public static let deviceLinkUpdateInterval: TimeInterval = 60 - - // MARK: Multi Device Destination - public struct MultiDeviceDestination : Hashable { - public let publicKey: String - public let isMaster: Bool - } - - // MARK: - General - - @objc(isUnlinkDeviceMessage:) - public static func isUnlinkDeviceMessage(_ dataMessage: SSKProtoDataMessage) -> Bool { - let unlinkDeviceFlag = SSKProtoDataMessage.SSKProtoDataMessageFlags.unlinkDevice - return dataMessage.flags & UInt32(unlinkDeviceFlag.rawValue) != 0 - } - - public static func getUserLinkedDevices() -> Set { - var result: Set = [] - storage.dbReadConnection.read { transaction in - result = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction) - } - return result - } - - @objc public static func isSlaveThread(_ thread: TSThread) -> Bool { - guard let thread = thread as? TSContactThread else { return false } - var isSlaveThread = false - storage.dbReadConnection.read { transaction in - isSlaveThread = storage.getMasterHexEncodedPublicKey(for: thread.contactIdentifier(), in: transaction) != nil - } - return isSlaveThread - } - - // MARK: - Sending (Part 1) - - @objc(isMultiDeviceRequiredForMessage:toPublicKey:) - public static func isMultiDeviceRequired(for message: TSOutgoingMessage, to publicKey: String) -> Bool { - return !(message is DeviceLinkMessage) && !(message is UnlinkDeviceMessage) && (message.thread as? TSGroupThread)?.groupModel.groupType != .openGroup - && !Storage.getUserClosedGroupPublicKeys().contains(publicKey) - } - - private static func copy(_ messageSend: OWSMessageSend, for destination: MultiDeviceDestination, with seal: Resolver) -> OWSMessageSend { - var recipient: SignalRecipient! - storage.dbReadConnection.read { transaction in - recipient = SignalRecipient.getOrBuildUnsavedRecipient(forRecipientId: destination.publicKey, transaction: transaction) - } - // TODO: Why is it okay that the thread, sender certificate, etc. don't get changed? - return OWSMessageSend(message: messageSend.message, thread: messageSend.thread, recipient: recipient, - senderCertificate: messageSend.senderCertificate, udAccess: messageSend.udAccess, localNumber: messageSend.localNumber, success: { - seal.fulfill(()) - }, failure: { error in - seal.reject(error) - }) - } - - private static func sendMessage(_ messageSend: OWSMessageSend, to destination: MultiDeviceDestination, in transaction: YapDatabaseReadTransaction) -> Promise { - let (threadPromise, threadPromiseSeal) = Promise.pending() - if messageSend.message.thread.isGroupThread() { - threadPromiseSeal.fulfill(messageSend.message.thread) - } else if let thread = TSContactThread.getWithContactId(destination.publicKey, transaction: transaction) { - threadPromiseSeal.fulfill(thread) - } else { - Storage.write { transaction in - let thread = TSContactThread.getOrCreateThread(withContactId: destination.publicKey, transaction: transaction) - threadPromiseSeal.fulfill(thread) - } - } - return threadPromise.then2 { thread -> Promise in - let message = messageSend.message - let messageSender = SSKEnvironment.shared.messageSender - let (promise, seal) = Promise.pending() - let messageSendCopy = copy(messageSend, for: destination, with: seal) - OWSDispatch.sendingQueue().async { - messageSender.sendMessage(messageSendCopy) - } - return promise - } - } - - /// See [Multi Device Message Sending](https://github.com/loki-project/session-protocol-docs/wiki/Multi-Device-Message-Sending) for more information. - @objc(sendMessageToDestinationAndLinkedDevices:transaction:) - public static func sendMessageToDestinationAndLinkedDevices(_ messageSend: OWSMessageSend, in transaction: YapDatabaseReadTransaction) { -// if !messageSend.isUDSend && messageSend.recipient.recipientId() != getUserHexEncodedPublicKey() { -// #if DEBUG -// preconditionFailure() -// #endif -// } - let message = messageSend.message - let messageSender = SSKEnvironment.shared.messageSender - if !isMultiDeviceRequired(for: message, to: messageSend.recipient.recipientId()) { - print("[Loki] sendMessageToDestinationAndLinkedDevices(_:in:) invoked for a message that doesn't require multi device routing.") - OWSDispatch.sendingQueue().async { - messageSender.sendMessage(messageSend) - } - return - } - print("[Loki] Sending \(type(of: message)) message using multi device routing.") - let publicKey = messageSend.recipient.recipientId() - getMultiDeviceDestinations(for: publicKey, in: transaction).done2 { destinations in - var promises: [Promise] = [] - let masterDestination = destinations.first { $0.isMaster } - if let masterDestination = masterDestination { - storage.dbReadConnection.read { transaction in - promises.append(sendMessage(messageSend, to: masterDestination, in: transaction)) - } - } - let slaveDestinations = destinations.filter { !$0.isMaster } - slaveDestinations.forEach { slaveDestination in - storage.dbReadConnection.read { transaction in - promises.append(sendMessage(messageSend, to: slaveDestination, in: transaction)) - } - } - when(resolved: promises).done(on: OWSDispatch.sendingQueue()) { results in - let errors = results.compactMap { result -> Error? in - if case Result.rejected(let error) = result { - return error - } else { - return nil - } - } - if errors.isEmpty { - messageSend.success() - } else { - messageSend.failure(errors.first!) - } - } - }.catch2 { error in - // Proceed even if updating the recipient's device links failed, so that message sending - // is independent of whether the file server is online - OWSDispatch.sendingQueue().async { - messageSender.sendMessage(messageSend) - } - } - } - - @objc(updateDeviceLinksIfNeededForPublicKey:transaction:) - public static func updateDeviceLinksIfNeeded(for publicKey: String, in transaction: YapDatabaseReadTransaction) -> AnyPromise { - return AnyPromise.from(getMultiDeviceDestinations(for: publicKey, in: transaction)) - } - - // MARK: - Receiving - - @objc(handleDeviceLinkMessageIfNeeded:wrappedIn:transaction:) - public static func handleDeviceLinkMessageIfNeeded(_ protoContent: SSKProtoContent, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { - let publicKey = envelope.source! // Set during UD decryption - guard let deviceLinkMessage = protoContent.lokiDeviceLinkMessage, let master = deviceLinkMessage.masterPublicKey, - let slave = deviceLinkMessage.slavePublicKey, let slaveSignature = deviceLinkMessage.slaveSignature else { - return print("[Loki] Received an invalid device link message.") - } - let deviceLinkingSession = DeviceLinkingSession.current - if let masterSignature = deviceLinkMessage.masterSignature { // Authorization - print("[Loki] Received a device link authorization from: \(publicKey).") // Intentionally not `master` - if let deviceLinkingSession = deviceLinkingSession { - deviceLinkingSession.processLinkingAuthorization(from: master, for: slave, masterSignature: masterSignature, slaveSignature: slaveSignature) - } else { - print("[Loki] Received a device link authorization without a session; ignoring.") - } - // Set any profile info (the device link authorization also includes the master device's profile info) - if let dataMessage = protoContent.dataMessage { - SessionMetaProtocol.updateDisplayNameIfNeeded(for: master, using: dataMessage, in: transaction) - SessionMetaProtocol.updateProfileKeyIfNeeded(for: master, using: dataMessage) - } - } else { // Request - print("[Loki] Received a device link request from: \(publicKey).") // Intentionally not `slave` - if let deviceLinkingSession = deviceLinkingSession { - deviceLinkingSession.processLinkingRequest(from: slave, to: master, with: slaveSignature) - } else { - NotificationCenter.default.post(name: .unexpectedDeviceLinkRequestReceived, object: nil) - } - } - } - - @objc(handleUnlinkDeviceMessage:wrappedIn:transaction:) - public static func handleUnlinkDeviceMessage(_ dataMessage: SSKProtoDataMessage, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { - let publicKey = envelope.source! // Set during UD decryption - // Check that the request was sent by our master device - let userPublicKey = getUserHexEncodedPublicKey() - guard let userMasterPublicKey = storage.getMasterHexEncodedPublicKey(for: userPublicKey, in: transaction) else { return } - let wasSentByMasterDevice = (userMasterPublicKey == publicKey) - guard wasSentByMasterDevice else { return } - // Ignore the request if we don't know about the device link in question - let masterDeviceLinks = storage.getDeviceLinks(for: userMasterPublicKey, in: transaction) - if !masterDeviceLinks.contains(where: { - $0.master.publicKey == userMasterPublicKey && $0.slave.publicKey == userPublicKey - }) { - return - } - FileServerAPI.getDeviceLinks(associatedWith: userPublicKey).done2 { slaveDeviceLinks in - // Check that the device link IS present on the file server. - // Note that the device link as seen from the master device's perspective has been deleted at this point, but the - // device link as seen from the slave perspective hasn't. - if slaveDeviceLinks.contains(where: { - $0.master.publicKey == userMasterPublicKey && $0.slave.publicKey == userPublicKey - }) { - for deviceLink in slaveDeviceLinks { // In theory there should only be one - FileServerAPI.removeDeviceLink(deviceLink) // Attempt to clean up on the file server - } - UserDefaults.standard[.wasUnlinked] = true - DispatchQueue.main.async { - NotificationCenter.default.post(name: .dataNukeRequested, object: nil) - } - } - } - } -} - -// MARK: - Sending (Part 2) - -// Here (in a non-@objc extension) because it doesn't interoperate well with Obj-C -public extension MultiDeviceProtocol { - - fileprivate static func getMultiDeviceDestinations(for publicKey: String, in transaction: YapDatabaseReadTransaction) -> Promise> { - let (promise, seal) = Promise>.pending() - func getDestinations(in transaction: YapDatabaseReadTransaction? = nil) { - storage.dbReadConnection.read { transaction in - var destinations: Set = [] - let masterPublicKey = storage.getMasterHexEncodedPublicKey(for: publicKey, in: transaction) ?? publicKey - let masterDestination = MultiDeviceDestination(publicKey: masterPublicKey, isMaster: true) - destinations.insert(masterDestination) - let deviceLinks = storage.getDeviceLinks(for: masterPublicKey, in: transaction) - let slaveDestinations = deviceLinks.map { MultiDeviceDestination(publicKey: $0.slave.publicKey, isMaster: false) } - destinations.formUnion(slaveDestinations) - seal.fulfill(destinations) - } - } - let timeSinceLastUpdate: TimeInterval - if let lastDeviceLinkUpdate = lastDeviceLinkUpdate[publicKey] { - timeSinceLastUpdate = Date().timeIntervalSince(lastDeviceLinkUpdate) - } else { - timeSinceLastUpdate = .infinity - } - if timeSinceLastUpdate > deviceLinkUpdateInterval { - let masterPublicKey = storage.getMasterHexEncodedPublicKey(for: publicKey, in: transaction) ?? publicKey - FileServerAPI.getDeviceLinks(associatedWith: masterPublicKey).done2 { _ in - getDestinations() - lastDeviceLinkUpdate[publicKey] = Date() - }.catch2 { error in - if (error as? DotNetAPI.DotNetAPIError) == DotNetAPI.DotNetAPIError.parsingFailed { - // Don't immediately re-fetch in case of failure due to a parsing error - lastDeviceLinkUpdate[publicKey] = Date() - getDestinations() - } else { - print("[Loki] Failed to get device links due to error: \(error).") - seal.reject(error) - } - } - } else { - getDestinations() - } - return promise - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/ClosedGroupParser.swift b/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/ClosedGroupParser.swift deleted file mode 100644 index 3118f569d..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/ClosedGroupParser.swift +++ /dev/null @@ -1,30 +0,0 @@ - -@objc public final class ClosedGroupParser : NSObject { - private let data: Data - - @objc public init(data: Data) { - self.data = data - } - - @objc public func parseGroupModels() -> [TSGroupModel] { - var index = 0 - var result: [TSGroupModel] = [] - while index < data.endIndex { - var uncheckedSize: UInt32? = try? data[index..<(index+4)].withUnsafeBytes { $0.pointee } - if let size = uncheckedSize, size >= data.count, let intermediate = try? data[index..<(index+4)].reversed() { - uncheckedSize = Data(intermediate).withUnsafeBytes { $0.pointee } - } - guard let size = uncheckedSize, size < data.count else { break } - let sizeAsInt = Int(size) - index += 4 - guard index + sizeAsInt <= data.count else { break } - let protoAsData = data[index..<(index+sizeAsInt)] - guard let proto = try? SSKProtoGroupDetails.parseData(protoAsData) else { break } - index += sizeAsInt - var groupModel = TSGroupModel(title: proto.name, memberIds: proto.members, image: nil, - groupId: proto.id, groupType: GroupType.closedGroup, adminIds: proto.admins) - result.append(groupModel) - } - return result - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/ContactParser.swift b/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/ContactParser.swift deleted file mode 100644 index 035bb580f..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/ContactParser.swift +++ /dev/null @@ -1,28 +0,0 @@ - -public final class ContactParser { - private let data: Data - - public init(data: Data) { - self.data = data - } - - public func parse() -> [(publicKey: String, isBlocked: Bool)] { - var index = 0 - var result: [(String, Bool)] = [] - while index < data.endIndex { - var uncheckedSize: UInt32? = try? data[index..<(index+4)].withUnsafeBytes { $0.pointee } - if let size = uncheckedSize, size >= data.count, let intermediate = try? data[index..<(index+4)].reversed() { - uncheckedSize = Data(intermediate).withUnsafeBytes { $0.pointee } - } - guard let size = uncheckedSize, size < data.count else { break } - let sizeAsInt = Int(size) - index += 4 - guard index + sizeAsInt <= data.count else { break } - let protoAsData = data[index..<(index+sizeAsInt)] - guard let proto = try? SSKProtoContactDetails.parseData(protoAsData) else { break } - index += sizeAsInt - result.append((publicKey: proto.number, isBlocked: proto.blocked)) - } - return result - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/LKSyncOpenGroupsMessage.h b/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/LKSyncOpenGroupsMessage.h deleted file mode 100644 index 674c74180..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/LKSyncOpenGroupsMessage.h +++ /dev/null @@ -1,14 +0,0 @@ -#import "OWSOutgoingSyncMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -NS_SWIFT_NAME(SyncOpenGroupsMessage) -@interface LKSyncOpenGroupsMessage : OWSOutgoingSyncMessage - -- (instancetype)init NS_DESIGNATED_INITIALIZER; - -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/LKSyncOpenGroupsMessage.m b/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/LKSyncOpenGroupsMessage.m deleted file mode 100644 index 07930fb81..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/LKSyncOpenGroupsMessage.m +++ /dev/null @@ -1,43 +0,0 @@ -#import "LKSyncOpenGroupsMessage.h" -#import "OWSPrimaryStorage.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation LKSyncOpenGroupsMessage - -- (instancetype)init -{ - return [super init]; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (nullable SSKProtoSyncMessageBuilder *)syncMessageBuilder -{ - NSError *error; - NSMutableArray *openGroupSyncMessages = @[].mutableCopy; - __block NSDictionary *openGroups; - [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - openGroups = [LKDatabaseUtilities getAllPublicChats:transaction]; - }]; - for (LKPublicChat *openGroup in openGroups.allValues) { - SSKProtoSyncMessageOpenGroupDetailsBuilder *openGroupSyncMessageBuilder = [SSKProtoSyncMessageOpenGroupDetails builderWithUrl:openGroup.server channelID:openGroup.channel]; - SSKProtoSyncMessageOpenGroupDetails *_Nullable openGroupSyncMessage = [openGroupSyncMessageBuilder buildAndReturnError:&error]; - if (error || !openGroupSyncMessage) { - OWSFailDebug(@"Couldn't build protobuf due to error: %@.", error); - return nil; - } - [openGroupSyncMessages addObject:openGroupSyncMessage]; - } - SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; - [syncMessageBuilder setOpenGroups:openGroupSyncMessages]; - return syncMessageBuilder; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/SyncMessagesProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/SyncMessagesProtocol.swift deleted file mode 100644 index 00e9096c4..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Shelved/Sync Messages/SyncMessagesProtocol.swift +++ /dev/null @@ -1,297 +0,0 @@ -import PromiseKit - -// A few notes about making changes in this file: -// -// • Don't use a database transaction if you can avoid it. -// • If you do need to use a database transaction, use a read transaction if possible. -// • For write transactions, consider making it the caller's responsibility to manage the database transaction (this helps avoid unnecessary transactions). -// • Think carefully about adding a function; there might already be one for what you need. -// • Document the expected cases in which a function will be used -// • Express those cases in tests. - -@objc(LKSyncMessagesProtocol) -public final class SyncMessagesProtocol : NSObject { - - /// Only ever modified from the message processing queue (`OWSBatchMessageProcessor.processingQueue`). - private static var syncMessageTimestamps: [String:Set] = [:] - - internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } - - // MARK: - Error - - @objc(LKSyncMessagesProtocolError) - public class SyncMessagesProtocolError : NSError { // Not called `Error` for Obj-C interoperablity - - @objc public static let privateKeyMissing = SyncMessagesProtocolError(domain: "SyncMessagesProtocolErrorDomain", code: 1, userInfo: [ NSLocalizedDescriptionKey : "Couldn't get private key for SSK based closed group." ]) - } - - // MARK: - Sending - - @objc public static func syncProfile() { - Storage.writeSync { transaction in - let userPublicKey = getUserHexEncodedPublicKey() - let userLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: userPublicKey, in: transaction) - for device in userLinkedDevices { - guard device != userPublicKey else { continue } - let thread = TSContactThread.getOrCreateThread(withContactId: device, transaction: transaction) - thread.save(with: transaction) - let syncMessage = OWSOutgoingSyncMessage(in: thread, messageBody: "", attachmentId: nil) - syncMessage.save(with: transaction) - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - messageSenderJobQueue.add(message: syncMessage, transaction: transaction) - } - } - } - - @objc(syncContactWithPublicKey:) - public static func syncContact(_ publicKey: String) -> AnyPromise { - let syncManager = SSKEnvironment.shared.syncManager - return syncManager.syncContacts(for: [ SignalAccount(recipientId: publicKey) ]) - } - - private static func getContactsToSync(using transaction: YapDatabaseReadTransaction) -> Set { - return Set(TSContactThread.allObjectsInCollection().compactMap { $0 as? TSContactThread } - .filter { $0.shouldThreadBeVisible } - .map { $0.contactIdentifier() } - .filter { ECKeyPair.isValidHexEncodedPublicKey(candidate: $0) } - .filter { storage.getMasterHexEncodedPublicKey(for: $0, in: transaction) == nil } // Exclude secondary devices - .filter { !LokiDatabaseUtilities.isUserLinkedDevice($0, transaction: transaction) }) - } - - @objc public static func syncAllContacts() -> AnyPromise { - var publicKeys: [String] = [] - storage.dbReadConnection.read { transaction in - publicKeys = [String](getContactsToSync(using: transaction)) - } - let accounts = Set(publicKeys).map { SignalAccount(recipientId: $0) } - let syncManager = SSKEnvironment.shared.syncManager - let promises = accounts.chunked(by: 3).map { accounts -> Promise in // TODO: Does this always fit? - return Promise(syncManager.syncContacts(for: accounts)).map2 { _ in } - } - return AnyPromise.from(when(fulfilled: promises)) - } - - @objc(syncClosedGroup:transaction:) - public static func syncClosedGroup(_ thread: TSGroupThread, using transaction: YapDatabaseReadWriteTransaction) -> AnyPromise { - // Prepare - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - let group = thread.groupModel - let groupPublicKey = LKGroupUtilities.getDecodedGroupID(group.groupId) - let name = group.groupName! - let members = group.groupMemberIds.map { Data(hex: $0) } - let admins = group.groupAdminIds.map { Data(hex: $0) } - guard let groupPrivateKey = Storage.getClosedGroupPrivateKey(for: groupPublicKey) else { - print("[Loki] Couldn't get private key for SSK based closed group.") - return AnyPromise.from(Promise(error: SyncMessagesProtocolError.privateKeyMissing)) - } - // Generate ratchets for the user's linked devices - let userPublicKey = getUserHexEncodedPublicKey() - let masterPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? userPublicKey - let deviceLinks = storage.getDeviceLinks(for: masterPublicKey, in: transaction) - let linkedDevices = deviceLinks.flatMap { [ $0.master.publicKey, $0.slave.publicKey ] }.filter { $0 != userPublicKey } - let senderKeys: [ClosedGroupSenderKey] = linkedDevices.map { publicKey in - let ratchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: publicKey, using: transaction) - return ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: Data(hex: publicKey)) - } - // Send a closed group update message to the existing members with the linked devices' ratchets (this message is aimed at the group) - func sendMessageToGroup() { - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: senderKeys, - members: members, admins: admins) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) - } - sendMessageToGroup() - // Send closed group update messages to the linked devices using established channels - func sendMessageToLinkedDevices() { - var allSenderKeys = Storage.getAllClosedGroupSenderKeys(for: groupPublicKey) - allSenderKeys.formUnion(senderKeys) - let thread = TSContactThread.getOrCreateThread(withContactId: masterPublicKey, transaction: transaction) - thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, - groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: [ClosedGroupSenderKey](allSenderKeys), members: members, admins: admins) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // This internally takes care of multi device - } - sendMessageToLinkedDevices() - // Return a dummy promise - return AnyPromise.from(Promise { $0.fulfill(()) }) - } - - @objc public static func syncAllClosedGroups() -> AnyPromise { - var closedGroups: [TSGroupThread] = [] - TSGroupThread.enumerateCollectionObjects { object, _ in - guard let closedGroup = object as? TSGroupThread, closedGroup.groupModel.groupType == .closedGroup, - closedGroup.shouldThreadBeVisible else { return } - closedGroups.append(closedGroup) - } - let syncManager = SSKEnvironment.shared.syncManager - let promises = closedGroups.map { group -> Promise in - return Promise(syncManager.syncGroup(for: group)).map2 { _ in } - } - return AnyPromise.from(when(fulfilled: promises)) - } - - @objc public static func syncAllOpenGroups() -> AnyPromise { - let openGroupSyncMessage = SyncOpenGroupsMessage() - let (promise, seal) = Promise.pending() - let messageSender = SSKEnvironment.shared.messageSender - messageSender.send(openGroupSyncMessage, success: { - seal.fulfill(()) - }, failure: { error in - seal.reject(error) - }) - return AnyPromise.from(promise) - } - - // MARK: - Receiving - - @objc(isValidSyncMessage:transaction:) - public static func isValidSyncMessage(_ envelope: SSKProtoEnvelope, transaction: YapDatabaseReadTransaction) -> Bool { - let publicKey = envelope.source! // Set during UD decryption - return LokiDatabaseUtilities.isUserLinkedDevice(publicKey, transaction: transaction) - } - - public static func dropFromSyncMessageTimestampCache(_ timestamp: UInt64, for publicKey: String) { - var timestamps: Set = syncMessageTimestamps[publicKey] ?? [] - if timestamps.contains(timestamp) { timestamps.remove(timestamp) } - syncMessageTimestamps[publicKey] = timestamps - } - - @objc(isDuplicateSyncMessage:fromPublicKey:) - public static func isDuplicateSyncMessage(_ protoContent: SSKProtoContent, from publicKey: String) -> Bool { - guard let syncMessage = protoContent.syncMessage?.sent else { return false } - var timestamps: Set = syncMessageTimestamps[publicKey] ?? [] - let hasTimestamp = syncMessage.timestamp != 0 - guard hasTimestamp else { return false } - let result = timestamps.contains(syncMessage.timestamp) - timestamps.insert(syncMessage.timestamp) - syncMessageTimestamps[publicKey] = timestamps - return result - } - - @objc(updateProfileFromSyncMessageIfNeeded:wrappedIn:transaction:) - public static func updateProfileFromSyncMessageIfNeeded(_ dataMessage: SSKProtoDataMessage, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { - let publicKey = envelope.source! // Set during UD decryption - guard let userMasterPublicKey = storage.getMasterHexEncodedPublicKey(for: getUserHexEncodedPublicKey(), in: transaction) else { return } - let wasSentByMasterDevice = (userMasterPublicKey == publicKey) - guard wasSentByMasterDevice else { return } - SessionMetaProtocol.updateDisplayNameIfNeeded(for: userMasterPublicKey, using: dataMessage, in: transaction) - SessionMetaProtocol.updateProfileKeyIfNeeded(for: userMasterPublicKey, using: dataMessage) - } - - /// - Note: Deprecated. - @objc(handleClosedGroupUpdateSyncMessageIfNeeded:wrappedIn:transaction:) - public static func handleClosedGroupUpdateSyncMessageIfNeeded(_ transcript: OWSIncomingSentMessageTranscript, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { - // Check preconditions - let publicKey = envelope.source! // Set during UD decryption - let userLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction) - let wasSentByLinkedDevice = userLinkedDevices.contains(publicKey) - guard wasSentByLinkedDevice, let group = transcript.dataMessage.group, let name = group.name else { return } - // Create or update the group - let id = group.id - let members = group.members - let newGroupThread = TSGroupThread.getOrCreateThread(withGroupId: id, groupType: .closedGroup, transaction: transaction) - let newGroupModel = TSGroupModel(title: name, memberIds: members, image: nil, groupId: id, groupType: .closedGroup, adminIds: group.admins) - newGroupThread.save(with: transaction) - newGroupThread.setGroupModel(newGroupModel, with: transaction) - OWSDisappearingMessagesJob.shared().becomeConsistent(withDisappearingDuration: transcript.dataMessage.expireTimer, thread: newGroupThread, createdByRemoteRecipientId: nil, createdInExistingGroup: true, transaction: transaction) - // Try to establish sessions with all members for which none exists yet when a group is created or updated - ClosedGroupsProtocol.establishSessionsIfNeeded(with: members, using: transaction) - // Notify the user - let contactsManager = SSKEnvironment.shared.contactsManager - let infoMessageText = newGroupThread.groupModel.getInfoStringAboutUpdate(to: newGroupModel, contactsManager: contactsManager) - let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: newGroupThread, messageType: .typeGroupUpdate, customMessage: infoMessageText) - infoMessage.save(with: transaction) - } - - /// - Note: Deprecated. - @objc(handleClosedGroupQuitSyncMessageIfNeeded:wrappedIn:transaction:) - public static func handleClosedGroupQuitSyncMessageIfNeeded(_ transcript: OWSIncomingSentMessageTranscript, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { - // Check preconditions - let publicKey = envelope.source! // Set during UD decryption - let userLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction) - let wasSentByLinkedDevice = userLinkedDevices.contains(publicKey) - guard wasSentByLinkedDevice, let group = transcript.dataMessage.group else { return } - // Leave the group - let groupThread = TSGroupThread.getOrCreateThread(withGroupId: group.id, groupType: .closedGroup, transaction: transaction) - groupThread.save(with: transaction) - groupThread.leaveGroup(with: transaction) - // Notify the user - let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: groupThread, messageType: .typeGroupQuit, customMessage: NSLocalizedString("GROUP_YOU_LEFT", comment: "")) - infoMessage.save(with: transaction) - } - - @objc(handleContactSyncMessageIfNeeded:wrappedIn:transaction:) - public static func handleContactSyncMessageIfNeeded(_ syncMessage: SSKProtoSyncMessage, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { - let publicKey = envelope.source! // Set during UD decryption - let userLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction) - let wasSentByLinkedDevice = userLinkedDevices.contains(publicKey) - guard wasSentByLinkedDevice, let contacts = syncMessage.contacts, let contactsAsData = contacts.data, !contactsAsData.isEmpty else { return } - print("[Loki] Contact sync message received.") - handleContactSyncMessageData(contactsAsData, using: transaction) - } - - public static func handleContactSyncMessageData(_ data: Data, using transaction: YapDatabaseReadWriteTransaction) { - let parser = ContactParser(data: data) - let tuples = parser.parse() - let blockedPublicKeys = tuples.filter { $0.isBlocked }.map { $0.publicKey } - let userPublicKey = getUserHexEncodedPublicKey() - let userLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: userPublicKey, in: transaction) - // Try to establish sessions - for (publicKey, isBlocked) in tuples { - guard !userLinkedDevices.contains(publicKey) else { continue } // Skip self and linked devices - let thread = TSContactThread.getOrCreateThread(withContactId: publicKey, transaction: transaction) - thread.shouldThreadBeVisible = true - thread.save(with: transaction) - if !isBlocked { - SessionManagementProtocol.sendSessionRequestIfNeeded(to: publicKey, using: transaction) - } - } - // Update the blocked contacts list - transaction.addCompletionQueue(DispatchQueue.main) { - SSKEnvironment.shared.blockingManager.setBlockedPhoneNumbers(blockedPublicKeys, sendSyncMessage: false) - NotificationCenter.default.post(name: .blockedContactsUpdated, object: nil) - } - } - - /// - Note: Deprecated. - @objc(handleClosedGroupSyncMessageIfNeeded:wrappedIn:transaction:) - public static func handleClosedGroupSyncMessageIfNeeded(_ syncMessage: SSKProtoSyncMessage, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { - let publicKey = envelope.source! // Set during UD decryption - let userLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction) - let wasSentByLinkedDevice = userLinkedDevices.contains(publicKey) - guard wasSentByLinkedDevice, let groups = syncMessage.groups, let groupsAsData = groups.data, !groupsAsData.isEmpty else { return } - print("[Loki] Closed group sync message received.") - let parser = ClosedGroupParser(data: groupsAsData) - let closedGroups = parser.parseGroupModels() - for closedGroup in closedGroups { - var thread: TSGroupThread! = TSGroupThread(groupId: closedGroup.groupId, transaction: transaction) - if thread == nil { - thread = TSGroupThread.getOrCreateThread(with: closedGroup, transaction: transaction) - thread.shouldThreadBeVisible = true - thread.save(with: transaction) - } - ClosedGroupsProtocol.establishSessionsIfNeeded(with: closedGroup.groupMemberIds, using: transaction) - } - } - - @objc(handleOpenGroupSyncMessageIfNeeded:wrappedIn:transaction:) - public static func handleOpenGroupSyncMessageIfNeeded(_ syncMessage: SSKProtoSyncMessage, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { - let publicKey = envelope.source! // Set during UD decryption - let userLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction) - let wasSentByLinkedDevice = userLinkedDevices.contains(publicKey) - guard wasSentByLinkedDevice else { return } - let openGroups = syncMessage.openGroups - guard !openGroups.isEmpty else { return } - print("[Loki] Open group sync message received.") - let openGroupManager = PublicChatManager.shared - let userPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? getUserHexEncodedPublicKey() - let userDisplayName = SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: userPublicKey, transaction: transaction) - for openGroup in openGroups { - guard openGroupManager.getChat(server: openGroup.url, channel: openGroup.channelID) == nil else { return } - openGroupManager.addChat(server: openGroup.url, channel: openGroup.channelID) - PublicChatAPI.setDisplayName(to: userDisplayName, on: openGroup.url) - // TODO: Should we also set the profile picture here? - } - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Utilities/GroupUtilities.swift b/SignalServiceKit/src/Loki/Protocol/Utilities/GroupUtilities.swift deleted file mode 100644 index 69b5f9ff1..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Utilities/GroupUtilities.swift +++ /dev/null @@ -1,25 +0,0 @@ - -public enum GroupUtilities { - - public static func getClosedGroupMembers(_ closedGroup: TSGroupThread) -> [String] { - var result: [String]! - OWSPrimaryStorage.shared().dbReadConnection.read { transaction in - result = getClosedGroupMembers(closedGroup, with: transaction) - } - return result - } - - public static func getClosedGroupMembers(_ closedGroup: TSGroupThread, with transaction: YapDatabaseReadTransaction) -> [String] { - return closedGroup.groupModel.groupMemberIds.filter { member in - OWSPrimaryStorage.shared().getMasterHexEncodedPublicKey(for: member, in: transaction) == nil // Don't show slave devices - } - } - - public static func getClosedGroupMemberCount(_ closedGroup: TSGroupThread) -> Int { - return getClosedGroupMembers(closedGroup).count - } - - public static func getClosedGroupMemberCount(_ closedGroup: TSGroupThread, with transaction: YapDatabaseReadTransaction) -> Int { - return getClosedGroupMembers(closedGroup, with: transaction).count - } -} diff --git a/SignalServiceKit/src/Loki/Protocol/Utilities/LKGroupUtilities.h b/SignalServiceKit/src/Loki/Protocol/Utilities/LKGroupUtilities.h deleted file mode 100644 index fc5947699..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Utilities/LKGroupUtilities.h +++ /dev/null @@ -1,24 +0,0 @@ -NS_ASSUME_NONNULL_BEGIN - -@interface LKGroupUtilities : NSObject - -+(NSString *)getEncodedOpenGroupID:(NSString *)groupID; -+(NSData *)getEncodedOpenGroupIDAsData:(NSString *)groupID; - -+(NSString *)getEncodedRSSFeedID:(NSString *)groupID; -+(NSData *)getEncodedRSSFeedIDAsData:(NSString *)groupID; - -+(NSString *)getEncodedClosedGroupID:(NSString *)groupID; -+(NSData *)getEncodedClosedGroupIDAsData:(NSString *)groupID; - -+(NSString *)getEncodedMMSGroupID:(NSString *)groupID; -+(NSData *)getEncodedMMSGroupIDAsData:(NSString *)groupID; - -+(NSString *)getEncodedGroupID:(NSData *)groupID; - -+(NSString *)getDecodedGroupID:(NSData *)groupID; -+(NSData *)getDecodedGroupIDAsData:(NSData *)groupID; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Loki/Protocol/Utilities/LKGroupUtilities.m b/SignalServiceKit/src/Loki/Protocol/Utilities/LKGroupUtilities.m deleted file mode 100644 index 38cdca4c6..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Utilities/LKGroupUtilities.m +++ /dev/null @@ -1,76 +0,0 @@ -#import "LKGroupUtilities.h" - -@implementation LKGroupUtilities - -#define ClosedGroupPrefix @"__textsecure_group__!" // a.k.a. private group chat -#define MMSGroupPrefix @"__signal_mms_group__!" -#define OpenGroupPrefix @"__loki_public_chat_group__!" // a.k.a. public group chat -#define RSSFeedPrefix @"__loki_rss_feed_group__!" - -+(NSString *)getEncodedOpenGroupID:(NSString *)groupID -{ - return [OpenGroupPrefix stringByAppendingString:groupID]; -} - -+(NSData *)getEncodedOpenGroupIDAsData:(NSString *)groupID -{ - return [[OpenGroupPrefix stringByAppendingString:groupID] dataUsingEncoding:NSUTF8StringEncoding]; -} - -+(NSString *)getEncodedRSSFeedID:(NSString *)groupID -{ - return [RSSFeedPrefix stringByAppendingString:groupID]; -} - -+(NSData *)getEncodedRSSFeedIDAsData:(NSString *)groupID -{ - return [[RSSFeedPrefix stringByAppendingString:groupID] dataUsingEncoding:NSUTF8StringEncoding]; -} - -+(NSString *)getEncodedClosedGroupID:(NSString *)groupID -{ - return [ClosedGroupPrefix stringByAppendingString:groupID]; -} - -+(NSData *)getEncodedClosedGroupIDAsData:(NSString *)groupID -{ - return [[ClosedGroupPrefix stringByAppendingString:groupID] dataUsingEncoding:NSUTF8StringEncoding]; -} - -+(NSString *)getEncodedMMSGroupID:(NSString *)groupID -{ - return [MMSGroupPrefix stringByAppendingString:groupID]; -} - -+(NSData *)getEncodedMMSGroupIDAsData:(NSString *)groupID -{ - return [[MMSGroupPrefix stringByAppendingString:groupID] dataUsingEncoding:NSUTF8StringEncoding]; -} - -+(NSString *)getEncodedGroupID: (NSData *)groupID -{ - return [[NSString alloc] initWithData:groupID encoding:NSUTF8StringEncoding]; -} - -+(NSString *)getDecodedGroupID:(NSData *)groupID -{ - OWSAssertDebug(groupID.length > 0); - NSString *encodedGroupID = [[NSString alloc] initWithData:groupID encoding:NSUTF8StringEncoding]; - if ([encodedGroupID componentsSeparatedByString:@"!"].count > 1) { - return [encodedGroupID componentsSeparatedByString:@"!"][1]; - } - return [encodedGroupID componentsSeparatedByString:@"!"][0]; -} - -+(NSData *)getDecodedGroupIDAsData:(NSData *)groupID -{ - OWSAssertDebug(groupID.length > 0); - NSString *encodedGroupID = [[NSString alloc]initWithData:groupID encoding:NSUTF8StringEncoding]; - NSString *decodedGroupID = [encodedGroupID componentsSeparatedByString:@"!"][0]; - if ([encodedGroupID componentsSeparatedByString:@"!"].count > 1) { - decodedGroupID = [encodedGroupID componentsSeparatedByString:@"!"][1]; - } - return [decodedGroupID dataUsingEncoding:NSUTF8StringEncoding]; -} - -@end diff --git a/SignalServiceKit/src/Loki/Protocol/Utilities/TTLUtilities.swift b/SignalServiceKit/src/Loki/Protocol/Utilities/TTLUtilities.swift deleted file mode 100644 index ebe8219db..000000000 --- a/SignalServiceKit/src/Loki/Protocol/Utilities/TTLUtilities.swift +++ /dev/null @@ -1,32 +0,0 @@ - -@objc(LKTTLUtilities) -public final class TTLUtilities : NSObject { - - /// If a message type specifies an invalid TTL, this will be used. - public static let fallbackMessageTTL: UInt64 = 2 * kDayInMs - - @objc(LKMessageType) - public enum MessageType : Int { - // Unimportant control messages - case call, typingIndicator - // Somewhat important control messages - case linkDevice - // Important control messages - case closedGroupUpdate, disappearingMessagesConfiguration, ephemeral, profileKey, receipt, sessionRequest, sync, unlinkDevice - // Visible messages - case regular - } - - @objc public static func getTTL(for messageType: MessageType) -> UInt64 { - switch messageType { - // Unimportant control messages - case .call, .typingIndicator: return 1 * kMinuteInMs - // Somewhat important control messages - case .linkDevice: return 1 * kHourInMs - // Important control messages - case .closedGroupUpdate, .disappearingMessagesConfiguration, .ephemeral, .profileKey, .receipt, .sessionRequest, .sync, .unlinkDevice: return 2 * kDayInMs - 1 * kHourInMs - // Visible messages - case .regular: return 2 * kDayInMs - } - } -} diff --git a/SignalServiceKit/src/Loki/Push Notifications/LokiPushNotificationManager.swift b/SignalServiceKit/src/Loki/Push Notifications/LokiPushNotificationManager.swift deleted file mode 100644 index 253e63be7..000000000 --- a/SignalServiceKit/src/Loki/Push Notifications/LokiPushNotificationManager.swift +++ /dev/null @@ -1,153 +0,0 @@ -import PromiseKit - -@objc(LKPushNotificationManager) -public final class LokiPushNotificationManager : NSObject { - - // MARK: Settings - #if DEBUG - private static let server = "https://live.apns.getsession.org" - #else - private static let server = "https://live.apns.getsession.org" - #endif - internal static let pnServerPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" - private static let maxRetryCount: UInt = 4 - private static let tokenExpirationInterval: TimeInterval = 12 * 60 * 60 - - public enum ClosedGroupOperation: String { - case subscribe = "subscribe_closed_group" - case unsubscribe = "unsubscribe_closed_group" - } - - // MARK: Initialization - private override init() { } - - // MARK: Registration - /// Unregisters the user from push notifications. Only the user's device token is needed for this. - static func unregister(with token: Data, isForcedUpdate: Bool) -> Promise { - let hexEncodedToken = token.toHexString() - let parameters = [ "token" : hexEncodedToken ] - let url = URL(string: "\(server)/unregister")! - let request = TSRequest(url: url, method: "POST", parameters: parameters) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json" ] - let promise: Promise = attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global()) { - OnionRequestAPI.sendOnionRequest(request, to: server, using: pnServerPublicKey).map2 { response in - guard let json = response["body"] as? JSON else { - return print("[Loki] Couldn't unregister from push notifications.") - } - guard json["code"] as? Int != 0 else { - return print("[Loki] Couldn't unregister from push notifications due to error: \(json["message"] as? String ?? "nil").") - } - } - } - promise.catch2 { error in - print("[Loki] Couldn't unregister from push notifications.") - } - // Unsubscribe from all closed groups - Storage.getUserClosedGroupPublicKeys().forEach { closedGroup in - performOperation(.unsubscribe, for: closedGroup, publicKey: getUserHexEncodedPublicKey()) - } - return promise - } - - /// Unregisters the user from push notifications. Only the user's device token is needed for this. - @objc(unregisterWithToken:isForcedUpdate:) - static func objc_unregister(with token: Data, isForcedUpdate: Bool) -> AnyPromise { - return AnyPromise.from(unregister(with: token, isForcedUpdate: isForcedUpdate)) - } - - /// Registers the user for push notifications. Requires the user's device - /// token and their Session ID. - static func register(with token: Data, publicKey: String, isForcedUpdate: Bool) -> Promise { - let hexEncodedToken = token.toHexString() - let userDefaults = UserDefaults.standard - let oldToken = userDefaults[.deviceToken] - let lastUploadTime = userDefaults[.lastDeviceTokenUpload] - let now = Date().timeIntervalSince1970 - guard isForcedUpdate || hexEncodedToken != oldToken || now - lastUploadTime > tokenExpirationInterval else { - print("[Loki] Device token hasn't changed or expired; no need to re-upload.") - return Promise { $0.fulfill(()) } - } - let parameters = [ "token" : hexEncodedToken, "pubKey" : publicKey] - let url = URL(string: "\(server)/register")! - let request = TSRequest(url: url, method: "POST", parameters: parameters) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json" ] - let promise: Promise = attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global()) { - OnionRequestAPI.sendOnionRequest(request, to: server, using: pnServerPublicKey).map2 { response in - guard let json = response["body"] as? JSON else { - return print("[Loki] Couldn't register device token.") - } - guard json["code"] as? Int != 0 else { - return print("[Loki] Couldn't register device token due to error: \(json["message"] as? String ?? "nil").") - } - userDefaults[.deviceToken] = hexEncodedToken - userDefaults[.lastDeviceTokenUpload] = now - userDefaults[.isUsingFullAPNs] = true - } - } - promise.catch2 { error in - print("[Loki] Couldn't register device token.") - } - // Subscribe to all closed groups - Storage.getUserClosedGroupPublicKeys().forEach { closedGroup in - performOperation(.subscribe, for: closedGroup, publicKey: publicKey) - } - return promise - } - - /// Registers the user for push notifications. Requires the user's device - /// token and their Session ID. - @objc(registerWithToken:hexEncodedPublicKey:isForcedUpdate:) - static func objc_register(with token: Data, publicKey: String, isForcedUpdate: Bool) -> AnyPromise { - return AnyPromise.from(register(with: token, publicKey: publicKey, isForcedUpdate: isForcedUpdate)) - } - - static func performOperation(_ operation: ClosedGroupOperation, for closedGroupPublicKey: String, publicKey: String) -> Promise { - let isUsingFullAPNs = UserDefaults.standard[.isUsingFullAPNs] - guard isUsingFullAPNs else { return Promise { $0.fulfill(()) } } - let parameters = [ "closedGroupPublicKey" : closedGroupPublicKey, "pubKey" : publicKey] - let url = URL(string: "\(server)/\(operation.rawValue)")! - let request = TSRequest(url: url, method: "POST", parameters: parameters) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json" ] - let promise: Promise = attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global()) { - OnionRequestAPI.sendOnionRequest(request, to: server, using: pnServerPublicKey).map2 { response in - guard let json = response["body"] as? JSON else { - return print("[Loki] Couldn't subscribe/unsubscribe closed group: \(closedGroupPublicKey).") - } - guard json["code"] as? Int != 0 else { - return print("[Loki] Couldn't subscribe/unsubscribe for closed group: \(closedGroupPublicKey) due to error: \(json["message"] as? String ?? "nil").") - } - } - } - promise.catch2 { error in - print("[Loki] Couldn't subscribe/unsubscribe closed group: \(closedGroupPublicKey).") - } - return promise - } - - static func notify(for signalMessage: SignalMessage) -> Promise { - let message = LokiMessage.from(signalMessage: signalMessage)! - let parameters = [ "data" : message.data.description, "send_to" : message.recipientPublicKey] - let url = URL(string: "\(server)/notify")! - let request = TSRequest(url: url, method: "POST", parameters: parameters) - request.allHTTPHeaderFields = [ "Content-Type" : "application/json" ] - let promise: Promise = attempt(maxRetryCount: maxRetryCount, recoveringOn: DispatchQueue.global()) { - OnionRequestAPI.sendOnionRequest(request, to: server, using: pnServerPublicKey).map2 { response in - guard let json = response["body"] as? JSON else { - return print("[Loki] Couldn't notify PN server.") - } - guard json["code"] as? Int != 0 else { - return print("[Loki] Couldn't notify PN server due to error: \(json["message"] as? String ?? "nil").") - } - } - } - promise.catch2 { error in - print("[Loki] Couldn't notify PN server.") - } - return promise - } - - @objc(notifyForMessage:) - static func objc_notify(for signalMessage: SignalMessage) -> AnyPromise { - return AnyPromise.from(notify(for: signalMessage)) - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/AnyPromise+Conversion.swift b/SignalServiceKit/src/Loki/Utilities/AnyPromise+Conversion.swift deleted file mode 100644 index 1c15fc554..000000000 --- a/SignalServiceKit/src/Loki/Utilities/AnyPromise+Conversion.swift +++ /dev/null @@ -1,10 +0,0 @@ -import PromiseKit - -public extension AnyPromise { - - public static func from(_ promise: Promise) -> AnyPromise { - let result = AnyPromise(promise) - result.retainUntilComplete() - return result - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/Array+Description.swift b/SignalServiceKit/src/Loki/Utilities/Array+Description.swift deleted file mode 100644 index 3d58476dc..000000000 --- a/SignalServiceKit/src/Loki/Utilities/Array+Description.swift +++ /dev/null @@ -1,7 +0,0 @@ - -public extension Array where Element : CustomStringConvertible { - - public var prettifiedDescription: String { - return "[ " + map { $0.description }.joined(separator: ", ") + " ]" - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/BuildConfiguration.swift b/SignalServiceKit/src/Loki/Utilities/BuildConfiguration.swift deleted file mode 100644 index 807a3ab42..000000000 --- a/SignalServiceKit/src/Loki/Utilities/BuildConfiguration.swift +++ /dev/null @@ -1,21 +0,0 @@ - -public enum BuildConfiguration : String, CustomStringConvertible { - case debug, production - - public static let current: BuildConfiguration = { - #if DEBUG - return .debug - #else - return .production - #endif - }() - - public var description: String { return rawValue } -} - -@objc public final class LKBuildConfiguration : NSObject { - - override private init() { } - - @objc public static var current: String { return BuildConfiguration.current.description } -} diff --git a/SignalServiceKit/src/Loki/Utilities/Data+SecureRandom.swift b/SignalServiceKit/src/Loki/Utilities/Data+SecureRandom.swift deleted file mode 100644 index e520e4553..000000000 --- a/SignalServiceKit/src/Loki/Utilities/Data+SecureRandom.swift +++ /dev/null @@ -1,12 +0,0 @@ - -public extension Data { - - /// Returns `size` bytes of random data generated using the default secure random number generator. See - /// [SecRandomCopyBytes](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes) for more information. - public static func getSecureRandomData(ofSize size: UInt) -> Data? { - var data = Data(count: Int(size)) - let result = data.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, Int(size), $0.baseAddress!) } - guard result == errSecSuccess else { return nil } - return data - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/Data+Streaming.swift b/SignalServiceKit/src/Loki/Utilities/Data+Streaming.swift deleted file mode 100644 index 745dc3792..000000000 --- a/SignalServiceKit/src/Loki/Utilities/Data+Streaming.swift +++ /dev/null @@ -1,22 +0,0 @@ - -extension Data { - - init(from inputStream: InputStream) throws { - self.init() - inputStream.open() - defer { inputStream.close() } - let bufferSize = 1024 - let buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) - defer { buffer.deallocate() } - while inputStream.hasBytesAvailable { - let count = inputStream.read(buffer, maxLength: bufferSize) - if count < 0 { - throw inputStream.streamError! - } else if count == 0 { - break - } else { - append(buffer, count: count) - } - } - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/Debugging.swift b/SignalServiceKit/src/Loki/Utilities/Debugging.swift deleted file mode 100644 index 87a709f44..000000000 --- a/SignalServiceKit/src/Loki/Utilities/Debugging.swift +++ /dev/null @@ -1,12 +0,0 @@ - -// For some reason NSLog doesn't seem to work from SignalServiceKit. This is a workaround to still allow debugging from Obj-C. - -@objc(LKLogger) -public final class ObjC_Logger : NSObject { - - private override init() { } - - @objc public static func print(_ message: String) { - Swift.print(message) - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/Dictionary+Description.swift b/SignalServiceKit/src/Loki/Utilities/Dictionary+Description.swift deleted file mode 100644 index c7aedbfe6..000000000 --- a/SignalServiceKit/src/Loki/Utilities/Dictionary+Description.swift +++ /dev/null @@ -1,13 +0,0 @@ - -public extension Dictionary { - - public var prettifiedDescription: String { - return "[ " + map { key, value in - let keyDescription = String(describing: key) - let valueDescription = String(describing: value) - let maxLength = 20 - let truncatedValueDescription = valueDescription.count > maxLength ? valueDescription.prefix(maxLength) + "..." : valueDescription - return keyDescription + " : " + truncatedValueDescription - }.joined(separator: ", ") + " ]" - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities.swift b/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities.swift deleted file mode 100644 index f4866b5be..000000000 --- a/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities.swift +++ /dev/null @@ -1,68 +0,0 @@ - -@objc(LKUserDisplayNameUtilities) -public final class UserDisplayNameUtilities : NSObject { - - override private init() { } - - private static var userPublicKey: String { - return getUserHexEncodedPublicKey() - } - - private static var userDisplayName: String? { - return SSKEnvironment.shared.profileManager.localProfileName() - } - - // MARK: Sessions - @objc(getPrivateChatDisplayNameAvoidWriteTransaction:) - public static func getPrivateChatDisplayNameAvoidingWriteTransaction(for publicKey: String) -> String? { - if publicKey == userPublicKey { - return userDisplayName - } else { - return SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: publicKey, avoidingWriteTransaction: true) - } - } - - @objc public static func getPrivateChatDisplayName(for publicKey: String) -> String? { - if publicKey == userPublicKey { - return userDisplayName - } else { - return SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: publicKey) - } - } - - // MARK: Open Groups - @objc public static func getPublicChatDisplayName(for publicKey: String, in channel: UInt64, on server: String) -> String? { - var result: String? - OWSPrimaryStorage.shared().dbReadConnection.read { transaction in - result = getPublicChatDisplayName(for: publicKey, in: channel, on: server, using: transaction) - } - return result - } - - @objc public static func getPublicChatDisplayName(for publicKey: String, in channel: UInt64, on server: String, using transaction: YapDatabaseReadTransaction) -> String? { - if publicKey == userPublicKey { - return userDisplayName - } else { - let collection = "\(server).\(channel)" - return transaction.object(forKey: publicKey, inCollection: collection) as! String? - } - } -} - -@objc(LKGroupDisplayNameUtilities) -public final class GroupDisplayNameUtilities : NSObject { - - override private init() { } - - // MARK: Closed Groups - @objc public static func getDefaultDisplayName(for group: TSGroupThread) -> String { - let members = group.groupModel.groupMemberIds - let displayNames = members.map { hexEncodedPublicKey -> String in - guard let displayName = UserDisplayNameUtilities.getPrivateChatDisplayName(for: hexEncodedPublicKey) else { return hexEncodedPublicKey } - let regex = try! NSRegularExpression(pattern: ".* \\(\\.\\.\\.[0-9a-fA-F]*\\)") - guard regex.hasMatch(input: displayName) else { return displayName } - return String(displayName[displayName.startIndex..<(displayName.index(displayName.endIndex, offsetBy: -14))]) - }.sorted() - return displayNames.joined(separator: ", ") - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities2.swift b/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities2.swift deleted file mode 100644 index e5ed3c41e..000000000 --- a/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities2.swift +++ /dev/null @@ -1,28 +0,0 @@ - -@objc(LKDisplayNameUtilities2) -public final class DisplayNameUtilities2 : NSObject { - - private override init() { } - - @objc(getDisplayNameForPublicKey:threadID:transaction:) - public static func getDisplayName(for publicKey: String, inThreadWithID threadID: String, using transaction: YapDatabaseReadWriteTransaction) -> String { - // Case 1: The public key belongs to the user themselves - if publicKey == getUserHexEncodedPublicKey() { return SSKEnvironment.shared.profileManager.localProfileName() ?? publicKey } - // Case 2: The given thread is an open group - var openGroup: PublicChat? = nil - Storage.read { transaction in - openGroup = LokiDatabaseUtilities.getPublicChat(for: threadID, in: transaction) - } - if let openGroup = openGroup { - var displayName: String? = nil - Storage.read { transaction in - displayName = transaction.object(forKey: publicKey, inCollection: openGroup.id) as! String? - } - if let displayName = displayName { return displayName } - } - // Case 3: The given thread is a closed group or a one-to-one conversation - // FIXME: The line below opens a write transaction under certain circumstances. We should move away from this and towards passing - // a write transaction into this function. - return SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: publicKey) ?? publicKey - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/ECKeyPair+Hexadecimal.swift b/SignalServiceKit/src/Loki/Utilities/ECKeyPair+Hexadecimal.swift deleted file mode 100644 index 894fd19ca..000000000 --- a/SignalServiceKit/src/Loki/Utilities/ECKeyPair+Hexadecimal.swift +++ /dev/null @@ -1,22 +0,0 @@ - -public extension ECKeyPair { - - @objc public var hexEncodedPrivateKey: String { - return privateKey.map { String(format: "%02hhx", $0) }.joined() - } - - @objc public var hexEncodedPublicKey: String { - // Prefixing with "05" is necessary for what seems to be a sort of Signal public key versioning system - return "05" + publicKey.map { String(format: "%02hhx", $0) }.joined() - } - - @objc public static func isValidHexEncodedPublicKey(candidate: String) -> Bool { - // Check that it's a valid hexadecimal encoding - let allowedCharacters = CharacterSet(charactersIn: "0123456789ABCDEF") - guard candidate.uppercased().unicodeScalars.allSatisfy({ allowedCharacters.contains($0) }) else { return false } - // Check that it has length 66 and a leading "05" - guard candidate.count == 66 && candidate.hasPrefix("05") else { return false } - // It appears to be a valid public key - return true - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/GeneralUtilities.swift b/SignalServiceKit/src/Loki/Utilities/GeneralUtilities.swift deleted file mode 100644 index 62fee4eb1..000000000 --- a/SignalServiceKit/src/Loki/Utilities/GeneralUtilities.swift +++ /dev/null @@ -1,7 +0,0 @@ - -public func getUserHexEncodedPublicKey() -> String { - if let keyPair = OWSIdentityManager.shared().identityKeyPair() { // Can be nil under some circumstances - return keyPair.hexEncodedPublicKey - } - return "" -} diff --git a/SignalServiceKit/src/Loki/Utilities/JSON.swift b/SignalServiceKit/src/Loki/Utilities/JSON.swift deleted file mode 100644 index 7335b1e3d..000000000 --- a/SignalServiceKit/src/Loki/Utilities/JSON.swift +++ /dev/null @@ -1,2 +0,0 @@ - -public typealias JSON = [String:Any] diff --git a/SignalServiceKit/src/Loki/Utilities/LKUserDefaults.swift b/SignalServiceKit/src/Loki/Utilities/LKUserDefaults.swift deleted file mode 100644 index 0ce6bea81..000000000 --- a/SignalServiceKit/src/Loki/Utilities/LKUserDefaults.swift +++ /dev/null @@ -1,73 +0,0 @@ -import Foundation - -public enum LKUserDefaults { - - public enum Bool : Swift.String { - case hasLaunchedOnce - case hasSeenGIFMetadataWarning - case hasViewedSeed - case isUsingFullAPNs - /// Whether the device was unlinked as a slave device (used to notify the user on the landing screen). - case wasUnlinked - } - - public enum Date : Swift.String { - case lastProfilePictureUpload - } - - public enum Double : Swift.String { - /// - Note: Deprecated - case lastDeviceTokenUpload = "lastDeviceTokenUploadTime" - } - - public enum Int: Swift.String { - case appMode - } - - public enum String { - case slaveDeviceName(Swift.String) - case deviceToken - /// `nil` if this is a master device or if the user hasn't linked a device. - case masterHexEncodedPublicKey - - public var key: Swift.String { - switch self { - case .slaveDeviceName(let hexEncodedPublicKey): return "\(hexEncodedPublicKey)_display_name" - case .deviceToken: return "deviceToken" - case .masterHexEncodedPublicKey: return "masterDeviceHexEncodedPublicKey" - } - } - } -} - -public extension UserDefaults { - - public subscript(bool: LKUserDefaults.Bool) -> Bool { - get { return self.bool(forKey: bool.rawValue) } - set { set(newValue, forKey: bool.rawValue) } - } - - public subscript(date: LKUserDefaults.Date) -> Date? { - get { return self.object(forKey: date.rawValue) as? Date } - set { set(newValue, forKey: date.rawValue) } - } - - public subscript(double: LKUserDefaults.Double) -> Double { - get { return self.double(forKey: double.rawValue) } - set { set(newValue, forKey: double.rawValue) } - } - - public subscript(int: LKUserDefaults.Int) -> Int { - get { return self.integer(forKey: int.rawValue) } - set { set(newValue, forKey: int.rawValue) } - } - - public subscript(string: LKUserDefaults.String) -> String? { - get { return self.string(forKey: string.key) } - set { set(newValue, forKey: string.key) } - } - - public var isMasterDevice: Bool { - return (self[.masterHexEncodedPublicKey] == nil) - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/NSArray+Functional.h b/SignalServiceKit/src/Loki/Utilities/NSArray+Functional.h deleted file mode 100644 index 75c63332f..000000000 --- a/SignalServiceKit/src/Loki/Utilities/NSArray+Functional.h +++ /dev/null @@ -1,8 +0,0 @@ - -@interface NSArray (Functional) - -- (BOOL)contains:(BOOL (^)(id))predicate; -- (NSArray *)filtered:(BOOL (^)(id))isIncluded; -- (NSArray *)map:(id (^)(id))transform; - -@end diff --git a/SignalServiceKit/src/Loki/Utilities/NSArray+Functional.m b/SignalServiceKit/src/Loki/Utilities/NSArray+Functional.m deleted file mode 100644 index 8e8e5a131..000000000 --- a/SignalServiceKit/src/Loki/Utilities/NSArray+Functional.m +++ /dev/null @@ -1,32 +0,0 @@ -#import "NSArray+Functional.h" - -@implementation NSArray (Functional) - -- (BOOL)contains:(BOOL (^)(id))predicate { - for (id object in self) { - BOOL isPredicateSatisfied = predicate(object); - if (isPredicateSatisfied) { return YES; } - } - return NO; -} - -- (NSArray *)filtered:(BOOL (^)(id))isIncluded { - NSMutableArray *result = [NSMutableArray new]; - for (id object in self) { - if (isIncluded(object)) { - [result addObject:object]; - } - } - return result; -} - -- (NSArray *)map:(id (^)(id))transform { - NSMutableArray *result = [NSMutableArray new]; - for (id object in self) { - id transformedObject = transform(object); - [result addObject:transformedObject]; - } - return result; -} - -@end diff --git a/SignalServiceKit/src/Loki/Utilities/NSObject+Casting.h b/SignalServiceKit/src/Loki/Utilities/NSObject+Casting.h deleted file mode 100644 index f22c78d60..000000000 --- a/SignalServiceKit/src/Loki/Utilities/NSObject+Casting.h +++ /dev/null @@ -1,6 +0,0 @@ - -@interface NSObject (Casting) - -- (id)as:(Class)cls; - -@end diff --git a/SignalServiceKit/src/Loki/Utilities/NSObject+Casting.m b/SignalServiceKit/src/Loki/Utilities/NSObject+Casting.m deleted file mode 100644 index 33afb994e..000000000 --- a/SignalServiceKit/src/Loki/Utilities/NSObject+Casting.m +++ /dev/null @@ -1,10 +0,0 @@ -#import "NSObject+Casting.h" - -@implementation NSObject (Casting) - -- (id)as:(Class)cls { - if ([self isKindOfClass:cls]) { return self; } - return nil; -} - -@end diff --git a/SignalServiceKit/src/Loki/Utilities/NSSet+Functional.h b/SignalServiceKit/src/Loki/Utilities/NSSet+Functional.h deleted file mode 100644 index e812e1810..000000000 --- a/SignalServiceKit/src/Loki/Utilities/NSSet+Functional.h +++ /dev/null @@ -1,8 +0,0 @@ - -@interface NSSet (Functional) - -- (BOOL)contains:(BOOL (^)(id))predicate; -- (NSSet *)filtered:(BOOL (^)(id))isIncluded; -- (NSSet *)map:(id (^)(id))transform; - -@end diff --git a/SignalServiceKit/src/Loki/Utilities/NSSet+Functional.m b/SignalServiceKit/src/Loki/Utilities/NSSet+Functional.m deleted file mode 100644 index c19d814fd..000000000 --- a/SignalServiceKit/src/Loki/Utilities/NSSet+Functional.m +++ /dev/null @@ -1,32 +0,0 @@ -#import "NSSet+Functional.h" - -@implementation NSSet (Functional) - -- (BOOL)contains:(BOOL (^)(id))predicate { - for (id object in self) { - BOOL isPredicateSatisfied = predicate(object); - if (isPredicateSatisfied) { return YES; } - } - return NO; -} - -- (NSSet *)filtered:(BOOL (^)(id))isIncluded { - NSMutableSet *result = [NSMutableSet new]; - for (id object in self) { - if (isIncluded(object)) { - [result addObject:object]; - } - } - return result; -} - -- (NSSet *)map:(id (^)(id))transform { - NSMutableSet *result = [NSMutableSet new]; - for (id object in self) { - id transformedObject = transform(object); - [result addObject:transformedObject]; - } - return result; -} - -@end diff --git a/SignalServiceKit/src/Loki/Utilities/Notification+Loki.swift b/SignalServiceKit/src/Loki/Utilities/Notification+Loki.swift deleted file mode 100644 index da6894d6d..000000000 --- a/SignalServiceKit/src/Loki/Utilities/Notification+Loki.swift +++ /dev/null @@ -1,52 +0,0 @@ - -public extension Notification.Name { - - // State changes - public static let blockedContactsUpdated = Notification.Name("blockedContactsUpdated") - public static let contactOnlineStatusChanged = Notification.Name("contactOnlineStatusChanged") - public static let groupThreadUpdated = Notification.Name("groupThreadUpdated") - public static let threadDeleted = Notification.Name("threadDeleted") - public static let threadSessionRestoreDevicesChanged = Notification.Name("threadSessionRestoreDevicesChanged") - // Message status changes - public static let calculatingPoW = Notification.Name("calculatingPoW") - public static let routing = Notification.Name("routing") - public static let messageSending = Notification.Name("messageSending") - public static let messageSent = Notification.Name("messageSent") - public static let messageFailed = Notification.Name("messageFailed") - // Onboarding - public static let seedViewed = Notification.Name("seedViewed") - // Interaction - public static let dataNukeRequested = Notification.Name("dataNukeRequested") - // Device linking - public static let unexpectedDeviceLinkRequestReceived = Notification.Name("unexpectedDeviceLinkRequestReceived") - // Onion requests - public static let buildingPaths = Notification.Name("buildingPaths") - public static let pathsBuilt = Notification.Name("pathsBuilt") - public static let onionRequestPathCountriesLoaded = Notification.Name("onionRequestPathCountriesLoaded") -} - -@objc public extension NSNotification { - - // State changes - @objc public static let blockedContactsUpdated = Notification.Name.blockedContactsUpdated.rawValue as NSString - @objc public static let contactOnlineStatusChanged = Notification.Name.contactOnlineStatusChanged.rawValue as NSString - @objc public static let groupThreadUpdated = Notification.Name.groupThreadUpdated.rawValue as NSString - @objc public static let threadDeleted = Notification.Name.threadDeleted.rawValue as NSString - @objc public static let threadSessionRestoreDevicesChanged = Notification.Name.threadSessionRestoreDevicesChanged.rawValue as NSString - // Message statuses - @objc public static let calculatingPoW = Notification.Name.calculatingPoW.rawValue as NSString - @objc public static let routing = Notification.Name.routing.rawValue as NSString - @objc public static let messageSending = Notification.Name.messageSending.rawValue as NSString - @objc public static let messageSent = Notification.Name.messageSent.rawValue as NSString - @objc public static let messageFailed = Notification.Name.messageFailed.rawValue as NSString - // Onboarding - @objc public static let seedViewed = Notification.Name.seedViewed.rawValue as NSString - // Interaction - @objc public static let dataNukeRequested = Notification.Name.dataNukeRequested.rawValue as NSString - // Device linking - @objc public static let unexpectedDeviceLinkRequestReceived = Notification.Name.unexpectedDeviceLinkRequestReceived.rawValue as NSString - // Onion requests - @objc public static let buildingPaths = Notification.Name.buildingPaths.rawValue as NSString - @objc public static let pathsBuilt = Notification.Name.pathsBuilt.rawValue as NSString - @objc public static let onionRequestPathCountriesLoaded = Notification.Name.onionRequestPathCountriesLoaded.rawValue as NSString -} diff --git a/SignalServiceKit/src/Loki/Utilities/Promise+Delaying.swift b/SignalServiceKit/src/Loki/Utilities/Promise+Delaying.swift deleted file mode 100644 index 533ee70e7..000000000 --- a/SignalServiceKit/src/Loki/Utilities/Promise+Delaying.swift +++ /dev/null @@ -1,11 +0,0 @@ -import PromiseKit - -/// Delay the execution of the promise constructed in `body` by `delay` seconds. -internal func withDelay(_ delay: TimeInterval, completionQueue: DispatchQueue, body: @escaping () -> Promise) -> Promise { - AssertIsOnMainThread() // Timers don't do well on background queues - let (promise, seal) = Promise.pending() - Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { _ in - body().done(on: completionQueue) { seal.fulfill($0) }.catch(on: completionQueue) { seal.reject($0) } - } - return promise -} diff --git a/SignalServiceKit/src/Loki/Utilities/Promise+Hashing.swift b/SignalServiceKit/src/Loki/Utilities/Promise+Hashing.swift deleted file mode 100644 index 47bb42c62..000000000 --- a/SignalServiceKit/src/Loki/Utilities/Promise+Hashing.swift +++ /dev/null @@ -1,13 +0,0 @@ -import PromiseKit - -extension Promise : Hashable { - - public func hash(into hasher: inout Hasher) { - let reference = ObjectIdentifier(self) - hasher.combine(reference.hashValue) - } - - public static func == (lhs: Promise, rhs: Promise) -> Bool { - return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/Promise+Retrying.swift b/SignalServiceKit/src/Loki/Utilities/Promise+Retrying.swift deleted file mode 100644 index a386b23fd..000000000 --- a/SignalServiceKit/src/Loki/Utilities/Promise+Retrying.swift +++ /dev/null @@ -1,14 +0,0 @@ -import PromiseKit - -/// Retry the promise constructed in `body` up to `maxRetryCount` times. -internal func attempt(maxRetryCount: UInt, recoveringOn queue: DispatchQueue, body: @escaping () -> Promise) -> Promise { - var retryCount = 0 - func attempt() -> Promise { - return body().recover(on: queue) { error -> Promise in - guard retryCount < maxRetryCount else { throw error } - retryCount += 1 - return attempt() - } - } - return attempt() -} diff --git a/SignalServiceKit/src/Loki/Utilities/Promise+Threading.swift b/SignalServiceKit/src/Loki/Utilities/Promise+Threading.swift deleted file mode 100644 index f2f4864bd..000000000 --- a/SignalServiceKit/src/Loki/Utilities/Promise+Threading.swift +++ /dev/null @@ -1,76 +0,0 @@ -import PromiseKit - -public extension Thenable { - - func then2(_ body: @escaping (T) throws -> U) -> Promise where U : Thenable { - return then(on: SnodeAPI.workQueue, body) - } - - func map2(_ transform: @escaping (T) throws -> U) -> Promise { - return map(on: SnodeAPI.workQueue, transform) - } - - func done2(_ body: @escaping (T) throws -> Void) -> Promise { - return done(on: SnodeAPI.workQueue, body) - } - - func get2(_ body: @escaping (T) throws -> Void) -> Promise { - return get(on: SnodeAPI.workQueue, body) - } -} - -public extension Thenable where T: Sequence { - - func mapValues2(_ transform: @escaping (T.Iterator.Element) throws -> U) -> Promise<[U]> { - return mapValues(on: SnodeAPI.workQueue, transform) - } -} - -public extension Guarantee { - - func then2(_ body: @escaping (T) -> Guarantee) -> Guarantee { - return then(on: SnodeAPI.workQueue, body) - } - - func map2(_ body: @escaping (T) -> U) -> Guarantee { - return map(on: SnodeAPI.workQueue, body) - } - - func done2(_ body: @escaping (T) -> Void) -> Guarantee { - return done(on: SnodeAPI.workQueue, body) - } - - func get2(_ body: @escaping (T) -> Void) -> Guarantee { - return get(on: SnodeAPI.workQueue, body) - } -} - -public extension CatchMixin { - - func catch2(_ body: @escaping (Error) -> Void) -> PMKFinalizer { - return self.catch(on: SnodeAPI.workQueue, body) - } - - func recover2(_ body: @escaping(Error) throws -> U) -> Promise where U.T == T { - return recover(on: SnodeAPI.workQueue, body) - } - - func recover2(_ body: @escaping(Error) -> Guarantee) -> Guarantee { - return recover(on: SnodeAPI.workQueue, body) - } - - func ensure2(_ body: @escaping () -> Void) -> Promise { - return ensure(on: SnodeAPI.workQueue, body) - } -} - -public extension CatchMixin where T == Void { - - func recover2(_ body: @escaping(Error) -> Void) -> Guarantee { - return recover(on: SnodeAPI.workQueue, body) - } - - func recover2(_ body: @escaping(Error) throws -> Void) -> Promise { - return recover(on: SnodeAPI.workQueue, body) - } -} diff --git a/SignalServiceKit/src/Loki/Utilities/String+Trimming.swift b/SignalServiceKit/src/Loki/Utilities/String+Trimming.swift deleted file mode 100644 index 0e9078269..000000000 --- a/SignalServiceKit/src/Loki/Utilities/String+Trimming.swift +++ /dev/null @@ -1,18 +0,0 @@ - -public extension String { - - public func removing05PrefixIfNeeded() -> String { - var result = self - if result.count == 66 && result.hasPrefix("05") { result.removeFirst(2) } - return result - } -} - -@objc extension NSString { - - @objc public func removing05PrefixIfNeeded() -> NSString { - var result = self as String - if result.count == 66 && result.hasPrefix("05") { result.removeFirst(2) } - return result as NSString - } -} diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.h b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.h deleted file mode 100644 index 74b9553f8..000000000 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const kAttachmentDownloadProgressNotification; -extern NSString *const kAttachmentDownloadProgressKey; -extern NSString *const kAttachmentDownloadAttachmentIDKey; - -@class SSKProtoAttachmentPointer; -@class TSAttachment; -@class TSAttachmentPointer; -@class TSAttachmentStream; -@class TSMessage; -@class YapDatabaseReadTransaction; -@class YapDatabaseReadWriteTransaction; - -#pragma mark - - -/** - * Given incoming attachment protos, determines which we support. - * It can download those that we support and notifies threads when it receives unsupported attachments. - */ -@interface OWSAttachmentDownloads : NSObject - -- (nullable NSNumber *)downloadProgressForAttachmentId:(NSString *)attachmentId; - -// This will try to download all un-downloaded attachments for a given message. -// Any attachments for the message which are already downloaded are skipped BUT -// they are included in the success callback. -// -// success/failure are always called on a worker queue. -- (void)downloadAttachmentsForMessage:(TSMessage *)message - transaction:(YapDatabaseReadTransaction *)transaction - success:(void (^)(NSArray *attachmentStreams))success - failure:(void (^)(NSError *error))failure; - -// This will try to download a single attachment. -// -// success/failure are always called on a worker queue. -- (void)downloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer - success:(void (^)(NSArray *attachmentStreams))success - failure:(void (^)(NSError *error))failure; - -- (void)continueDownloadIfPossible; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m deleted file mode 100644 index f0471f14d..000000000 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m +++ /dev/null @@ -1,556 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSAttachmentDownloads.h" -#import "AppContext.h" -#import "MIMETypeUtil.h" -#import "NSNotificationCenter+OWS.h" -#import "OWSBackgroundTask.h" -#import "OWSDispatch.h" -#import "OWSError.h" -#import "OWSFileSystem.h" -#import "OWSPrimaryStorage.h" -#import "OWSRequestFactory.h" -#import "SSKEnvironment.h" -#import "TSAttachmentPointer.h" -#import "TSAttachmentStream.h" -#import "TSGroupModel.h" -#import "TSGroupThread.h" -#import "TSInfoMessage.h" -#import "TSMessage.h" -#import "TSNetworkManager.h" -#import "TSThread.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const kAttachmentDownloadProgressNotification = @"kAttachmentDownloadProgressNotification"; -NSString *const kAttachmentDownloadProgressKey = @"kAttachmentDownloadProgressKey"; -NSString *const kAttachmentDownloadAttachmentIDKey = @"kAttachmentDownloadAttachmentIDKey"; - -// Use a slightly non-zero value to ensure that the progress -// indicator shows up as quickly as possible. -static const CGFloat kAttachmentDownloadProgressTheta = 0.001f; - -typedef void (^AttachmentDownloadSuccess)(TSAttachmentStream *attachmentStream); -typedef void (^AttachmentDownloadFailure)(NSError *error); - -@interface OWSAttachmentDownloadJob : NSObject - -@property (nonatomic, readonly) TSAttachmentPointer *attachmentPointer; -@property (nonatomic, readonly, nullable) TSMessage *message; -@property (nonatomic, readonly) AttachmentDownloadSuccess success; -@property (nonatomic, readonly) AttachmentDownloadFailure failure; -@property (atomic) CGFloat progress; - -@end - -#pragma mark - - -@implementation OWSAttachmentDownloadJob - -- (instancetype)initWithAttachmentPointer:(TSAttachmentPointer *)attachmentPointer - message:(nullable TSMessage *)message - success:(AttachmentDownloadSuccess)success - failure:(AttachmentDownloadFailure)failure -{ - self = [super init]; - if (!self) { - return self; - } - - _attachmentPointer = attachmentPointer; - _message = message; - _success = success; - _failure = failure; - - return self; -} - -@end - -#pragma mark - - -@interface OWSAttachmentDownloads () - -// This property should only be accessed while synchronized on this class. -@property (nonatomic, readonly) NSMutableDictionary *downloadingJobMap; -// This property should only be accessed while synchronized on this class. -@property (nonatomic, readonly) NSMutableArray *attachmentDownloadJobQueue; - -@end - -#pragma mark - - -@implementation OWSAttachmentDownloads - -#pragma mark - Dependencies - -- (OWSPrimaryStorage *)primaryStorage -{ - return SSKEnvironment.shared.primaryStorage; -} - -- (TSNetworkManager *)networkManager -{ - return SSKEnvironment.shared.networkManager; -} - - -#pragma mark - - -- (instancetype)init -{ - self = [super init]; - if (!self) { - return self; - } - - _downloadingJobMap = [NSMutableDictionary new]; - _attachmentDownloadJobQueue = [NSMutableArray new]; - - return self; -} - -#pragma mark - - -- (nullable NSNumber *)downloadProgressForAttachmentId:(NSString *)attachmentId -{ - - @synchronized(self) { - OWSAttachmentDownloadJob *_Nullable job = self.downloadingJobMap[attachmentId]; - if (!job) { - return nil; - } - return @(job.progress); - } -} - -- (void)downloadAttachmentsForMessage:(TSMessage *)message - transaction:(YapDatabaseReadTransaction *)transaction - success:(void (^)(NSArray *attachmentStreams))success - failure:(void (^)(NSError *error))failure -{ - OWSAssertDebug(transaction); - OWSAssertDebug(message); - - NSMutableArray *attachmentStreams = [NSMutableArray array]; - NSMutableArray *attachmentPointers = [NSMutableArray new]; - - for (TSAttachment *attachment in [message attachmentsWithTransaction:transaction]) { - if ([attachment isKindOfClass:[TSAttachmentStream class]]) { - TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; - [attachmentStreams addObject:attachmentStream]; - } else if ([attachment isKindOfClass:[TSAttachmentPointer class]]) { - TSAttachmentPointer *attachmentPointer = (TSAttachmentPointer *)attachment; - if (attachmentPointer.pointerType != TSAttachmentPointerTypeIncoming) { - OWSLogInfo(@"Ignoring restoring attachment."); - continue; - } - [attachmentPointers addObject:attachmentPointer]; - } else { - OWSFailDebug(@"Unexpected attachment type: %@", attachment.class); - } - } - - [self enqueueJobsForAttachmentStreams:attachmentStreams - attachmentPointers:attachmentPointers - message:message - success:success - failure:failure]; -} - -- (void)downloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer - success:(void (^)(NSArray *attachmentStreams))success - failure:(void (^)(NSError *error))failure -{ - OWSAssertDebug(attachmentPointer); - - [self enqueueJobsForAttachmentStreams:@[] - attachmentPointers:@[ - attachmentPointer, - ] - message:nil - success:success - failure:failure]; -} - -- (void)enqueueJobsForAttachmentStreams:(NSArray *)attachmentStreamsParam - attachmentPointers:(NSArray *)attachmentPointers - message:(nullable TSMessage *)message - success:(void (^)(NSArray *attachmentStreams))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - OWSAssertDebug(attachmentStreamsParam); - - // To avoid deadlocks, synchronize on self outside of the transaction. - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - if (attachmentPointers.count < 1) { - OWSAssertDebug(attachmentStreamsParam.count > 0); - successHandler(attachmentStreamsParam); - return; - } - - NSMutableArray *attachmentStreams = [attachmentStreamsParam mutableCopy]; - NSMutableArray *promises = [NSMutableArray array]; - for (TSAttachmentPointer *attachmentPointer in attachmentPointers) { - AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) { - [self enqueueJobForAttachmentPointer:attachmentPointer - message:message - success:^(TSAttachmentStream *attachmentStream) { - @synchronized(attachmentStreams) { - [attachmentStreams addObject:attachmentStream]; - } - - resolve(@(1)); - } - failure:^(NSError *error) { - resolve(error); - }]; - }]; - [promises addObject:promise]; - } - - // We use PMKJoin(), not PMKWhen(), because we don't want the - // completion promise to execute until _all_ promises - // have either succeeded or failed. PMKWhen() executes as - // soon as any of its input promises fail. - AnyPromise *completionPromise - = PMKJoin(promises) - .then(^(id value) { - NSArray *attachmentStreamsCopy; - @synchronized(attachmentStreams) { - attachmentStreamsCopy = [attachmentStreams copy]; - } - OWSLogInfo(@"Attachment downloads succeeded: %lu.", (unsigned long)attachmentStreamsCopy.count); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - successHandler(attachmentStreamsCopy); - }); - }) - .catch(^(NSError *error) { - OWSLogError(@"Attachment downloads failed."); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - failureHandler(error); - }); - }); - [completionPromise retainUntilComplete]; - }); -} - -- (void)enqueueJobForAttachmentPointer:(TSAttachmentPointer *)attachmentPointer - message:(nullable TSMessage *)message - success:(void (^)(TSAttachmentStream *attachmentStream))success - failure:(void (^)(NSError *error))failure -{ - OWSAssertDebug(attachmentPointer); - - OWSAttachmentDownloadJob *job = [[OWSAttachmentDownloadJob alloc] initWithAttachmentPointer:attachmentPointer - message:message - success:success - failure:failure]; - - @synchronized(self) { - [self.attachmentDownloadJobQueue addObject:job]; - } - - [self startDownloadIfPossible]; -} - -- (void)startDownloadIfPossible -{ - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - OWSAttachmentDownloadJob *_Nullable job; - - @synchronized(self) { - const NSUInteger kMaxSimultaneousDownloads = 4; - if (self.downloadingJobMap.count >= kMaxSimultaneousDownloads) { - return; - } - job = self.attachmentDownloadJobQueue.firstObject; - if (!job) { - return; - } - if (self.downloadingJobMap[job.attachmentPointer.uniqueId] != nil) { - // Ensure we only have one download in flight at a time for a given attachment. - OWSLogWarn(@"Ignoring duplicate download."); - return; - } - [self.attachmentDownloadJobQueue removeObjectAtIndex:0]; - self.downloadingJobMap[job.attachmentPointer.uniqueId] = job; - } - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - job.attachmentPointer.state = TSAttachmentPointerStateDownloading; - [job.attachmentPointer saveWithTransaction:transaction]; - - if (job.message) { - [job.message touchWithTransaction:transaction]; - } - }]; - - [self retrieveAttachmentForJob:job - success:^(TSAttachmentStream *attachmentStream) { - OWSLogVerbose(@"Attachment download succeeded."); - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [attachmentStream saveWithTransaction:transaction]; - - if (job.message) { - [job.message touchWithTransaction:transaction]; - } - }]; - - job.success(attachmentStream); - - @synchronized(self) { - [self.downloadingJobMap removeObjectForKey:job.attachmentPointer.uniqueId]; - } - - [self startDownloadIfPossible]; - } - failure:^(NSError *error) { - OWSLogError(@"Attachment download failed with error: %@", error); - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - job.attachmentPointer.mostRecentFailureLocalizedText = error.localizedDescription; - job.attachmentPointer.state = TSAttachmentPointerStateFailed; - [job.attachmentPointer saveWithTransaction:transaction]; - - if (job.message) { - [job.message touchWithTransaction:transaction]; - } - }]; - - @synchronized(self) { - [self.downloadingJobMap removeObjectForKey:job.attachmentPointer.uniqueId]; - } - - job.failure(error); - - [self startDownloadIfPossible]; - }]; - }); -} - -#pragma mark - - -- (void)continueDownloadIfPossible -{ - if (self.attachmentDownloadJobQueue.count > 0) { - [LKLogger print:@"[Loki] Continuing unfinished attachment download tasks."]; - [self startDownloadIfPossible]; - } -} - -#pragma mark - - -- (void)retrieveAttachmentForJob:(OWSAttachmentDownloadJob *)job - success:(void (^)(TSAttachmentStream *attachmentStream))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - OWSAssertDebug(job); - TSAttachmentPointer *attachmentPointer = job.attachmentPointer; - - __block OWSBackgroundTask *_Nullable backgroundTask = - [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - - void (^markAndHandleFailure)(NSError *) = ^(NSError *error) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - failureHandler(error); - - OWSAssertDebug(backgroundTask); - backgroundTask = nil; - }); - }; - - void (^markAndHandleSuccess)(TSAttachmentStream *attachmentStream) = ^(TSAttachmentStream *attachmentStream) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - successHandler(attachmentStream); - - OWSAssertDebug(backgroundTask); - backgroundTask = nil; - }); - }; - - __block NSUInteger retryCount = 0; - NSUInteger maxRetryCount = 4; - __block void (^attempt)(); - attempt = ^() { - dispatch_async([OWSDispatch attachmentsQueue], ^{ - [self downloadFromLocation:attachmentPointer.downloadURL - job:job - success:^(NSString *encryptedDataFilePath) { - [self decryptAttachmentPath:encryptedDataFilePath - attachmentPointer:attachmentPointer - success:markAndHandleSuccess - failure:markAndHandleFailure]; - } - failure:^(NSURLSessionTask *task, NSError *error) { - if (retryCount == maxRetryCount) { - markAndHandleFailure(error); - } else { - retryCount += 1; - attempt(); - } - }]; - }); - }; - attempt(); -} - -- (void)decryptAttachmentPath:(NSString *)encryptedDataFilePath - attachmentPointer:(TSAttachmentPointer *)attachmentPointer - success:(void (^)(TSAttachmentStream *attachmentStream))success - failure:(void (^)(NSError *error))failure -{ - OWSAssertDebug(encryptedDataFilePath.length > 0); - OWSAssertDebug(attachmentPointer); - - // Use attachmentDecryptSerialQueue to ensure that we only load into memory - // & decrypt a single attachment at a time. - dispatch_async(self.attachmentDecryptSerialQueue, ^{ - @autoreleasepool { - NSData *_Nullable encryptedData = [NSData dataWithContentsOfFile:encryptedDataFilePath]; - if (!encryptedData) { - OWSLogError(@"Could not load encrypted data."); - NSError *error = OWSErrorWithCodeDescription( - OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); - return failure(error); - } - - [self decryptAttachmentData:encryptedData - attachmentPointer:attachmentPointer - success:success - failure:failure]; - - if (![OWSFileSystem deleteFile:encryptedDataFilePath]) { - OWSLogError(@"Could not delete temporary file."); - } - } - }); -} - -- (void)decryptAttachmentData:(NSData *)cipherText - attachmentPointer:(TSAttachmentPointer *)attachmentPointer - success:(void (^)(TSAttachmentStream *attachmentStream))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - OWSAssertDebug(attachmentPointer); - - NSError *decryptError; - NSData *_Nullable plaintext; - if (attachmentPointer.encryptionKey != nil) { - plaintext = [Cryptography decryptAttachment:cipherText - withKey:attachmentPointer.encryptionKey - digest:attachmentPointer.digest - unpaddedSize:attachmentPointer.byteCount - error:&decryptError]; - } else { - plaintext = cipherText; // Loki: Public chat attachments are unencrypted - } - - if (decryptError) { - OWSLogError(@"failed to decrypt with error: %@", decryptError); - failureHandler(decryptError); - return; - } - - if (!plaintext) { - NSError *error = OWSErrorWithCodeDescription( - OWSErrorCodeFailedToDecryptMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); - failureHandler(error); - return; - } - - TSAttachmentStream *stream = [[TSAttachmentStream alloc] initWithPointer:attachmentPointer]; - - NSError *writeError; - [stream writeData:plaintext error:&writeError]; - if (writeError) { - OWSLogError(@"Failed writing attachment stream with error: %@", writeError); - failureHandler(writeError); - return; - } - - successHandler(stream); -} - -- (dispatch_queue_t)attachmentDecryptSerialQueue -{ - static dispatch_queue_t _serialQueue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _serialQueue = dispatch_queue_create("org.whispersystems.attachment.decrypt", DISPATCH_QUEUE_SERIAL); - }); - - return _serialQueue; -} - -- (void)downloadFromLocation:(NSString *)location - job:(OWSAttachmentDownloadJob *)job - success:(void (^)(NSString *encryptedDataPath))successHandler - failure:(void (^)(NSURLSessionTask *_Nullable task, NSError *error))failureHandlerParam -{ - OWSAssertDebug(job); - TSAttachmentPointer *attachmentPointer = job.attachmentPointer; - - // We want to avoid large downloads from a compromised or buggy service. - const long kMaxDownloadSize = 10 * 1024 * 1024; - __block BOOL hasCheckedContentLength = NO; - - NSString *tempFilePath = - [OWSTemporaryDirectoryAccessibleAfterFirstAuth() stringByAppendingPathComponent:[NSUUID UUID].UUIDString]; - NSURL *tempFileURL = [NSURL fileURLWithPath:tempFilePath]; - __block NSURLSessionDownloadTask *task; - void (^failureHandler)(NSError *) = ^(NSError *error) { - OWSLogError(@"Failed to download attachment with error: %@", error.description); - - if (![OWSFileSystem deleteFileIfExists:tempFilePath]) { - OWSLogError(@"Could not delete temporary file #1."); - } - - failureHandlerParam(task, error); - }; - - [[LKFileServerAPI downloadAttachmentFrom:location].then(^(NSData *data) { - BOOL success = [data writeToFile:tempFilePath atomically:YES]; - if (success) { - successHandler(tempFilePath); - } - - NSNumber *_Nullable fileSize = [OWSFileSystem fileSizeOfPath:tempFilePath]; - if (!fileSize) { - OWSLogError(@"Could not determine attachment file size."); - NSError *error = OWSErrorWithCodeDescription( - OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); - return failureHandler(error); - } - if (fileSize.unsignedIntegerValue > kMaxDownloadSize) { - OWSLogError(@"Attachment download length exceeds max size."); - NSError *error = OWSErrorWithCodeDescription( - OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @"")); - return failureHandler(error); - } - }) retainUntilComplete]; -} - -- (void)fireProgressNotification:(CGFloat)progress attachmentId:(NSString *)attachmentId -{ - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter postNotificationNameAsync:kAttachmentDownloadProgressNotification - object:nil - userInfo:@{ - kAttachmentDownloadProgressKey : @(progress), - kAttachmentDownloadAttachmentIDKey : attachmentId - }]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Attachments/OWSMediaUtils.swift b/SignalServiceKit/src/Messages/Attachments/OWSMediaUtils.swift deleted file mode 100644 index c74223e9a..000000000 --- a/SignalServiceKit/src/Messages/Attachments/OWSMediaUtils.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import AVFoundation - -public enum OWSMediaError: Error { - case failure(description: String) -} - -@objc public class OWSMediaUtils: NSObject { - - @available(*, unavailable, message:"do not instantiate this class.") - private override init() { - } - - @objc public class func thumbnail(forImageAtPath path: String, maxDimension: CGFloat) throws -> UIImage { - Logger.verbose("thumbnailing image: \(path)") - - guard FileManager.default.fileExists(atPath: path) else { - throw OWSMediaError.failure(description: "Media file missing.") - } - guard NSData.ows_isValidImage(atPath: path) else { - throw OWSMediaError.failure(description: "Invalid image.") - } - guard let originalImage = UIImage(contentsOfFile: path) else { - throw OWSMediaError.failure(description: "Could not load original image.") - } - guard let thumbnailImage = originalImage.resized(withMaxDimensionPoints: maxDimension) else { - throw OWSMediaError.failure(description: "Could not thumbnail image.") - } - return thumbnailImage - } - - @objc public class func thumbnail(forVideoAtPath path: String, maxDimension: CGFloat) throws -> UIImage { - Logger.verbose("thumbnailing video: \(path)") - - guard isVideoOfValidContentTypeAndSize(path: path) else { - throw OWSMediaError.failure(description: "Media file has missing or invalid length.") - } - - let maxSize = CGSize(width: maxDimension, height: maxDimension) - let url = URL(fileURLWithPath: path) - let asset = AVURLAsset(url: url, options: nil) - guard isValidVideo(asset: asset) else { - throw OWSMediaError.failure(description: "Invalid video.") - } - - let generator = AVAssetImageGenerator(asset: asset) - generator.maximumSize = maxSize - generator.appliesPreferredTrackTransform = true - let time: CMTime = CMTimeMake(value: 1, timescale: 60) - let cgImage = try generator.copyCGImage(at: time, actualTime: nil) - let image = UIImage(cgImage: cgImage) - return image - } - - @objc public class func isValidVideo(path: String) -> Bool { - guard isVideoOfValidContentTypeAndSize(path: path) else { - Logger.error("Media file has missing or invalid length.") - return false - } - - let url = URL(fileURLWithPath: path) - let asset = AVURLAsset(url: url, options: nil) - return isValidVideo(asset: asset) - } - - private class func isVideoOfValidContentTypeAndSize(path: String) -> Bool { - guard FileManager.default.fileExists(atPath: path) else { - Logger.error("Media file missing.") - return false - } - let fileExtension = URL(fileURLWithPath: path).pathExtension - guard let contentType = MIMETypeUtil.mimeType(forFileExtension: fileExtension) else { - Logger.error("Media file has unknown content type.") - return false - } - guard MIMETypeUtil.isSupportedVideoMIMEType(contentType) else { - Logger.error("Media file has invalid content type.") - return false - } - - guard let fileSize = OWSFileSystem.fileSize(ofPath: path) else { - Logger.error("Media file has unknown length.") - return false - } - return fileSize.uintValue <= kMaxFileSizeVideo - } - - private class func isValidVideo(asset: AVURLAsset) -> Bool { - var maxTrackSize = CGSize.zero - for track: AVAssetTrack in asset.tracks(withMediaType: .video) { - let trackSize: CGSize = track.naturalSize - maxTrackSize.width = max(maxTrackSize.width, trackSize.width) - maxTrackSize.height = max(maxTrackSize.height, trackSize.height) - } - if maxTrackSize.width < 1.0 || maxTrackSize.height < 1.0 { - Logger.error("Invalid video size: \(maxTrackSize)") - return false - } - if maxTrackSize.width > kMaxVideoDimensions || maxTrackSize.height > kMaxVideoDimensions { - Logger.error("Invalid video dimensions: \(maxTrackSize)") - return false - } - return true - } - - // MARK: Constants - - /** - * Media Size constraints from Signal-Android - * - * https://github.com/signalapp/Signal-Android/blob/master/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java - */ - @objc - public static var kMaxFileSizeAnimatedImage: UInt { UInt(Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier) } - @objc - public static var kMaxFileSizeImage: UInt { UInt(Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier) } - @objc - public static var kMaxFileSizeVideo: UInt { UInt(Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier) } - @objc - public static var kMaxFileSizeAudio: UInt { UInt(Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier) } - @objc - public static var kMaxFileSizeGeneric: UInt { UInt(Double(FileServerAPI.maxFileSize) / FileServerAPI.fileSizeORMultiplier) } - - @objc - public static let kMaxVideoDimensions: CGFloat = 3 * 1024 - @objc - public static let kMaxAnimatedImageDimensions: UInt = 1 * 1024 - @objc - public static let kMaxStillImageDimensions: UInt = 8 * 1024 -} diff --git a/SignalServiceKit/src/Messages/Attachments/OWSThumbnailService.swift b/SignalServiceKit/src/Messages/Attachments/OWSThumbnailService.swift deleted file mode 100644 index d65a502d5..000000000 --- a/SignalServiceKit/src/Messages/Attachments/OWSThumbnailService.swift +++ /dev/null @@ -1,178 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import AVFoundation - -public enum OWSThumbnailError: Error { - case failure(description: String) - case assertionFailure(description: String) - case externalError(description: String, underlyingError:Error) -} - -@objc public class OWSLoadedThumbnail: NSObject { - public typealias DataSourceBlock = () throws -> Data - - @objc - public let image: UIImage - let dataSourceBlock: DataSourceBlock - - @objc - public init(image: UIImage, filePath: String) { - self.image = image - self.dataSourceBlock = { - return try Data(contentsOf: URL(fileURLWithPath: filePath)) - } - } - - @objc - public init(image: UIImage, data: Data) { - self.image = image - self.dataSourceBlock = { - return data - } - } - - @objc - public func data() throws -> Data { - return try dataSourceBlock() - } -} - -private struct OWSThumbnailRequest { - public typealias SuccessBlock = (OWSLoadedThumbnail) -> Void - public typealias FailureBlock = (Error) -> Void - - let attachment: TSAttachmentStream - let thumbnailDimensionPoints: UInt - let success: SuccessBlock - let failure: FailureBlock - - init(attachment: TSAttachmentStream, thumbnailDimensionPoints: UInt, success: @escaping SuccessBlock, failure: @escaping FailureBlock) { - self.attachment = attachment - self.thumbnailDimensionPoints = thumbnailDimensionPoints - self.success = success - self.failure = failure - } -} - -@objc public class OWSThumbnailService: NSObject { - - // MARK: - Singleton class - - @objc(shared) - public static let shared = OWSThumbnailService() - - public typealias SuccessBlock = (OWSLoadedThumbnail) -> Void - public typealias FailureBlock = (Error) -> Void - - private let serialQueue = DispatchQueue(label: "OWSThumbnailService") - - // This property should only be accessed on the serialQueue. - // - // We want to process requests in _reverse_ order in which they - // arrive so that we prioritize the most recent view state. - private var thumbnailRequestStack = [OWSThumbnailRequest]() - - private override init() { - super.init() - - SwiftSingletons.register(self) - } - - private func canThumbnailAttachment(attachment: TSAttachmentStream) -> Bool { - return attachment.isImage || attachment.isAnimated || attachment.isVideo - } - - // success and failure will be called async _off_ the main thread. - @objc - public func ensureThumbnail(forAttachment attachment: TSAttachmentStream, - thumbnailDimensionPoints: UInt, - success: @escaping SuccessBlock, - failure: @escaping FailureBlock) { - serialQueue.async { - let thumbnailRequest = OWSThumbnailRequest(attachment: attachment, thumbnailDimensionPoints: thumbnailDimensionPoints, success: success, failure: failure) - self.thumbnailRequestStack.append(thumbnailRequest) - - self.processNextRequestSync() - } - } - - private func processNextRequestAsync() { - serialQueue.async { - self.processNextRequestSync() - } - } - - // This should only be called on the serialQueue. - private func processNextRequestSync() { - guard let thumbnailRequest = thumbnailRequestStack.popLast() else { - return - } - - do { - let loadedThumbnail = try process(thumbnailRequest: thumbnailRequest) - DispatchQueue.global().async { - thumbnailRequest.success(loadedThumbnail) - } - } catch { - Logger.error("Could not create thumbnail: \(error)") - - DispatchQueue.global().async { - thumbnailRequest.failure(error) - } - } - } - - // This should only be called on the serialQueue. - // - // It should be safe to assume that an attachment will never end up with two thumbnails of - // the same size since: - // - // * Thumbnails are only added by this method. - // * This method checks for an existing thumbnail using the same connection. - // * This method is performed on the serial queue. - private func process(thumbnailRequest: OWSThumbnailRequest) throws -> OWSLoadedThumbnail { - let attachment = thumbnailRequest.attachment - guard canThumbnailAttachment(attachment: attachment) else { - throw OWSThumbnailError.failure(description: "Cannot thumbnail attachment.") - } - let thumbnailPath = attachment.path(forThumbnailDimensionPoints: thumbnailRequest.thumbnailDimensionPoints) - if FileManager.default.fileExists(atPath: thumbnailPath) { - guard let image = UIImage(contentsOfFile: thumbnailPath) else { - throw OWSThumbnailError.failure(description: "Could not load thumbnail.") - } - return OWSLoadedThumbnail(image: image, filePath: thumbnailPath) - } - - Logger.verbose("Creating thumbnail of size: \(thumbnailRequest.thumbnailDimensionPoints)") - - let thumbnailDirPath = (thumbnailPath as NSString).deletingLastPathComponent - guard OWSFileSystem.ensureDirectoryExists(thumbnailDirPath) else { - throw OWSThumbnailError.failure(description: "Could not create attachment's thumbnail directory.") - } - guard let originalFilePath = attachment.originalFilePath else { - throw OWSThumbnailError.failure(description: "Missing original file path.") - } - let maxDimension = CGFloat(thumbnailRequest.thumbnailDimensionPoints) - let thumbnailImage: UIImage - if attachment.isImage || attachment.isAnimated { - thumbnailImage = try OWSMediaUtils.thumbnail(forImageAtPath: originalFilePath, maxDimension: maxDimension) - } else if attachment.isVideo { - thumbnailImage = try OWSMediaUtils.thumbnail(forVideoAtPath: originalFilePath, maxDimension: maxDimension) - } else { - throw OWSThumbnailError.assertionFailure(description: "Invalid attachment type.") - } - guard let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.85) else { - throw OWSThumbnailError.failure(description: "Could not convert thumbnail to JPEG.") - } - do { - try thumbnailData.write(to: URL(fileURLWithPath: thumbnailPath), options: .atomicWrite) - } catch let error as NSError { - throw OWSThumbnailError.externalError(description: "File write failed: \(thumbnailPath), \(error)", underlyingError: error) - } - OWSFileSystem.protectFileOrFolder(atPath: thumbnailPath) - return OWSLoadedThumbnail(image: thumbnailImage, data: thumbnailData) - } -} diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachment.h b/SignalServiceKit/src/Messages/Attachments/TSAttachment.h deleted file mode 100644 index e4b15002c..000000000 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachment.h +++ /dev/null @@ -1,103 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSYapDatabaseObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@class TSAttachmentPointer; -@class TSMessage; - -typedef NS_ENUM(NSUInteger, TSAttachmentType) { - TSAttachmentTypeDefault = 0, - TSAttachmentTypeVoiceMessage = 1, -}; - -@interface TSAttachment : TSYapDatabaseObject { - -@protected - NSString *_contentType; -} - -// TSAttachment is a base class for TSAttachmentPointer (a yet-to-be-downloaded -// incoming attachment) and TSAttachmentStream (an outgoing or already-downloaded -// incoming attachment). -// -// The attachmentSchemaVersion and serverId properties only apply to -// TSAttachmentPointer, which can be distinguished by the isDownloaded -// property. -@property (atomic, readwrite) UInt64 serverId; -@property (atomic, readwrite, nullable) NSData *encryptionKey; -@property (nonatomic, readonly) NSString *contentType; -@property (atomic, readwrite) BOOL isDownloaded; -@property (nonatomic) TSAttachmentType attachmentType; -@property (nonatomic) NSString *downloadURL; - -// Though now required, may incorrectly be 0 on legacy attachments. -@property (nonatomic, readonly) UInt32 byteCount; - -// Represents the "source" filename sent or received in the protos, -// not the filename on disk. -@property (nonatomic, readonly, nullable) NSString *sourceFilename; - -#pragma mark - Media Album - -@property (nonatomic, readonly, nullable) NSString *caption; -@property (nonatomic, nullable) NSString *albumMessageId; -- (nullable TSMessage *)fetchAlbumMessageWithTransaction:(YapDatabaseReadTransaction *)transaction; - -// `migrateAlbumMessageId` is only used in the migration to the new multi-attachment message scheme, -// and shouldn't be used as a general purpose setter. Instead, `albumMessageId` should be passed as -// an initializer param. -- (void)migrateAlbumMessageId:(NSString *)albumMesssageId; - -#pragma mark - - -// This constructor is used for new instances of TSAttachmentPointer, -// i.e. undownloaded incoming attachments. -- (instancetype)initWithServerId:(UInt64)serverId - encryptionKey:(nullable NSData *)encryptionKey - byteCount:(UInt32)byteCount - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId; - -// This constructor is used for new instances of TSAttachmentPointer, -// i.e. undownloaded restoring attachments. -- (instancetype)initForRestoreWithUniqueId:(NSString *)uniqueId - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId; - -// This constructor is used for new instances of TSAttachmentStream -// that represent new, un-uploaded outgoing attachments. -- (instancetype)initWithContentType:(NSString *)contentType - byteCount:(UInt32)byteCount - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId; - -// This constructor is used for new instances of TSAttachmentStream -// that represent downloaded incoming attachments. -- (instancetype)initWithPointer:(TSAttachmentPointer *)pointer; - -- (nullable instancetype)initWithCoder:(NSCoder *)coder; - -- (void)upgradeFromAttachmentSchemaVersion:(NSUInteger)attachmentSchemaVersion; - -@property (nonatomic, readonly) BOOL isAnimated; -@property (nonatomic, readonly) BOOL isImage; -@property (nonatomic, readonly) BOOL isVideo; -@property (nonatomic, readonly) BOOL isAudio; -@property (nonatomic, readonly) BOOL isVoiceMessage; -@property (nonatomic, readonly) BOOL isVisualMedia; -@property (nonatomic, readonly) BOOL isOversizeText; - -+ (NSString *)emojiForMimeType:(NSString *)contentType; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachment.m b/SignalServiceKit/src/Messages/Attachments/TSAttachment.m deleted file mode 100644 index 999f0a718..000000000 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachment.m +++ /dev/null @@ -1,306 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSAttachment.h" -#import "MIMETypeUtil.h" -#import "NSString+SSK.h" -#import "TSAttachmentPointer.h" -#import "TSMessage.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -NSUInteger const TSAttachmentSchemaVersion = 4; - -@interface TSAttachment () - -@property (nonatomic, readonly) NSUInteger attachmentSchemaVersion; - -@property (nonatomic, nullable) NSString *sourceFilename; - -@property (nonatomic) NSString *contentType; - -@end - -@implementation TSAttachment - -// This constructor is used for new instances of TSAttachmentPointer, -// i.e. undownloaded incoming attachments. -- (instancetype)initWithServerId:(UInt64)serverId - encryptionKey:(nullable NSData *)encryptionKey - byteCount:(UInt32)byteCount - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId -{ - OWSAssertDebug(serverId > 0); - if (byteCount <= 0) { - // This will fail with legacy iOS clients which don't upload attachment size. - OWSLogWarn(@"Missing byteCount for attachment with serverId: %lld", serverId); - } - if (contentType.length < 1) { - OWSLogWarn(@"incoming attachment has invalid content type"); - - contentType = OWSMimeTypeApplicationOctetStream; - } - OWSAssertDebug(contentType.length > 0); - - self = [super init]; - if (!self) { - return self; - } - - _serverId = serverId; - _encryptionKey = encryptionKey; - _byteCount = byteCount; - _contentType = contentType; - _sourceFilename = sourceFilename; - _caption = caption; - _albumMessageId = albumMessageId; - - _attachmentSchemaVersion = TSAttachmentSchemaVersion; - - return self; -} - -// This constructor is used for new instances of TSAttachmentPointer, -// i.e. undownloaded restoring attachments. -- (instancetype)initForRestoreWithUniqueId:(NSString *)uniqueId - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId -{ - OWSAssertDebug(uniqueId.length > 0); - if (contentType.length < 1) { - OWSLogWarn(@"incoming attachment has invalid content type"); - - contentType = OWSMimeTypeApplicationOctetStream; - } - OWSAssertDebug(contentType.length > 0); - - // If saved, this AttachmentPointer would replace the AttachmentStream in the attachments collection. - // However we only use this AttachmentPointer should only be used during the export process so it - // won't be saved until we restore the backup (when there will be no AttachmentStream to replace). - self = [super initWithUniqueId:uniqueId]; - if (!self) { - return self; - } - - _contentType = contentType; - _sourceFilename = sourceFilename; - _caption = caption; - _albumMessageId = albumMessageId; - - _attachmentSchemaVersion = TSAttachmentSchemaVersion; - - return self; -} - -// This constructor is used for new instances of TSAttachmentStream -// that represent new, un-uploaded outgoing attachments. -- (instancetype)initWithContentType:(NSString *)contentType - byteCount:(UInt32)byteCount - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId -{ - if (contentType.length < 1) { - OWSLogWarn(@"outgoing attachment has invalid content type"); - - contentType = OWSMimeTypeApplicationOctetStream; - } - OWSAssertDebug(contentType.length > 0); - - self = [super init]; - if (!self) { - return self; - } - OWSLogVerbose(@"init attachment with uniqueId: %@", self.uniqueId); - - _contentType = contentType; - _byteCount = byteCount; - _sourceFilename = sourceFilename; - _caption = caption; - _albumMessageId = albumMessageId; - - _attachmentSchemaVersion = TSAttachmentSchemaVersion; - - return self; -} - -// This constructor is used for new instances of TSAttachmentStream -// that represent downloaded incoming attachments. -- (instancetype)initWithPointer:(TSAttachmentPointer *)pointer -{ - if (!pointer.lazyRestoreFragment) { - OWSAssertDebug(pointer.serverId > 0); - if (pointer.byteCount <= 0) { - // This will fail with legacy iOS clients which don't upload attachment size. - OWSLogWarn(@"Missing pointer.byteCount for attachment with serverId: %lld", pointer.serverId); - } - } - OWSAssertDebug(pointer.contentType.length > 0); - - // Once saved, this AttachmentStream will replace the AttachmentPointer in the attachments collection. - self = [super initWithUniqueId:pointer.uniqueId]; - if (!self) { - return self; - } - - _serverId = pointer.serverId; - _encryptionKey = pointer.encryptionKey; - _byteCount = pointer.byteCount; - _sourceFilename = pointer.sourceFilename; - NSString *contentType = pointer.contentType; - if (contentType.length < 1) { - OWSLogWarn(@"incoming attachment has invalid content type"); - - contentType = OWSMimeTypeApplicationOctetStream; - } - _contentType = contentType; - _caption = pointer.caption; - _albumMessageId = pointer.albumMessageId; - - _attachmentSchemaVersion = TSAttachmentSchemaVersion; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - if (_attachmentSchemaVersion < TSAttachmentSchemaVersion) { - [self upgradeFromAttachmentSchemaVersion:_attachmentSchemaVersion]; - _attachmentSchemaVersion = TSAttachmentSchemaVersion; - } - - if (!_sourceFilename) { - // renamed _filename to _sourceFilename - _sourceFilename = [coder decodeObjectForKey:@"filename"]; - OWSAssertDebug(!_sourceFilename || [_sourceFilename isKindOfClass:[NSString class]]); - } - - if (_contentType.length < 1) { - OWSLogWarn(@"legacy attachment has invalid content type"); - - _contentType = OWSMimeTypeApplicationOctetStream; - } - - return self; -} - -- (void)upgradeFromAttachmentSchemaVersion:(NSUInteger)attachmentSchemaVersion -{ - // This method is overridden by the base classes TSAttachmentPointer and - // TSAttachmentStream. -} - -+ (NSString *)collection { - return @"TSAttachements"; -} - -- (NSString *)description { - NSString *attachmentString = NSLocalizedString(@"ATTACHMENT", nil); - - if ([MIMETypeUtil isAudio:self.contentType]) { - // a missing filename is the legacy way to determine if an audio attachment is - // a voice note vs. other arbitrary audio attachments. - if (self.isVoiceMessage || !self.sourceFilename || self.sourceFilename.length == 0) { - attachmentString = NSLocalizedString(@"ATTACHMENT_TYPE_VOICE_MESSAGE", - @"Short text label for a voice message attachment, used for thread preview and on the lock screen"); - return [NSString stringWithFormat:@"🎤 %@", attachmentString]; - } - } - - return [NSString stringWithFormat:@"%@ %@", [TSAttachment emojiForMimeType:self.contentType], attachmentString]; -} - -+ (NSString *)emojiForMimeType:(NSString *)contentType -{ - if ([MIMETypeUtil isImage:contentType]) { - return @"📷"; - } else if ([MIMETypeUtil isVideo:contentType]) { - return @"🎥"; - } else if ([MIMETypeUtil isAudio:contentType]) { - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 0)) { - return @"🎧"; - } else { - return @"📻"; - } - } else if ([MIMETypeUtil isAnimated:contentType]) { - return @"🎡"; - } else { - return @"📎"; - } -} - -- (BOOL)isImage -{ - return [MIMETypeUtil isImage:self.contentType]; -} - -- (BOOL)isVideo -{ - return [MIMETypeUtil isVideo:self.contentType]; -} - -- (BOOL)isAudio -{ - return [MIMETypeUtil isAudio:self.contentType]; -} - -- (BOOL)isAnimated -{ - return [MIMETypeUtil isAnimated:self.contentType]; -} - -- (BOOL)isVoiceMessage -{ - return self.attachmentType == TSAttachmentTypeVoiceMessage; -} - -- (BOOL)isVisualMedia -{ - return [MIMETypeUtil isVisualMedia:self.contentType]; -} - -- (BOOL)isOversizeText -{ - return [self.contentType isEqualToString:OWSMimeTypeOversizeTextMessage]; -} - -- (nullable NSString *)sourceFilename -{ - return _sourceFilename.filterFilename; -} - -- (NSString *)contentType -{ - return _contentType.filterFilename; -} - -#pragma mark - Relationships - -- (nullable TSMessage *)fetchAlbumMessageWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - if (self.albumMessageId == nil) { - return nil; - } - return [TSMessage fetchObjectWithUniqueID:self.albumMessageId transaction:transaction]; -} - -- (void)migrateAlbumMessageId:(NSString *)albumMesssageId -{ - _albumMessageId = albumMesssageId; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.h b/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.h deleted file mode 100644 index 02af7b9a7..000000000 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.h +++ /dev/null @@ -1,74 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSAttachment.h" - -NS_ASSUME_NONNULL_BEGIN - -@class OWSBackupFragment; -@class SSKProtoAttachmentPointer; -@class TSAttachmentStream; -@class TSMessage; - -typedef NS_ENUM(NSUInteger, TSAttachmentPointerType) { - TSAttachmentPointerTypeUnknown = 0, - TSAttachmentPointerTypeIncoming = 1, - TSAttachmentPointerTypeRestoring = 2, -}; - -typedef NS_ENUM(NSUInteger, TSAttachmentPointerState) { - TSAttachmentPointerStateEnqueued = 0, - TSAttachmentPointerStateDownloading = 1, - TSAttachmentPointerStateFailed = 2, -}; - -/** - * A TSAttachmentPointer is a yet-to-be-downloaded attachment. - */ -@interface TSAttachmentPointer : TSAttachment - -@property (nonatomic) TSAttachmentPointerType pointerType; -@property (atomic) TSAttachmentPointerState state; -@property (nullable, atomic) NSString *mostRecentFailureLocalizedText; - -// Though now required, `digest` may be null for pre-existing records or from -// messages received from other clients -@property (nullable, nonatomic, readonly) NSData *digest; - -@property (nonatomic, readonly) CGSize mediaSize; - -// Non-nil for attachments which need "lazy backup restore." -- (nullable OWSBackupFragment *)lazyRestoreFragment; - -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithServerId:(UInt64)serverId - key:(nullable NSData *)key - digest:(nullable NSData *)digest - byteCount:(UInt32)byteCount - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId - attachmentType:(TSAttachmentType)attachmentType - mediaSize:(CGSize)mediaSize NS_DESIGNATED_INITIALIZER; - -- (instancetype)initForRestoreWithAttachmentStream:(TSAttachmentStream *)attachmentStream NS_DESIGNATED_INITIALIZER; - -+ (nullable TSAttachmentPointer *)attachmentPointerFromProto:(SSKProtoAttachmentPointer *)attachmentProto - albumMessage:(nullable TSMessage *)message; - -+ (NSArray *)attachmentPointersFromProtos: - (NSArray *)attachmentProtos - albumMessage:(TSMessage *)message; - -#pragma mark - Update With... Methods - -// Marks attachment as needing "lazy backup restore." -- (void)markForLazyRestoreWithFragment:(OWSBackupFragment *)lazyRestoreFragment - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.m deleted file mode 100644 index 690daf2d6..000000000 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentPointer.m +++ /dev/null @@ -1,265 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSAttachmentPointer.h" -#import "OWSBackupFragment.h" -#import "TSAttachmentStream.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface TSAttachmentStream (TSAttachmentPointer) - -- (CGSize)cachedMediaSize; - -@end - -#pragma mark - - - -@interface TSAttachmentPointer () - -// Optional property. Only set for attachments which need "lazy backup restore." -@property (nonatomic, nullable) NSString *lazyRestoreFragmentId; - -@end - -#pragma mark - - -@implementation TSAttachmentPointer - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - // A TSAttachmentPointer is a yet-to-be-downloaded attachment. - // If this is an old TSAttachmentPointer from another session, - // we know that it failed to complete before the session completed. - if (![coder containsValueForKey:@"state"]) { - _state = TSAttachmentPointerStateFailed; - } - - if (_pointerType == TSAttachmentPointerTypeUnknown) { - _pointerType = TSAttachmentPointerTypeIncoming; - } - - return self; -} - -- (instancetype)initWithServerId:(UInt64)serverId - key:(nullable NSData *)key - digest:(nullable NSData *)digest - byteCount:(UInt32)byteCount - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId - attachmentType:(TSAttachmentType)attachmentType - mediaSize:(CGSize)mediaSize -{ - self = [super initWithServerId:serverId - encryptionKey:key - byteCount:byteCount - contentType:contentType - sourceFilename:sourceFilename - caption:caption - albumMessageId:albumMessageId]; - if (!self) { - return self; - } - - _digest = digest; - _state = TSAttachmentPointerStateEnqueued; - self.attachmentType = attachmentType; - _pointerType = TSAttachmentPointerTypeIncoming; - _mediaSize = mediaSize; - - return self; -} - -- (instancetype)initForRestoreWithAttachmentStream:(TSAttachmentStream *)attachmentStream -{ - OWSAssertDebug(attachmentStream); - - self = [super initForRestoreWithUniqueId:attachmentStream.uniqueId - contentType:attachmentStream.contentType - sourceFilename:attachmentStream.sourceFilename - caption:attachmentStream.caption - albumMessageId:attachmentStream.albumMessageId]; - if (!self) { - return self; - } - - _state = TSAttachmentPointerStateEnqueued; - self.attachmentType = attachmentStream.attachmentType; - _pointerType = TSAttachmentPointerTypeRestoring; - _mediaSize = (attachmentStream.shouldHaveImageSize ? attachmentStream.cachedMediaSize : CGSizeZero); - - return self; -} - -+ (nullable TSAttachmentPointer *)attachmentPointerFromProto:(SSKProtoAttachmentPointer *)attachmentProto - albumMessage:(nullable TSMessage *)albumMessage -{ - if (attachmentProto.id < 1) { - OWSFailDebug(@"Invalid attachment id."); - return nil; - } - /* - if (attachmentProto.key.length < 1) { - OWSFailDebug(@"Invalid attachment key."); - return nil; - } - */ - NSString *_Nullable fileName = attachmentProto.fileName; - NSString *_Nullable contentType = attachmentProto.contentType; - if (contentType.length < 1) { - // Content type might not set if the sending client can't - // infer a MIME type from the file extension. - OWSLogWarn(@"Invalid attachment content type."); - NSString *_Nullable fileExtension = [fileName pathExtension].lowercaseString; - if (fileExtension.length > 0) { - contentType = [MIMETypeUtil mimeTypeForFileExtension:fileExtension]; - } - if (contentType.length < 1) { - contentType = OWSMimeTypeApplicationOctetStream; - } - } - - // digest will be empty for old clients. - NSData *_Nullable digest = attachmentProto.hasDigest ? attachmentProto.digest : nil; - - TSAttachmentType attachmentType = TSAttachmentTypeDefault; - if ([attachmentProto hasFlags]) { - UInt32 flags = attachmentProto.flags; - if ((flags & (UInt32)SSKProtoAttachmentPointerFlagsVoiceMessage) > 0) { - attachmentType = TSAttachmentTypeVoiceMessage; - } - } - NSString *_Nullable caption; - if (attachmentProto.hasCaption) { - caption = attachmentProto.caption; - } - - NSString *_Nullable albumMessageId; - if (albumMessage != nil) { - albumMessageId = albumMessage.uniqueId; - } - - CGSize mediaSize = CGSizeZero; - if (attachmentProto.hasWidth && attachmentProto.hasHeight && attachmentProto.width > 0 - && attachmentProto.height > 0) { - mediaSize = CGSizeMake(attachmentProto.width, attachmentProto.height); - } - - TSAttachmentPointer *pointer = [[TSAttachmentPointer alloc] initWithServerId:attachmentProto.id - key:attachmentProto.key - digest:digest - byteCount:attachmentProto.size - contentType:contentType - sourceFilename:fileName - caption:caption - albumMessageId:albumMessageId - attachmentType:attachmentType - mediaSize:mediaSize]; - - pointer.downloadURL = attachmentProto.url; // Loki - - return pointer; -} - -+ (NSArray *)attachmentPointersFromProtos: - (NSArray *)attachmentProtos - albumMessage:(TSMessage *)albumMessage -{ - OWSAssertDebug(attachmentProtos); - OWSAssertDebug(albumMessage); - - NSMutableArray *attachmentPointers = [NSMutableArray new]; - for (SSKProtoAttachmentPointer *attachmentProto in attachmentProtos) { - TSAttachmentPointer *_Nullable attachmentPointer = - [self attachmentPointerFromProto:attachmentProto albumMessage:albumMessage]; - if (attachmentPointer) { - [attachmentPointers addObject:attachmentPointer]; - } - } - return [attachmentPointers copy]; -} - -- (BOOL)isDecimalNumberText:(NSString *)text -{ - return [text componentsSeparatedByCharactersInSet:[NSCharacterSet decimalDigitCharacterSet]].count == 1; -} - -- (void)upgradeFromAttachmentSchemaVersion:(NSUInteger)attachmentSchemaVersion -{ - // Legacy instances of TSAttachmentPointer apparently used the serverId as their - // uniqueId. - if (attachmentSchemaVersion < 2 && self.serverId == 0) { - OWSAssertDebug([self isDecimalNumberText:self.uniqueId]); - if ([self isDecimalNumberText:self.uniqueId]) { - // For legacy instances, try to parse the serverId from the uniqueId. - self.serverId = [self.uniqueId integerValue]; - } else { - OWSLogError(@"invalid legacy attachment uniqueId: %@.", self.uniqueId); - } - } -} - -- (nullable OWSBackupFragment *)lazyRestoreFragment -{ - if (!self.lazyRestoreFragmentId) { - return nil; - } - OWSBackupFragment *_Nullable backupFragment = - [OWSBackupFragment fetchObjectWithUniqueID:self.lazyRestoreFragmentId]; - OWSAssertDebug(backupFragment); - return backupFragment; -} - -#pragma mark - Update With... Methods - -- (void)markForLazyRestoreWithFragment:(OWSBackupFragment *)lazyRestoreFragment - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(lazyRestoreFragment); - OWSAssertDebug(transaction); - - if (!lazyRestoreFragment.uniqueId) { - // If metadata hasn't been saved yet, save now. - [lazyRestoreFragment saveWithTransaction:transaction]; - - OWSAssertDebug(lazyRestoreFragment.uniqueId); - } - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSAttachmentPointer *attachment) { - [attachment setLazyRestoreFragmentId:lazyRestoreFragment.uniqueId]; - }]; -} - -- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ -#ifdef DEBUG - if (self.uniqueId.length > 0) { - id _Nullable oldObject = [transaction objectForKey:self.uniqueId inCollection:TSAttachment.collection]; - if ([oldObject isKindOfClass:[TSAttachmentStream class]]) { - OWSFailDebug(@"We should never overwrite a TSAttachmentStream with a TSAttachmentPointer."); - } - } else { - OWSFailDebug(@"Missing uniqueId."); - } -#endif - - [super saveWithTransaction:transaction]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h deleted file mode 100644 index 3c071d152..000000000 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h +++ /dev/null @@ -1,108 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "DataSource.h" -#import "TSAttachment.h" - -#if TARGET_OS_IPHONE -#import - -#endif - -NS_ASSUME_NONNULL_BEGIN - -@class SSKProtoAttachmentPointer; -@class TSAttachmentPointer; -@class YapDatabaseReadWriteTransaction; - -typedef void (^OWSThumbnailSuccess)(UIImage *image); -typedef void (^OWSThumbnailFailure)(void); - -@interface TSAttachmentStream : TSAttachment - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithContentType:(NSString *)contentType - byteCount:(UInt32)byteCount - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithPointer:(TSAttachmentPointer *)pointer NS_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -// Though now required, `digest` may be null for pre-existing records or from -// messages received from other clients -@property (nullable, nonatomic) NSData *digest; - -// This only applies for attachments being uploaded. -@property (atomic) BOOL isUploaded; - -@property (nonatomic, readonly) NSDate *creationTimestamp; - -#if TARGET_OS_IPHONE -- (nullable NSData *)validStillImageData; -#endif - -@property (nonatomic, readonly, nullable) UIImage *originalImage; -@property (nonatomic, readonly, nullable) NSString *originalFilePath; -@property (nonatomic, readonly, nullable) NSURL *originalMediaURL; - -- (NSArray *)allThumbnailPaths; - -+ (BOOL)hasThumbnailForMimeType:(NSString *)contentType; - -- (nullable NSData *)readDataFromFileWithError:(NSError **)error; -- (BOOL)writeData:(NSData *)data error:(NSError **)error; -- (BOOL)writeDataSource:(DataSource *)dataSource; - -+ (void)deleteAttachments; - -+ (NSString *)attachmentsFolder; -+ (NSString *)legacyAttachmentsDirPath; -+ (NSString *)sharedDataAttachmentsDirPath; - -- (BOOL)shouldHaveImageSize; -- (CGSize)imageSize; - -- (CGFloat)audioDurationSeconds; - -+ (nullable NSError *)migrateToSharedData; - -#pragma mark - Thumbnails - -// On cache hit, the thumbnail will be returned synchronously and completion will never be invoked. -// On cache miss, nil will be returned and success will be invoked if thumbnail can be generated; -// otherwise failure will be invoked. -// -// success and failure are invoked async on main. -- (nullable UIImage *)thumbnailImageWithSizeHint:(CGSize)sizeHint - success:(OWSThumbnailSuccess)success - failure:(OWSThumbnailFailure)failure; -- (nullable UIImage *)thumbnailImageSmallWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure; -- (nullable UIImage *)thumbnailImageMediumWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure; -- (nullable UIImage *)thumbnailImageLargeWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure; -- (nullable UIImage *)thumbnailImageSmallSync; - -// This method should only be invoked by OWSThumbnailService. -- (NSString *)pathForThumbnailDimensionPoints:(NSUInteger)thumbnailDimensionPoints; - -#pragma mark - Validation - -@property (nonatomic, readonly) BOOL isValidImage; -@property (nonatomic, readonly) BOOL isValidVideo; -@property (nonatomic, readonly) BOOL isValidVisualMedia; - -#pragma mark - Update With... Methods - -- (nullable TSAttachmentStream *)cloneAsThumbnail; - -#pragma mark - Protobuf - -+ (nullable SSKProtoAttachmentPointer *)buildProtoForAttachmentId:(nullable NSString *)attachmentId; - -- (nullable SSKProtoAttachmentPointer *)buildProto; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m deleted file mode 100644 index 44c96241c..000000000 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m +++ /dev/null @@ -1,887 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSAttachmentStream.h" -#import "MIMETypeUtil.h" -#import "NSData+Image.h" -#import "OWSFileSystem.h" -#import "TSAttachmentPointer.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -const NSUInteger kThumbnailDimensionPointsSmall = 200; -const NSUInteger kThumbnailDimensionPointsMedium = 450; -// This size is large enough to render full screen. -const NSUInteger ThumbnailDimensionPointsLarge() -{ - CGSize screenSizePoints = UIScreen.mainScreen.bounds.size; - const CGFloat kMinZoomFactor = 2.f; - return MAX(screenSizePoints.width, screenSizePoints.height) * kMinZoomFactor; -} - -typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail); - -@interface TSAttachmentStream () - -// We only want to generate the file path for this attachment once, so that -// changes in the file path generation logic don't break existing attachments. -@property (nullable, nonatomic) NSString *localRelativeFilePath; - -// These properties should only be accessed while synchronized on self. -@property (nullable, nonatomic) NSNumber *cachedImageWidth; -@property (nullable, nonatomic) NSNumber *cachedImageHeight; - -// This property should only be accessed on the main thread. -@property (nullable, nonatomic) NSNumber *cachedAudioDurationSeconds; - -@property (atomic, nullable) NSNumber *isValidImageCached; -@property (atomic, nullable) NSNumber *isValidVideoCached; - -@end - -#pragma mark - - -@implementation TSAttachmentStream - -- (instancetype)initWithContentType:(NSString *)contentType - byteCount:(UInt32)byteCount - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId -{ - self = [super initWithContentType:contentType - byteCount:byteCount - sourceFilename:sourceFilename - caption:caption - albumMessageId:albumMessageId]; - if (!self) { - return self; - } - - self.isDownloaded = YES; - // TSAttachmentStream doesn't have any "incoming vs. outgoing" - // state, but this constructor is used only for new outgoing - // attachments which haven't been uploaded yet. - _isUploaded = NO; - _creationTimestamp = [NSDate new]; - - [self ensureFilePath]; - - return self; -} - -- (instancetype)initWithPointer:(TSAttachmentPointer *)pointer -{ - // Once saved, this AttachmentStream will replace the AttachmentPointer in the attachments collection. - self = [super initWithPointer:pointer]; - if (!self) { - return self; - } - - _contentType = pointer.contentType; - self.isDownloaded = YES; - // TSAttachmentStream doesn't have any "incoming vs. outgoing" - // state, but this constructor is used only for new incoming - // attachments which don't need to be uploaded. - _isUploaded = YES; - self.attachmentType = pointer.attachmentType; - _creationTimestamp = [NSDate new]; - - [self ensureFilePath]; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - // OWS105AttachmentFilePaths will ensure the file path is saved if necessary. - [self ensureFilePath]; - - // OWS105AttachmentFilePaths will ensure the creation timestamp is saved if necessary. - if (!_creationTimestamp) { - _creationTimestamp = [NSDate new]; - } - - return self; -} - -- (void)upgradeFromAttachmentSchemaVersion:(NSUInteger)attachmentSchemaVersion -{ - [super upgradeFromAttachmentSchemaVersion:attachmentSchemaVersion]; - - if (attachmentSchemaVersion < 3) { - // We want to treat any legacy TSAttachmentStream as though - // they have already been uploaded. If it needs to be reuploaded, - // the OWSUploadingService will update this progress when the - // upload begins. - self.isUploaded = YES; - } - - if (attachmentSchemaVersion < 4) { - // Legacy image sizes don't correctly reflect image orientation. - @synchronized(self) - { - self.cachedImageWidth = nil; - self.cachedImageHeight = nil; - } - } -} - -- (void)ensureFilePath -{ - if (self.localRelativeFilePath) { - return; - } - - NSString *attachmentsFolder = [[self class] attachmentsFolder]; - NSString *filePath = [MIMETypeUtil filePathForAttachment:self.uniqueId - ofMIMEType:self.contentType - sourceFilename:self.sourceFilename - inFolder:attachmentsFolder]; - if (!filePath) { - OWSFailDebug(@"Could not generate path for attachment."); - return; - } - if (![filePath hasPrefix:attachmentsFolder]) { - OWSFailDebug(@"Attachment paths should all be in the attachments folder."); - return; - } - NSString *localRelativeFilePath = [filePath substringFromIndex:attachmentsFolder.length]; - if (localRelativeFilePath.length < 1) { - OWSFailDebug(@"Empty local relative attachment paths."); - return; - } - - self.localRelativeFilePath = localRelativeFilePath; - OWSAssertDebug(self.originalFilePath); -} - -#pragma mark - File Management - -- (nullable NSData *)readDataFromFileWithError:(NSError **)error -{ - *error = nil; - NSString *_Nullable filePath = self.originalFilePath; - if (!filePath) { - OWSFailDebug(@"Missing path for attachment."); - return nil; - } - return [NSData dataWithContentsOfFile:filePath options:0 error:error]; -} - -- (BOOL)writeData:(NSData *)data error:(NSError **)error -{ - OWSAssertDebug(data); - - *error = nil; - NSString *_Nullable filePath = self.originalFilePath; - if (!filePath) { - OWSFailDebug(@"Missing path for attachment."); - return NO; - } - OWSLogDebug(@"Writing attachment to file: %@", filePath); - return [data writeToFile:filePath options:0 error:error]; -} - -- (BOOL)writeDataSource:(DataSource *)dataSource -{ - OWSAssertDebug(dataSource); - - NSString *_Nullable filePath = self.originalFilePath; - if (!filePath) { - OWSFailDebug(@"Missing path for attachment."); - return NO; - } - OWSLogDebug(@"Writing attachment to file: %@", filePath); - return [dataSource writeToPath:filePath]; -} - -+ (NSString *)legacyAttachmentsDirPath -{ - return [[OWSFileSystem appDocumentDirectoryPath] stringByAppendingPathComponent:@"Attachments"]; -} - -+ (NSString *)sharedDataAttachmentsDirPath -{ - return [[OWSFileSystem appSharedDataDirectoryPath] stringByAppendingPathComponent:@"Attachments"]; -} - -+ (nullable NSError *)migrateToSharedData -{ - OWSLogInfo(@""); - - return [OWSFileSystem moveAppFilePath:self.legacyAttachmentsDirPath - sharedDataFilePath:self.sharedDataAttachmentsDirPath]; -} - -+ (NSString *)attachmentsFolder -{ - static NSString *attachmentsFolder = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - attachmentsFolder = TSAttachmentStream.sharedDataAttachmentsDirPath; - - [OWSFileSystem ensureDirectoryExists:attachmentsFolder]; - }); - return attachmentsFolder; -} - -- (nullable NSString *)originalFilePath -{ - if (!self.localRelativeFilePath) { - OWSFailDebug(@"Attachment missing local file path."); - return nil; - } - - return [[[self class] attachmentsFolder] stringByAppendingPathComponent:self.localRelativeFilePath]; -} - -- (nullable NSString *)legacyThumbnailPath -{ - NSString *filePath = self.originalFilePath; - if (!filePath) { - OWSFailDebug(@"Attachment missing local file path."); - return nil; - } - - if (!self.isImage && !self.isVideo && !self.isAnimated) { - return nil; - } - - NSString *filename = filePath.lastPathComponent.stringByDeletingPathExtension; - NSString *containingDir = filePath.stringByDeletingLastPathComponent; - NSString *newFilename = [filename stringByAppendingString:@"-signal-ios-thumbnail"]; - - return [[containingDir stringByAppendingPathComponent:newFilename] stringByAppendingPathExtension:@"jpg"]; -} - -- (NSString *)thumbnailsDirPath -{ - if (!self.localRelativeFilePath) { - OWSFailDebug(@"Attachment missing local file path."); - return nil; - } - - // Thumbnails are written to the caches directory, so that iOS can - // remove them if necessary. - NSString *dirName = [NSString stringWithFormat:@"%@-thumbnails", self.uniqueId]; - return [OWSFileSystem.cachesDirectoryPath stringByAppendingPathComponent:dirName]; -} - -- (NSString *)pathForThumbnailDimensionPoints:(NSUInteger)thumbnailDimensionPoints -{ - NSString *filename = [NSString stringWithFormat:@"thumbnail-%lu.jpg", (unsigned long)thumbnailDimensionPoints]; - return [self.thumbnailsDirPath stringByAppendingPathComponent:filename]; -} - -- (nullable NSURL *)originalMediaURL -{ - NSString *_Nullable filePath = self.originalFilePath; - if (!filePath) { - OWSFailDebug(@"Missing path for attachment."); - return nil; - } - return [NSURL fileURLWithPath:filePath]; -} - -- (void)removeFileWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - NSError *error; - - NSString *thumbnailsDirPath = self.thumbnailsDirPath; - if ([[NSFileManager defaultManager] fileExistsAtPath:thumbnailsDirPath]) { - BOOL success = [[NSFileManager defaultManager] removeItemAtPath:thumbnailsDirPath error:&error]; - if (error || !success) { - OWSLogError(@"remove thumbnails dir failed with: %@", error); - } - } - - NSString *_Nullable legacyThumbnailPath = self.legacyThumbnailPath; - if (legacyThumbnailPath) { - BOOL success = [[NSFileManager defaultManager] removeItemAtPath:legacyThumbnailPath error:&error]; - - if (error || !success) { - OWSLogError(@"remove legacy thumbnail failed with: %@", error); - } - } - - NSString *_Nullable filePath = self.originalFilePath; - if (!filePath) { - OWSFailDebug(@"Missing path for attachment."); - return; - } - BOOL success = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; - if (error || !success) { - OWSLogError(@"remove file failed with: %@", error); - } -} - -- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [super removeWithTransaction:transaction]; - [self removeFileWithTransaction:transaction]; -} - -- (BOOL)isValidVisualMedia -{ - if (self.isImage && self.isValidImage) { - return YES; - } - - if (self.isVideo && self.isValidVideo) { - return YES; - } - - if (self.isAnimated && self.isValidImage) { - return YES; - } - - return NO; -} - -#pragma mark - Image Validation - -- (BOOL)isValidImage -{ - OWSAssertDebug(self.isImage || self.isAnimated); - - BOOL result; - BOOL didUpdateCache = NO; - @synchronized(self) { - if (!self.isValidImageCached) { - OWSLogVerbose(@"Updating isValidImageCached."); - self.isValidImageCached = @([NSData ows_isValidImageAtPath:self.originalFilePath - mimeType:self.contentType]); - didUpdateCache = YES; - } - result = self.isValidImageCached.boolValue; - } - - if (didUpdateCache) { - [self applyChangeAsyncToLatestCopyWithChangeBlock:^(TSAttachmentStream *latestInstance) { - latestInstance.isValidImageCached = @(result); - }]; - } - - return result; -} - -- (BOOL)isValidVideo -{ - OWSAssertDebug(self.isVideo); - - BOOL result; - BOOL didUpdateCache = NO; - @synchronized(self) { - if (!self.isValidVideoCached) { - OWSLogVerbose(@"Updating isValidVideoCached."); - self.isValidVideoCached = @([OWSMediaUtils isValidVideoWithPath:self.originalFilePath]); - didUpdateCache = YES; - } - result = self.isValidVideoCached.boolValue; - } - - if (didUpdateCache) { - [self applyChangeAsyncToLatestCopyWithChangeBlock:^(TSAttachmentStream *latestInstance) { - latestInstance.isValidVideoCached = @(result); - }]; - } - - return result; -} - -#pragma mark - - -- (nullable UIImage *)originalImage -{ - if ([self isVideo]) { - return [self videoStillImage]; - } else if ([self isImage] || [self isAnimated]) { - NSURL *_Nullable mediaUrl = self.originalMediaURL; - if (!mediaUrl) { - return nil; - } - if (![self isValidImage]) { - return nil; - } - return [[UIImage alloc] initWithContentsOfFile:self.originalFilePath]; - } else { - return nil; - } -} - -- (nullable NSData *)validStillImageData -{ - if ([self isVideo]) { - OWSFailDebug(@"isVideo was unexpectedly true"); - return nil; - } - if ([self isAnimated]) { - OWSFailDebug(@"isAnimated was unexpectedly true"); - return nil; - } - - if (![NSData ows_isValidImageAtPath:self.originalFilePath mimeType:self.contentType]) { - OWSFailDebug(@"skipping invalid image"); - return nil; - } - - return [NSData dataWithContentsOfFile:self.originalFilePath]; -} - -+ (BOOL)hasThumbnailForMimeType:(NSString *)contentType -{ - return ([MIMETypeUtil isVideo:contentType] || [MIMETypeUtil isImage:contentType] || - [MIMETypeUtil isAnimated:contentType]); -} - -- (nullable UIImage *)videoStillImage -{ - NSError *error; - UIImage *_Nullable image = [OWSMediaUtils thumbnailForVideoAtPath:self.originalFilePath - maxDimension:ThumbnailDimensionPointsLarge() - error:&error]; - if (error || !image) { - OWSLogError(@"Could not create video still: %@.", error); - return nil; - } - return image; -} - -+ (void)deleteAttachments -{ - NSError *error; - NSFileManager *fileManager = [NSFileManager defaultManager]; - - NSURL *fileURL = [NSURL fileURLWithPath:self.attachmentsFolder]; - NSArray *contents = - [fileManager contentsOfDirectoryAtURL:fileURL includingPropertiesForKeys:nil options:0 error:&error]; - - if (error) { - OWSFailDebug(@"failed to get contents of attachments folder: %@ with error: %@", self.attachmentsFolder, error); - return; - } - - for (NSURL *url in contents) { - [fileManager removeItemAtURL:url error:&error]; - if (error) { - OWSFailDebug(@"failed to remove item at path: %@ with error: %@", url, error); - } - } -} - -- (CGSize)calculateImageSize -{ - if ([self isVideo]) { - if (![self isValidVideo]) { - return CGSizeZero; - } - return [self videoStillImage].size; - } else if ([self isImage] || [self isAnimated]) { - // imageSizeForFilePath checks validity. - return [NSData imageSizeForFilePath:self.originalFilePath mimeType:self.contentType]; - } else { - return CGSizeZero; - } -} - -- (BOOL)shouldHaveImageSize -{ - return ([self isVideo] || [self isImage] || [self isAnimated]); -} - -- (CGSize)imageSize -{ - // Avoid crash in dev mode - // OWSAssertDebug(self.shouldHaveImageSize); - - @synchronized(self) - { - if (self.cachedImageWidth && self.cachedImageHeight) { - return CGSizeMake(self.cachedImageWidth.floatValue, self.cachedImageHeight.floatValue); - } - - CGSize imageSize = [self calculateImageSize]; - if (imageSize.width <= 0 || imageSize.height <= 0) { - return CGSizeZero; - } - self.cachedImageWidth = @(imageSize.width); - self.cachedImageHeight = @(imageSize.height); - - [self applyChangeAsyncToLatestCopyWithChangeBlock:^(TSAttachmentStream *latestInstance) { - latestInstance.cachedImageWidth = @(imageSize.width); - latestInstance.cachedImageHeight = @(imageSize.height); - }]; - - return imageSize; - } -} - -- (CGSize)cachedMediaSize -{ - OWSAssertDebug(self.shouldHaveImageSize); - - @synchronized(self) { - if (self.cachedImageWidth && self.cachedImageHeight) { - return CGSizeMake(self.cachedImageWidth.floatValue, self.cachedImageHeight.floatValue); - } else { - return CGSizeZero; - } - } -} - -#pragma mark - Update With... - -- (void)applyChangeAsyncToLatestCopyWithChangeBlock:(void (^)(TSAttachmentStream *))changeBlock -{ - OWSAssertDebug(changeBlock); - - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - NSString *collection = [TSAttachmentStream collection]; - TSAttachmentStream *latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection]; - if (!latestInstance) { - // This attachment has either not yet been saved or has been deleted; do nothing. - // This isn't an error per se, but these race conditions should be - // _very_ rare. - // - // An exception is incoming group avatar updates which we don't ever save. - OWSLogWarn(@"Attachment not yet saved."); - } else if (![latestInstance isKindOfClass:[TSAttachmentStream class]]) { - OWSFailDebug(@"Attachment has unexpected type: %@", latestInstance.class); - } else { - changeBlock(latestInstance); - - [latestInstance saveWithTransaction:transaction]; - } - }]; -} - -#pragma mark - - -- (CGFloat)calculateAudioDurationSeconds -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug([self isAudio]); - - NSError *error; - AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:self.originalMediaURL error:&error]; - if (error && [error.domain isEqualToString:NSOSStatusErrorDomain] - && (error.code == kAudioFileInvalidFileError || error.code == kAudioFileStreamError_InvalidFile)) { - // Ignore "invalid audio file" errors. - return 0.f; - } - if (!error) { - return (CGFloat)[audioPlayer duration]; - } else { - OWSLogError(@"Could not find audio duration: %@", self.originalMediaURL); - return 0; - } -} - -- (CGFloat)audioDurationSeconds -{ - OWSAssertIsOnMainThread(); - - if (self.cachedAudioDurationSeconds) { - return self.cachedAudioDurationSeconds.floatValue; - } - - CGFloat audioDurationSeconds = [self calculateAudioDurationSeconds]; - self.cachedAudioDurationSeconds = @(audioDurationSeconds); - - [self applyChangeAsyncToLatestCopyWithChangeBlock:^(TSAttachmentStream *latestInstance) { - latestInstance.cachedAudioDurationSeconds = @(audioDurationSeconds); - }]; - - return audioDurationSeconds; -} - -#pragma mark - Thumbnails - -- (nullable UIImage *)thumbnailImageWithSizeHint:(CGSize)sizeHint - success:(OWSThumbnailSuccess)success - failure:(OWSThumbnailFailure)failure -{ - CGFloat maxDimensionHint = MAX(sizeHint.width, sizeHint.height); - NSUInteger thumbnailDimensionPoints; - if (maxDimensionHint <= kThumbnailDimensionPointsSmall) { - thumbnailDimensionPoints = kThumbnailDimensionPointsSmall; - } else if (maxDimensionHint <= kThumbnailDimensionPointsMedium) { - thumbnailDimensionPoints = kThumbnailDimensionPointsMedium; - } else { - thumbnailDimensionPoints = ThumbnailDimensionPointsLarge(); - } - - return [self thumbnailImageWithThumbnailDimensionPoints:thumbnailDimensionPoints success:success failure:failure]; -} - -- (nullable UIImage *)thumbnailImageSmallWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure -{ - return [self thumbnailImageWithThumbnailDimensionPoints:kThumbnailDimensionPointsSmall - success:success - failure:failure]; -} - -- (nullable UIImage *)thumbnailImageMediumWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure -{ - return [self thumbnailImageWithThumbnailDimensionPoints:kThumbnailDimensionPointsMedium - success:success - failure:failure]; -} - -- (nullable UIImage *)thumbnailImageLargeWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure -{ - return [self thumbnailImageWithThumbnailDimensionPoints:ThumbnailDimensionPointsLarge() - success:success - failure:failure]; -} - -- (nullable UIImage *)thumbnailImageWithThumbnailDimensionPoints:(NSUInteger)thumbnailDimensionPoints - success:(OWSThumbnailSuccess)success - failure:(OWSThumbnailFailure)failure -{ - OWSLoadedThumbnail *_Nullable loadedThumbnail; - loadedThumbnail = [self loadedThumbnailWithThumbnailDimensionPoints:thumbnailDimensionPoints - success:^(OWSLoadedThumbnail *thumbnail) { - DispatchMainThreadSafe(^{ - success(thumbnail.image); - }); - } - failure:^{ - DispatchMainThreadSafe(^{ - failure(); - }); - }]; - return loadedThumbnail.image; -} - -- (nullable OWSLoadedThumbnail *)loadedThumbnailWithThumbnailDimensionPoints:(NSUInteger)thumbnailDimensionPoints - success:(OWSLoadedThumbnailSuccess)success - failure:(OWSThumbnailFailure)failure -{ - CGSize originalSize = self.imageSize; - if (originalSize.width < 1 || originalSize.height < 1) { - // Any time we return nil from this method we have to call the failure handler - // or else the caller waits for an async thumbnail - failure(); - return nil; - } - if (originalSize.width <= thumbnailDimensionPoints || originalSize.height <= thumbnailDimensionPoints) { - // There's no point in generating a thumbnail if the original is smaller than the - // thumbnail size. - return [[OWSLoadedThumbnail alloc] initWithImage:self.originalImage filePath:self.originalFilePath]; - } - - NSString *thumbnailPath = [self pathForThumbnailDimensionPoints:thumbnailDimensionPoints]; - if ([[NSFileManager defaultManager] fileExistsAtPath:thumbnailPath]) { - UIImage *_Nullable image = [UIImage imageWithContentsOfFile:thumbnailPath]; - if (!image) { - OWSFailDebug(@"couldn't load image."); - // Any time we return nil from this method we have to call the failure handler - // or else the caller waits for an async thumbnail - failure(); - return nil; - } - return [[OWSLoadedThumbnail alloc] initWithImage:image filePath:thumbnailPath]; - } - - [OWSThumbnailService.shared ensureThumbnailForAttachment:self - thumbnailDimensionPoints:thumbnailDimensionPoints - success:success - failure:^(NSError *error) { - OWSLogError(@"Failed to create thumbnail: %@", error); - failure(); - }]; - return nil; -} - -- (nullable OWSLoadedThumbnail *)loadedThumbnailSmallSync -{ - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - __block OWSLoadedThumbnail *_Nullable asyncLoadedThumbnail = nil; - OWSLoadedThumbnail *_Nullable syncLoadedThumbnail = nil; - syncLoadedThumbnail = [self loadedThumbnailWithThumbnailDimensionPoints:kThumbnailDimensionPointsSmall - success:^(OWSLoadedThumbnail *thumbnail) { - @synchronized(self) { - asyncLoadedThumbnail = thumbnail; - } - dispatch_semaphore_signal(semaphore); - } - failure:^{ - dispatch_semaphore_signal(semaphore); - }]; - - if (syncLoadedThumbnail) { - return syncLoadedThumbnail; - } - - // Wait up to N seconds. - dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC))); - @synchronized(self) { - return asyncLoadedThumbnail; - } -} - -- (nullable UIImage *)thumbnailImageSmallSync -{ - OWSLoadedThumbnail *_Nullable loadedThumbnail = [self loadedThumbnailSmallSync]; - if (!loadedThumbnail) { - OWSLogInfo(@"Couldn't load small thumbnail sync."); - return nil; - } - return loadedThumbnail.image; -} - -- (nullable NSData *)thumbnailDataSmallSync -{ - OWSLoadedThumbnail *_Nullable loadedThumbnail = [self loadedThumbnailSmallSync]; - if (!loadedThumbnail) { - OWSLogInfo(@"Couldn't load small thumbnail sync."); - return nil; - } - NSError *error; - NSData *_Nullable data = [loadedThumbnail dataAndReturnError:&error]; - if (error || !data) { - OWSFailDebug(@"Couldn't load thumbnail data: %@", error); - return nil; - } - return data; -} - -- (NSArray *)allThumbnailPaths -{ - NSMutableArray *result = [NSMutableArray new]; - - NSString *thumbnailsDirPath = self.thumbnailsDirPath; - if ([[NSFileManager defaultManager] fileExistsAtPath:thumbnailsDirPath]) { - NSError *error; - NSArray *_Nullable fileNames = - [[NSFileManager defaultManager] contentsOfDirectoryAtPath:thumbnailsDirPath error:&error]; - if (error || !fileNames) { - OWSFailDebug(@"contentsOfDirectoryAtPath failed with error: %@", error); - } else { - for (NSString *fileName in fileNames) { - NSString *filePath = [thumbnailsDirPath stringByAppendingPathComponent:fileName]; - [result addObject:filePath]; - } - } - } - - NSString *_Nullable legacyThumbnailPath = self.legacyThumbnailPath; - if (legacyThumbnailPath && [[NSFileManager defaultManager] fileExistsAtPath:legacyThumbnailPath]) { - [result addObject:legacyThumbnailPath]; - } - - return result; -} - -#pragma mark - Update With... Methods - -- (nullable TSAttachmentStream *)cloneAsThumbnail -{ - if (!self.isValidVisualMedia) { - return nil; - } - - NSData *_Nullable thumbnailData = self.thumbnailDataSmallSync; - // Only some media types have thumbnails - if (!thumbnailData) { - return nil; - } - - // Copy the thumbnail to a new attachment. - NSString *thumbnailName = [NSString stringWithFormat:@"quoted-thumbnail-%@", self.sourceFilename]; - TSAttachmentStream *thumbnailAttachment = - [[TSAttachmentStream alloc] initWithContentType:OWSMimeTypeImageJpeg - byteCount:(uint32_t)thumbnailData.length - sourceFilename:thumbnailName - caption:nil - albumMessageId:nil]; - - NSError *error; - BOOL success = [thumbnailAttachment writeData:thumbnailData error:&error]; - if (!success || error) { - OWSLogError(@"Couldn't copy attachment data for message sent to self: %@.", error); - return nil; - } - - return thumbnailAttachment; -} - -// MARK: Protobuf serialization - -+ (nullable SSKProtoAttachmentPointer *)buildProtoForAttachmentId:(nullable NSString *)attachmentId -{ - OWSAssertDebug(attachmentId.length > 0); - - // TODO we should past in a transaction, rather than sneakily generate one in `fetch...` to make sure we're - // getting a consistent view in the message sending process. A brief glance shows it touches quite a bit of code, - // but should be straight forward. - TSAttachment *attachment = [TSAttachmentStream fetchObjectWithUniqueID:attachmentId]; - if (![attachment isKindOfClass:[TSAttachmentStream class]]) { - OWSLogError(@"Unexpected type for attachment builder: %@", attachment); - return nil; - } - - TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; - return [attachmentStream buildProto]; -} - - -- (nullable SSKProtoAttachmentPointer *)buildProto -{ - SSKProtoAttachmentPointerBuilder *builder = [SSKProtoAttachmentPointer builderWithId:self.serverId]; - - OWSAssertDebug(self.contentType.length > 0); - builder.contentType = self.contentType; - - OWSLogVerbose(@"Sending attachment with filename: '%@'", self.sourceFilename); - if (self.sourceFilename.length > 0) { - builder.fileName = self.sourceFilename; - } - if (self.caption.length > 0) { - builder.caption = self.caption; - } - - builder.size = self.byteCount; - builder.key = self.encryptionKey; - builder.digest = self.digest; - builder.flags = self.isVoiceMessage ? SSKProtoAttachmentPointerFlagsVoiceMessage : 0; - - if (self.shouldHaveImageSize) { - CGSize imageSize = self.imageSize; - if (imageSize.width < NSIntegerMax && imageSize.height < NSIntegerMax) { - NSInteger imageWidth = (NSInteger)round(imageSize.width); - NSInteger imageHeight = (NSInteger)round(imageSize.height); - if (imageWidth > 0 && imageHeight > 0) { - builder.width = (UInt32)imageWidth; - builder.height = (UInt32)imageHeight; - } - } - } - - builder.url = self.downloadURL; - - NSError *error; - SSKProtoAttachmentPointer *_Nullable attachmentProto = [builder buildAndReturnError:&error]; - if (error || !attachmentProto) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - return attachmentProto; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.h b/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.h deleted file mode 100644 index 87c8ea79e..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSContact; -@class OWSLinkPreview; -@class SSKProtoAttachmentPointer; -@class SSKProtoDataMessage; -@class SSKProtoSyncMessageSent; -@class TSQuotedMessage; -@class TSThread; -@class YapDatabaseReadWriteTransaction; - -/** - * Represents notification of a message sent on our behalf from another device. - * E.g. When we send a message from Signal-Desktop we want to see it in our conversation on iPhone. - */ -@interface OWSIncomingSentMessageTranscript : NSObject - -- (instancetype)initWithProto:(SSKProtoSyncMessageSent *)sentProto - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -@property (nonatomic, readonly) SSKProtoDataMessage *dataMessage; -@property (nonatomic, readonly) NSString *recipientId; -@property (nonatomic, readonly) uint64_t timestamp; -@property (nonatomic, readonly) uint64_t expirationStartedAt; -@property (nonatomic, readonly) uint32_t expirationDuration; -@property (nonatomic, readonly) BOOL isGroupUpdate; -@property (nonatomic, readonly) BOOL isGroupQuit; -@property (nonatomic, readonly) BOOL isExpirationTimerUpdate; -@property (nonatomic, readonly) BOOL isEndSessionMessage; -@property (nonatomic, readonly, nullable) NSData *groupId; -@property (nonatomic, readonly) NSString *body; -@property (nonatomic, readonly) NSArray *attachmentPointerProtos; -@property (nonatomic, readonly, nullable) TSThread *thread; -@property (nonatomic, readonly, nullable) TSQuotedMessage *quotedMessage; -@property (nonatomic, readonly, nullable) OWSContact *contact; -@property (nonatomic, readonly, nullable) OWSLinkPreview *linkPreview; -@property (nonatomic, readonly) BOOL isRecipientUpdate; - -// If either nonUdRecipientIds or udRecipientIds is nil, -// this is either a legacy transcript or it reflects a legacy sync message. -@property (nonatomic, readonly, nullable) NSArray *nonUdRecipientIds; -@property (nonatomic, readonly, nullable) NSArray *udRecipientIds; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m deleted file mode 100644 index 3a0b2abe5..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m +++ /dev/null @@ -1,108 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSIncomingSentMessageTranscript.h" -#import "OWSContact.h" -#import "OWSMessageManager.h" -#import "OWSPrimaryStorage.h" -#import "TSContactThread.h" -#import "TSGroupModel.h" -#import "TSGroupThread.h" -#import "TSOutgoingMessage.h" -#import "TSQuotedMessage.h" -#import "TSThread.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSIncomingSentMessageTranscript - -- (instancetype)initWithProto:(SSKProtoSyncMessageSent *)sentProto - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - self = [super init]; - if (!self) { - return self; - } - - _dataMessage = sentProto.message; - _recipientId = sentProto.destination; - _timestamp = sentProto.timestamp; - _expirationStartedAt = sentProto.expirationStartTimestamp; - _expirationDuration = sentProto.message.expireTimer; - _body = _dataMessage.body; - _groupId = _dataMessage.group.id; - _isGroupUpdate = _dataMessage.group != nil && (_dataMessage.group.type == SSKProtoGroupContextTypeUpdate); - _isGroupQuit = _dataMessage.group != nil && (_dataMessage.group.type == SSKProtoGroupContextTypeQuit); - _isExpirationTimerUpdate = (_dataMessage.flags & SSKProtoDataMessageFlagsExpirationTimerUpdate) != 0; - _isEndSessionMessage = (_dataMessage.flags & SSKProtoDataMessageFlagsEndSession) != 0; - _isRecipientUpdate = sentProto.isRecipientUpdate; - - if (self.isRecipientUpdate) { - // Fetch, don't create. We don't want recipient updates to resurrect messages or threads. - if (self.dataMessage.group) { - _thread = [TSGroupThread threadWithGroupId:_dataMessage.group.id transaction:transaction]; - } else { - OWSFailDebug(@"We should never receive a 'recipient update' for messages in contact threads."); - } - // Skip the other processing for recipient updates. - } else { - if (self.dataMessage.group) { - _thread = [TSGroupThread getOrCreateThreadWithGroupId:_dataMessage.group.id groupType:closedGroup transaction:transaction]; - } else { - _thread = [TSContactThread getOrCreateThreadWithContactId:_recipientId transaction:transaction]; - } - - _quotedMessage = - [TSQuotedMessage quotedMessageForDataMessage:_dataMessage thread:_thread transaction:transaction]; - _contact = [OWSContacts contactForDataMessage:_dataMessage transaction:transaction]; - - NSError *linkPreviewError; - _linkPreview = [OWSLinkPreview buildValidatedLinkPreviewWithDataMessage:_dataMessage - body:_body - transaction:transaction - error:&linkPreviewError]; - if (linkPreviewError && ![OWSLinkPreview isNoPreviewError:linkPreviewError]) { - OWSLogError(@"linkPreviewError: %@", linkPreviewError); - } - } - - if (sentProto.unidentifiedStatus.count > 0) { - NSMutableArray *nonUdRecipientIds = [NSMutableArray new]; - NSMutableArray *udRecipientIds = [NSMutableArray new]; - for (SSKProtoSyncMessageSentUnidentifiedDeliveryStatus *statusProto in sentProto.unidentifiedStatus) { - if (!statusProto.hasDestination || statusProto.destination.length < 1) { - OWSFailDebug(@"Delivery status proto is missing destination."); - continue; - } - if (!statusProto.hasUnidentified) { - OWSFailDebug(@"Delivery status proto is missing value."); - continue; - } - NSString *recipientId = statusProto.destination; - if (statusProto.unidentified) { - [udRecipientIds addObject:recipientId]; - } else { - [nonUdRecipientIds addObject:recipientId]; - } - } - _nonUdRecipientIds = [nonUdRecipientIds copy]; - _udRecipientIds = [udRecipientIds copy]; - } - - return self; -} - -- (NSArray *)attachmentPointerProtos -{ - if (self.isGroupUpdate && self.dataMessage.group.avatar) { - return @[ self.dataMessage.group.avatar ]; - } else { - return self.dataMessage.attachments; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentMessageTranscript.h b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentMessageTranscript.h deleted file mode 100644 index ff47ee6e2..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentMessageTranscript.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingSyncMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class TSOutgoingMessage; - -/** - * Notifies your other registered devices (if you have any) that you've sent a message. - * This way the message you just sent can appear on all your devices. - */ -@interface OWSOutgoingSentMessageTranscript : OWSOutgoingSyncMessage - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithOutgoingMessage:(TSOutgoingMessage *)message - isRecipientUpdate:(BOOL)isRecipientUpdate NS_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentMessageTranscript.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentMessageTranscript.m deleted file mode 100644 index fa1d9050e..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentMessageTranscript.m +++ /dev/null @@ -1,118 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingSentMessageTranscript.h" -#import "TSOutgoingMessage.h" -#import "TSThread.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface TSOutgoingMessage (OWSOutgoingSentMessageTranscript) - -/** - * Normally this is private, but we need to embed this - * data structure within our own. - * - * recipientId is nil when building "sent" sync messages for messages - * sent to groups. - */ -- (nullable SSKProtoDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId; - -@end - -#pragma mark - - -@interface OWSOutgoingSentMessageTranscript () - -@property (nonatomic, readonly) TSOutgoingMessage *message; - -// sentRecipientId is the recipient of message, for contact thread messages. -// It is used to identify the thread/conversation to desktop. -@property (nonatomic, readonly, nullable) NSString *sentRecipientId; - -@property (nonatomic, readonly) BOOL isRecipientUpdate; - -@end - -#pragma mark - - -@implementation OWSOutgoingSentMessageTranscript - -- (instancetype)initWithOutgoingMessage:(TSOutgoingMessage *)message isRecipientUpdate:(BOOL)isRecipientUpdate -{ - self = [super initWithTimestamp:message.timestamp]; - - if (!self) { - return self; - } - - _message = message; - // This will be nil for groups. - _sentRecipientId = message.thread.contactIdentifier; - _isRecipientUpdate = isRecipientUpdate; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (nullable SSKProtoSyncMessageBuilder *)syncMessageBuilder -{ - SSKProtoSyncMessageSentBuilder *sentBuilder = [SSKProtoSyncMessageSent builder]; - [sentBuilder setTimestamp:self.message.timestamp]; - [sentBuilder setDestination:self.sentRecipientId]; - [sentBuilder setIsRecipientUpdate:self.isRecipientUpdate]; - - SSKProtoDataMessage *_Nullable dataMessage = [self.message buildDataMessage:self.sentRecipientId]; - if (!dataMessage) { - OWSFailDebug(@"could not build protobuf."); - return nil; - } - [sentBuilder setMessage:dataMessage]; - [sentBuilder setExpirationStartTimestamp:self.message.timestamp]; - - for (NSString *recipientId in self.message.sentRecipientIds) { - TSOutgoingMessageRecipientState *_Nullable recipientState = - [self.message recipientStateForRecipientId:recipientId]; - if (!recipientState) { - continue; - } - if (recipientState.state != OWSOutgoingMessageRecipientStateSent) { - OWSFailDebug(@"unexpected recipient state for: %@", recipientId); - continue; - } - - NSError *error; - SSKProtoSyncMessageSentUnidentifiedDeliveryStatusBuilder *statusBuilder = - [SSKProtoSyncMessageSentUnidentifiedDeliveryStatus builder]; - [statusBuilder setDestination:recipientId]; - [statusBuilder setUnidentified:recipientState.wasSentByUD]; - SSKProtoSyncMessageSentUnidentifiedDeliveryStatus *_Nullable status = - [statusBuilder buildAndReturnError:&error]; - if (error || !status) { - OWSFailDebug(@"Couldn't build UD status proto: %@", error); - continue; - } - [sentBuilder addUnidentifiedStatus:status]; - } - - NSError *error; - SSKProtoSyncMessageSent *_Nullable sentProto = [sentBuilder buildAndReturnError:&error]; - if (error || !sentProto) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - - SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; - [syncMessageBuilder setSent:sentProto]; - return syncMessageBuilder; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.h b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.h deleted file mode 100644 index 926a5fa87..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSOutgoingMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - * Abstract base class used for the family of sync messages which take care - * of keeping your multiple registered devices consistent. E.g. sharing contacts, sharing groups, - * notifiying your devices of sent messages, and "read" receipts. - */ -@interface OWSOutgoingSyncMessage : TSOutgoingMessage - -- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSMutableArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - isVoiceMessage:(BOOL)isVoiceMessage - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_UNAVAILABLE; - -- (instancetype)initWithTimestamp:(uint64_t)timestamp; - -- (instancetype)init NS_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.m deleted file mode 100644 index 1631ba5be..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSyncMessage.m +++ /dev/null @@ -1,125 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingSyncMessage.h" -#import "ProtoUtils.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSOutgoingSyncMessage - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (instancetype)init -{ - // MJK TODO - remove SenderTimestamp - self = [super initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:nil - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - - if (!self) { - return self; - } - - return self; -} - -- (instancetype)initWithTimestamp:(uint64_t)timestamp -{ - self = [super initOutgoingMessageWithTimestamp:timestamp - inThread:nil - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - - if (!self) { - return self; - } - - return self; -} - -- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeSync]; } - -- (BOOL)shouldBeSaved -{ - return NO; -} - -- (BOOL)shouldSyncTranscript -{ - return NO; -} - -// This method should not be overridden, since we want to add random padding to *every* sync message -- (nullable SSKProtoSyncMessage *)buildSyncMessage -{ - SSKProtoSyncMessageBuilder *_Nullable builder = [self syncMessageBuilder]; - if (!builder) { - return nil; - } - - // Add a random 1-512 bytes to obscure sync message type - size_t paddingBytesLength = arc4random_uniform(512) + 1; - builder.padding = [Cryptography generateRandomBytes:paddingBytesLength]; - - NSError *error; - SSKProtoSyncMessage *_Nullable proto = [builder buildAndReturnError:&error]; - if (error || !proto) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - return proto; -} - -- (nullable SSKProtoSyncMessageBuilder *)syncMessageBuilder -{ - OWSAbstractMethod(); - - return [SSKProtoSyncMessage builder]; -} - -- (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient -{ - SSKProtoSyncMessage *_Nullable syncMessage = [self buildSyncMessage]; - if (!syncMessage) { - return nil; - } - - SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; - [contentBuilder setSyncMessage:syncMessage]; - - NSError *error; - NSData *_Nullable data = [contentBuilder buildSerializedDataAndReturnError:&error]; - if (error || !data) { - OWSFailDebug(@"could not serialize protobuf: %@", error); - return nil; - } - - return data; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncConfigurationMessage.h b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncConfigurationMessage.h deleted file mode 100644 index 4d88ff8bb..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncConfigurationMessage.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingSyncMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSSyncConfigurationMessage : OWSOutgoingSyncMessage - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithReadReceiptsEnabled:(BOOL)readReceiptsEnabled - showUnidentifiedDeliveryIndicators:(BOOL)showUnidentifiedDeliveryIndicators - showTypingIndicators:(BOOL)showTypingIndicators - sendLinkPreviews:(BOOL)sendLinkPreviews NS_DESIGNATED_INITIALIZER; - -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncConfigurationMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncConfigurationMessage.m deleted file mode 100644 index 78972b69f..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncConfigurationMessage.m +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSSyncConfigurationMessage.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSSyncConfigurationMessage () - -@property (nonatomic, readonly) BOOL areReadReceiptsEnabled; -@property (nonatomic, readonly) BOOL showUnidentifiedDeliveryIndicators; -@property (nonatomic, readonly) BOOL showTypingIndicators; -@property (nonatomic, readonly) BOOL sendLinkPreviews; - -@end - -@implementation OWSSyncConfigurationMessage - -- (instancetype)initWithReadReceiptsEnabled:(BOOL)areReadReceiptsEnabled - showUnidentifiedDeliveryIndicators:(BOOL)showUnidentifiedDeliveryIndicators - showTypingIndicators:(BOOL)showTypingIndicators - sendLinkPreviews:(BOOL)sendLinkPreviews -{ - self = [super init]; - if (!self) { - return nil; - } - - _areReadReceiptsEnabled = areReadReceiptsEnabled; - _showUnidentifiedDeliveryIndicators = showUnidentifiedDeliveryIndicators; - _showTypingIndicators = showTypingIndicators; - _sendLinkPreviews = sendLinkPreviews; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (nullable SSKProtoSyncMessageBuilder *)syncMessageBuilder -{ - SSKProtoSyncMessageConfigurationBuilder *configurationBuilder = [SSKProtoSyncMessageConfiguration builder]; - configurationBuilder.readReceipts = self.areReadReceiptsEnabled; - configurationBuilder.unidentifiedDeliveryIndicators = self.showUnidentifiedDeliveryIndicators; - configurationBuilder.typingIndicators = self.showTypingIndicators; - configurationBuilder.linkPreviews = self.sendLinkPreviews; - - NSError *error; - SSKProtoSyncMessageConfiguration *_Nullable configurationProto = [configurationBuilder buildAndReturnError:&error]; - if (error || !configurationProto) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - - SSKProtoSyncMessageBuilder *builder = [SSKProtoSyncMessage builder]; - builder.configuration = configurationProto; - return builder; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.h b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.h deleted file mode 100644 index 89b3c8862..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingSyncMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@protocol ProfileManagerProtocol; - -@class OWSIdentityManager; -@class SignalAccount; - -@interface OWSSyncContactsMessage : OWSOutgoingSyncMessage - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithSignalAccounts:(NSArray *)signalAccounts - identityManager:(OWSIdentityManager *)identityManager - profileManager:(id)profileManager NS_DESIGNATED_INITIALIZER; - -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -- (nullable NSData *)buildPlainTextAttachmentDataWithTransaction:(YapDatabaseReadTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m deleted file mode 100644 index 8b9f83240..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m +++ /dev/null @@ -1,157 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSSyncContactsMessage.h" -#import "Contact.h" -#import "ContactsManagerProtocol.h" -#import "OWSContactsOutputStream.h" -#import "OWSIdentityManager.h" -#import "ProfileManagerProtocol.h" -#import "ProtoUtils.h" -#import "SSKEnvironment.h" -#import "SignalAccount.h" -#import "TSAccountManager.h" -#import "TSAttachment.h" -#import "TSAttachmentStream.h" -#import "TSContactThread.h" -#import -#import -#import "OWSPrimaryStorage.h" - -@import Contacts; - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSSyncContactsMessage () - -@property (nonatomic, readonly) NSArray *signalAccounts; -@property (nonatomic, readonly) OWSIdentityManager *identityManager; -@property (nonatomic, readonly) id profileManager; - -@end - -@implementation OWSSyncContactsMessage - -- (instancetype)initWithSignalAccounts:(NSArray *)signalAccounts - identityManager:(OWSIdentityManager *)identityManager - profileManager:(id)profileManager -{ - self = [super init]; - if (!self) { - return self; - } - - _signalAccounts = signalAccounts; - _identityManager = identityManager; - _profileManager = profileManager; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -#pragma mark - Dependencies - -- (id)contactsManager { - return SSKEnvironment.shared.contactsManager; -} - -- (TSAccountManager *)tsAccountManager { - return TSAccountManager.sharedInstance; -} - -#pragma mark - - -- (nullable SSKProtoSyncMessageBuilder *)syncMessageBuilder -{ - NSError *error; - if (self.attachmentIds.count > 1) { - OWSLogError(@"Expected sync contact message to have one or zero attachments, but found %lu.", (unsigned long)self.attachmentIds.count); - } - - SSKProtoSyncMessageContactsBuilder *contactsBuilder; - if (self.attachmentIds.count == 0) { - SSKProtoAttachmentPointerBuilder *attachmentProtoBuilder = [SSKProtoAttachmentPointer builderWithId:0]; - SSKProtoAttachmentPointer *attachmentProto = [attachmentProtoBuilder buildAndReturnError:&error]; - contactsBuilder = [SSKProtoSyncMessageContacts builder]; - [contactsBuilder setBlob:attachmentProto]; - __block NSData *data; - [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - data = [self buildPlainTextAttachmentDataWithTransaction:transaction]; - }]; - [contactsBuilder setData:data]; - } else { - SSKProtoAttachmentPointer *attachmentProto = [TSAttachmentStream buildProtoForAttachmentId:self.attachmentIds.firstObject]; - if (attachmentProto == nil) { - OWSFailDebug(@"Couldn't build protobuf."); - return nil; - } - contactsBuilder = [SSKProtoSyncMessageContacts builder]; - [contactsBuilder setBlob:attachmentProto]; - } - [contactsBuilder setIsComplete:YES]; - - SSKProtoSyncMessageContacts *contactsProto = [contactsBuilder buildAndReturnError:&error]; - if (error || contactsProto == nil) { - OWSFailDebug(@"Couldn't build protobuf due to error: %@.", error); - return nil; - } - SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; - [syncMessageBuilder setContacts:contactsProto]; - - return syncMessageBuilder; -} - -- (nullable NSData *)buildPlainTextAttachmentDataWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - NSMutableArray *signalAccounts = [self.signalAccounts mutableCopy]; - - // TODO use temp file stream to avoid loading everything into memory at once - // First though, we need to re-engineer our attachment process to accept streams (encrypting with stream, - // and uploading with streams). - NSOutputStream *dataOutputStream = [NSOutputStream outputStreamToMemory]; - [dataOutputStream open]; - OWSContactsOutputStream *contactsOutputStream = - [[OWSContactsOutputStream alloc] initWithOutputStream:dataOutputStream]; - - for (SignalAccount *signalAccount in signalAccounts) { - OWSRecipientIdentity *_Nullable recipientIdentity = - [self.identityManager recipientIdentityForRecipientId:signalAccount.recipientId]; - NSData *_Nullable profileKeyData = [self.profileManager profileKeyDataForRecipientId:signalAccount.recipientId]; - - OWSDisappearingMessagesConfiguration *_Nullable disappearingMessagesConfiguration; - NSString *conversationColorName; - - TSContactThread *_Nullable contactThread = [TSContactThread getThreadWithContactId:signalAccount.recipientId transaction:transaction]; - if (contactThread) { - conversationColorName = contactThread.conversationColorName; - disappearingMessagesConfiguration = [contactThread disappearingMessagesConfigurationWithTransaction:transaction]; - } else { - conversationColorName = [TSThread stableColorNameForNewConversationWithString:signalAccount.recipientId]; - } - - [contactsOutputStream writeSignalAccount:signalAccount - recipientIdentity:recipientIdentity - profileKeyData:profileKeyData - contactsManager:self.contactsManager - conversationColorName:conversationColorName - disappearingMessagesConfiguration:disappearingMessagesConfiguration]; - } - - [dataOutputStream close]; - - if (contactsOutputStream.hasError) { - OWSFailDebug(@"Could not write contacts sync stream."); - return nil; - } - - return [dataOutputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.h b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.h deleted file mode 100644 index 6f7ccc188..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingSyncMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class YapDatabaseReadTransaction; -@class TSGroupThread; - -@interface OWSSyncGroupsMessage : OWSOutgoingSyncMessage - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithGroupThread:(TSGroupThread *)thread NS_DESIGNATED_INITIALIZER; - -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -- (nullable NSData *)buildPlainTextAttachmentDataWithTransaction:(YapDatabaseReadTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m deleted file mode 100644 index 6ea0dafc0..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m +++ /dev/null @@ -1,104 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSSyncGroupsMessage.h" -#import "OWSGroupsOutputStream.h" -#import "TSAttachment.h" -#import "TSAttachmentStream.h" -#import "TSContactThread.h" -#import "TSGroupModel.h" -#import "TSGroupThread.h" -#import -#import -#import "OWSPrimaryStorage.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSSyncGroupsMessage () - -@property (nonatomic, readonly) TSGroupThread *groupThread; - -@end - -@implementation OWSSyncGroupsMessage - -- (instancetype)initWithGroupThread:(TSGroupThread *)thread -{ - self = [super init]; - if (!self) { - return self; - } - - _groupThread = thread; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (nullable SSKProtoSyncMessageBuilder *)syncMessageBuilder -{ - NSError *error; - if (self.attachmentIds.count > 1) { - OWSLogError(@"Expected sync group message to have one or zero attachments, but found %lu.", (unsigned long)self.attachmentIds.count); - } - - SSKProtoSyncMessageGroupsBuilder *groupsBuilder; - if (self.attachmentIds.count == 0) { - SSKProtoAttachmentPointerBuilder *attachmentProtoBuilder = [SSKProtoAttachmentPointer builderWithId:0]; - SSKProtoAttachmentPointer *attachmentProto = [attachmentProtoBuilder buildAndReturnError:&error]; - groupsBuilder = [SSKProtoSyncMessageGroups builder]; - [groupsBuilder setBlob:attachmentProto]; - __block NSData *data; - [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - data = [self buildPlainTextAttachmentDataWithTransaction:transaction]; - }]; - [groupsBuilder setData:data]; - } else { - SSKProtoAttachmentPointer *attachmentProto = [TSAttachmentStream buildProtoForAttachmentId:self.attachmentIds.firstObject]; - if (attachmentProto == nil) { - OWSFailDebug(@"Couldn't build protobuf."); - return nil; - } - groupsBuilder = [SSKProtoSyncMessageGroups builder]; - [groupsBuilder setBlob:attachmentProto]; - } - - SSKProtoSyncMessageGroups *_Nullable groupsProto = [groupsBuilder buildAndReturnError:&error]; - if (error || !groupsProto) { - OWSFailDebug(@"Couldn't build protobuf due to error: %@.", error); - return nil; - } - - SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; - [syncMessageBuilder setGroups:groupsProto]; - - return syncMessageBuilder; -} - -- (nullable NSData *)buildPlainTextAttachmentDataWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - // TODO use temp file stream to avoid loading everything into memory at once - // First though, we need to re-engineer our attachment process to accept streams (encrypting with stream, - // and uploading with streams). - NSOutputStream *dataOutputStream = [NSOutputStream outputStreamToMemory]; - [dataOutputStream open]; - OWSGroupsOutputStream *groupsOutputStream = [[OWSGroupsOutputStream alloc] initWithOutputStream:dataOutputStream]; - [groupsOutputStream writeGroup:self.groupThread transaction:transaction]; - [dataOutputStream close]; - - if (groupsOutputStream.hasError) { - OWSFailDebug(@"Could not write groups sync stream."); - return nil; - } - - return [dataOutputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsRequestMessage.h b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsRequestMessage.h deleted file mode 100644 index a3e5202ee..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsRequestMessage.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingSyncMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSSyncGroupsRequestMessage : TSOutgoingMessage - -- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSMutableArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - isVoiceMessage:(BOOL)isVoiceMessage - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_UNAVAILABLE; - -- (instancetype)initWithThread:(nullable TSThread *)thread groupId:(NSData *)groupId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsRequestMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsRequestMessage.m deleted file mode 100644 index c4357b470..000000000 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsRequestMessage.m +++ /dev/null @@ -1,85 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSSyncGroupsRequestMessage.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSSyncGroupsRequestMessage () - -@property (nonatomic) NSData *groupId; - -@end - -#pragma mark - - -@implementation OWSSyncGroupsRequestMessage - -- (instancetype)initWithThread:(nullable TSThread *)thread groupId:(NSData *)groupId -{ - // MJK TODO - remove senderTimestamp - self = [super initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - if (!self) { - return self; - } - - OWSAssertDebug(groupId.length > 0); - _groupId = groupId; - - return self; -} - -- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeSync]; } - -- (BOOL)shouldBeSaved -{ - return NO; -} - -- (BOOL)shouldSyncTranscript -{ - return NO; -} - -- (BOOL)isSilent -{ - // Avoid "phantom messages" - - return YES; -} - -- (nullable id)dataMessageBuilder -{ - SSKProtoGroupContextBuilder *groupContextBuilder = - [SSKProtoGroupContext builderWithId:self.groupId type:SSKProtoGroupContextTypeRequestInfo]; - - NSError *error; - SSKProtoGroupContext *_Nullable groupContextProto = [groupContextBuilder buildAndReturnError:&error]; - if (error || !groupContextProto) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - - SSKProtoDataMessageBuilder *builder = [SSKProtoDataMessage builder]; - [builder setTimestamp:self.timestamp]; - [builder setGroup:groupContextProto]; - - return builder; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h b/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h deleted file mode 100644 index 8ccdbdd8b..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact+Private.h +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSContact.h" - -NS_ASSUME_NONNULL_BEGIN - -// These private interfaces expose setter accessors to facilitate -// construction of fake messages, etc. -@interface OWSContactPhoneNumber (Private) - -@property (nonatomic) OWSContactPhoneType phoneType; -@property (nonatomic, nullable) NSString *label; - -@property (nonatomic) NSString *phoneNumber; - -@end - -#pragma mark - - -@interface OWSContactEmail (Private) - -@property (nonatomic) OWSContactEmailType emailType; -@property (nonatomic, nullable) NSString *label; - -@property (nonatomic) NSString *email; - -@end - -#pragma mark - - -@interface OWSContactAddress (Private) - -@property (nonatomic) OWSContactAddressType addressType; -@property (nonatomic, nullable) NSString *label; - -@property (nonatomic, nullable) NSString *street; -@property (nonatomic, nullable) NSString *pobox; -@property (nonatomic, nullable) NSString *neighborhood; -@property (nonatomic, nullable) NSString *city; -@property (nonatomic, nullable) NSString *region; -@property (nonatomic, nullable) NSString *postcode; -@property (nonatomic, nullable) NSString *country; - -@end - -#pragma mark - - -@interface OWSContact (Private) - -@property (nonatomic) NSArray *phoneNumbers; -@property (nonatomic) NSArray *emails; -@property (nonatomic) NSArray *addresses; - -@property (nonatomic) BOOL isProfileAvatar; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.h b/SignalServiceKit/src/Messages/Interactions/OWSContact.h deleted file mode 100644 index dc878044f..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.h +++ /dev/null @@ -1,183 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class CNContact; -@class OWSAttachmentInfo; -@class SSKProtoDataMessage; -@class SSKProtoDataMessageContact; -@class TSAttachment; -@class TSAttachmentStream; -@class YapDatabaseReadTransaction; -@class YapDatabaseReadWriteTransaction; - -extern BOOL kIsSendingContactSharesEnabled; - -typedef NS_ENUM(NSUInteger, OWSContactPhoneType) { - OWSContactPhoneType_Home = 1, - OWSContactPhoneType_Mobile, - OWSContactPhoneType_Work, - OWSContactPhoneType_Custom, -}; - -NSString *NSStringForContactPhoneType(OWSContactPhoneType value); - -@protocol OWSContactField - -- (BOOL)ows_isValid; - -- (NSString *)localizedLabel; - -@end - -#pragma mark - - -@interface OWSContactPhoneNumber : MTLModel - -@property (nonatomic, readonly) OWSContactPhoneType phoneType; -// Applies in the OWSContactPhoneType_Custom case. -@property (nonatomic, readonly, nullable) NSString *label; - -@property (nonatomic, readonly) NSString *phoneNumber; - -- (nullable NSString *)tryToConvertToE164; - -@end - -#pragma mark - - -typedef NS_ENUM(NSUInteger, OWSContactEmailType) { - OWSContactEmailType_Home = 1, - OWSContactEmailType_Mobile, - OWSContactEmailType_Work, - OWSContactEmailType_Custom, -}; - -NSString *NSStringForContactEmailType(OWSContactEmailType value); - -@interface OWSContactEmail : MTLModel - -@property (nonatomic, readonly) OWSContactEmailType emailType; -// Applies in the OWSContactEmailType_Custom case. -@property (nonatomic, readonly, nullable) NSString *label; - -@property (nonatomic, readonly) NSString *email; - -@end - -#pragma mark - - -typedef NS_ENUM(NSUInteger, OWSContactAddressType) { - OWSContactAddressType_Home = 1, - OWSContactAddressType_Work, - OWSContactAddressType_Custom, -}; - -NSString *NSStringForContactAddressType(OWSContactAddressType value); - -@interface OWSContactAddress : MTLModel - -@property (nonatomic, readonly) OWSContactAddressType addressType; -// Applies in the OWSContactAddressType_Custom case. -@property (nonatomic, readonly, nullable) NSString *label; - -@property (nonatomic, readonly, nullable) NSString *street; -@property (nonatomic, readonly, nullable) NSString *pobox; -@property (nonatomic, readonly, nullable) NSString *neighborhood; -@property (nonatomic, readonly, nullable) NSString *city; -@property (nonatomic, readonly, nullable) NSString *region; -@property (nonatomic, readonly, nullable) NSString *postcode; -@property (nonatomic, readonly, nullable) NSString *country; - -@end - -#pragma mark - - -@interface OWSContactName : MTLModel - -// The "name parts". -@property (nonatomic, nullable) NSString *givenName; -@property (nonatomic, nullable) NSString *familyName; -@property (nonatomic, nullable) NSString *nameSuffix; -@property (nonatomic, nullable) NSString *namePrefix; -@property (nonatomic, nullable) NSString *middleName; - -@property (nonatomic, nullable) NSString *organizationName; - -@property (nonatomic) NSString *displayName; - -// Returns true if any of the name parts (which doesn't include -// organization name) is non-empty. -- (BOOL)hasAnyNamePart; - -@end - -#pragma mark - - -@interface OWSContact : MTLModel - -@property (nonatomic) OWSContactName *name; - -@property (nonatomic, readonly) NSArray *phoneNumbers; -@property (nonatomic, readonly) NSArray *emails; -@property (nonatomic, readonly) NSArray *addresses; - -@property (nonatomic, readonly, nullable) NSString *avatarAttachmentId; -- (nullable TSAttachment *)avatarAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (void)removeAvatarAttachmentWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)saveAvatarImage:(UIImage *)image transaction:(YapDatabaseReadWriteTransaction *)transaction; -// "Profile" avatars should _not_ be saved to device contacts. -@property (nonatomic, readonly) BOOL isProfileAvatar; - -- (instancetype)init NS_UNAVAILABLE; - -- (void)normalize; - -- (BOOL)ows_isValid; - -- (NSString *)debugDescription; - -#pragma mark - Creation and Derivation - -- (OWSContact *)newContactWithName:(OWSContactName *)name; - -- (OWSContact *)copyContactWithName:(OWSContactName *)name; - -#pragma mark - Phone Numbers and Recipient IDs - -- (NSArray *)systemContactsWithSignalAccountPhoneNumbers:(id)contactsManager - NS_SWIFT_NAME(systemContactsWithSignalAccountPhoneNumbers(_:)); -- (NSArray *)systemContactPhoneNumbers:(id)contactsManager - NS_SWIFT_NAME(systemContactPhoneNumbers(_:)); -- (NSArray *)e164PhoneNumbers NS_SWIFT_NAME(e164PhoneNumbers()); - -@end - -#pragma mark - - -// TODO: Move to separate source file, rename to OWSContactConversion. -@interface OWSContacts : NSObject - -#pragma mark - System Contact Conversion - -// `contactForSystemContact` does *not* handle avatars. That must be delt with by the caller -+ (nullable OWSContact *)contactForSystemContact:(CNContact *)systemContact; - -+ (nullable CNContact *)systemContactForContact:(OWSContact *)contact imageData:(nullable NSData *)imageData; - -#pragma mark - Proto Serialization - -+ (nullable SSKProtoDataMessageContact *)protoForContact:(OWSContact *)contact; - -+ (nullable OWSContact *)contactForDataMessage:(SSKProtoDataMessage *)dataMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContact.m b/SignalServiceKit/src/Messages/Interactions/OWSContact.m deleted file mode 100644 index 920cd183a..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSContact.m +++ /dev/null @@ -1,1126 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSContact.h" -#import "Contact.h" -#import "MimeTypeUtil.h" -#import "NSString+SSK.h" -#import "OWSContact+Private.h" -#import "PhoneNumber.h" -#import "TSAttachment.h" -#import "TSAttachmentPointer.h" -#import "TSAttachmentStream.h" -#import -#import - -@import Contacts; - -NS_ASSUME_NONNULL_BEGIN - -// NOTE: When changing the value of this feature flag, you also need -// to update the filtering in the SAE's info.plist. -BOOL kIsSendingContactSharesEnabled = YES; - -NSString *NSStringForContactPhoneType(OWSContactPhoneType value) -{ - switch (value) { - case OWSContactPhoneType_Home: - return @"Home"; - case OWSContactPhoneType_Mobile: - return @"Mobile"; - case OWSContactPhoneType_Work: - return @"Work"; - case OWSContactPhoneType_Custom: - return @"Custom"; - } -} - -@interface OWSContactPhoneNumber () - -@property (nonatomic) OWSContactPhoneType phoneType; -@property (nonatomic, nullable) NSString *label; - -@property (nonatomic) NSString *phoneNumber; - -@end - -#pragma mark - - -@implementation OWSContactPhoneNumber - -- (BOOL)ows_isValid -{ - if (self.phoneNumber.ows_stripped.length < 1) { - OWSLogWarn(@"invalid phone number: %@.", self.phoneNumber); - return NO; - } - return YES; -} - -- (NSString *)localizedLabel -{ - switch (self.phoneType) { - case OWSContactPhoneType_Home: - return [CNLabeledValue localizedStringForLabel:CNLabelHome]; - case OWSContactPhoneType_Mobile: - return [CNLabeledValue localizedStringForLabel:CNLabelPhoneNumberMobile]; - case OWSContactPhoneType_Work: - return [CNLabeledValue localizedStringForLabel:CNLabelWork]; - default: - if (self.label.ows_stripped.length < 1) { - return NSLocalizedString(@"CONTACT_PHONE", @"Label for a contact's phone number."); - } - return self.label.ows_stripped; - } -} - -- (NSString *)debugDescription -{ - NSMutableString *result = [NSMutableString new]; - [result appendFormat:@"[Phone Number: %@, ", NSStringForContactPhoneType(self.phoneType)]; - - if (self.label.length > 0) { - [result appendFormat:@"label: %@, ", self.label]; - } - if (self.phoneNumber.length > 0) { - [result appendFormat:@"phoneNumber: %@, ", self.phoneNumber]; - } - - [result appendString:@"]"]; - return result; -} - -- (nullable NSString *)tryToConvertToE164 -{ - PhoneNumber *_Nullable parsedPhoneNumber; - parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromE164:self.phoneNumber]; - if (!parsedPhoneNumber) { - parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:self.phoneNumber]; - } - if (parsedPhoneNumber) { - return parsedPhoneNumber.toE164; - } - return nil; -} - -@end - -#pragma mark - - -NSString *NSStringForContactEmailType(OWSContactEmailType value) -{ - switch (value) { - case OWSContactEmailType_Home: - return @"Home"; - case OWSContactEmailType_Mobile: - return @"Mobile"; - case OWSContactEmailType_Work: - return @"Work"; - case OWSContactEmailType_Custom: - return @"Custom"; - } -} - -@interface OWSContactEmail () - -@property (nonatomic) OWSContactEmailType emailType; -@property (nonatomic, nullable) NSString *label; - -@property (nonatomic) NSString *email; - -@end - -#pragma mark - - -@implementation OWSContactEmail - -- (BOOL)ows_isValid -{ - if (self.email.ows_stripped.length < 1) { - OWSLogWarn(@"invalid email: %@.", self.email); - return NO; - } - return YES; -} - -- (NSString *)localizedLabel -{ - switch (self.emailType) { - case OWSContactEmailType_Home: - return [CNLabeledValue localizedStringForLabel:CNLabelHome]; - case OWSContactEmailType_Mobile: - return [CNLabeledValue localizedStringForLabel:CNLabelPhoneNumberMobile]; - case OWSContactEmailType_Work: - return [CNLabeledValue localizedStringForLabel:CNLabelWork]; - default: - if (self.label.ows_stripped.length < 1) { - return NSLocalizedString(@"CONTACT_EMAIL", @"Label for a contact's email address."); - } - return self.label.ows_stripped; - } -} - -- (NSString *)debugDescription -{ - NSMutableString *result = [NSMutableString new]; - [result appendFormat:@"[Email: %@, ", NSStringForContactEmailType(self.emailType)]; - - if (self.label.length > 0) { - [result appendFormat:@"label: %@, ", self.label]; - } - if (self.email.length > 0) { - [result appendFormat:@"email: %@, ", self.email]; - } - - [result appendString:@"]"]; - return result; -} - -@end - -#pragma mark - - -NSString *NSStringForContactAddressType(OWSContactAddressType value) -{ - switch (value) { - case OWSContactAddressType_Home: - return @"Home"; - case OWSContactAddressType_Work: - return @"Work"; - case OWSContactAddressType_Custom: - return @"Custom"; - } -} -@interface OWSContactAddress () - -@property (nonatomic) OWSContactAddressType addressType; -@property (nonatomic, nullable) NSString *label; - -@property (nonatomic, nullable) NSString *street; -@property (nonatomic, nullable) NSString *pobox; -@property (nonatomic, nullable) NSString *neighborhood; -@property (nonatomic, nullable) NSString *city; -@property (nonatomic, nullable) NSString *region; -@property (nonatomic, nullable) NSString *postcode; -@property (nonatomic, nullable) NSString *country; - -@end - -#pragma mark - - -@implementation OWSContactAddress - -- (BOOL)ows_isValid -{ - if (self.street.ows_stripped.length < 1 && self.pobox.ows_stripped.length < 1 - && self.neighborhood.ows_stripped.length < 1 && self.city.ows_stripped.length < 1 - && self.region.ows_stripped.length < 1 && self.postcode.ows_stripped.length < 1 - && self.country.ows_stripped.length < 1) { - OWSLogWarn(@"invalid address; empty."); - return NO; - } - return YES; -} - -- (NSString *)localizedLabel -{ - switch (self.addressType) { - case OWSContactAddressType_Home: - return [CNLabeledValue localizedStringForLabel:CNLabelHome]; - case OWSContactAddressType_Work: - return [CNLabeledValue localizedStringForLabel:CNLabelWork]; - default: - if (self.label.ows_stripped.length < 1) { - return NSLocalizedString(@"CONTACT_ADDRESS", @"Label for a contact's postal address."); - } - return self.label.ows_stripped; - } -} - -- (NSString *)debugDescription -{ - NSMutableString *result = [NSMutableString new]; - [result appendFormat:@"[Address: %@, ", NSStringForContactAddressType(self.addressType)]; - - if (self.label.length > 0) { - [result appendFormat:@"label: %@, ", self.label]; - } - if (self.street.length > 0) { - [result appendFormat:@"street: %@, ", self.street]; - } - if (self.pobox.length > 0) { - [result appendFormat:@"pobox: %@, ", self.pobox]; - } - if (self.neighborhood.length > 0) { - [result appendFormat:@"neighborhood: %@, ", self.neighborhood]; - } - if (self.city.length > 0) { - [result appendFormat:@"city: %@, ", self.city]; - } - if (self.region.length > 0) { - [result appendFormat:@"region: %@, ", self.region]; - } - if (self.postcode.length > 0) { - [result appendFormat:@"postcode: %@, ", self.postcode]; - } - if (self.country.length > 0) { - [result appendFormat:@"country: %@, ", self.country]; - } - - [result appendString:@"]"]; - return result; -} - -@end - -#pragma mark - - -@implementation OWSContactName - -- (NSString *)logDescription -{ - NSMutableString *result = [NSMutableString new]; - [result appendString:@"["]; - - if (self.givenName.length > 0) { - [result appendFormat:@"givenName: %@, ", self.givenName]; - } - if (self.familyName.length > 0) { - [result appendFormat:@"familyName: %@, ", self.familyName]; - } - if (self.middleName.length > 0) { - [result appendFormat:@"middleName: %@, ", self.middleName]; - } - if (self.namePrefix.length > 0) { - [result appendFormat:@"namePrefix: %@, ", self.namePrefix]; - } - if (self.nameSuffix.length > 0) { - [result appendFormat:@"nameSuffix: %@, ", self.nameSuffix]; - } - if (self.displayName.length > 0) { - [result appendFormat:@"displayName: %@, ", self.displayName]; - } - - [result appendString:@"]"]; - return result; -} - -- (NSString *)displayName -{ - [self ensureDisplayName]; - - if (_displayName.length < 1) { - OWSFailDebug(@"could not derive a valid display name."); - return NSLocalizedString(@"CONTACT_WITHOUT_NAME", @"Indicates that a contact has no name."); - } - return _displayName; -} - -- (void)ensureDisplayName -{ - if (_displayName.length < 1) { - CNContact *_Nullable cnContact = [self systemContactForName]; - _displayName = [CNContactFormatter stringFromContact:cnContact style:CNContactFormatterStyleFullName]; - } - if (_displayName.length < 1) { - // Fall back to using the organization name. - _displayName = self.organizationName; - } -} - -- (void)updateDisplayName -{ - _displayName = nil; - - [self ensureDisplayName]; -} - -- (nullable CNContact *)systemContactForName -{ - CNMutableContact *systemContact = [CNMutableContact new]; - systemContact.givenName = self.givenName.ows_stripped; - systemContact.middleName = self.middleName.ows_stripped; - systemContact.familyName = self.familyName.ows_stripped; - systemContact.namePrefix = self.namePrefix.ows_stripped; - systemContact.nameSuffix = self.nameSuffix.ows_stripped; - // We don't need to set display name, it's implicit for system contacts. - systemContact.organizationName = self.organizationName.ows_stripped; - return systemContact; -} - -- (BOOL)hasAnyNamePart -{ - return (self.givenName.ows_stripped.length > 0 || self.middleName.ows_stripped.length > 0 - || self.familyName.ows_stripped.length > 0 || self.namePrefix.ows_stripped.length > 0 - || self.nameSuffix.ows_stripped.length > 0); -} - -@end - -#pragma mark - - -@interface OWSContact () - -@property (nonatomic) NSArray *phoneNumbers; -@property (nonatomic) NSArray *emails; -@property (nonatomic) NSArray *addresses; - -@property (nonatomic, nullable) NSString *avatarAttachmentId; -@property (nonatomic) BOOL isProfileAvatar; - -@property (nonatomic, nullable) NSArray *e164PhoneNumbersCached; - -@end - -#pragma mark - - -@implementation OWSContact - -- (instancetype)init -{ - if (self = [super init]) { - _name = [OWSContactName new]; - _phoneNumbers = @[]; - _emails = @[]; - _addresses = @[]; - } - - return self; -} - -- (void)normalize -{ - self.phoneNumbers = [self.phoneNumbers - filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(OWSContactPhoneNumber *value, - NSDictionary *_Nullable bindings) { - return value.ows_isValid; - }]]; - self.emails = [self.emails filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(OWSContactEmail *value, - NSDictionary *_Nullable bindings) { - return value.ows_isValid; - }]]; - self.addresses = - [self.addresses filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(OWSContactAddress *value, - NSDictionary *_Nullable bindings) { - return value.ows_isValid; - }]]; -} - -- (BOOL)ows_isValid -{ - if (self.name.displayName.ows_stripped.length < 1) { - OWSLogWarn(@"invalid contact; no display name."); - return NO; - } - BOOL hasValue = NO; - for (OWSContactPhoneNumber *phoneNumber in self.phoneNumbers) { - if (!phoneNumber.ows_isValid) { - return NO; - } - hasValue = YES; - } - for (OWSContactEmail *email in self.emails) { - if (!email.ows_isValid) { - return NO; - } - hasValue = YES; - } - for (OWSContactAddress *address in self.addresses) { - if (!address.ows_isValid) { - return NO; - } - hasValue = YES; - } - return hasValue; -} - -- (NSString *)debugDescription -{ - NSMutableString *result = [NSMutableString new]; - [result appendString:@"["]; - - [result appendFormat:@"%@, ", self.name.logDescription]; - - for (OWSContactPhoneNumber *phoneNumber in self.phoneNumbers) { - [result appendFormat:@"%@, ", phoneNumber.debugDescription]; - } - for (OWSContactEmail *email in self.emails) { - [result appendFormat:@"%@, ", email.debugDescription]; - } - for (OWSContactAddress *address in self.addresses) { - [result appendFormat:@"%@, ", address.debugDescription]; - } - - [result appendString:@"]"]; - return result; -} - -- (OWSContact *)newContactWithName:(OWSContactName *)name -{ - OWSAssertDebug(name); - - OWSContact *newContact = [OWSContact new]; - - newContact.name = name; - - [name updateDisplayName]; - - return newContact; -} - -- (OWSContact *)copyContactWithName:(OWSContactName *)name -{ - OWSAssertDebug(name); - - OWSContact *contactCopy = [self copy]; - - contactCopy.name = name; - - [name updateDisplayName]; - - return contactCopy; -} - -#pragma mark - Avatar - -- (nullable TSAttachment *)avatarAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - return [TSAttachment fetchObjectWithUniqueID:self.avatarAttachmentId transaction:transaction]; -} - -- (void)saveAvatarImage:(UIImage *)image transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - NSData *imageData = UIImageJPEGRepresentation(image, (CGFloat)0.9); - - TSAttachmentStream *attachmentStream = [[TSAttachmentStream alloc] initWithContentType:OWSMimeTypeImageJpeg - byteCount:(UInt32)imageData.length - sourceFilename:nil - caption:nil - albumMessageId:nil]; - - NSError *error; - BOOL success = [attachmentStream writeData:imageData error:&error]; - OWSAssertDebug(success && !error); - - [attachmentStream saveWithTransaction:transaction]; - self.avatarAttachmentId = attachmentStream.uniqueId; -} - -- (void)removeAvatarAttachmentWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - TSAttachment *_Nullable attachment = - [TSAttachment fetchObjectWithUniqueID:self.avatarAttachmentId transaction:transaction]; - [attachment removeWithTransaction:transaction]; -} - -#pragma mark - Phone Numbers and Recipient IDs - -- (NSArray *)systemContactsWithSignalAccountPhoneNumbers:(id)contactsManager -{ - OWSAssertDebug(contactsManager); - - return [self.e164PhoneNumbers - filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *_Nullable recipientId, - NSDictionary *_Nullable bindings) { - return [contactsManager isSystemContactWithSignalAccount:recipientId]; - }]]; -} - -- (NSArray *)systemContactPhoneNumbers:(id)contactsManager -{ - OWSAssertDebug(contactsManager); - - return [self.e164PhoneNumbers - filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *_Nullable recipientId, - NSDictionary *_Nullable bindings) { - return [contactsManager isSystemContact:recipientId]; - }]]; -} - -- (NSArray *)e164PhoneNumbers -{ - if (self.e164PhoneNumbersCached) { - return self.e164PhoneNumbersCached; - } - NSMutableArray *e164PhoneNumbers = [NSMutableArray new]; - for (OWSContactPhoneNumber *phoneNumber in self.phoneNumbers) { - PhoneNumber *_Nullable parsedPhoneNumber; - parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromE164:phoneNumber.phoneNumber]; - if (!parsedPhoneNumber) { - parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumber.phoneNumber]; - } - if (parsedPhoneNumber) { - [e164PhoneNumbers addObject:parsedPhoneNumber.toE164]; - } - } - self.e164PhoneNumbersCached = e164PhoneNumbers; - return e164PhoneNumbers; -} - -@end - -#pragma mark - - -@implementation OWSContacts - -#pragma mark - System Contact Conversion - -// `contactForSystemContact` does *not* handle avatars. That must be delt with by the caller -+ (nullable OWSContact *)contactForSystemContact:(CNContact *)systemContact -{ - if (!systemContact) { - OWSFailDebug(@"Missing contact."); - return nil; - } - - OWSContact *contact = [OWSContact new]; - - OWSContactName *contactName = [OWSContactName new]; - contactName.givenName = systemContact.givenName.ows_stripped; - contactName.middleName = systemContact.middleName.ows_stripped; - contactName.familyName = systemContact.familyName.ows_stripped; - contactName.namePrefix = systemContact.namePrefix.ows_stripped; - contactName.nameSuffix = systemContact.nameSuffix.ows_stripped; - contactName.organizationName = systemContact.organizationName.ows_stripped; - [contactName ensureDisplayName]; - contact.name = contactName; - - NSMutableArray *phoneNumbers = [NSMutableArray new]; - for (CNLabeledValue *phoneNumberField in systemContact.phoneNumbers) { - OWSContactPhoneNumber *phoneNumber = [OWSContactPhoneNumber new]; - - // Make a best effort to parse the phone number to e164. - NSString *unparsedPhoneNumber = phoneNumberField.value.stringValue; - PhoneNumber *_Nullable parsedPhoneNumber; - parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromE164:unparsedPhoneNumber]; - if (!parsedPhoneNumber) { - parsedPhoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:unparsedPhoneNumber]; - } - if (parsedPhoneNumber) { - phoneNumber.phoneNumber = parsedPhoneNumber.toE164; - } else { - phoneNumber.phoneNumber = unparsedPhoneNumber; - } - - if ([phoneNumberField.label isEqualToString:CNLabelHome]) { - phoneNumber.phoneType = OWSContactPhoneType_Home; - } else if ([phoneNumberField.label isEqualToString:CNLabelWork]) { - phoneNumber.phoneType = OWSContactPhoneType_Work; - } else if ([phoneNumberField.label isEqualToString:CNLabelPhoneNumberMobile]) { - phoneNumber.phoneType = OWSContactPhoneType_Mobile; - } else { - phoneNumber.phoneType = OWSContactPhoneType_Custom; - phoneNumber.label = [Contact localizedStringForCNLabel:phoneNumberField.label]; - } - [phoneNumbers addObject:phoneNumber]; - } - contact.phoneNumbers = phoneNumbers; - - NSMutableArray *emails = [NSMutableArray new]; - for (CNLabeledValue *emailField in systemContact.emailAddresses) { - OWSContactEmail *email = [OWSContactEmail new]; - email.email = emailField.value; - if ([emailField.label isEqualToString:CNLabelHome]) { - email.emailType = OWSContactEmailType_Home; - } else if ([emailField.label isEqualToString:CNLabelWork]) { - email.emailType = OWSContactEmailType_Work; - } else { - email.emailType = OWSContactEmailType_Custom; - email.label = [Contact localizedStringForCNLabel:emailField.label]; - } - [emails addObject:email]; - } - contact.emails = emails; - - NSMutableArray *addresses = [NSMutableArray new]; - for (CNLabeledValue *addressField in systemContact.postalAddresses) { - OWSContactAddress *address = [OWSContactAddress new]; - address.street = addressField.value.street; - // TODO: Is this the correct mapping? - // address.neighborhood = addressField.value.subLocality; - address.city = addressField.value.city; - // TODO: Is this the correct mapping? - // address.region = addressField.value.subAdministrativeArea; - address.region = addressField.value.state; - address.postcode = addressField.value.postalCode; - // TODO: Should we be using 2-letter codes, 3-letter codes or names? - address.country = addressField.value.ISOCountryCode; - - if ([addressField.label isEqualToString:CNLabelHome]) { - address.addressType = OWSContactAddressType_Home; - } else if ([addressField.label isEqualToString:CNLabelWork]) { - address.addressType = OWSContactAddressType_Work; - } else { - address.addressType = OWSContactAddressType_Custom; - address.label = [Contact localizedStringForCNLabel:addressField.label]; - } - [addresses addObject:address]; - } - contact.addresses = addresses; - - return contact; -} - -+ (nullable CNContact *)systemContactForContact:(OWSContact *)contact imageData:(nullable NSData *)imageData -{ - if (!contact) { - OWSFailDebug(@"Missing contact."); - return nil; - } - - CNMutableContact *systemContact = [CNMutableContact new]; - - systemContact.givenName = contact.name.givenName; - systemContact.middleName = contact.name.middleName; - systemContact.familyName = contact.name.familyName; - systemContact.namePrefix = contact.name.namePrefix; - systemContact.nameSuffix = contact.name.nameSuffix; - // We don't need to set display name, it's implicit for system contacts. - systemContact.organizationName = contact.name.organizationName; - - NSMutableArray *> *systemPhoneNumbers = [NSMutableArray new]; - for (OWSContactPhoneNumber *phoneNumber in contact.phoneNumbers) { - switch (phoneNumber.phoneType) { - case OWSContactPhoneType_Home: - [systemPhoneNumbers - addObject:[CNLabeledValue - labeledValueWithLabel:CNLabelHome - value:[CNPhoneNumber - phoneNumberWithStringValue:phoneNumber.phoneNumber]]]; - break; - case OWSContactPhoneType_Mobile: - [systemPhoneNumbers - addObject:[CNLabeledValue - labeledValueWithLabel:CNLabelPhoneNumberMobile - value:[CNPhoneNumber - phoneNumberWithStringValue:phoneNumber.phoneNumber]]]; - break; - case OWSContactPhoneType_Work: - [systemPhoneNumbers - addObject:[CNLabeledValue - labeledValueWithLabel:CNLabelWork - value:[CNPhoneNumber - phoneNumberWithStringValue:phoneNumber.phoneNumber]]]; - break; - case OWSContactPhoneType_Custom: - [systemPhoneNumbers - addObject:[CNLabeledValue - labeledValueWithLabel:phoneNumber.label - value:[CNPhoneNumber - phoneNumberWithStringValue:phoneNumber.phoneNumber]]]; - break; - } - } - systemContact.phoneNumbers = systemPhoneNumbers; - - NSMutableArray *> *systemEmails = [NSMutableArray new]; - for (OWSContactEmail *email in contact.emails) { - switch (email.emailType) { - case OWSContactEmailType_Home: - [systemEmails addObject:[CNLabeledValue labeledValueWithLabel:CNLabelHome value:email.email]]; - break; - case OWSContactEmailType_Mobile: - [systemEmails addObject:[CNLabeledValue labeledValueWithLabel:@"Mobile" value:email.email]]; - break; - case OWSContactEmailType_Work: - [systemEmails addObject:[CNLabeledValue labeledValueWithLabel:CNLabelWork value:email.email]]; - break; - case OWSContactEmailType_Custom: - [systemEmails addObject:[CNLabeledValue labeledValueWithLabel:email.label value:email.email]]; - break; - } - } - systemContact.emailAddresses = systemEmails; - - NSMutableArray *> *systemAddresses = [NSMutableArray new]; - for (OWSContactAddress *address in contact.addresses) { - CNMutablePostalAddress *systemAddress = [CNMutablePostalAddress new]; - systemAddress.street = address.street; - // TODO: Is this the correct mapping? - // systemAddress.subLocality = address.neighborhood; - systemAddress.city = address.city; - // TODO: Is this the correct mapping? - // systemAddress.subAdministrativeArea = address.region; - systemAddress.state = address.region; - systemAddress.postalCode = address.postcode; - // TODO: Should we be using 2-letter codes, 3-letter codes or names? - systemAddress.ISOCountryCode = address.country; - - switch (address.addressType) { - case OWSContactAddressType_Home: - [systemAddresses addObject:[CNLabeledValue labeledValueWithLabel:CNLabelHome value:systemAddress]]; - break; - case OWSContactAddressType_Work: - [systemAddresses addObject:[CNLabeledValue labeledValueWithLabel:CNLabelWork value:systemAddress]]; - break; - case OWSContactAddressType_Custom: - [systemAddresses addObject:[CNLabeledValue labeledValueWithLabel:address.label value:systemAddress]]; - break; - } - } - systemContact.postalAddresses = systemAddresses; - systemContact.imageData = imageData; - - return systemContact; -} - -#pragma mark - Proto Serialization - -+ (nullable SSKProtoDataMessageContact *)protoForContact:(OWSContact *)contact -{ - OWSAssertDebug(contact); - - SSKProtoDataMessageContactBuilder *contactBuilder = [SSKProtoDataMessageContact builder]; - - SSKProtoDataMessageContactNameBuilder *nameBuilder = [SSKProtoDataMessageContactName builder]; - - OWSContactName *contactName = contact.name; - if (contactName.givenName.ows_stripped.length > 0) { - nameBuilder.givenName = contactName.givenName.ows_stripped; - } - if (contactName.familyName.ows_stripped.length > 0) { - nameBuilder.familyName = contactName.familyName.ows_stripped; - } - if (contactName.middleName.ows_stripped.length > 0) { - nameBuilder.middleName = contactName.middleName.ows_stripped; - } - if (contactName.namePrefix.ows_stripped.length > 0) { - nameBuilder.prefix = contactName.namePrefix.ows_stripped; - } - if (contactName.nameSuffix.ows_stripped.length > 0) { - nameBuilder.suffix = contactName.nameSuffix.ows_stripped; - } - if (contactName.organizationName.ows_stripped.length > 0) { - contactBuilder.organization = contactName.organizationName.ows_stripped; - } - nameBuilder.displayName = contactName.displayName; - - NSError *error; - SSKProtoDataMessageContactName *_Nullable nameProto = [nameBuilder buildAndReturnError:&error]; - if (error || !nameProto) { - OWSLogError(@"could not build protobuf: %@", error); - } else { - [contactBuilder setName:nameProto]; - } - - for (OWSContactPhoneNumber *phoneNumber in contact.phoneNumbers) { - SSKProtoDataMessageContactPhoneBuilder *phoneBuilder = [SSKProtoDataMessageContactPhone builder]; - phoneBuilder.value = phoneNumber.phoneNumber; - if (phoneNumber.label.ows_stripped.length > 0) { - phoneBuilder.label = phoneNumber.label.ows_stripped; - } - switch (phoneNumber.phoneType) { - case OWSContactPhoneType_Home: - phoneBuilder.type = SSKProtoDataMessageContactPhoneTypeHome; - break; - case OWSContactPhoneType_Mobile: - phoneBuilder.type = SSKProtoDataMessageContactPhoneTypeMobile; - break; - case OWSContactPhoneType_Work: - phoneBuilder.type = SSKProtoDataMessageContactPhoneTypeWork; - break; - case OWSContactPhoneType_Custom: - phoneBuilder.type = SSKProtoDataMessageContactPhoneTypeCustom; - break; - } - SSKProtoDataMessageContactPhone *_Nullable numberProto = [phoneBuilder buildAndReturnError:&error]; - if (error || !numberProto) { - OWSLogError(@"could not build protobuf: %@", error); - } else { - [contactBuilder addNumber:numberProto]; - } - } - - for (OWSContactEmail *email in contact.emails) { - SSKProtoDataMessageContactEmailBuilder *emailBuilder = [SSKProtoDataMessageContactEmail builder]; - emailBuilder.value = email.email; - if (email.label.ows_stripped.length > 0) { - emailBuilder.label = email.label.ows_stripped; - } - switch (email.emailType) { - case OWSContactEmailType_Home: - emailBuilder.type = SSKProtoDataMessageContactEmailTypeHome; - break; - case OWSContactEmailType_Mobile: - emailBuilder.type = SSKProtoDataMessageContactEmailTypeMobile; - break; - case OWSContactEmailType_Work: - emailBuilder.type = SSKProtoDataMessageContactEmailTypeWork; - break; - case OWSContactEmailType_Custom: - emailBuilder.type = SSKProtoDataMessageContactEmailTypeCustom; - break; - } - SSKProtoDataMessageContactEmail *_Nullable emailProto = [emailBuilder buildAndReturnError:&error]; - if (error || !emailProto) { - OWSLogError(@"could not build protobuf: %@", error); - } else { - [contactBuilder addEmail:emailProto]; - } - } - - for (OWSContactAddress *address in contact.addresses) { - SSKProtoDataMessageContactPostalAddressBuilder *addressBuilder = - [SSKProtoDataMessageContactPostalAddress builder]; - if (address.label.ows_stripped.length > 0) { - addressBuilder.label = address.label.ows_stripped; - } - if (address.street.ows_stripped.length > 0) { - addressBuilder.street = address.street.ows_stripped; - } - if (address.pobox.ows_stripped.length > 0) { - addressBuilder.pobox = address.pobox.ows_stripped; - } - if (address.neighborhood.ows_stripped.length > 0) { - addressBuilder.neighborhood = address.neighborhood.ows_stripped; - } - if (address.city.ows_stripped.length > 0) { - addressBuilder.city = address.city.ows_stripped; - } - if (address.region.ows_stripped.length > 0) { - addressBuilder.region = address.region.ows_stripped; - } - if (address.postcode.ows_stripped.length > 0) { - addressBuilder.postcode = address.postcode.ows_stripped; - } - if (address.country.ows_stripped.length > 0) { - addressBuilder.country = address.country.ows_stripped; - } - SSKProtoDataMessageContactPostalAddress *_Nullable addressProto = [addressBuilder buildAndReturnError:&error]; - if (error || !addressProto) { - OWSLogError(@"could not build protobuf: %@", error); - } else { - [contactBuilder addAddress:addressProto]; - } - } - - if (contact.avatarAttachmentId) { - SSKProtoAttachmentPointer *_Nullable attachmentProto = - [TSAttachmentStream buildProtoForAttachmentId:contact.avatarAttachmentId]; - if (!attachmentProto) { - OWSLogError(@"could not build protobuf: %@", error); - } else { - SSKProtoDataMessageContactAvatarBuilder *avatarBuilder = [SSKProtoDataMessageContactAvatar builder]; - avatarBuilder.avatar = attachmentProto; - SSKProtoDataMessageContactAvatar *_Nullable avatarProto = [avatarBuilder buildAndReturnError:&error]; - if (error || !avatarProto) { - OWSLogError(@"could not build protobuf: %@", error); - } else { - contactBuilder.avatar = avatarProto; - } - } - } - - SSKProtoDataMessageContact *_Nullable contactProto = [contactBuilder buildAndReturnError:&error]; - if (error || !contactProto) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - if (contactProto.number.count < 1 && contactProto.email.count < 1 && contactProto.address.count < 1) { - OWSFailDebug(@"contact has neither phone, email or address."); - return nil; - } - return contactProto; -} - -+ (nullable OWSContact *)contactForDataMessage:(SSKProtoDataMessage *)dataMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(dataMessage); - - if (dataMessage.contact.count < 1) { - return nil; - } - OWSAssertDebug(dataMessage.contact.count == 1); - SSKProtoDataMessageContact *contactProto = dataMessage.contact.firstObject; - - OWSContact *contact = [OWSContact new]; - - OWSContactName *contactName = [OWSContactName new]; - if (contactProto.name) { - SSKProtoDataMessageContactName *nameProto = contactProto.name; - - if (nameProto.givenName) { - contactName.givenName = nameProto.givenName.ows_stripped; - } - if (nameProto.familyName) { - contactName.familyName = nameProto.familyName.ows_stripped; - } - if (nameProto.prefix) { - contactName.namePrefix = nameProto.prefix.ows_stripped; - } - if (nameProto.suffix) { - contactName.nameSuffix = nameProto.suffix.ows_stripped; - } - if (nameProto.middleName) { - contactName.middleName = nameProto.middleName.ows_stripped; - } - if (nameProto.displayName) { - contactName.displayName = nameProto.displayName.ows_stripped; - } - } - if (contactProto.organization) { - contactName.organizationName = contactProto.organization.ows_stripped; - } - [contactName ensureDisplayName]; - contact.name = contactName; - - NSMutableArray *phoneNumbers = [NSMutableArray new]; - for (SSKProtoDataMessageContactPhone *phoneNumberProto in contactProto.number) { - OWSContactPhoneNumber *_Nullable phoneNumber = [self phoneNumberForProto:phoneNumberProto]; - if (phoneNumber) { - [phoneNumbers addObject:phoneNumber]; - } - } - contact.phoneNumbers = [phoneNumbers copy]; - - NSMutableArray *emails = [NSMutableArray new]; - for (SSKProtoDataMessageContactEmail *emailProto in contactProto.email) { - OWSContactEmail *_Nullable email = [self emailForProto:emailProto]; - if (email) { - [emails addObject:email]; - } - } - contact.emails = [emails copy]; - - NSMutableArray *addresses = [NSMutableArray new]; - for (SSKProtoDataMessageContactPostalAddress *addressProto in contactProto.address) { - OWSContactAddress *_Nullable address = [self addressForProto:addressProto]; - if (address) { - [addresses addObject:address]; - } - } - contact.addresses = [addresses copy]; - - if (contactProto.avatar) { - SSKProtoDataMessageContactAvatar *avatarInfo = contactProto.avatar; - - if (avatarInfo.avatar) { - SSKProtoAttachmentPointer *avatarAttachment = avatarInfo.avatar; - - TSAttachmentPointer *_Nullable attachmentPointer = - [TSAttachmentPointer attachmentPointerFromProto:avatarAttachment albumMessage:nil]; - if (attachmentPointer) { - [attachmentPointer saveWithTransaction:transaction]; - contact.avatarAttachmentId = attachmentPointer.uniqueId; - contact.isProfileAvatar = avatarInfo.isProfile; - } else { - OWSFailDebug(@"Invalid avatar attachment."); - } - } else { - OWSFailDebug(@"avatarInfo.hasAvatar was unexpectedly false"); - } - } - - - return contact; -} - -+ (nullable OWSContactPhoneNumber *)phoneNumberForProto: - (SSKProtoDataMessageContactPhone *)phoneNumberProto -{ - OWSContactPhoneNumber *result = [OWSContactPhoneNumber new]; - result.phoneType = OWSContactPhoneType_Custom; - if (phoneNumberProto.hasType) { - switch (phoneNumberProto.type) { - case SSKProtoDataMessageContactPhoneTypeHome: - result.phoneType = OWSContactPhoneType_Home; - break; - case SSKProtoDataMessageContactPhoneTypeMobile: - result.phoneType = OWSContactPhoneType_Mobile; - break; - case SSKProtoDataMessageContactPhoneTypeWork: - result.phoneType = OWSContactPhoneType_Work; - break; - default: - break; - } - } - if (phoneNumberProto.hasLabel) { - result.label = phoneNumberProto.label.ows_stripped; - } - if (phoneNumberProto.hasValue) { - result.phoneNumber = phoneNumberProto.value.ows_stripped; - } else { - return nil; - } - return result; -} - -+ (nullable OWSContactEmail *)emailForProto:(SSKProtoDataMessageContactEmail *)emailProto -{ - OWSContactEmail *result = [OWSContactEmail new]; - result.emailType = OWSContactEmailType_Custom; - if (emailProto.hasType) { - switch (emailProto.type) { - case SSKProtoDataMessageContactEmailTypeHome: - result.emailType = OWSContactEmailType_Home; - break; - case SSKProtoDataMessageContactEmailTypeMobile: - result.emailType = OWSContactEmailType_Mobile; - break; - case SSKProtoDataMessageContactEmailTypeWork: - result.emailType = OWSContactEmailType_Work; - break; - default: - break; - } - } - if (emailProto.hasLabel) { - result.label = emailProto.label.ows_stripped; - } - if (emailProto.hasValue) { - result.email = emailProto.value.ows_stripped; - } else { - return nil; - } - return result; -} - -+ (nullable OWSContactAddress *)addressForProto:(SSKProtoDataMessageContactPostalAddress *)addressProto -{ - OWSContactAddress *result = [OWSContactAddress new]; - result.addressType = OWSContactAddressType_Custom; - if (addressProto.hasType) { - switch (addressProto.type) { - case SSKProtoDataMessageContactPostalAddressTypeHome: - result.addressType = OWSContactAddressType_Home; - break; - case SSKProtoDataMessageContactPostalAddressTypeWork: - result.addressType = OWSContactAddressType_Work; - break; - default: - break; - } - } - if (addressProto.hasLabel) { - result.label = addressProto.label.ows_stripped; - } - if (addressProto.hasStreet) { - result.street = addressProto.street.ows_stripped; - } - if (addressProto.hasPobox) { - result.pobox = addressProto.pobox.ows_stripped; - } - if (addressProto.hasNeighborhood) { - result.neighborhood = addressProto.neighborhood.ows_stripped; - } - if (addressProto.hasCity) { - result.city = addressProto.city.ows_stripped; - } - if (addressProto.hasRegion) { - result.region = addressProto.region.ows_stripped; - } - if (addressProto.hasPostcode) { - result.postcode = addressProto.postcode.ows_stripped; - } - if (addressProto.hasCountry) { - result.country = addressProto.country.ows_stripped; - } - return result; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.h b/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.h deleted file mode 100644 index b530124fa..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSInfoMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class OWSDisappearingMessagesConfiguration; -@class TSThread; - -@interface OWSDisappearingConfigurationUpdateInfoMessage : TSInfoMessage - -@property (nonatomic, readonly) BOOL configurationIsEnabled; - -/** - * @param remoteName is nil when created by the local user - */ -// MJK TODO - can we remove sendertimestamp here -- (instancetype)initWithTimestamp:(uint64_t)timestamp - thread:(TSThread *)thread - configuration:(OWSDisappearingMessagesConfiguration *)configuration - createdByRemoteName:(nullable NSString *)remoteName - createdInExistingGroup:(BOOL)createdInExistingGroup; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.m b/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.m deleted file mode 100644 index c10ab76fe..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.m +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDisappearingConfigurationUpdateInfoMessage.h" -#import "NSString+SSK.h" -#import "OWSDisappearingMessagesConfiguration.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSDisappearingConfigurationUpdateInfoMessage () - -@property (nonatomic, readonly, nullable) NSString *createdByRemoteName; -@property (nonatomic, readonly) BOOL createdInExistingGroup; -@property (nonatomic, readonly) uint32_t configurationDurationSeconds; - -@end - -@implementation OWSDisappearingConfigurationUpdateInfoMessage - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - thread:(TSThread *)thread - configuration:(OWSDisappearingMessagesConfiguration *)configuration - createdByRemoteName:(nullable NSString *)remoteName - createdInExistingGroup:(BOOL)createdInExistingGroup -{ - self = [super initWithTimestamp:timestamp inThread:thread messageType:TSInfoMessageTypeDisappearingMessagesUpdate]; - if (!self) { - return self; - } - - _configurationIsEnabled = configuration.isEnabled; - _configurationDurationSeconds = configuration.durationSeconds; - - // At most one should be set - OWSAssertDebug(!remoteName || !createdInExistingGroup); - - _createdByRemoteName = remoteName; - _createdInExistingGroup = createdInExistingGroup; - - return self; -} - -- (BOOL)shouldUseReceiptDateForSorting -{ - // Use the timestamp, not the "received at" timestamp to sort, - // since we're creating these interactions after the fact and back-dating them. - return NO; -} - --(NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - if (self.createdInExistingGroup) { - OWSAssertDebug(self.configurationIsEnabled && self.configurationDurationSeconds > 0); - NSString *infoFormat = NSLocalizedString(@"DISAPPEARING_MESSAGES_CONFIGURATION_GROUP_EXISTING_FORMAT", - @"Info Message when added to a group which has enabled disappearing messages. Embeds {{time amount}} " - @"before messages disappear, see the *_TIME_AMOUNT strings for context."); - - NSString *durationString = [NSString formatDurationSeconds:self.configurationDurationSeconds useShortFormat:NO]; - return [NSString stringWithFormat:infoFormat, durationString]; - } else if (self.createdByRemoteName) { - if (self.configurationIsEnabled && self.configurationDurationSeconds > 0) { - NSString *infoFormat = NSLocalizedString(@"OTHER_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION", - @"Info Message when {{other user}} updates message expiration to {{time amount}}, see the " - @"*_TIME_AMOUNT " - @"strings for context."); - - NSString *durationString = - [NSString formatDurationSeconds:self.configurationDurationSeconds useShortFormat:NO]; - return [NSString stringWithFormat:infoFormat, self.createdByRemoteName, durationString]; - } else { - NSString *infoFormat = NSLocalizedString(@"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION", - @"Info Message when {{other user}} disables or doesn't support disappearing messages"); - return [NSString stringWithFormat:infoFormat, self.createdByRemoteName]; - } - } else { - // Changed by localNumber on this device or via synced transcript - if (self.configurationIsEnabled && self.configurationDurationSeconds > 0) { - NSString *infoFormat = NSLocalizedString(@"YOU_UPDATED_DISAPPEARING_MESSAGES_CONFIGURATION", - @"Info message embedding a {{time amount}}, see the *_TIME_AMOUNT strings for context."); - - NSString *durationString = - [NSString formatDurationSeconds:self.configurationDurationSeconds useShortFormat:NO]; - return [NSString stringWithFormat:infoFormat, durationString]; - } else { - return NSLocalizedString(@"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION", - @"Info Message when you disable disappearing messages"); - } - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSDisappearingMessagesConfigurationMessage.h b/SignalServiceKit/src/Messages/Interactions/OWSDisappearingMessagesConfigurationMessage.h deleted file mode 100644 index 7c96e20f2..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSDisappearingMessagesConfigurationMessage.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSOutgoingMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class OWSDisappearingMessagesConfiguration; - -@interface OWSDisappearingMessagesConfigurationMessage : TSOutgoingMessage - -// MJK TODO - remove senderTimestamp -- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSMutableArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - isVoiceMessage:(BOOL)isVoiceMessage - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_UNAVAILABLE; - -- (instancetype)initWithConfiguration:(OWSDisappearingMessagesConfiguration *)configuration thread:(TSThread *)thread; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSDisappearingMessagesConfigurationMessage.m b/SignalServiceKit/src/Messages/Interactions/OWSDisappearingMessagesConfigurationMessage.m deleted file mode 100644 index 71538b24d..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSDisappearingMessagesConfigurationMessage.m +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSDisappearingMessagesConfigurationMessage.h" -#import "OWSDisappearingMessagesConfiguration.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSDisappearingMessagesConfigurationMessage () - -@property (nonatomic, readonly) OWSDisappearingMessagesConfiguration *configuration; - -@end - -#pragma mark - - -@implementation OWSDisappearingMessagesConfigurationMessage - -- (BOOL)shouldBeSaved -{ - return NO; -} - -- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeDisappearingMessagesConfiguration]; } - -- (instancetype)initWithConfiguration:(OWSDisappearingMessagesConfiguration *)configuration thread:(TSThread *)thread -{ - // MJK TODO - remove sender timestamp - self = [super initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - if (!self) { - return self; - } - - _configuration = configuration; - - return self; -} - - -- (nullable id)dataMessageBuilder -{ - SSKProtoDataMessageBuilder *_Nullable dataMessageBuilder = [super dataMessageBuilder]; - if (!dataMessageBuilder) { - return nil; - } - [dataMessageBuilder setTimestamp:self.timestamp]; - [dataMessageBuilder setFlags:SSKProtoDataMessageFlagsExpirationTimerUpdate]; - if (self.configuration.isEnabled) { - [dataMessageBuilder setExpireTimer:self.configuration.durationSeconds]; - } else { - [dataMessageBuilder setExpireTimer:0]; - } - - return dataMessageBuilder; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSDynamicOutgoingMessage.h b/SignalServiceKit/src/Messages/Interactions/OWSDynamicOutgoingMessage.h deleted file mode 100644 index 655eda029..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSDynamicOutgoingMessage.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSOutgoingMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class SSKProtoDataMessageBuilder; -@class SignalRecipient; - -typedef NSData *_Nonnull (^DynamicOutgoingMessageBlock)(SignalRecipient *); - -/// This class is only used in debug tools -@interface OWSDynamicOutgoingMessage : TSOutgoingMessage - -- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSMutableArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - isVoiceMessage:(BOOL)isVoiceMessage - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_UNAVAILABLE; - -- (instancetype)initWithPlainTextDataBlock:(DynamicOutgoingMessageBlock)block thread:(nullable TSThread *)thread; -- (instancetype)initWithPlainTextDataBlock:(DynamicOutgoingMessageBlock)block - timestamp:(uint64_t)timestamp - thread:(nullable TSThread *)thread; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSDynamicOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/OWSDynamicOutgoingMessage.m deleted file mode 100644 index 0e1bf12ca..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSDynamicOutgoingMessage.m +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSDynamicOutgoingMessage.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSDynamicOutgoingMessage () - -@property (nonatomic, readonly) DynamicOutgoingMessageBlock block; - -@end - -#pragma mark - - -@implementation OWSDynamicOutgoingMessage - -- (instancetype)initWithPlainTextDataBlock:(DynamicOutgoingMessageBlock)block thread:(nullable TSThread *)thread -{ - return [self initWithPlainTextDataBlock:block timestamp:[NSDate ows_millisecondTimeStamp] thread:thread]; -} - -// MJK TODO can we remove sender timestamp? -- (instancetype)initWithPlainTextDataBlock:(DynamicOutgoingMessageBlock)block - timestamp:(uint64_t)timestamp - thread:(nullable TSThread *)thread -{ - self = [super initOutgoingMessageWithTimestamp:timestamp - inThread:thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - - if (self) { - _block = block; - } - - return self; -} - -- (BOOL)shouldBeSaved -{ - return NO; -} - -- (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient -{ - NSData *plainTextData = self.block(recipient); - OWSAssertDebug(plainTextData); - return plainTextData; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSEndSessionMessage.h b/SignalServiceKit/src/Messages/Interactions/OWSEndSessionMessage.h deleted file mode 100644 index cd45fc648..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSEndSessionMessage.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSOutgoingMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -NS_SWIFT_NAME(EndSessionMessage) -@interface OWSEndSessionMessage : TSOutgoingMessage - -- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSMutableArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - isVoiceMessage:(BOOL)isVoiceMessage - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_UNAVAILABLE; - -// MJK TODO can we remove the sender timestamp? -- (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(nullable TSThread *)thread NS_DESIGNATED_INITIALIZER; -- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSEndSessionMessage.m b/SignalServiceKit/src/Messages/Interactions/OWSEndSessionMessage.m deleted file mode 100644 index 01450736f..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSEndSessionMessage.m +++ /dev/null @@ -1,74 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSEndSessionMessage.h" -#import "OWSPrimaryStorage+Loki.h" -#import "SignalRecipient.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSEndSessionMessage - -- (instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(nullable TSThread *)thread -{ - return [super initOutgoingMessageWithTimestamp:timestamp - inThread:thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; -} - -- (BOOL)shouldBeSaved -{ - return NO; -} - -- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeEphemeral]; } - -- (nullable id)dataMessageBuilder -{ - SSKProtoDataMessageBuilder *_Nullable builder = [super dataMessageBuilder]; - if (!builder) { - return nil; - } - [builder setTimestamp:self.timestamp]; - [builder setFlags:SSKProtoDataMessageFlagsEndSession]; - - return builder; -} - -- (nullable id)prepareCustomContentBuilder:(SignalRecipient *)recipient { - SSKProtoContentBuilder *builder = [super prepareCustomContentBuilder:recipient]; - - PreKeyBundle *bundle = [OWSPrimaryStorage.sharedManager generatePreKeyBundleForContact:recipient.recipientId]; - SSKProtoPrekeyBundleMessageBuilder *preKeyBuilder = [SSKProtoPrekeyBundleMessage builderFromPreKeyBundle:bundle]; - - // Build the pre key bundle message - NSError *error; - SSKProtoPrekeyBundleMessage *_Nullable message = [preKeyBuilder buildAndReturnError:&error]; - if (error || !message) { - OWSFailDebug(@"Failed to build pre key bundle for: %@ due to error: %@.", recipient.recipientId, error); - } else { - [builder setPrekeyBundleMessage:message]; - } - - return builder; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift b/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift deleted file mode 100644 index 80faf4545..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift +++ /dev/null @@ -1,907 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit - -@objc -public enum LinkPreviewError: Int, Error { - case invalidInput - case noPreview - case assertionFailure - case couldNotDownload - case featureDisabled - case invalidContent - case invalidMediaContent - case attachmentFailedToSave -} - -// MARK: - OWSLinkPreviewDraft - -public class OWSLinkPreviewContents: NSObject { - @objc - public var title: String? - - @objc - public var imageUrl: String? - - public init(title: String?, imageUrl: String? = nil) { - self.title = title - self.imageUrl = imageUrl - - super.init() - } -} - -// This contains the info for a link preview "draft". -public class OWSLinkPreviewDraft: NSObject { - @objc - public var urlString: String - - @objc - public var title: String? - - @objc - public var jpegImageData: Data? - - public init(urlString: String, title: String?, jpegImageData: Data? = nil) { - self.urlString = urlString - self.title = title - self.jpegImageData = jpegImageData - - super.init() - } - - fileprivate func isValid() -> Bool { - var hasTitle = false - if let titleValue = title { - hasTitle = titleValue.count > 0 - } - let hasImage = jpegImageData != nil - return hasTitle || hasImage - } - - @objc - public func displayDomain() -> String? { - return OWSLinkPreview.displayDomain(forUrl: urlString) - } -} - -// MARK: - OWSLinkPreview - -@objc -public class OWSLinkPreview: MTLModel { - @objc - public static let featureEnabled = true - - @objc - public var urlString: String? - - @objc - public var title: String? - - @objc - public var imageAttachmentId: String? - - // Whether this preview can be rendered as an attachment - @objc - public var isDirectAttachment: Bool = false - - @objc - public init(urlString: String, title: String?, imageAttachmentId: String?, isDirectAttachment: Bool = false) { - self.urlString = urlString - self.title = title - self.imageAttachmentId = imageAttachmentId - self.isDirectAttachment = isDirectAttachment - - super.init() - } - - @objc - public override init() { - super.init() - } - - @objc - public required init!(coder: NSCoder) { - super.init(coder: coder) - } - - @objc - public required init(dictionary dictionaryValue: [String: Any]!) throws { - try super.init(dictionary: dictionaryValue) - } - - @objc - public class func isNoPreviewError(_ error: Error) -> Bool { - guard let error = error as? LinkPreviewError else { - return false - } - return error == .noPreview - } - - @objc - public class func isInvalidContentError(_ error: Error) -> Bool { - guard let error = error as? LinkPreviewError else { return false } - return error == .invalidContent - } - - @objc - public class func buildValidatedLinkPreview(dataMessage: SSKProtoDataMessage, - body: String?, - transaction: YapDatabaseReadWriteTransaction) throws -> OWSLinkPreview { - guard OWSLinkPreview.featureEnabled else { - throw LinkPreviewError.noPreview - } - guard let previewProto = dataMessage.preview.first else { - throw LinkPreviewError.noPreview - } - guard dataMessage.attachments.count < 1 else { - Logger.error("Discarding link preview; message has attachments.") - throw LinkPreviewError.invalidInput - } - let urlString = previewProto.url - - guard URL(string: urlString) != nil else { - Logger.error("Could not parse preview URL.") - throw LinkPreviewError.invalidInput - } - - guard let body = body else { - Logger.error("Preview for message without body.") - throw LinkPreviewError.invalidInput - } - let previewUrls = allPreviewUrls(forMessageBodyText: body) - guard previewUrls.contains(urlString) else { - Logger.error("URL not present in body.") - throw LinkPreviewError.invalidInput - } - - guard isValidLinkUrl(urlString) else { - Logger.verbose("Invalid link URL \(urlString).") - Logger.error("Invalid link URL.") - throw LinkPreviewError.invalidInput - } - - var title: String? - if let rawTitle = previewProto.title { - let normalizedTitle = OWSLinkPreview.normalizeTitle(title: rawTitle) - if normalizedTitle.count > 0 { - title = normalizedTitle - } - } - - var imageAttachmentId: String? - if let imageProto = previewProto.image { - if let imageAttachmentPointer = TSAttachmentPointer(fromProto: imageProto, albumMessage: nil) { - imageAttachmentPointer.save(with: transaction) - imageAttachmentId = imageAttachmentPointer.uniqueId - } else { - Logger.error("Could not parse image proto.") - throw LinkPreviewError.invalidInput - } - } - - let linkPreview = OWSLinkPreview(urlString: urlString, title: title, imageAttachmentId: imageAttachmentId) - - guard linkPreview.isValid() else { - Logger.error("Preview has neither title nor image.") - throw LinkPreviewError.invalidInput - } - - return linkPreview - } - - @objc - public class func buildValidatedLinkPreview(fromInfo info: OWSLinkPreviewDraft, - transaction: YapDatabaseReadWriteTransaction) throws -> OWSLinkPreview { - guard OWSLinkPreview.featureEnabled else { - throw LinkPreviewError.noPreview - } - guard SSKPreferences.areLinkPreviewsEnabled else { - throw LinkPreviewError.noPreview - } - let imageAttachmentId = OWSLinkPreview.saveAttachmentIfPossible(jpegImageData: info.jpegImageData, - transaction: transaction) - - let linkPreview = OWSLinkPreview(urlString: info.urlString, title: info.title, imageAttachmentId: imageAttachmentId) - - guard linkPreview.isValid() else { - owsFailDebug("Preview has neither title nor image.") - throw LinkPreviewError.invalidInput - } - - return linkPreview - } - - private class func saveAttachmentIfPossible(jpegImageData: Data?, - transaction: YapDatabaseReadWriteTransaction) -> String? { - return saveAttachmentIfPossible(imageData: jpegImageData, mimeType: OWSMimeTypeImageJpeg, transaction: transaction); - } - - private class func saveAttachmentIfPossible(imageData: Data?, mimeType: String, transaction: YapDatabaseReadWriteTransaction) -> String? { - guard let imageData = imageData else { return nil } - - let fileSize = imageData.count - guard fileSize > 0 else { - owsFailDebug("Invalid file size for image data.") - return nil - } - - guard let fileExtension = fileExtension(forMimeType: mimeType) else { return nil } - let filePath = OWSFileSystem.temporaryFilePath(withFileExtension: fileExtension) - do { - try imageData.write(to: NSURL.fileURL(withPath: filePath), options: .atomicWrite) - } catch let error as NSError { - owsFailDebug("file write failed: \(filePath), \(error)") - return nil - } - - guard let dataSource = DataSourcePath.dataSource(withFilePath: filePath, shouldDeleteOnDeallocation: true) else { - owsFailDebug("Could not create data source for path: \(filePath)") - return nil - } - let attachment = TSAttachmentStream(contentType: mimeType, byteCount: UInt32(fileSize), sourceFilename: nil, caption: nil, albumMessageId: nil) - guard attachment.write(dataSource) else { - owsFailDebug("Could not write data source for path: \(filePath)") - return nil - } - attachment.save(with: transaction) - - return attachment.uniqueId - } - - private func isValid() -> Bool { - var hasTitle = false - if let titleValue = title { - hasTitle = titleValue.count > 0 - } - let hasImage = imageAttachmentId != nil - return hasTitle || hasImage - } - - @objc - public func removeAttachment(transaction: YapDatabaseReadWriteTransaction) { - guard let imageAttachmentId = imageAttachmentId else { - owsFailDebug("No attachment id.") - return - } - guard let attachment = TSAttachment.fetch(uniqueId: imageAttachmentId, transaction: transaction) else { - owsFailDebug("Could not load attachment.") - return - } - attachment.remove(with: transaction) - } - - private class func normalizeTitle(title: String) -> String { - var result = title - // Truncate title after 2 lines of text. - let maxLineCount = 2 - var components = result.components(separatedBy: .newlines) - if components.count > maxLineCount { - components = Array(components[0.. maxCharacterCount { - let endIndex = result.index(result.startIndex, offsetBy: maxCharacterCount) - result = String(result[.. String? { - return OWSLinkPreview.displayDomain(forUrl: urlString) - } - - @objc - public class func displayDomain(forUrl urlString: String?) -> String? { - guard let urlString = urlString else { - owsFailDebug("Missing url.") - return nil - } - guard let url = URL(string: urlString) else { - owsFailDebug("Invalid url.") - return nil - } - guard let result = whitelistedDomain(forUrl: url, - domainWhitelist: OWSLinkPreview.linkDomainWhitelist, - allowSubdomains: false) else { - Logger.error("Missing domain.") - return nil - } - return result - } - - @objc - public class func isValidLinkUrl(_ urlString: String) -> Bool { - guard let url = URL(string: urlString) else { - return false - } - return whitelistedDomain(forUrl: url, - domainWhitelist: OWSLinkPreview.linkDomainWhitelist, - allowSubdomains: false) != nil - } - - @objc - public class func isValidMediaUrl(_ urlString: String) -> Bool { - guard let url = URL(string: urlString) else { - return false - } - return whitelistedDomain(forUrl: url, - domainWhitelist: OWSLinkPreview.mediaDomainWhitelist, - allowSubdomains: true) != nil - } - - private class func whitelistedDomain(forUrl url: URL, domainWhitelist: [String], allowSubdomains: Bool) -> String? { - guard let urlProtocol = url.scheme?.lowercased() else { - return nil - } - guard protocolWhitelist.contains(urlProtocol) else { - return nil - } - guard let domain = url.host?.lowercased() else { - return nil - } - guard url.path.count > 1 else { - // URL must have non-empty path. - return nil - } - - for whitelistedDomain in domainWhitelist { - if domain == whitelistedDomain.lowercased() { - return whitelistedDomain - } - if allowSubdomains, - domain.hasSuffix("." + whitelistedDomain.lowercased()) { - return whitelistedDomain - } - } - return nil - } - - // MARK: - Serial Queue - - private static let serialQueue = DispatchQueue(label: "org.signal.linkPreview") - - private class func assertIsOnSerialQueue() { - if _isDebugAssertConfiguration(), #available(iOS 10.0, *) { - assertOnQueue(serialQueue) - } - } - - // MARK: - Text Parsing - - // This cache should only be accessed on main thread. - private static var previewUrlCache: NSCache = NSCache() - - @objc - public class func previewUrl(forRawBodyText body: String?, selectedRange: NSRange) -> String? { - return previewUrl(forMessageBodyText: body, selectedRange: selectedRange) - } - - @objc - public class func previewURL(forRawBodyText body: String?) -> String? { - return previewUrl(forMessageBodyText: body, selectedRange: nil) - } - - public class func previewUrl(forMessageBodyText body: String?, selectedRange: NSRange?) -> String? { - AssertIsOnMainThread() - - // Exit early if link previews are not enabled in order to avoid - // tainting the cache. - guard OWSLinkPreview.featureEnabled else { - return nil - } - - guard SSKPreferences.areLinkPreviewsEnabled else { - return nil - } - - guard let body = body else { - return nil - } - - if let cachedUrl = previewUrlCache.object(forKey: body as NSString) as String? { - Logger.verbose("URL parsing cache hit.") - guard cachedUrl.count > 0 else { - return nil - } - return cachedUrl - } - let previewUrlMatches = allPreviewUrlMatches(forMessageBodyText: body) - guard let urlMatch = previewUrlMatches.first else { - // Use empty string to indicate "no preview URL" in the cache. - previewUrlCache.setObject("", forKey: body as NSString) - return nil - } - - if let selectedRange = selectedRange { - Logger.verbose("match: urlString: \(urlMatch.urlString) range: \(urlMatch.matchRange) selectedRange: \(selectedRange)") - let cursorAtEndOfMatch = urlMatch.matchRange.location + urlMatch.matchRange.length == selectedRange.location - if selectedRange.location != body.count, - (urlMatch.matchRange.intersection(selectedRange) != nil || cursorAtEndOfMatch) { - Logger.debug("ignoring URL, since the user is currently editing it.") - // we don't want to cache the result here, as we want to fetch the link preview - // if the user moves the cursor. - return nil - } - Logger.debug("considering URL, since the user is not currently editing it.") - } - - previewUrlCache.setObject(urlMatch.urlString as NSString, forKey: body as NSString) - return urlMatch.urlString - } - - struct URLMatchResult { - let urlString: String - let matchRange: NSRange - } - - class func allPreviewUrls(forMessageBodyText body: String) -> [String] { - return allPreviewUrlMatches(forMessageBodyText: body).map { $0.urlString } - } - - class func allPreviewUrlMatches(forMessageBodyText body: String) -> [URLMatchResult] { - guard OWSLinkPreview.featureEnabled else { - return [] - } - guard SSKPreferences.areLinkPreviewsEnabled else { - return [] - } - - let detector: NSDataDetector - do { - detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) - } catch { - owsFailDebug("Could not create NSDataDetector: \(error).") - return [] - } - - var urlMatches: [URLMatchResult] = [] - let matches = detector.matches(in: body, options: [], range: NSRange(location: 0, length: body.count)) - for match in matches { - guard let matchURL = match.url else { - owsFailDebug("Match missing url") - continue - } - let urlString = matchURL.absoluteString - if isValidLinkUrl(urlString) { - let matchResult = URLMatchResult(urlString: urlString, matchRange: match.range) - urlMatches.append(matchResult) - } - } - return urlMatches - } - - // MARK: - Preview Construction - - // This cache should only be accessed on serialQueue. - // - // We should only maintain a "cache" of the last known draft. - private static var linkPreviewDraftCache: OWSLinkPreviewDraft? - - private class func cachedLinkPreview(forPreviewUrl previewUrl: String) -> OWSLinkPreviewDraft? { - return serialQueue.sync { - guard let linkPreviewDraft = linkPreviewDraftCache, - linkPreviewDraft.urlString == previewUrl else { - Logger.verbose("----- Cache miss.") - return nil - } - Logger.verbose("----- Cache hit.") - return linkPreviewDraft - } - } - - private class func setCachedLinkPreview(_ linkPreviewDraft: OWSLinkPreviewDraft, - forPreviewUrl previewUrl: String) { - assert(previewUrl == linkPreviewDraft.urlString) - - // Exit early if link previews are not enabled in order to avoid - // tainting the cache. - guard OWSLinkPreview.featureEnabled else { - return - } - guard SSKPreferences.areLinkPreviewsEnabled else { - return - } - - serialQueue.sync { - linkPreviewDraftCache = linkPreviewDraft - } - } - - @objc - public class func tryToBuildPreviewInfoObjc(previewUrl: String?) -> AnyPromise { - return AnyPromise(tryToBuildPreviewInfo(previewUrl: previewUrl)) - } - - public class func tryToBuildPreviewInfo(previewUrl: String?) -> Promise { - guard OWSLinkPreview.featureEnabled else { - return Promise(error: LinkPreviewError.featureDisabled) - } - guard SSKPreferences.areLinkPreviewsEnabled else { - return Promise(error: LinkPreviewError.featureDisabled) - } - guard let previewUrl = previewUrl else { - return Promise(error: LinkPreviewError.invalidInput) - } - if let cachedInfo = cachedLinkPreview(forPreviewUrl: previewUrl) { - Logger.verbose("Link preview info cache hit.") - return Promise.value(cachedInfo) - } - return downloadLink(url: previewUrl) - .then(on: DispatchQueue.global()) { (data) -> Promise in - return parseLinkDataAndBuildDraft(linkData: data, linkUrlString: previewUrl) - }.then(on: DispatchQueue.global()) { (linkPreviewDraft) -> Promise in - guard linkPreviewDraft.isValid() else { - throw LinkPreviewError.noPreview - } - setCachedLinkPreview(linkPreviewDraft, forPreviewUrl: previewUrl) - - return Promise.value(linkPreviewDraft) - } - } - - class func downloadLink(url urlString: String, - remainingRetries: UInt = 3) -> Promise { - - Logger.verbose("url: \(urlString)") - - // let sessionConfiguration = ContentProxy.sessionConfiguration() // Loki: Signal's proxy appears to have been banned by YouTube - let sessionConfiguration = URLSessionConfiguration.ephemeral - - // Don't use any caching to protect privacy of these requests. - sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData - sessionConfiguration.urlCache = nil - - let sessionManager = AFHTTPSessionManager(baseURL: nil, - sessionConfiguration: sessionConfiguration) - sessionManager.requestSerializer = AFHTTPRequestSerializer() - sessionManager.responseSerializer = AFHTTPResponseSerializer() - - guard ContentProxy.configureSessionManager(sessionManager: sessionManager, forUrl: urlString) else { - owsFailDebug("Could not configure url: \(urlString).") - return Promise(error: LinkPreviewError.assertionFailure) - } - - let (promise, resolver) = Promise.pending() - sessionManager.get(urlString, - parameters: [String: AnyObject](), - progress: nil, - success: { task, value in - - guard let response = task.response as? HTTPURLResponse else { - Logger.warn("Invalid response: \(type(of: task.response)).") - resolver.reject(LinkPreviewError.assertionFailure) - return - } - if let contentType = response.allHeaderFields["Content-Type"] as? String { - guard contentType.lowercased().hasPrefix("text/") else { - Logger.warn("Invalid content type: \(contentType).") - resolver.reject(LinkPreviewError.invalidContent) - return - } - } - guard let data = value as? Data else { - Logger.warn("Result is not data: \(type(of: value)).") - resolver.reject(LinkPreviewError.assertionFailure) - return - } - guard data.count > 0 else { - Logger.warn("Empty data: \(type(of: value)).") - resolver.reject(LinkPreviewError.invalidContent) - return - } - resolver.fulfill(data) - }, - failure: { _, error in - Logger.verbose("Error: \(error)") - - guard isRetryable(error: error) else { - Logger.warn("Error is not retryable.") - resolver.reject(LinkPreviewError.couldNotDownload) - return - } - - guard remainingRetries > 0 else { - Logger.warn("No more retries.") - resolver.reject(LinkPreviewError.couldNotDownload) - return - } - OWSLinkPreview.downloadLink(url: urlString, remainingRetries: remainingRetries - 1) - .done(on: DispatchQueue.global()) { (data) in - resolver.fulfill(data) - }.catch(on: DispatchQueue.global()) { (error) in - resolver.reject(error) - }.retainUntilComplete() - }) - return promise - } - - private class func downloadImage(url urlString: String, imageMimeType: String) -> Promise { - - Logger.verbose("url: \(urlString)") - - guard let url = URL(string: urlString) else { - Logger.error("Could not parse URL.") - return Promise(error: LinkPreviewError.invalidInput) - } - - guard let assetDescription = ProxiedContentAssetDescription(url: url as NSURL) else { - Logger.error("Could not create asset description.") - return Promise(error: LinkPreviewError.invalidInput) - } - let (promise, resolver) = Promise.pending() - DispatchQueue.main.async { - _ = ProxiedContentDownloader.defaultDownloader.requestAsset(assetDescription: assetDescription, - priority: .high, - success: { (_, asset) in - resolver.fulfill(asset) - }, failure: { (_) in - Logger.warn("Error downloading asset") - resolver.reject(LinkPreviewError.couldNotDownload) - }) - } - return promise.then(on: DispatchQueue.global()) { (asset: ProxiedContentAsset) -> Promise in - do { - let imageSize = NSData.imageSize(forFilePath: asset.filePath, mimeType: imageMimeType) - guard imageSize.width > 0, imageSize.height > 0 else { - Logger.error("Link preview is invalid or has invalid size.") - return Promise(error: LinkPreviewError.invalidContent) - } - let data = try Data(contentsOf: URL(fileURLWithPath: asset.filePath)) - - guard let srcImage = UIImage(data: data) else { - Logger.error("Could not parse image.") - return Promise(error: LinkPreviewError.invalidContent) - } - - // Loki: If it's a GIF then ensure its validity and don't download it as a JPG - if (imageMimeType == OWSMimeTypeImageGif && NSData(data: data).ows_isValidImage(withMimeType: OWSMimeTypeImageGif)) { return Promise.value(data) } - - let maxImageSize: CGFloat = 1024 - let shouldResize = imageSize.width > maxImageSize || imageSize.height > maxImageSize - guard shouldResize else { - guard let dstData = srcImage.jpegData(compressionQuality: 0.8) else { - Logger.error("Could not write resized image.") - return Promise(error: LinkPreviewError.invalidContent) - } - return Promise.value(dstData) - } - - guard let dstImage = srcImage.resized(withMaxDimensionPoints: maxImageSize) else { - Logger.error("Could not resize image.") - return Promise(error: LinkPreviewError.invalidContent) - } - guard let dstData = dstImage.jpegData(compressionQuality: 0.8) else { - Logger.error("Could not write resized image.") - return Promise(error: LinkPreviewError.invalidContent) - } - return Promise.value(dstData) - } catch { - owsFailDebug("Could not load asset data: \(type(of: asset.filePath)).") - return Promise(error: LinkPreviewError.assertionFailure) - } - } - } - - private class func isRetryable(error: Error) -> Bool { - let nsError = error as NSError - if nsError.domain == kCFErrorDomainCFNetwork as String { - // Network failures are retried. - return true - } - return false - } - - class func parseLinkDataAndBuildDraft(linkData: Data, - linkUrlString: String) -> Promise { - do { - let contents = try parse(linkData: linkData) - - let title = contents.title - guard let imageUrl = contents.imageUrl else { - return Promise.value(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) - } - - guard isValidMediaUrl(imageUrl) else { - Logger.error("Invalid image URL.") - return Promise.value(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) - } - guard let imageFileExtension = fileExtension(forImageUrl: imageUrl) else { - Logger.error("Image URL has unknown or invalid file extension: \(imageUrl).") - return Promise.value(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) - } - guard let imageMimeType = mimetype(forImageFileExtension: imageFileExtension) else { - Logger.error("Image URL has unknown or invalid content type: \(imageUrl).") - return Promise.value(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) - } - - return downloadImage(url: imageUrl, imageMimeType: imageMimeType) - .map(on: DispatchQueue.global()) { (imageData: Data) -> OWSLinkPreviewDraft in - // We always recompress images to Jpeg. - let linkPreviewDraft = OWSLinkPreviewDraft(urlString: linkUrlString, title: title, jpegImageData: imageData) - return linkPreviewDraft - } - .recover(on: DispatchQueue.global()) { (_) -> Promise in - return Promise.value(OWSLinkPreviewDraft(urlString: linkUrlString, title: title)) - } - } catch { - owsFailDebug("Could not parse link data: \(error).") - return Promise(error: error) - } - } - - // Example: - // - // - // - class func parse(linkData: Data) throws -> OWSLinkPreviewContents { - guard let linkText = String(bytes: linkData, encoding: .utf8) else { - owsFailDebug("Could not parse link text.") - throw LinkPreviewError.invalidInput - } - - var title: String? - if let rawTitle = NSRegularExpression.parseFirstMatch(pattern: "]*content\\s*=\\s*\"(.*?)\"\\s*[^>]*/?>", - text: linkText, - options: .dotMatchesLineSeparators) { - if let decodedTitle = decodeHTMLEntities(inString: rawTitle) { - let normalizedTitle = OWSLinkPreview.normalizeTitle(title: decodedTitle) - if normalizedTitle.count > 0 { - title = normalizedTitle - } - } - } - - Logger.verbose("title: \(String(describing: title))") - - guard let rawImageUrlString = NSRegularExpression.parseFirstMatch(pattern: "]*content\\s*=\\s*\"(.*?)\"[^>]*/?>", text: linkText) else { - return OWSLinkPreviewContents(title: title) - } - guard let imageUrlString = decodeHTMLEntities(inString: rawImageUrlString)?.ows_stripped() else { - return OWSLinkPreviewContents(title: title) - } - - return OWSLinkPreviewContents(title: title, imageUrl: imageUrlString) - } - - class func fileExtension(forImageUrl urlString: String) -> String? { - guard let imageUrl = URL(string: urlString) else { - Logger.error("Could not parse image URL.") - return nil - } - let imageFilename = imageUrl.lastPathComponent - let imageFileExtension = (imageFilename as NSString).pathExtension.lowercased() - guard imageFileExtension.count > 0 else { - return nil - } - return imageFileExtension - } - - class func fileExtension(forMimeType mimeType: String) -> String? { - switch mimeType { - case OWSMimeTypeImageGif: return "gif" - case OWSMimeTypeImagePng: return "png" - case OWSMimeTypeImageJpeg: return "jpg" - default: return nil - } - } - - class func mimetype(forImageFileExtension imageFileExtension: String) -> String? { - guard imageFileExtension.count > 0 else { - return nil - } - guard let imageMimeType = MIMETypeUtil.mimeType(forFileExtension: imageFileExtension) else { - Logger.error("Image URL has unknown content type: \(imageFileExtension).") - return nil - } - let kValidMimeTypes = [ - OWSMimeTypeImagePng, - OWSMimeTypeImageJpeg, - OWSMimeTypeImageGif, - ] - guard kValidMimeTypes.contains(imageMimeType) else { - Logger.error("Image URL has invalid content type: \(imageMimeType).") - return nil - } - return imageMimeType - } - - private class func decodeHTMLEntities(inString value: String) -> String? { - guard let data = value.data(using: .utf8) else { - return nil - } - - let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [ - NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html, - NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue - ] - - guard let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) else { - return nil - } - - return attributedString.string - } -} diff --git a/SignalServiceKit/src/Messages/Interactions/OWSVerificationStateChangeMessage.h b/SignalServiceKit/src/Messages/Interactions/OWSVerificationStateChangeMessage.h deleted file mode 100644 index 775bd14c4..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSVerificationStateChangeMessage.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSRecipientIdentity.h" -#import "TSInfoMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class TSThread; - -@interface OWSVerificationStateChangeMessage : TSInfoMessage - -@property (nonatomic, readonly) NSString *recipientId; -@property (nonatomic, readonly) OWSVerificationState verificationState; -@property (nonatomic, readonly) BOOL isLocalChange; - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - thread:(TSThread *)thread - recipientId:(NSString *)recipientId - verificationState:(OWSVerificationState)verificationState - isLocalChange:(BOOL)isLocalChange; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/OWSVerificationStateChangeMessage.m b/SignalServiceKit/src/Messages/Interactions/OWSVerificationStateChangeMessage.m deleted file mode 100644 index ca57a6212..000000000 --- a/SignalServiceKit/src/Messages/Interactions/OWSVerificationStateChangeMessage.m +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSVerificationStateChangeMessage.h" -#import "OWSDisappearingMessagesConfiguration.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSVerificationStateChangeMessage - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - thread:(TSThread *)thread - recipientId:(NSString *)recipientId - verificationState:(OWSVerificationState)verificationState - isLocalChange:(BOOL)isLocalChange -{ - OWSAssertDebug(recipientId.length > 0); - - self = [super initWithTimestamp:timestamp inThread:thread messageType:TSInfoMessageVerificationStateChange]; - if (!self) { - return self; - } - - _recipientId = recipientId; - _verificationState = verificationState; - _isLocalChange = isLocalChange; - - return self; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSErrorMessage.h b/SignalServiceKit/src/Messages/Interactions/TSErrorMessage.h deleted file mode 100644 index ba44c1f5e..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSErrorMessage.h +++ /dev/null @@ -1,76 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSReadTracking.h" -#import "TSMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class SSKProtoEnvelope; - -typedef NS_ENUM(int32_t, TSErrorMessageType) { - TSErrorMessageNoSession, - // DEPRECATED: We no longer create TSErrorMessageWrongTrustedIdentityKey, but - // persisted legacy messages could exist indefinitly. - TSErrorMessageWrongTrustedIdentityKey, - TSErrorMessageInvalidKeyException, - // unused - TSErrorMessageMissingKeyId, - TSErrorMessageInvalidMessage, - // unused - TSErrorMessageDuplicateMessage, - TSErrorMessageInvalidVersion, - TSErrorMessageNonBlockingIdentityChange, - TSErrorMessageUnknownContactBlockOffer, - TSErrorMessageGroupCreationFailed, -}; - -@interface TSErrorMessage : TSMessage - -- (instancetype)initMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contact - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_UNAVAILABLE; - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt NS_UNAVAILABLE; - -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - failedMessageType:(TSErrorMessageType)errorMessageType - recipientId:(nullable NSString *)recipientId NS_DESIGNATED_INITIALIZER; - -+ (instancetype)corruptedMessageWithEnvelope:(SSKProtoEnvelope *)envelope - withTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -+ (instancetype)corruptedMessageInUnknownThread; - -+ (instancetype)invalidVersionWithEnvelope:(SSKProtoEnvelope *)envelope - withTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -+ (instancetype)invalidKeyExceptionWithEnvelope:(SSKProtoEnvelope *)envelope - withTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -+ (instancetype)missingSessionWithEnvelope:(SSKProtoEnvelope *)envelope - withTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -+ (instancetype)nonblockingIdentityChangeInThread:(TSThread *)thread recipientId:(NSString *)recipientId; - -@property (nonatomic, readonly) TSErrorMessageType errorType; -@property (nullable, nonatomic, readonly) NSString *recipientId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSErrorMessage.m b/SignalServiceKit/src/Messages/Interactions/TSErrorMessage.m deleted file mode 100644 index 712cf8e98..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSErrorMessage.m +++ /dev/null @@ -1,228 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSErrorMessage.h" -#import "ContactsManagerProtocol.h" -#import "OWSMessageManager.h" -#import "SSKEnvironment.h" -#import "TSContactThread.h" -#import "TSErrorMessage_privateConstructor.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSUInteger TSErrorMessageSchemaVersion = 1; - -@interface TSErrorMessage () - -@property (nonatomic, getter=wasRead) BOOL read; - -@property (nonatomic, readonly) NSUInteger errorMessageSchemaVersion; - -@end - -#pragma mark - - -@implementation TSErrorMessage - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - if (self.errorMessageSchemaVersion < 1) { - _read = YES; - } - - _errorMessageSchemaVersion = TSErrorMessageSchemaVersion; - - if (self.isDynamicInteraction) { - self.read = YES; - } - - return self; -} - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - failedMessageType:(TSErrorMessageType)errorMessageType - recipientId:(nullable NSString *)recipientId -{ - self = [super initMessageWithTimestamp:timestamp - inThread:thread - messageBody:nil - attachmentIds:@[] - expiresInSeconds:0 - expireStartedAt:0 - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - - if (!self) { - return self; - } - - _errorType = errorMessageType; - _recipientId = recipientId; - _errorMessageSchemaVersion = TSErrorMessageSchemaVersion; - - if (self.isDynamicInteraction) { - self.read = YES; - } - - return self; -} - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - failedMessageType:(TSErrorMessageType)errorMessageType -{ - return [self initWithTimestamp:timestamp inThread:thread failedMessageType:errorMessageType recipientId:nil]; -} - -- (instancetype)initWithEnvelope:(SSKProtoEnvelope *)envelope - withTransaction:(YapDatabaseReadWriteTransaction *)transaction - failedMessageType:(TSErrorMessageType)errorMessageType -{ - NSString *source = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source; - TSContactThread *contactThread = - [TSContactThread getOrCreateThreadWithContactId:source transaction:transaction]; - - // Legit usage of senderTimestamp. We don't actually currently surface it in the UI, but it serves as - // a reference to the envelope which we failed to process. - return [self initWithTimestamp:envelope.timestamp inThread:contactThread failedMessageType:errorMessageType]; -} - -- (OWSInteractionType)interactionType -{ - return OWSInteractionType_Error; -} - -- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - switch (_errorType) { - case TSErrorMessageNoSession: - return NSLocalizedString(@"ERROR_MESSAGE_NO_SESSION", @""); - case TSErrorMessageInvalidMessage: - return NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @""); - case TSErrorMessageInvalidVersion: - return NSLocalizedString(@"ERROR_MESSAGE_INVALID_VERSION", @""); - case TSErrorMessageDuplicateMessage: - return NSLocalizedString(@"ERROR_MESSAGE_DUPLICATE_MESSAGE", @""); - case TSErrorMessageInvalidKeyException: - return NSLocalizedString(@"ERROR_MESSAGE_INVALID_KEY_EXCEPTION", @""); - case TSErrorMessageWrongTrustedIdentityKey: - return NSLocalizedString(@"ERROR_MESSAGE_WRONG_TRUSTED_IDENTITY_KEY", @""); - case TSErrorMessageNonBlockingIdentityChange: { - if (self.recipientId) { - NSString *messageFormat = NSLocalizedString(@"ERROR_MESSAGE_NON_BLOCKING_IDENTITY_CHANGE_FORMAT", - @"Shown when signal users safety numbers changed, embeds the user's {{name or phone number}}"); - - NSString *recipientDisplayName = - [SSKEnvironment.shared.contactsManager displayNameForPhoneIdentifier:self.recipientId - transaction:transaction]; - return [NSString stringWithFormat:messageFormat, recipientDisplayName]; - } else { - // recipientId will be nil for legacy errors - return NSLocalizedString( - @"ERROR_MESSAGE_NON_BLOCKING_IDENTITY_CHANGE", @"Shown when signal users safety numbers changed"); - } - break; - } - case TSErrorMessageUnknownContactBlockOffer: - return NSLocalizedString(@"UNKNOWN_CONTACT_BLOCK_OFFER", - @"Message shown in conversation view that offers to block an unknown user."); - case TSErrorMessageGroupCreationFailed: - return NSLocalizedString(@"GROUP_CREATION_FAILED", - @"Message shown in conversation view that indicates there were issues with group creation."); - default: - return NSLocalizedString(@"ERROR_MESSAGE_UNKNOWN_ERROR", @""); - break; - } -} - -+ (instancetype)corruptedMessageWithEnvelope:(SSKProtoEnvelope *)envelope - withTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - return [[self alloc] initWithEnvelope:envelope - withTransaction:transaction - failedMessageType:TSErrorMessageInvalidMessage]; -} - -+ (instancetype)corruptedMessageInUnknownThread -{ - // MJK TODO - Seems like we could safely remove this timestamp - return [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:nil - failedMessageType:TSErrorMessageInvalidMessage]; -} - -+ (instancetype)invalidVersionWithEnvelope:(SSKProtoEnvelope *)envelope - withTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - return [[self alloc] initWithEnvelope:envelope - withTransaction:transaction - failedMessageType:TSErrorMessageInvalidVersion]; -} - -+ (instancetype)invalidKeyExceptionWithEnvelope:(SSKProtoEnvelope *)envelope - withTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - return [[self alloc] initWithEnvelope:envelope - withTransaction:transaction - failedMessageType:TSErrorMessageInvalidKeyException]; -} - -+ (instancetype)missingSessionWithEnvelope:(SSKProtoEnvelope *)envelope - withTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - return - [[self alloc] initWithEnvelope:envelope withTransaction:transaction failedMessageType:TSErrorMessageNoSession]; -} - -+ (instancetype)nonblockingIdentityChangeInThread:(TSThread *)thread recipientId:(NSString *)recipientId -{ - // MJK TODO - should be safe to remove this senderTimestamp - return [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - failedMessageType:TSErrorMessageNonBlockingIdentityChange - recipientId:recipientId]; -} - -#pragma mark - OWSReadTracking - -- (uint64_t)expireStartedAt -{ - return 0; -} - -- (BOOL)shouldAffectUnreadCounts -{ - return NO; -} - -- (void)markAsReadAtTimestamp:(uint64_t)readTimestamp - sendReadReceipt:(BOOL)sendReadReceipt - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - if (_read) { - return; - } - - OWSLogDebug(@"marking as read uniqueId: %@ which has timestamp: %llu", self.uniqueId, self.timestamp); - _read = YES; - [self saveWithTransaction:transaction]; - - // Ignore sendReadReceipt - it doesn't apply to error messages. -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSErrorMessage_privateConstructor.h b/SignalServiceKit/src/Messages/Interactions/TSErrorMessage_privateConstructor.h deleted file mode 100644 index 77e5a39f4..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSErrorMessage_privateConstructor.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSErrorMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface TSErrorMessage () - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - failedMessageType:(TSErrorMessageType)errorMessageType NS_DESIGNATED_INITIALIZER; - -@property (atomic, nullable) NSData *envelopeData; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSIncomingMessage.h b/SignalServiceKit/src/Messages/Interactions/TSIncomingMessage.h deleted file mode 100644 index 771723369..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSIncomingMessage.h +++ /dev/null @@ -1,90 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSReadTracking.h" -#import "TSMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class TSContactThread; -@class TSGroupThread; - -@interface TSIncomingMessage : TSMessage - -@property (nonatomic, readonly, nullable) NSNumber *serverTimestamp; - -@property (nonatomic, readonly) BOOL wasReceivedByUD; - -- (instancetype)initMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_UNAVAILABLE; - -/** - * Inits an incoming group message that expires. - * - * @param timestamp - * When the message was created in milliseconds since epoch - * @param thread - * Thread to which the message belongs - * @param authorId - * Signal ID (i.e. e164) of the user who sent the message - * @param sourceDeviceId - * Numeric ID of the device used to send the message. Used to detect duplicate messages. - * @param body - * Body of the message - * @param attachmentIds - * The uniqueIds for the message's attachments, possibly an empty list. - * @param expiresInSeconds - * Seconds from when the message is read until it is deleted. - * @param quotedMessage - * If this message is a quoted reply to another message, contains data about that message. - * - * @return initiated incoming group message - */ -- (instancetype)initIncomingMessageWithTimestamp:(uint64_t)timestamp - inThread:(TSThread *)thread - authorId:(NSString *)authorId - sourceDeviceId:(uint32_t)sourceDeviceId - messageBody:(nullable NSString *)body - attachmentIds:(NSArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview - serverTimestamp:(nullable NSNumber *)serverTimestamp - wasReceivedByUD:(BOOL)wasReceivedByUD NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -/* - * Find a message matching the senderId and timestamp, if any. - * - * @param authorId - * Signal ID (i.e. e164) of the user who sent the message - * @params timestamp - * When the message was created in milliseconds since epoch - * - */ -+ (nullable instancetype)findMessageWithAuthorId:(NSString *)authorId - timestamp:(uint64_t)timestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -// This will be 0 for messages created before we were tracking sourceDeviceId -@property (nonatomic, readonly) UInt32 sourceDeviceId; - -@property (nonatomic, readonly) NSString *authorId; - -// convenience method for expiring a message which was just read -- (void)markAsReadNowWithSendReadReceipt:(BOOL)sendReadReceipt - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSIncomingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSIncomingMessage.m deleted file mode 100644 index 9fcd887f9..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSIncomingMessage.m +++ /dev/null @@ -1,180 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSIncomingMessage.h" -#import "NSNotificationCenter+OWS.h" -#import "OWSDisappearingMessagesConfiguration.h" -#import "OWSDisappearingMessagesJob.h" -#import "OWSReadReceiptManager.h" -#import "TSAttachmentPointer.h" -#import "TSContactThread.h" -#import "TSDatabaseSecondaryIndexes.h" -#import "TSGroupThread.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface TSIncomingMessage () - -@property (nonatomic, getter=wasRead) BOOL read; - -@property (nonatomic, nullable) NSNumber *serverTimestamp; - -@end - -#pragma mark - - -@implementation TSIncomingMessage - -- (instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - if (_authorId == nil) { - OWSAssertDebug([self.uniqueThreadId hasPrefix:TSContactThreadPrefix]); - _authorId = [TSContactThread contactIdFromThreadId:self.uniqueThreadId]; - } - - return self; -} - -- (instancetype)initIncomingMessageWithTimestamp:(uint64_t)timestamp - inThread:(TSThread *)thread - authorId:(NSString *)authorId - sourceDeviceId:(uint32_t)sourceDeviceId - messageBody:(nullable NSString *)body - attachmentIds:(NSArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview - serverTimestamp:(nullable NSNumber *)serverTimestamp - wasReceivedByUD:(BOOL)wasReceivedByUD -{ - self = [super initMessageWithTimestamp:timestamp - inThread:thread - messageBody:body - attachmentIds:attachmentIds - expiresInSeconds:expiresInSeconds - expireStartedAt:0 - quotedMessage:quotedMessage - contactShare:contactShare - linkPreview:linkPreview]; - - if (!self) { - return self; - } - - _authorId = authorId; - _sourceDeviceId = sourceDeviceId; - _read = NO; - _serverTimestamp = serverTimestamp; - _wasReceivedByUD = wasReceivedByUD; - - return self; -} - -+ (nullable instancetype)findMessageWithAuthorId:(NSString *)authorId - timestamp:(uint64_t)timestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - __block TSIncomingMessage *foundMessage; - // In theory we could build a new secondaryIndex for (authorId,timestamp), but in practice there should - // be *very* few (millisecond) timestamps with multiple authors. - [TSDatabaseSecondaryIndexes - enumerateMessagesWithTimestamp:timestamp - withBlock:^(NSString *collection, NSString *key, BOOL *stop) { - TSInteraction *interaction = - [TSInteraction fetchObjectWithUniqueID:key transaction:transaction]; - if ([interaction isKindOfClass:[TSIncomingMessage class]]) { - TSIncomingMessage *message = (TSIncomingMessage *)interaction; - - OWSAssertDebug(message.authorId > 0); - - if ([message.authorId isEqualToString:authorId]) { - foundMessage = message; - } - } - } - usingTransaction:transaction]; - - return foundMessage; -} - - -- (OWSInteractionType)interactionType -{ - return OWSInteractionType_IncomingMessage; -} - -- (BOOL)shouldStartExpireTimerWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - for (NSString *attachmentId in self.attachmentIds) { - TSAttachment *_Nullable attachment = - [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction]; - if ([attachment isKindOfClass:[TSAttachmentPointer class]]) { - return NO; - } - } - return self.isExpiringMessage; -} - -#pragma mark - OWSReadTracking - -- (BOOL)shouldAffectUnreadCounts -{ - return YES; -} - -- (void)markAsReadNowWithSendReadReceipt:(BOOL)sendReadReceipt - transaction:(YapDatabaseReadWriteTransaction *)transaction; -{ - [self markAsReadAtTimestamp:[NSDate ows_millisecondTimeStamp] - sendReadReceipt:sendReadReceipt - transaction:transaction]; -} - -- (void)markAsReadAtTimestamp:(uint64_t)readTimestamp - sendReadReceipt:(BOOL)sendReadReceipt - transaction:(YapDatabaseReadWriteTransaction *)transaction; -{ - OWSAssertDebug(transaction); - - if (_read && readTimestamp >= self.expireStartedAt) { - return; - } - - NSTimeInterval secondsAgoRead = ((NSTimeInterval)[NSDate ows_millisecondTimeStamp] - (NSTimeInterval)readTimestamp) / 1000; - OWSLogDebug(@"marking uniqueId: %@ which has timestamp: %llu as read: %f seconds ago", - self.uniqueId, - self.timestamp, - secondsAgoRead); - _read = YES; - [self saveWithTransaction:transaction]; - - [transaction addCompletionQueue:nil - completionBlock:^{ - [[NSNotificationCenter defaultCenter] - postNotificationNameAsync:kIncomingMessageMarkedAsReadNotification - object:self]; - }]; - - [[OWSDisappearingMessagesJob sharedJob] startAnyExpirationForMessage:self - expirationStartedAt:readTimestamp - transaction:transaction]; - - if (sendReadReceipt) { - [OWSReadReceiptManager.sharedManager messageWasReadLocally:self]; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.h b/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.h deleted file mode 100644 index 26c42e819..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSReadTracking.h" -#import "TSMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface TSInfoMessage : TSMessage - -typedef NS_ENUM(NSInteger, TSInfoMessageType) { - TSInfoMessageTypeSessionDidEnd, - TSInfoMessageUserNotRegistered, - // TSInfoMessageTypeUnsupportedMessage appears to be obsolete. - TSInfoMessageTypeUnsupportedMessage, - TSInfoMessageTypeGroupUpdate, - TSInfoMessageTypeGroupQuit, - TSInfoMessageTypeDisappearingMessagesUpdate, - TSInfoMessageAddToContactsOffer, - TSInfoMessageVerificationStateChange, - TSInfoMessageAddUserToProfileWhitelistOffer, - TSInfoMessageAddGroupToProfileWhitelistOffer, - TSInfoMessageTypeLokiSessionResetInProgress, - TSInfoMessageTypeLokiSessionResetDone, -}; - -+ (instancetype)userNotRegisteredMessageInThread:(TSThread *)thread recipientId:(NSString *)recipientId; - -@property (atomic, readonly) TSInfoMessageType messageType; -@property (atomic, readonly, nullable) NSString *customMessage; -@property (atomic, readonly, nullable) NSString *unregisteredRecipientId; - -- (instancetype)initMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contact - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_UNAVAILABLE; - -- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - inThread:(TSThread *)contact - messageType:(TSInfoMessageType)infoMessage NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - inThread:(TSThread *)thread - messageType:(TSInfoMessageType)infoMessage - customMessage:(NSString *)customMessage; - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - inThread:(TSThread *)thread - messageType:(TSInfoMessageType)infoMessage - unregisteredRecipientId:(NSString *)unregisteredRecipientId; - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.m b/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.m deleted file mode 100644 index c1facd05b..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSInfoMessage.m +++ /dev/null @@ -1,194 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSInfoMessage.h" -#import "ContactsManagerProtocol.h" -#import "SSKEnvironment.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSUInteger TSInfoMessageSchemaVersion = 1; - -@interface TSInfoMessage () - -@property (nonatomic, getter=wasRead) BOOL read; - -@property (nonatomic, readonly) NSUInteger infoMessageSchemaVersion; - -@end - -#pragma mark - - -@implementation TSInfoMessage - -- (instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - if (self.infoMessageSchemaVersion < 1) { - _read = YES; - } - - _infoMessageSchemaVersion = TSInfoMessageSchemaVersion; - - if (self.isDynamicInteraction) { - self.read = YES; - } - - return self; -} - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - inThread:(TSThread *)thread - messageType:(TSInfoMessageType)infoMessage -{ - // MJK TODO - remove senderTimestamp - self = [super initMessageWithTimestamp:timestamp - inThread:thread - messageBody:nil - attachmentIds:@[] - expiresInSeconds:0 - expireStartedAt:0 - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - - if (!self) { - return self; - } - - _messageType = infoMessage; - _infoMessageSchemaVersion = TSInfoMessageSchemaVersion; - - if (self.isDynamicInteraction) { - self.read = YES; - } - - return self; -} - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - inThread:(TSThread *)thread - messageType:(TSInfoMessageType)infoMessage - customMessage:(NSString *)customMessage -{ - self = [self initWithTimestamp:timestamp inThread:thread messageType:infoMessage]; - if (self) { - _customMessage = customMessage; - } - return self; -} - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - inThread:(TSThread *)thread - messageType:(TSInfoMessageType)infoMessage - unregisteredRecipientId:(NSString *)unregisteredRecipientId -{ - self = [self initWithTimestamp:timestamp inThread:thread messageType:infoMessage]; - if (self) { - _unregisteredRecipientId = unregisteredRecipientId; - } - return self; -} - -+ (instancetype)userNotRegisteredMessageInThread:(TSThread *)thread recipientId:(NSString *)recipientId -{ - OWSAssertDebug(thread); - OWSAssertDebug(recipientId); - - // MJK TODO - remove senderTimestamp - return [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - messageType:TSInfoMessageUserNotRegistered - unregisteredRecipientId:recipientId]; -} - -- (OWSInteractionType)interactionType -{ - return OWSInteractionType_Info; -} - -- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - switch (_messageType) { - case TSInfoMessageTypeLokiSessionResetInProgress: - return NSLocalizedString(@"Secure session reset in progress", nil); - case TSInfoMessageTypeLokiSessionResetDone: - return NSLocalizedString(@"Secure session reset done", nil); - case TSInfoMessageTypeSessionDidEnd: - return NSLocalizedString(@"SECURE_SESSION_RESET", nil); - case TSInfoMessageTypeUnsupportedMessage: - return NSLocalizedString(@"UNSUPPORTED_ATTACHMENT", nil); - case TSInfoMessageUserNotRegistered: - if (self.unregisteredRecipientId.length > 0) { - id contactsManager = SSKEnvironment.shared.contactsManager; - NSString *recipientName = [contactsManager displayNameForPhoneIdentifier:self.unregisteredRecipientId - transaction:transaction]; - return [NSString stringWithFormat:NSLocalizedString(@"ERROR_UNREGISTERED_USER_FORMAT", - @"Format string for 'unregistered user' error. Embeds {{the " - @"unregistered user's name or signal id}}."), - recipientName]; - } else { - return NSLocalizedString(@"CONTACT_DETAIL_COMM_TYPE_INSECURE", nil); - } - case TSInfoMessageTypeGroupQuit: - return NSLocalizedString(@"GROUP_YOU_LEFT", nil); - case TSInfoMessageTypeGroupUpdate: - return _customMessage != nil ? _customMessage : NSLocalizedString(@"GROUP_UPDATED", nil); - case TSInfoMessageAddToContactsOffer: - return NSLocalizedString(@"ADD_TO_CONTACTS_OFFER", - @"Message shown in conversation view that offers to add an unknown user to your phone's contacts."); - case TSInfoMessageVerificationStateChange: - return NSLocalizedString(@"VERIFICATION_STATE_CHANGE_GENERIC", - @"Generic message indicating that verification state changed for a given user."); - case TSInfoMessageAddUserToProfileWhitelistOffer: - return NSLocalizedString(@"ADD_USER_TO_PROFILE_WHITELIST_OFFER", - @"Message shown in conversation view that offers to share your profile with a user."); - case TSInfoMessageAddGroupToProfileWhitelistOffer: - return NSLocalizedString(@"ADD_GROUP_TO_PROFILE_WHITELIST_OFFER", - @"Message shown in conversation view that offers to share your profile with a group."); - default: - break; - } - - return @"Unknown Info Message Type"; -} - -#pragma mark - OWSReadTracking - -- (BOOL)shouldAffectUnreadCounts -{ - return NO; -} - -- (uint64_t)expireStartedAt -{ - return 0; -} - -- (void)markAsReadAtTimestamp:(uint64_t)readTimestamp - sendReadReceipt:(BOOL)sendReadReceipt - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - if (_read) { - return; - } - - OWSLogDebug(@"marking as read uniqueId: %@ which has timestamp: %llu", self.uniqueId, self.timestamp); - _read = YES; - [self saveWithTransaction:transaction]; - - // Ignore sendReadReceipt, it doesn't apply to info messages. -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSInteraction.h b/SignalServiceKit/src/Messages/Interactions/TSInteraction.h deleted file mode 100644 index 2d230db56..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSInteraction.h +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSYapDatabaseObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@class TSThread; - -typedef NS_ENUM(NSInteger, OWSInteractionType) { - OWSInteractionType_Unknown, - OWSInteractionType_IncomingMessage, - OWSInteractionType_OutgoingMessage, - OWSInteractionType_Error, - OWSInteractionType_Call, - OWSInteractionType_Info, - OWSInteractionType_Offer, - OWSInteractionType_TypingIndicator, -}; - -NSString *NSStringFromOWSInteractionType(OWSInteractionType value); - -@protocol OWSPreviewText - -- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction; - -@end - -@interface TSInteraction : TSYapDatabaseObject - -- (instancetype)initInteractionWithUniqueId:(NSString *)uniqueId - timestamp:(uint64_t)timestamp - inThread:(TSThread *)thread; -- (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread; - -@property (nonatomic, readonly) NSString *uniqueThreadId; -@property (nonatomic, readonly) TSThread *thread; -@property (nonatomic, readonly) uint64_t timestamp; -@property (nonatomic, readonly) uint64_t sortId; -@property (nonatomic, readonly) uint64_t receivedAtTimestamp; -@property (nonatomic, readonly) BOOL shouldUseServerTime; -/// Used for public chats where a message sent from a slave device is interpreted as having been sent from the master device. -@property (nonatomic) NSString *actualSenderHexEncodedPublicKey; - -- (void)setServerTimestampToReceivedTimestamp:(uint64_t)receivedAtTimestamp; - -- (uint64_t)timestampForUI; - -- (NSDate *)receivedAtDate; - -- (OWSInteractionType)interactionType; - -- (TSThread *)threadWithTransaction:(YapDatabaseReadTransaction *)transaction; - -/** - * When an interaction is updated, it often affects the UI for it's containing thread. Touching it's thread will notify - * any observers so they can redraw any related UI. - */ -- (void)touchThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -#pragma mark Utility Method - -+ (NSArray *)interactionsWithTimestamp:(uint64_t)timestamp - ofClass:(Class)clazz - withTransaction:(YapDatabaseReadTransaction *)transaction; - -+ (NSArray *)interactionsWithTimestamp:(uint64_t)timestamp - filter:(BOOL (^_Nonnull)(TSInteraction *))filter - withTransaction:(YapDatabaseReadTransaction *)transaction; - -- (uint64_t)timestampForLegacySorting; -- (NSComparisonResult)compareForSorting:(TSInteraction *)other; - -// "Dynamic" interactions are not messages or static events (like -// info messages, error messages, etc.). They are interactions -// created, updated and deleted by the views. -// -// These include block offers, "add to contact" offers, -// unseen message indicators, etc. -- (BOOL)isDynamicInteraction; - -- (void)saveNextSortIdWithTransaction:(YapDatabaseReadWriteTransaction *)transaction - NS_SWIFT_NAME(saveNextSortId(transaction:)); - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSInteraction.m b/SignalServiceKit/src/Messages/Interactions/TSInteraction.m deleted file mode 100644 index 2d818d150..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSInteraction.m +++ /dev/null @@ -1,292 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSInteraction.h" -#import "TSDatabaseSecondaryIndexes.h" -#import "TSThread.h" -#import "TSGroupThread.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *NSStringFromOWSInteractionType(OWSInteractionType value) -{ - switch (value) { - case OWSInteractionType_Unknown: - return @"OWSInteractionType_Unknown"; - case OWSInteractionType_IncomingMessage: - return @"OWSInteractionType_IncomingMessage"; - case OWSInteractionType_OutgoingMessage: - return @"OWSInteractionType_OutgoingMessage"; - case OWSInteractionType_Error: - return @"OWSInteractionType_Error"; - case OWSInteractionType_Call: - return @"OWSInteractionType_Call"; - case OWSInteractionType_Info: - return @"OWSInteractionType_Info"; - case OWSInteractionType_Offer: - return @"OWSInteractionType_Offer"; - case OWSInteractionType_TypingIndicator: - return @"OWSInteractionType_TypingIndicator"; - } -} - -@interface TSInteraction () - -@property (nonatomic) uint64_t sortId; - -@end - -@implementation TSInteraction - -@synthesize timestamp = _timestamp; - -+ (NSArray *)interactionsWithTimestamp:(uint64_t)timestamp - ofClass:(Class)clazz - withTransaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(timestamp > 0); - - // Accept any interaction. - return [self interactionsWithTimestamp:timestamp - filter:^(TSInteraction *interaction) { - return [interaction isKindOfClass:clazz]; - } - withTransaction:transaction]; -} - -+ (NSArray *)interactionsWithTimestamp:(uint64_t)timestamp - filter:(BOOL (^_Nonnull)(TSInteraction *))filter - withTransaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(timestamp > 0); - - NSMutableArray *interactions = [NSMutableArray new]; - - [TSDatabaseSecondaryIndexes - enumerateMessagesWithTimestamp:timestamp - withBlock:^(NSString *collection, NSString *key, BOOL *stop) { - TSInteraction *interaction = - [TSInteraction fetchObjectWithUniqueID:key transaction:transaction]; - if (!filter(interaction)) { - return; - } - [interactions addObject:interaction]; - } - usingTransaction:transaction]; - - return [interactions copy]; -} - -+ (NSString *)collection { - return @"TSInteraction"; -} - -- (instancetype)initInteractionWithUniqueId:(NSString *)uniqueId - timestamp:(uint64_t)timestamp - inThread:(TSThread *)thread -{ - OWSAssertDebug(timestamp > 0); - - self = [super initWithUniqueId:uniqueId]; - - if (!self) { - return self; - } - - _timestamp = timestamp; - _uniqueThreadId = thread.uniqueId; - - return self; -} - -- (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread -{ - OWSAssertDebug(timestamp > 0); - - self = [super initWithUniqueId:[[NSUUID UUID] UUIDString]]; - - if (!self) { - return self; - } - - _timestamp = timestamp; - _uniqueThreadId = thread.uniqueId; - _receivedAtTimestamp = [NSDate ows_millisecondTimeStamp]; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return nil; - } - - // Previously the receivedAtTimestamp field lived on TSMessage, but we've moved it up - // to the TSInteraction superclass. - if (_receivedAtTimestamp == 0) { - // Upgrade from the older "TSMessage.receivedAtDate" and "TSMessage.receivedAt" properties if - // necessary. - NSDate *receivedAtDate = [coder decodeObjectForKey:@"receivedAtDate"]; - if (!receivedAtDate) { - receivedAtDate = [coder decodeObjectForKey:@"receivedAt"]; - } - - if (receivedAtDate) { - _receivedAtTimestamp = [NSDate ows_millisecondsSince1970ForDate:receivedAtDate]; - } - - // For TSInteractions which are not TSMessage's, the timestamp *is* the receivedAtTimestamp - if (_receivedAtTimestamp == 0) { - _receivedAtTimestamp = _timestamp; - } - } - - return self; -} - -#pragma mark Thread - -- (TSThread *)thread -{ - return [TSThread fetchObjectWithUniqueID:self.uniqueThreadId]; -} - -- (TSThread *)threadWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - return [TSThread fetchObjectWithUniqueID:self.uniqueThreadId transaction:transaction]; -} - -- (void)touchThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [transaction touchObjectForKey:self.uniqueThreadId inCollection:[TSThread collection]]; -} - -- (void)applyChangeToSelfAndLatestCopy:(YapDatabaseReadWriteTransaction *)transaction - changeBlock:(void (^)(id))changeBlock -{ - OWSAssertDebug(transaction); - - [super applyChangeToSelfAndLatestCopy:transaction changeBlock:changeBlock]; - [self touchThreadWithTransaction:transaction]; -} - -#pragma mark Date operations - -- (uint64_t)timestampForUI -{ - if (_shouldUseServerTime) { - return _receivedAtTimestamp; - } - return _timestamp; -} - -- (uint64_t)timestampForLegacySorting -{ - return self.timestamp; -} - -- (void)setServerTimestampToReceivedTimestamp:(uint64_t)receivedAtTimestamp -{ - _shouldUseServerTime = YES; - _receivedAtTimestamp = receivedAtTimestamp; -} - -- (NSDate *)receivedAtDate -{ - return [NSDate ows_dateWithMillisecondsSince1970:self.receivedAtTimestamp]; -} - -- (NSComparisonResult)compareForSorting:(TSInteraction *)other -{ - OWSAssertDebug(other); - - // Sort the messages by the sender's timestamp (Signal uses sortId) - uint64_t sortId1 = self.timestamp; - uint64_t sortId2 = other.timestamp; - - // In open groups messages should be sorted by their server timestamp. `sortId` represents the order in which messages - // were processed. Since in the open group poller we sort messages by their server timestamp, sorting by `sortId` is - // effectively the same as sorting by server timestamp. - if (self.thread.isGroupThread) { - TSGroupThread *thread = (TSGroupThread *)self.thread; - if (thread.isPublicChat) { - sortId1 = self.sortId; - sortId2 = other.sortId; - } - } - - if (sortId1 > sortId2) { - return NSOrderedDescending; - } else if (sortId1 < sortId2) { - return NSOrderedAscending; - } else { - return NSOrderedSame; - } -} - -- (OWSInteractionType)interactionType -{ - OWSFailDebug(@"unknown interaction type."); - - return OWSInteractionType_Unknown; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"%@ in thread: %@ timestamp: %lu", - [super description], - self.uniqueThreadId, - (unsigned long)self.timestamp]; -} - -- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!self.uniqueId) { - OWSFailDebug(@"Missing uniqueId."); - self.uniqueId = [NSUUID new].UUIDString; - } - if (self.sortId == 0) { - self.sortId = [SSKIncrementingIdFinder nextIdWithKey:[TSInteraction collection] transaction:transaction]; - } - - [super saveWithTransaction:transaction]; - - TSThread *fetchedThread = [self threadWithTransaction:transaction]; - - [fetchedThread updateWithLastMessage:self transaction:transaction]; -} - -- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [super removeWithTransaction:transaction]; - - [self touchThreadWithTransaction:transaction]; -} - -- (BOOL)isDynamicInteraction -{ - return NO; -} - -#pragma mark - sorting migration - -- (void)saveNextSortIdWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (self.sortId != 0) { - // This could happen if something else in our startup process saved the interaction - // e.g. another migration ran. - // During the migration, since we're enumerating the interactions in the proper order, - // we want to ignore any previously assigned sortId - self.sortId = 0; - } - [self saveWithTransaction:transaction]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSMessage.h b/SignalServiceKit/src/Messages/Interactions/TSMessage.h deleted file mode 100644 index e4085c094..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSMessage.h +++ /dev/null @@ -1,84 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSInteraction.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - * Abstract message class. - */ - -@class OWSContact; -@class OWSLinkPreview; -@class TSAttachment; -@class TSAttachmentStream; -@class TSQuotedMessage; -@class YapDatabaseReadWriteTransaction; - -@interface TSMessage : TSInteraction - -@property (nonatomic, readonly) NSMutableArray *attachmentIds; -@property (nonatomic, readonly, nullable) NSString *body; -@property (nonatomic, readonly) uint32_t expiresInSeconds; -@property (nonatomic, readonly) uint64_t expireStartedAt; -@property (nonatomic, readonly) uint64_t expiresAt; -@property (nonatomic, readonly) BOOL isExpiringMessage; -@property (nonatomic, readonly, nullable) TSQuotedMessage *quotedMessage; -@property (nonatomic, readonly, nullable) OWSContact *contactShare; -@property (nonatomic, nullable) OWSLinkPreview *linkPreview; -@property BOOL skipSave; -// P2P -@property (nonatomic) BOOL isP2P; -// Open groups -@property (nonatomic) uint64_t openGroupServerMessageID; -@property (nonatomic, readonly) BOOL isOpenGroupMessage; - -- (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread NS_UNAVAILABLE; - -- (instancetype)initMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_DESIGNATED_INITIALIZER; - -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -- (BOOL)hasAttachments; -- (NSArray *)attachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (NSArray *)mediaAttachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (nullable TSAttachment *)oversizeTextAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (void)addAttachmentWithID:(NSString *)attachmentID in:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)removeAttachment:(TSAttachment *)attachment - transaction:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(removeAttachment(_:transaction:)); - -// Returns ids for all attachments, including message ("body") attachments, -// quoted reply thumbnails, contact share avatars, link preview images, etc. -- (NSArray *)allAttachmentIds; - -- (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream; - -- (nullable NSString *)oversizeTextWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (nullable NSString *)bodyTextWithTransaction:(YapDatabaseReadTransaction *)transaction; - -- (BOOL)shouldStartExpireTimerWithTransaction:(YapDatabaseReadTransaction *)transaction; - -#pragma mark - Update With... Methods - -- (void)updateWithExpireStartedAt:(uint64_t)expireStartedAt transaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)updateWithLinkPreview:(OWSLinkPreview *)linkPreview transaction:(YapDatabaseReadWriteTransaction *)transaction; - -#pragma mark - Open Groups - -- (void)saveOpenGroupServerMessageID:(uint64_t)serverMessageID in:(YapDatabaseReadWriteTransaction *_Nullable)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSMessage.m b/SignalServiceKit/src/Messages/Interactions/TSMessage.m deleted file mode 100644 index bd2173eec..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSMessage.m +++ /dev/null @@ -1,466 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSMessage.h" -#import "AppContext.h" -#import "MIMETypeUtil.h" -#import "NSString+SSK.h" -#import "OWSContact.h" -#import "OWSDisappearingMessagesConfiguration.h" -#import "TSAttachment.h" -#import "TSAttachmentStream.h" -#import "TSQuotedMessage.h" -#import "TSThread.h" -#import -#import -#import -#import -#import "OWSPrimaryStorage+Loki.h" -#import "TSContactThread.h" - -NS_ASSUME_NONNULL_BEGIN - -static const NSUInteger OWSMessageSchemaVersion = 4; - -#pragma mark - - -@interface TSMessage () - -@property (nonatomic, nullable) NSString *body; -@property (nonatomic) uint32_t expiresInSeconds; -@property (nonatomic) uint64_t expireStartedAt; - -/** - * The version of the model class's schema last used to serialize this model. Use this to manage data migrations during - * object de/serialization. - * - * e.g. - * - * - (id)initWithCoder:(NSCoder *)coder - * { - * self = [super initWithCoder:coder]; - * if (!self) { return self; } - * if (_schemaVersion < 2) { - * _newName = [coder decodeObjectForKey:@"oldName"] - * } - * ... - * _schemaVersion = 2; - * } - */ -@property (nonatomic, readonly) NSUInteger schemaVersion; - -@end - -#pragma mark - - -@implementation TSMessage - -- (instancetype)initMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview -{ - self = [super initInteractionWithTimestamp:timestamp inThread:thread]; - - if (!self) { - return self; - } - - _schemaVersion = OWSMessageSchemaVersion; - - _body = body; - _attachmentIds = attachmentIds ? [attachmentIds mutableCopy] : [NSMutableArray new]; - _expiresInSeconds = expiresInSeconds; - _expireStartedAt = expireStartedAt; - [self updateExpiresAt]; - _quotedMessage = quotedMessage; - _contactShare = contactShare; - _linkPreview = linkPreview; - _openGroupServerMessageID = -1; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - if (_schemaVersion < 2) { - // renamed _attachments to _attachmentIds - if (!_attachmentIds) { - _attachmentIds = [coder decodeObjectForKey:@"attachments"]; - } - } - - if (_schemaVersion < 3) { - _expiresInSeconds = 0; - _expireStartedAt = 0; - _expiresAt = 0; - } - - if (_schemaVersion < 4) { - // Wipe out the body field on these legacy attachment messages. - // - // Explantion: Historically, a message sent from iOS could be an attachment XOR a text message, - // but now we support sending an attachment+caption as a single message. - // - // Other clients have supported sending attachment+caption in a single message for a long time. - // So the way we used to handle receiving them was to make it look like they'd sent two messages: - // first the attachment+caption (we'd ignore this caption when rendering), followed by a separate - // message with just the caption (which we'd render as a simple independent text message), for - // which we'd offset the timestamp by a little bit to get the desired ordering. - // - // Now that we can properly render an attachment+caption message together, these legacy "dummy" text - // messages are not only unnecessary, but worse, would be rendered redundantly. For safety, rather - // than building the logic to try to find and delete the redundant "dummy" text messages which users - // have been seeing and interacting with, we delete the body field from the attachment message, - // which iOS users have never seen directly. - if (_attachmentIds.count > 0) { - _body = nil; - } - } - - if (!_attachmentIds) { - _attachmentIds = [NSMutableArray new]; - } - - _schemaVersion = OWSMessageSchemaVersion; - - return self; -} - -- (void)setExpiresInSeconds:(uint32_t)expiresInSeconds -{ - uint32_t maxExpirationDuration = [OWSDisappearingMessagesConfiguration maxDurationSeconds]; - if (expiresInSeconds > maxExpirationDuration) { - OWSFailDebug(@"using `maxExpirationDuration` instead of: %u", maxExpirationDuration); - } - - _expiresInSeconds = MIN(expiresInSeconds, maxExpirationDuration); - [self updateExpiresAt]; -} - -- (void)setExpireStartedAt:(uint64_t)expireStartedAt -{ - if (_expireStartedAt != 0 && _expireStartedAt < expireStartedAt) { - OWSLogDebug(@"ignoring later startedAt time"); - return; - } - - uint64_t now = [NSDate ows_millisecondTimeStamp]; - if (expireStartedAt > now) { - OWSLogWarn(@"using `now` instead of future time"); - } - - _expireStartedAt = MIN(now, expireStartedAt); - [self updateExpiresAt]; -} - -- (BOOL)shouldStartExpireTimerWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - return self.isExpiringMessage; -} - -// TODO a downloaded media doesn't start counting until download is complete. -- (void)updateExpiresAt -{ - if (_expiresInSeconds > 0 && _expireStartedAt > 0) { - _expiresAt = _expireStartedAt + _expiresInSeconds * 1000; - } else { - _expiresAt = 0; - } -} - -- (BOOL)hasAttachments -{ - return self.attachmentIds ? (self.attachmentIds.count > 0) : NO; -} - -- (NSArray *)allAttachmentIds -{ - NSMutableArray *result = [NSMutableArray new]; - if (self.attachmentIds.count > 0) { - [result addObjectsFromArray:self.attachmentIds]; - } - - if (self.quotedMessage) { - [result addObjectsFromArray:self.quotedMessage.thumbnailAttachmentStreamIds]; - } - - if (self.contactShare.avatarAttachmentId) { - [result addObject:self.contactShare.avatarAttachmentId]; - } - - if (self.linkPreview.imageAttachmentId) { - [result addObject:self.linkPreview.imageAttachmentId]; - } - - return [result copy]; -} - -- (NSArray *)attachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - NSMutableArray *attachments = [NSMutableArray new]; - for (NSString *attachmentId in self.attachmentIds) { - TSAttachment *_Nullable attachment = - [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction]; - if (attachment) { - [attachments addObject:attachment]; - } - } - return [attachments copy]; -} - -- (NSArray *)attachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction - contentType:(NSString *)contentType -{ - NSArray *attachments = [self attachmentsWithTransaction:transaction]; - return [attachments filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(TSAttachment *evaluatedObject, - NSDictionary *_Nullable bindings) { - return [evaluatedObject.contentType isEqualToString:contentType]; - }]]; -} - -- (NSArray *)attachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction - exceptContentType:(NSString *)contentType -{ - NSArray *attachments = [self attachmentsWithTransaction:transaction]; - return [attachments filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(TSAttachment *evaluatedObject, - NSDictionary *_Nullable bindings) { - return ![evaluatedObject.contentType isEqualToString:contentType]; - }]]; -} - -- (void)removeAttachment:(TSAttachment *)attachment transaction:(YapDatabaseReadWriteTransaction *)transaction; -{ - OWSAssertDebug([self.attachmentIds containsObject:attachment.uniqueId]); - [attachment removeWithTransaction:transaction]; - - [self.attachmentIds removeObject:attachment.uniqueId]; - - [self saveWithTransaction:transaction]; -} - -- (void)addAttachmentWithID:(NSString *)attachmentID in:(YapDatabaseReadWriteTransaction *)transaction { - if (!self.attachmentIds) { return; } - [self.attachmentIds addObject:attachmentID]; - [self saveWithTransaction:transaction]; -} - -- (NSString *)debugDescription -{ - if ([self hasAttachments] && self.body.length > 0) { - NSString *attachmentId = self.attachmentIds[0]; - return [NSString - stringWithFormat:@"Media Message with attachmentId: %@ and caption: '%@'", attachmentId, self.body]; - } else if ([self hasAttachments]) { - NSString *attachmentId = self.attachmentIds[0]; - return [NSString stringWithFormat:@"Media Message with attachmentId: %@", attachmentId]; - } else { - return [NSString stringWithFormat:@"%@ with body: %@", [self class], self.body]; - } -} - -- (nullable TSAttachment *)oversizeTextAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - return [self attachmentsWithTransaction:transaction contentType:OWSMimeTypeOversizeTextMessage].firstObject; -} - -- (NSArray *)mediaAttachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - return [self attachmentsWithTransaction:transaction exceptContentType:OWSMimeTypeOversizeTextMessage]; -} - -- (nullable NSString *)oversizeTextWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - TSAttachment *_Nullable attachment = [self oversizeTextAttachmentWithTransaction:transaction]; - if (!attachment) { - return nil; - } - - if (![attachment isKindOfClass:TSAttachmentStream.class]) { - return nil; - } - - TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; - - NSData *_Nullable data = [NSData dataWithContentsOfFile:attachmentStream.originalFilePath]; - if (!data) { - OWSFailDebug(@"Can't load oversize text data."); - return nil; - } - NSString *_Nullable text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - if (!text) { - OWSFailDebug(@"Can't parse oversize text data."); - return nil; - } - return text.filterStringForDisplay; -} - -- (nullable NSString *)bodyTextWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - NSString *_Nullable oversizeText = [self oversizeTextWithTransaction:transaction]; - if (oversizeText) { - return oversizeText; - } - - if (self.body.length > 0) { - return self.body.filterStringForDisplay; - } - - return nil; -} - -// TODO: This method contains view-specific logic and probably belongs in NotificationsManager, not in SSK. -- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - NSString *_Nullable bodyDescription = nil; - if (self.body.length > 0) { - bodyDescription = self.body; - } - - if (bodyDescription == nil) { - TSAttachment *_Nullable oversizeTextAttachment = [self oversizeTextAttachmentWithTransaction:transaction]; - if ([oversizeTextAttachment isKindOfClass:[TSAttachmentStream class]]) { - TSAttachmentStream *oversizeTextAttachmentStream = (TSAttachmentStream *)oversizeTextAttachment; - NSData *_Nullable data = [NSData dataWithContentsOfFile:oversizeTextAttachmentStream.originalFilePath]; - if (data) { - NSString *_Nullable text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - if (text) { - bodyDescription = text.filterStringForDisplay; - } - } - } - } - - NSString *_Nullable attachmentDescription = nil; - TSAttachment *_Nullable mediaAttachment = [self mediaAttachmentsWithTransaction:transaction].firstObject; - if (mediaAttachment != nil) { - attachmentDescription = mediaAttachment.description; - } - - if (attachmentDescription.length > 0 && bodyDescription.length > 0) { - // Attachment with caption. - if ([CurrentAppContext() isRTL]) { - return [[bodyDescription stringByAppendingString:@": "] stringByAppendingString:attachmentDescription]; - } else { - return [[attachmentDescription stringByAppendingString:@": "] stringByAppendingString:bodyDescription]; - } - } else if (bodyDescription.length > 0) { - return bodyDescription; - } else if (attachmentDescription.length > 0) { - return attachmentDescription; - } else if (self.contactShare) { - if (CurrentAppContext().isRTL) { - return [self.contactShare.name.displayName stringByAppendingString:@" 👤"]; - } else { - return [@"👤 " stringByAppendingString:self.contactShare.name.displayName]; - } - } else { - // TODO: We should do better here. - return @""; - } -} - -- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [super removeWithTransaction:transaction]; - - for (NSString *attachmentId in self.allAttachmentIds) { - // We need to fetch each attachment, since [TSAttachment removeWithTransaction:] does important work. - TSAttachment *_Nullable attachment = - [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction]; - if (!attachment) { - OWSFailDebug(@"couldn't load interaction's attachment for deletion."); - continue; - } - [attachment removeWithTransaction:transaction]; - }; -} - -- (BOOL)isExpiringMessage -{ - return self.expiresInSeconds > 0; -} - -- (uint64_t)timestampForLegacySorting -{ - if ([self shouldUseReceiptDateForSorting] && self.receivedAtTimestamp > 0) { - return self.receivedAtTimestamp; - } else { - OWSAssertDebug(self.timestamp > 0); - return self.timestamp; - } -} - -- (BOOL)shouldUseReceiptDateForSorting -{ - return YES; -} - -- (nullable NSString *)body -{ - return _body.filterStringForDisplay; -} - -- (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream -{ - OWSAssertDebug([attachmentStream isKindOfClass:[TSAttachmentStream class]]); - OWSAssertDebug(self.quotedMessage); - OWSAssertDebug(self.quotedMessage.quotedAttachments.count == 1); - - [self.quotedMessage setThumbnailAttachmentStream:attachmentStream]; -} - -#pragma mark - Update With... Methods - -- (void)updateWithExpireStartedAt:(uint64_t)expireStartedAt transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(expireStartedAt > 0); - - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSMessage *message) { - [message setExpireStartedAt:expireStartedAt]; - }]; -} - -- (void)updateWithLinkPreview:(OWSLinkPreview *)linkPreview transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(linkPreview); - OWSAssertDebug(transaction); - - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSMessage *message) { - [message setLinkPreview:linkPreview]; - }]; -} - -#pragma mark - Open Groups - -- (BOOL)isOpenGroupMessage { - return self.openGroupServerMessageID > 0; -} - -- (void)saveOpenGroupServerMessageID:(uint64_t)serverMessageID in:(YapDatabaseReadWriteTransaction *_Nullable)transaction { - self.openGroupServerMessageID = serverMessageID; - if (transaction == nil) { - [self save]; - [self.dbReadWriteConnection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:^{}]; - } else { - [self saveWithTransaction:transaction]; - [transaction.connection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:^{}]; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h deleted file mode 100644 index 8ea563e7b..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h +++ /dev/null @@ -1,265 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -// Feature flag. -// -// TODO: Remove. -BOOL AreRecipientUpdatesEnabled(void); - -typedef NS_ENUM(NSInteger, TSOutgoingMessageState) { - // The message is either: - // a) Enqueued for sending. - // b) Waiting on attachment upload(s). - // c) Being sent to the service. - TSOutgoingMessageStateSending, - // The failure state. - TSOutgoingMessageStateFailed, - // These two enum values have been combined into TSOutgoingMessageStateSent. - TSOutgoingMessageStateSent_OBSOLETE, - TSOutgoingMessageStateDelivered_OBSOLETE, - // The message has been sent to the service. - TSOutgoingMessageStateSent, -}; - -NSString *NSStringForOutgoingMessageState(TSOutgoingMessageState value); - -typedef NS_ENUM(NSInteger, OWSOutgoingMessageRecipientState) { - // Message could not be sent to recipient. - OWSOutgoingMessageRecipientStateFailed = 0, - // Message is being sent to the recipient (enqueued, uploading or sending). - OWSOutgoingMessageRecipientStateSending, - // The message was not sent because the recipient is not valid. - // For example, this recipient may have left the group. - OWSOutgoingMessageRecipientStateSkipped, - // The message has been sent to the service. It may also have been delivered or read. - OWSOutgoingMessageRecipientStateSent, - - OWSOutgoingMessageRecipientStateMin = OWSOutgoingMessageRecipientStateFailed, - OWSOutgoingMessageRecipientStateMax = OWSOutgoingMessageRecipientStateSent, -}; - -NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientState value); - -typedef NS_ENUM(NSInteger, TSGroupMetaMessage) { - TSGroupMetaMessageUnspecified, - TSGroupMetaMessageNew, - TSGroupMetaMessageUpdate, - TSGroupMetaMessageDeliver, - TSGroupMetaMessageQuit, - TSGroupMetaMessageRequestInfo, -}; - -@class SSKProtoAttachmentPointer; -@class SSKProtoContentBuilder; -@class SSKProtoDataMessage; -@class SSKProtoDataMessageBuilder; -@class SignalRecipient; - -@interface TSOutgoingMessageRecipientState : MTLModel - -@property (atomic, readonly) OWSOutgoingMessageRecipientState state; -// This property should only be set if state == .sent. -@property (atomic, nullable, readonly) NSNumber *deliveryTimestamp; -// This property should only be set if state == .sent. -@property (atomic, nullable, readonly) NSNumber *readTimestamp; - -@property (atomic, readonly) BOOL wasSentByUD; - -@end - -#pragma mark - - -@interface TSOutgoingMessage : TSMessage - -- (instancetype)initMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_UNAVAILABLE; - -// MJK TODO - Can we remove the sender timestamp param? -- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSMutableArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - isVoiceMessage:(BOOL)isVoiceMessage - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_DESIGNATED_INITIALIZER; - -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentId:(nullable NSString *)attachmentId; - -+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentId:(nullable NSString *)attachmentId - expiresInSeconds:(uint32_t)expiresInSeconds; - -+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentId:(nullable NSString *)attachmentId - expiresInSeconds:(uint32_t)expiresInSeconds - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - linkPreview:(nullable OWSLinkPreview *)linkPreview; - -+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - expiresInSeconds:(uint32_t)expiresInSeconds; - -@property (readonly) TSOutgoingMessageState messageState; -@property (readonly) BOOL wasDeliveredToAnyRecipient; -@property (readonly) BOOL wasSentToAnyRecipient; - -@property (atomic, readonly) BOOL hasSyncedTranscript; -@property (atomic, readonly) NSString *customMessage; -@property (atomic, readonly) NSString *mostRecentFailureText; -// A map of attachment id-to-"source" filename. -@property (nonatomic, readonly) NSMutableDictionary *attachmentFilenameMap; - -@property (atomic, readonly) TSGroupMetaMessage groupMetaMessage; - -@property (nonatomic, readonly) BOOL isVoiceMessage; - -// This property won't be accurate for legacy messages. -@property (atomic, readonly) BOOL isFromLinkedDevice; - -@property (nonatomic, readonly) BOOL isSilent; - -@property (nonatomic, readonly) BOOL isOnline; - -/// Loki: Whether proof of work is being calculated for this message. -@property (atomic, readonly) BOOL isCalculatingPoW; - -/// Loki: Time to live for the message in milliseconds. -@property (nonatomic, readonly) uint ttl; - -/** - * The data representation of this message, to be encrypted, before being sent. - */ -- (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient; - -/** - * Intermediate protobuf representation - * Subclasses can augment if they want to manipulate the data message before building. - */ -- (nullable id)dataMessageBuilder; - -- (nullable SSKProtoDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId; - -/** - * Allows subclasses to supply a custom content builder that has already prepared part of the message. - */ -- (nullable id)prepareCustomContentBuilder:(SignalRecipient *)recipient; - -/** - * Should this message be synced to the users other registered devices? This is - * generally always true, except in the case of the sync messages themseleves - * (so we don't end up in an infinite loop). - */ -- (BOOL)shouldSyncTranscript; - -- (BOOL)shouldBeSaved; - -// All recipients of this message. -- (NSArray *)recipientIds; - -// All recipients of this message who we are currently trying to send to (queued, uploading or during send). -- (NSArray *)sendingRecipientIds; - -// All recipients of this message to whom it has been sent (and possibly delivered or read). -- (NSArray *)sentRecipientIds; - -// All recipients of this message to whom it has been sent and delivered (and possibly read). -- (NSArray *)deliveredRecipientIds; - -// All recipients of this message to whom it has been sent, delivered and read. -- (NSArray *)readRecipientIds; - -// Number of recipients of this message to whom it has been sent. -- (NSUInteger)sentRecipientsCount; - -- (nullable TSOutgoingMessageRecipientState *)recipientStateForRecipientId:(NSString *)recipientId; - -#pragma mark - Update With... Methods - -// When sending a message, when proof of work calculation is started, we should mark it as such -- (void)saveIsCalculatingProofOfWork:(BOOL)isCalculatingPoW withTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -// This method is used to record a successful send to one recipient. -- (void)updateWithSentRecipient:(NSString *)recipientId - wasSentByUD:(BOOL)wasSentByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -// This method is used to record a skipped send to one recipient. -- (void)updateWithSkippedRecipient:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction; - -// On app launch, all "sending" recipients should be marked as "failed". -- (void)updateWithAllSendingRecipientsMarkedAsFailedWithTansaction:(YapDatabaseReadWriteTransaction *)transaction; - -// When we start a message send, all "failed" recipients should be marked as "sending". -- (void)updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -// This method is used to forge the message state for fake messages. -// -// NOTE: This method should only be used by Debug UI, etc. -- (void)updateWithFakeMessageState:(TSOutgoingMessageState)messageState - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -// This method is used to record a failed send to all "sending" recipients. -- (void)updateWithSendingError:(NSError *)error - transaction:(YapDatabaseReadWriteTransaction *)transaction - NS_SWIFT_NAME(update(sendingError:transaction:)); - -- (void)updateWithHasSyncedTranscript:(BOOL)hasSyncedTranscript - transaction:(YapDatabaseReadWriteTransaction *)transaction; -- (void)updateWithCustomMessage:(NSString *)customMessage transaction:(YapDatabaseReadWriteTransaction *)transaction; -- (void)updateWithCustomMessage:(NSString *)customMessage; - -// This method is used to record a successful delivery to one recipient. -// -// deliveryTimestamp is an optional parameter, since legacy -// delivery receipts don't have a "delivery timestamp". Those -// messages repurpose the "timestamp" field to indicate when the -// corresponding message was originally sent. -- (void)updateWithDeliveredRecipient:(NSString *)recipientId - deliveryTimestamp:(NSNumber *_Nullable)deliveryTimestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)updateWithWasSentFromLinkedDeviceWithUDRecipientIds:(nullable NSArray *)udRecipientIds - nonUdRecipientIds:(nullable NSArray *)nonUdRecipientIds - isSentUpdate:(BOOL)isSentUpdate - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -// This method is used to rewrite the recipient list with a single recipient. -// It is used to reply to a "group info request", which should only be -// delivered to the requestor. -- (void)updateWithSendingToSingleGroupRecipient:(NSString *)singleGroupRecipient - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -// This method is used to record a successful "read" by one recipient. -- (void)updateWithReadRecipientId:(NSString *)recipientId - readTimestamp:(uint64_t)readTimestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (nullable NSNumber *)firstRecipientReadTimestamp; - -- (NSString *)statusDescription; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m deleted file mode 100644 index db6f12b91..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ /dev/null @@ -1,1172 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -@import Foundation; - -#import "TSOutgoingMessage.h" -#import "NSString+SSK.h" -#import "OWSContact.h" -#import "OWSMessageSender.h" -#import "OWSOutgoingSyncMessage.h" -#import "OWSPrimaryStorage.h" -#import "ProfileManagerProtocol.h" -#import "ProtoUtils.h" -#import "SSKEnvironment.h" -#import "SignalRecipient.h" -#import "TSAccountManager.h" -#import "TSAttachmentStream.h" -#import "TSContactThread.h" -#import "TSGroupThread.h" -#import "TSQuotedMessage.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -BOOL AreRecipientUpdatesEnabled(void) -{ - return NO; -} - -NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRecipientAll"; - -NSString *NSStringForOutgoingMessageState(TSOutgoingMessageState value) -{ - switch (value) { - case TSOutgoingMessageStateSending: - return @"TSOutgoingMessageStateSending"; - case TSOutgoingMessageStateFailed: - return @"TSOutgoingMessageStateFailed"; - case TSOutgoingMessageStateSent_OBSOLETE: - return @"TSOutgoingMessageStateSent_OBSOLETE"; - case TSOutgoingMessageStateDelivered_OBSOLETE: - return @"TSOutgoingMessageStateDelivered_OBSOLETE"; - case TSOutgoingMessageStateSent: - return @"TSOutgoingMessageStateSent"; - } -} - -NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientState value) -{ - switch (value) { - case OWSOutgoingMessageRecipientStateFailed: - return @"OWSOutgoingMessageRecipientStateFailed"; - case OWSOutgoingMessageRecipientStateSending: - return @"OWSOutgoingMessageRecipientStateSending"; - case OWSOutgoingMessageRecipientStateSkipped: - return @"OWSOutgoingMessageRecipientStateSkipped"; - case OWSOutgoingMessageRecipientStateSent: - return @"OWSOutgoingMessageRecipientStateSent"; - } -} - -@interface TSOutgoingMessageRecipientState () - -@property (atomic) OWSOutgoingMessageRecipientState state; -@property (atomic, nullable) NSNumber *deliveryTimestamp; -@property (atomic, nullable) NSNumber *readTimestamp; -@property (atomic) BOOL wasSentByUD; - -@end - -#pragma mark - - -@implementation TSOutgoingMessageRecipientState - -@end - -#pragma mark - - -@interface TSOutgoingMessage () - -@property (atomic) BOOL hasSyncedTranscript; -@property (atomic) NSString *customMessage; -@property (atomic) NSString *mostRecentFailureText; -@property (atomic) BOOL isFromLinkedDevice; -@property (atomic) TSGroupMetaMessage groupMetaMessage; - -@property (nonatomic, readonly) TSOutgoingMessageState legacyMessageState; -@property (nonatomic, readonly) BOOL legacyWasDelivered; -@property (nonatomic, readonly) BOOL hasLegacyMessageState; - -@property (atomic, nullable) NSDictionary *recipientStateMap; - -@property (atomic) BOOL isCalculatingPoW; - -@end - -#pragma mark - - -@implementation TSOutgoingMessage - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - - if (self) { - if (!_attachmentFilenameMap) { - _attachmentFilenameMap = [NSMutableDictionary new]; - } - - if (!self.recipientStateMap) { - [self migrateRecipientStateMapWithCoder:coder]; - OWSAssertDebug(self.recipientStateMap); - } - } - - return self; -} - -- (void)migrateRecipientStateMapWithCoder:(NSCoder *)coder -{ - OWSAssertDebug(!self.recipientStateMap); - OWSAssertDebug(coder); - - // Determine the "overall message state." - TSOutgoingMessageState oldMessageState = TSOutgoingMessageStateFailed; - NSNumber *_Nullable messageStateValue = [coder decodeObjectForKey:@"messageState"]; - if (messageStateValue) { - oldMessageState = (TSOutgoingMessageState)messageStateValue.intValue; - } - _hasLegacyMessageState = YES; - _legacyMessageState = oldMessageState; - - OWSOutgoingMessageRecipientState defaultState; - switch (oldMessageState) { - case TSOutgoingMessageStateFailed: - defaultState = OWSOutgoingMessageRecipientStateFailed; - break; - case TSOutgoingMessageStateSending: - defaultState = OWSOutgoingMessageRecipientStateSending; - break; - case TSOutgoingMessageStateSent: - case TSOutgoingMessageStateSent_OBSOLETE: - case TSOutgoingMessageStateDelivered_OBSOLETE: - // Convert legacy values. - defaultState = OWSOutgoingMessageRecipientStateSent; - break; - } - - // Try to leverage the "per-recipient state." - NSDictionary *_Nullable recipientDeliveryMap = - [coder decodeObjectForKey:@"recipientDeliveryMap"]; - NSDictionary *_Nullable recipientReadMap = [coder decodeObjectForKey:@"recipientReadMap"]; - NSArray *_Nullable sentRecipients = [coder decodeObjectForKey:@"sentRecipients"]; - - NSMutableDictionary *recipientStateMap = [NSMutableDictionary new]; - __block BOOL isGroupThread = NO; - // Our default recipient list is the current thread members. - __block NSArray *recipientIds = @[]; - // To avoid deadlock while migrating these records, we use a dedicated - // migration connection. For legacy records (created more than ~9 months - // before the migration), we need to infer the recipient list for this - // message from the current thread membership. This inference isn't - // always accurate, so not using the same connection for both reads is - // acceptable. - [TSOutgoingMessage.dbMigrationConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - TSThread *thread = [self threadWithTransaction:transaction]; - recipientIds = [thread recipientIdentifiers]; - isGroupThread = [thread isGroupThread]; - }]; - - NSNumber *_Nullable wasDelivered = [coder decodeObjectForKey:@"wasDelivered"]; - _legacyWasDelivered = wasDelivered && wasDelivered.boolValue; - BOOL wasDeliveredToContact = NO; - if (isGroupThread) { - // If we have a `sentRecipients` list, prefer that as it is more accurate. - if (sentRecipients) { - recipientIds = sentRecipients; - } - } else { - // Special-case messages in contact threads; if "was delivered", we know - // it was delivered to the contact. - wasDeliveredToContact = _legacyWasDelivered; - } - - NSString *_Nullable singleGroupRecipient = [coder decodeObjectForKey:@"singleGroupRecipient"]; - if (singleGroupRecipient) { - OWSFailDebug(@"unexpected single group recipient message."); - // If this is a "single group recipient message", treat it as such. - recipientIds = @[ - singleGroupRecipient, - ]; - } - - for (NSString *recipientId in recipientIds) { - TSOutgoingMessageRecipientState *recipientState = [TSOutgoingMessageRecipientState new]; - - NSNumber *_Nullable readTimestamp = recipientReadMap[recipientId]; - NSNumber *_Nullable deliveryTimestamp = recipientDeliveryMap[recipientId]; - if (readTimestamp) { - // If we have a read timestamp for this recipient, mark it as read. - recipientState.state = OWSOutgoingMessageRecipientStateSent; - recipientState.readTimestamp = readTimestamp; - // deliveryTimestamp might be nil here. - recipientState.deliveryTimestamp = deliveryTimestamp; - } else if (deliveryTimestamp) { - // If we have a delivery timestamp for this recipient, mark it as delivered. - recipientState.state = OWSOutgoingMessageRecipientStateSent; - recipientState.deliveryTimestamp = deliveryTimestamp; - } else if (wasDeliveredToContact) { - OWSAssertDebug(!isGroupThread); - recipientState.state = OWSOutgoingMessageRecipientStateSent; - // Use message time as an estimate of delivery time. - recipientState.deliveryTimestamp = @(self.timestamp); - } else if ([sentRecipients containsObject:recipientId]) { - // If this recipient is in `sentRecipients`, mark it as sent. - recipientState.state = OWSOutgoingMessageRecipientStateSent; - } else { - // Use the default state for this message. - recipientState.state = defaultState; - } - - recipientStateMap[recipientId] = recipientState; - } - self.recipientStateMap = [recipientStateMap copy]; -} - -+ (YapDatabaseConnection *)dbMigrationConnection -{ - return SSKEnvironment.shared.migrationDBConnection; -} - -+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentId:(nullable NSString *)attachmentId -{ - return [self outgoingMessageInThread:thread - messageBody:body - attachmentId:attachmentId - expiresInSeconds:0 - quotedMessage:nil - linkPreview:nil]; -} - -+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentId:(nullable NSString *)attachmentId - expiresInSeconds:(uint32_t)expiresInSeconds -{ - return [self outgoingMessageInThread:thread - messageBody:body - attachmentId:attachmentId - expiresInSeconds:expiresInSeconds - quotedMessage:nil - linkPreview:nil]; -} - -+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentId:(nullable NSString *)attachmentId - expiresInSeconds:(uint32_t)expiresInSeconds - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - linkPreview:(nullable OWSLinkPreview *)linkPreview -{ - NSMutableArray *attachmentIds = [NSMutableArray new]; - if (attachmentId) { - [attachmentIds addObject:attachmentId]; - } - - // MJK TODO remove SenderTimestamp? - return [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - messageBody:body - attachmentIds:attachmentIds - expiresInSeconds:expiresInSeconds - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:quotedMessage - contactShare:nil - linkPreview:linkPreview]; -} - -+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - expiresInSeconds:(uint32_t)expiresInSeconds; -{ - // MJK TODO remove SenderTimestamp? - return [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:expiresInSeconds - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:groupMetaMessage - quotedMessage:nil - contactShare:nil - linkPreview:nil]; -} - -- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSMutableArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - isVoiceMessage:(BOOL)isVoiceMessage - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview -{ - self = [super initMessageWithTimestamp:timestamp - inThread:thread - messageBody:body - attachmentIds:attachmentIds - expiresInSeconds:expiresInSeconds - expireStartedAt:expireStartedAt - quotedMessage:quotedMessage - contactShare:contactShare - linkPreview:linkPreview]; - if (!self) { - return self; - } - - _hasSyncedTranscript = NO; - _isCalculatingPoW = NO; - - if ([thread isKindOfClass:TSGroupThread.class]) { - // Unless specified, we assume group messages are "Delivery" i.e. normal messages. - if (groupMetaMessage == TSGroupMetaMessageUnspecified) { - _groupMetaMessage = TSGroupMetaMessageDeliver; - } else { - _groupMetaMessage = groupMetaMessage; - } - } else { - OWSAssertDebug(groupMetaMessage == TSGroupMetaMessageUnspecified); - // Specifying a group meta message only makes sense for Group threads - _groupMetaMessage = TSGroupMetaMessageUnspecified; - } - - _isVoiceMessage = isVoiceMessage; - - _attachmentFilenameMap = [NSMutableDictionary new]; - - // New outgoing messages should immediately determine their - // recipient list from current thread state. - NSMutableDictionary *recipientStateMap = [NSMutableDictionary new]; - NSArray *recipientIds; - if ([self isKindOfClass:[OWSOutgoingSyncMessage class]]) { - NSString *_Nullable localNumber = [TSAccountManager localNumber]; - OWSAssertDebug(localNumber); - recipientIds = @[ - localNumber, - ]; - } else { - recipientIds = [thread recipientIdentifiers]; - } - for (NSString *recipientId in recipientIds) { - TSOutgoingMessageRecipientState *recipientState = [TSOutgoingMessageRecipientState new]; - recipientState.state = OWSOutgoingMessageRecipientStateSending; - recipientStateMap[recipientId] = recipientState; - } - self.recipientStateMap = [recipientStateMap copy]; - - return self; -} - -- (void)dealloc -{ - [self removeTemporaryAttachments]; -} - -// Each message has the responsibility for eagerly cleaning up its attachments. -// Normally this is done in [TSMessage removeWithTransaction], but that doesn't -// apply for "transient", unsaved messages (i.e. shouldBeSaved == NO). These -// messages should clean up their attachments upon deallocation. -- (void)removeTemporaryAttachments -{ - if (self.shouldBeSaved) { - // Message is not transient; no need to clean up attachments. - return; - } - NSArray *_Nullable attachmentIds = self.attachmentIds; - if (attachmentIds.count < 1) { - return; - } - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - for (NSString *attachmentId in attachmentIds) { - // We need to fetch each attachment, since [TSAttachment removeWithTransaction:] does important work. - TSAttachment *_Nullable attachment = - [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction]; - if (!attachment) { - OWSLogError(@"Couldn't load interaction's attachment for deletion."); - continue; - } - [attachment removeWithTransaction:transaction]; - }; - }]; -} - -#pragma mark - - -- (TSOutgoingMessageState)messageState -{ - TSOutgoingMessageState newMessageState = - [TSOutgoingMessage messageStateForRecipientStates:self.recipientStateMap.allValues]; - if (self.hasLegacyMessageState) { - if (newMessageState == TSOutgoingMessageStateSent || self.legacyMessageState == TSOutgoingMessageStateSent) { - return TSOutgoingMessageStateSent; - } - } - return newMessageState; -} - -- (BOOL)wasDeliveredToAnyRecipient -{ - if ([self deliveredRecipientIds].count > 0) { - return YES; - } - return (self.hasLegacyMessageState && self.legacyWasDelivered && self.messageState == TSOutgoingMessageStateSent); -} - -- (BOOL)wasSentToAnyRecipient -{ - if ([self sentRecipientIds].count > 0) { - return YES; - } - return (self.hasLegacyMessageState && self.messageState == TSOutgoingMessageStateSent); -} - -+ (TSOutgoingMessageState)messageStateForRecipientStates:(NSArray *)recipientStates -{ - OWSAssertDebug(recipientStates); - - // If there are any "sending" recipients, consider this message "sending". - BOOL hasFailed = NO; - for (TSOutgoingMessageRecipientState *recipientState in recipientStates) { - if (recipientState.state == OWSOutgoingMessageRecipientStateSending) { - return TSOutgoingMessageStateSending; - } else if (recipientState.state == OWSOutgoingMessageRecipientStateFailed) { - hasFailed = YES; - } - } - - // If there are any "failed" recipients, consider this message "failed". - if (hasFailed) { - return TSOutgoingMessageStateFailed; - } - - // Otherwise, consider the message "sent". - // - // NOTE: This includes messages with no recipients. - return TSOutgoingMessageStateSent; -} - -- (BOOL)shouldBeSaved -{ - if (self.groupMetaMessage == TSGroupMetaMessageDeliver || self.groupMetaMessage == TSGroupMetaMessageUnspecified) { - return YES; - } - - return NO; -} - -- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!self.shouldBeSaved) { - // There's no need to save this message, since it's not displayed to the user. - // - // Should we find a need to save this in the future, we need to exclude any non-serializable properties. - OWSLogDebug(@"Skipping save for transient outgoing message."); - - return; - } - - [super saveWithTransaction:transaction]; -} - -- (BOOL)shouldStartExpireTimerWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - // It's not clear if we should wait until _all_ recipients have reached "sent or later" - // (which could never occur if one group member is unregistered) or only wait until - // the first recipient has reached "sent or later" (which could cause partially delivered - // messages to expire). For now, we'll do the latter. - // - // TODO: Revisit this decision. - - if (!self.isExpiringMessage) { - return NO; - } else if (self.messageState == TSOutgoingMessageStateSent) { - return YES; - } else { - if (self.expireStartedAt > 0) { - // Our initial migration to populate the recipient state map was incomplete. It's since been - // addressed, but it's possible there are edge cases where a previously sent message would - // no longer be considered sent. - // So here we take extra care not to stop any expiration that had previously started. - // This can also happen under normal cirumstances with an outgoing group message. - OWSLogWarn(@"expiration previously started"); - - return YES; - } - - return NO; - } -} - -- (BOOL)isSilent -{ - return NO; -} - -- (BOOL)isOnline -{ - return NO; -} - -- (OWSInteractionType)interactionType -{ - return OWSInteractionType_OutgoingMessage; -} - -- (NSArray *)recipientIds -{ - return self.recipientStateMap.allKeys; -} - -- (NSArray *)sendingRecipientIds -{ - NSMutableArray *result = [NSMutableArray new]; - for (NSString *recipientId in self.recipientStateMap) { - TSOutgoingMessageRecipientState *recipientState = self.recipientStateMap[recipientId]; - if (recipientState.state == OWSOutgoingMessageRecipientStateSending) { - [result addObject:recipientId]; - } - } - return result; -} - -- (NSArray *)sentRecipientIds -{ - NSMutableArray *result = [NSMutableArray new]; - for (NSString *recipientId in self.recipientStateMap) { - TSOutgoingMessageRecipientState *recipientState = self.recipientStateMap[recipientId]; - if (recipientState.state == OWSOutgoingMessageRecipientStateSent) { - [result addObject:recipientId]; - } - } - return result; -} - -- (NSArray *)deliveredRecipientIds -{ - NSMutableArray *result = [NSMutableArray new]; - for (NSString *recipientId in self.recipientStateMap) { - TSOutgoingMessageRecipientState *recipientState = self.recipientStateMap[recipientId]; - if (recipientState.deliveryTimestamp != nil) { - [result addObject:recipientId]; - } - } - return result; -} - -- (NSArray *)readRecipientIds -{ - NSMutableArray *result = [NSMutableArray new]; - for (NSString *recipientId in self.recipientStateMap) { - TSOutgoingMessageRecipientState *recipientState = self.recipientStateMap[recipientId]; - if (recipientState.readTimestamp != nil) { - [result addObject:recipientId]; - } - } - return result; -} - -- (NSUInteger)sentRecipientsCount -{ - return [self.recipientStateMap.allValues - filteredArrayUsingPredicate:[NSPredicate - predicateWithBlock:^BOOL(TSOutgoingMessageRecipientState *recipientState, - NSDictionary *_Nullable bindings) { - return recipientState.state == OWSOutgoingMessageRecipientStateSent; - }]] - .count; -} - -- (nullable TSOutgoingMessageRecipientState *)recipientStateForRecipientId:(NSString *)recipientId -{ - OWSAssertDebug(recipientId.length > 0); - - TSOutgoingMessageRecipientState *_Nullable result = self.recipientStateMap[recipientId]; - OWSAssertDebug(result); - return [result copy]; -} - -#pragma mark - Update With... Methods - -- (void)updateWithSendingError:(NSError *)error transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(error); - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSOutgoingMessage *message) { - // Mark any "sending" recipients as "failed." - for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap - .allValues) { - if (recipientState.state == OWSOutgoingMessageRecipientStateSending) { - recipientState.state = OWSOutgoingMessageRecipientStateFailed; - } - } - [message setMostRecentFailureText:error.localizedDescription]; - [message setIsCalculatingPoW:NO]; - }]; -} - -- (void)updateWithAllSendingRecipientsMarkedAsFailedWithTansaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSOutgoingMessage *message) { - // Mark any "sending" recipients as "failed." - for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap - .allValues) { - if (recipientState.state == OWSOutgoingMessageRecipientStateSending) { - recipientState.state = OWSOutgoingMessageRecipientStateFailed; - } - } - [message setIsCalculatingPoW:NO]; - }]; -} - -- (void)updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSOutgoingMessage *message) { - // Mark any "sending" recipients as "failed." - for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap - .allValues) { - if (recipientState.state == OWSOutgoingMessageRecipientStateFailed) { - recipientState.state = OWSOutgoingMessageRecipientStateSending; - } - } - }]; -} - -- (void)updateWithHasSyncedTranscript:(BOOL)hasSyncedTranscript - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSOutgoingMessage *message) { - [message setHasSyncedTranscript:hasSyncedTranscript]; - }]; -} - -- (void)updateWithCustomMessage:(NSString *)customMessage transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(customMessage); - OWSAssertDebug(transaction); - - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSOutgoingMessage *message) { - [message setCustomMessage:customMessage]; - }]; -} - -- (void)updateWithCustomMessage:(NSString *)customMessage -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self updateWithCustomMessage:customMessage transaction:transaction]; - }]; -} - -- (void)saveIsCalculatingProofOfWork:(BOOL)isCalculatingPoW withTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - [self applyChangeToSelfAndLatestCopy:transaction changeBlock:^(TSOutgoingMessage *message) { - [message setIsCalculatingPoW:isCalculatingPoW]; - }]; -} - -- (void)updateWithSentRecipient:(NSString *)recipientId - wasSentByUD:(BOOL)wasSentByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction { - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSOutgoingMessage *message) { - TSOutgoingMessageRecipientState *_Nullable recipientState - = message.recipientStateMap[recipientId]; - if (!recipientState) { return; } - recipientState.state = OWSOutgoingMessageRecipientStateSent; - recipientState.wasSentByUD = wasSentByUD; - [message setIsCalculatingPoW:NO]; - }]; -} - -- (void)updateWithSkippedRecipient:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSOutgoingMessage *message) { - TSOutgoingMessageRecipientState *_Nullable recipientState - = message.recipientStateMap[recipientId]; - if (!recipientState) { return; } - recipientState.state = OWSOutgoingMessageRecipientStateSkipped; - [message setIsCalculatingPoW:NO]; - }]; -} - -- (void)updateWithDeliveredRecipient:(NSString *)recipientId - deliveryTimestamp:(NSNumber *_Nullable)deliveryTimestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - // If delivery notification doesn't include timestamp, use "now" as an estimate. - if (!deliveryTimestamp) { - deliveryTimestamp = @([NSDate ows_millisecondTimeStamp]); - } - - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSOutgoingMessage *message) { - TSOutgoingMessageRecipientState *_Nullable recipientState - = message.recipientStateMap[recipientId]; - if (!recipientState) { - // OWSFailDebug(@"Missing recipient state for delivered recipient: %@", recipientId); - return; - } - if (recipientState.state != OWSOutgoingMessageRecipientStateSent) { - OWSLogWarn(@"marking unsent message as delivered."); - } - recipientState.state = OWSOutgoingMessageRecipientStateSent; - recipientState.deliveryTimestamp = deliveryTimestamp; - [message setIsCalculatingPoW:NO]; - }]; -} - -- (void)updateWithReadRecipientId:(NSString *)recipientId - readTimestamp:(uint64_t)readTimestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSOutgoingMessage *message) { - TSOutgoingMessageRecipientState *_Nullable recipientState = message.recipientStateMap[recipientId]; - if (!recipientState) { return; } - if (recipientState.state != OWSOutgoingMessageRecipientStateSent) { - OWSLogWarn(@"Marking unsent message as delivered."); - } - recipientState.state = OWSOutgoingMessageRecipientStateSent; - recipientState.readTimestamp = @(readTimestamp); - [message setIsCalculatingPoW:NO]; - }]; -} - -- (void)updateWithWasSentFromLinkedDeviceWithUDRecipientIds:(nullable NSArray *)udRecipientIds - nonUdRecipientIds:(nullable NSArray *)nonUdRecipientIds - isSentUpdate:(BOOL)isSentUpdate - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - [self - applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSOutgoingMessage *message) { - if (udRecipientIds.count > 0 || nonUdRecipientIds.count > 0) { - // If we have specific recipient info from the transcript, - // build a new recipient state map. - NSMutableDictionary *recipientStateMap - = [NSMutableDictionary new]; - for (NSString *recipientId in udRecipientIds) { - if (recipientStateMap[recipientId]) { - OWSFailDebug( - @"recipient appears more than once in recipient lists: %@", recipientId); - continue; - } - TSOutgoingMessageRecipientState *recipientState = - [TSOutgoingMessageRecipientState new]; - recipientState.state = OWSOutgoingMessageRecipientStateSent; - recipientState.wasSentByUD = YES; - recipientStateMap[recipientId] = recipientState; - } - for (NSString *recipientId in nonUdRecipientIds) { - if (recipientStateMap[recipientId]) { - OWSFailDebug( - @"recipient appears more than once in recipient lists: %@", recipientId); - continue; - } - TSOutgoingMessageRecipientState *recipientState = - [TSOutgoingMessageRecipientState new]; - recipientState.state = OWSOutgoingMessageRecipientStateSent; - recipientState.wasSentByUD = NO; - recipientStateMap[recipientId] = recipientState; - } - - if (isSentUpdate) { - // If this is a "sent update", make sure that: - // - // a) "Sent updates" should never remove any recipients. We end up with the - // union of the existing and new recipients. - // b) "Sent updates" should never downgrade the "recipient state" for any - // recipients. Prefer existing "recipient state"; "sent updates" only - // add new recipients at the "sent" state. - // - // Therefore we retain all existing entries in the recipient state map. - [recipientStateMap addEntriesFromDictionary:self.recipientStateMap]; - } - - [message setRecipientStateMap:recipientStateMap]; - } else { - // Otherwise assume this is a legacy message before UD was introduced, and mark - // any "sending" recipient as "sent". Note that this will apply to non-legacy - // messages with no recipients. - for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap - .allValues) { - if (recipientState.state == OWSOutgoingMessageRecipientStateSending) { - recipientState.state = OWSOutgoingMessageRecipientStateSent; - } - } - } - - [message setIsCalculatingPoW:NO]; - - if (!isSentUpdate) { - [message setIsFromLinkedDevice:YES]; - } - }]; -} - -- (void)updateWithSendingToSingleGroupRecipient:(NSString *)singleGroupRecipient - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - OWSAssertDebug(singleGroupRecipient.length > 0); - - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSOutgoingMessage *message) { - TSOutgoingMessageRecipientState *recipientState = - [TSOutgoingMessageRecipientState new]; - recipientState.state = OWSOutgoingMessageRecipientStateSending; - [message setRecipientStateMap:@{ - singleGroupRecipient : recipientState, - }]; - }]; -} - -- (nullable NSNumber *)firstRecipientReadTimestamp -{ - NSNumber *result = nil; - for (TSOutgoingMessageRecipientState *recipientState in self.recipientStateMap.allValues) { - if (!recipientState.readTimestamp) { - continue; - } - if (!result || (result.unsignedLongLongValue > recipientState.readTimestamp.unsignedLongLongValue)) { - result = recipientState.readTimestamp; - } - } - return result; -} - -- (void)updateWithFakeMessageState:(TSOutgoingMessageState)messageState - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - [self applyChangeToSelfAndLatestCopy:transaction - changeBlock:^(TSOutgoingMessage *message) { - for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap - .allValues) { - switch (messageState) { - case TSOutgoingMessageStateSending: - recipientState.state = OWSOutgoingMessageRecipientStateSending; - break; - case TSOutgoingMessageStateFailed: - recipientState.state = OWSOutgoingMessageRecipientStateFailed; - break; - case TSOutgoingMessageStateSent: - recipientState.state = OWSOutgoingMessageRecipientStateSent; - break; - default: - OWSFailDebug(@"unexpected message state."); - break; - } - } - }]; -} - -#pragma mark - - -- (nullable id)dataMessageBuilder -{ - TSThread *thread = self.thread; - OWSAssertDebug(thread); - - SSKProtoDataMessageBuilder *builder = [SSKProtoDataMessage builder]; - [builder setTimestamp:self.timestamp]; - - if ([self.body lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= kOversizeTextMessageSizeThreshold) { - [builder setBody:self.body]; - } else { - OWSFailDebug(@"message body length too long."); - NSString *truncatedBody = [self.body copy]; - while ([truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > kOversizeTextMessageSizeThreshold) { - OWSLogError(@"truncating body which is too long: %lu", - (unsigned long)[truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); - truncatedBody = [truncatedBody substringToIndex:truncatedBody.length / 2]; - } - [builder setBody:truncatedBody]; - } - [builder setExpireTimer:self.expiresInSeconds]; - - // Group Messages - BOOL attachmentWasGroupAvatar = NO; - if ([thread isKindOfClass:[TSGroupThread class]]) { - TSGroupThread *gThread = (TSGroupThread *)thread; - SSKProtoGroupContextType groupMessageType; - switch (self.groupMetaMessage) { - case TSGroupMetaMessageQuit: - groupMessageType = SSKProtoGroupContextTypeQuit; - break; - case TSGroupMetaMessageUpdate: - case TSGroupMetaMessageNew: - groupMessageType = SSKProtoGroupContextTypeUpdate; - break; - default: - groupMessageType = SSKProtoGroupContextTypeDeliver; - break; - } - SSKProtoGroupContextBuilder *groupBuilder = - [SSKProtoGroupContext builderWithId:gThread.groupModel.groupId type:groupMessageType]; - if (groupMessageType == SSKProtoGroupContextTypeUpdate) { - if (gThread.groupModel.groupImage != nil && self.attachmentIds.count == 1) { - attachmentWasGroupAvatar = YES; - SSKProtoAttachmentPointer *_Nullable attachmentProto = - [TSAttachmentStream buildProtoForAttachmentId:self.attachmentIds.firstObject]; - if (!attachmentProto) { - OWSFailDebug(@"could not build protobuf."); - return nil; - } - [groupBuilder setAvatar:attachmentProto]; - } - - [groupBuilder setMembers:gThread.groupModel.groupMemberIds]; - [groupBuilder setName:gThread.groupModel.groupName]; - [groupBuilder setAdmins:gThread.groupModel.groupAdminIds]; - } - NSError *error; - SSKProtoGroupContext *_Nullable groupContextProto = [groupBuilder buildAndReturnError:&error]; - if (error || !groupContextProto) { - OWSFailDebug(@"could not build protobuf: %@.", error); - return nil; - } - [builder setGroup:groupContextProto]; - } - - // Message Attachments - if (!attachmentWasGroupAvatar) { - NSMutableArray *attachments = [NSMutableArray new]; - for (NSString *attachmentId in self.attachmentIds) { - SSKProtoAttachmentPointer *_Nullable attachmentProto = - [TSAttachmentStream buildProtoForAttachmentId:attachmentId]; - if (!attachmentProto) { - OWSFailDebug(@"could not build protobuf."); - return nil; - } - [attachments addObject:attachmentProto]; - } - [builder setAttachments:attachments]; - } - - // Quoted Reply - SSKProtoDataMessageQuoteBuilder *_Nullable quotedMessageBuilder = self.quotedMessageBuilder; - if (quotedMessageBuilder) { - NSError *error; - SSKProtoDataMessageQuote *_Nullable quoteProto = [quotedMessageBuilder buildAndReturnError:&error]; - if (error || !quoteProto) { - OWSFailDebug(@"could not build protobuf: %@.", error); - return nil; - } - [builder setQuote:quoteProto]; - } - - // Contact Share - if (self.contactShare) { - SSKProtoDataMessageContact *_Nullable contactProto = - [OWSContacts protoForContact:self.contactShare]; - if (contactProto) { - [builder addContact:contactProto]; - } else { - OWSFailDebug(@"contactProto was unexpectedly nil"); - } - } - - // Link Preview - if (self.linkPreview) { - SSKProtoDataMessagePreviewBuilder *previewBuilder = - [SSKProtoDataMessagePreview builderWithUrl:self.linkPreview.urlString]; - if (self.linkPreview.title.length > 0) { - [previewBuilder setTitle:self.linkPreview.title]; - } - if (self.linkPreview.imageAttachmentId) { - SSKProtoAttachmentPointer *_Nullable attachmentProto = - [TSAttachmentStream buildProtoForAttachmentId:self.linkPreview.imageAttachmentId]; - if (!attachmentProto) { - OWSFailDebug(@"Could not build link preview image protobuf."); - } else { - [previewBuilder setImage:attachmentProto]; - } - } - - NSError *error; - SSKProtoDataMessagePreview *_Nullable previewProto = [previewBuilder buildAndReturnError:&error]; - if (error || !previewProto) { - OWSFailDebug(@"Could not build link preview protobuf: %@.", error); - } else { - [builder addPreview:previewProto]; - } - } - - return builder; -} - -- (nullable SSKProtoDataMessageQuoteBuilder *)quotedMessageBuilder -{ - if (!self.quotedMessage) { - return nil; - } - TSQuotedMessage *quotedMessage = self.quotedMessage; - - SSKProtoDataMessageQuoteBuilder *quoteBuilder = - [SSKProtoDataMessageQuote builderWithId:quotedMessage.timestamp author:quotedMessage.authorId]; - - BOOL hasQuotedText = NO; - BOOL hasQuotedAttachment = NO; - if (self.quotedMessage.body.length > 0) { - hasQuotedText = YES; - [quoteBuilder setText:quotedMessage.body]; - } - - if (quotedMessage.quotedAttachments) { - for (OWSAttachmentInfo *attachment in quotedMessage.quotedAttachments) { - hasQuotedAttachment = YES; - - SSKProtoDataMessageQuoteQuotedAttachmentBuilder *quotedAttachmentBuilder = - [SSKProtoDataMessageQuoteQuotedAttachment builder]; - - quotedAttachmentBuilder.contentType = attachment.contentType; - quotedAttachmentBuilder.fileName = attachment.sourceFilename; - if (attachment.thumbnailAttachmentStreamId) { - quotedAttachmentBuilder.thumbnail = - [TSAttachmentStream buildProtoForAttachmentId:attachment.thumbnailAttachmentStreamId]; - } - - NSError *error; - SSKProtoDataMessageQuoteQuotedAttachment *_Nullable quotedAttachmentMessage = - [quotedAttachmentBuilder buildAndReturnError:&error]; - if (error || !quotedAttachmentMessage) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - - [quoteBuilder addAttachments:quotedAttachmentMessage]; - } - } - - if (hasQuotedText || hasQuotedAttachment) { - return quoteBuilder; - } else { - OWSFailDebug(@"Invalid quoted message data."); - return nil; - } -} - -// recipientId is nil when building "sent" sync messages for messages sent to groups. -- (nullable SSKProtoDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId -{ - OWSAssertDebug(self.thread); - SSKProtoDataMessageBuilder *_Nullable builder = [self dataMessageBuilder]; - if (builder == nil) { - OWSFailDebug(@"Couldn't build protobuf."); - return nil; - } - - [ProtoUtils addLocalProfileKeyIfNecessary:self.thread recipientId:recipientId dataMessageBuilder:builder]; - - id profileManager = SSKEnvironment.shared.profileManager; - NSString *displayName; - NSString *masterPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"]; - if (masterPublicKey != nil) { - displayName = [profileManager profileNameForRecipientWithID:masterPublicKey]; - } else { - displayName = profileManager.localProfileName; - } - NSString *profilePictureURL = profileManager.profilePictureURL; - SSKProtoDataMessageLokiProfileBuilder *profileBuilder = [SSKProtoDataMessageLokiProfile builder]; - [profileBuilder setDisplayName:displayName]; - [profileBuilder setProfilePicture:profilePictureURL ?: @""]; - SSKProtoDataMessageLokiProfile *profile = [profileBuilder buildAndReturnError:nil]; - [builder setProfile:profile]; - - NSError *error; - SSKProtoDataMessage *_Nullable dataProto = [builder buildAndReturnError:&error]; - if (error != nil || dataProto == nil) { - OWSFailDebug(@"Couldn't build protobuf due to error: %@.", error); - return nil; - } - return dataProto; -} - -- (nullable id)prepareCustomContentBuilder:(SignalRecipient *)recipient { - SSKProtoDataMessage *_Nullable dataMessage = [self buildDataMessage:recipient.recipientId]; - - if (dataMessage == nil) { - OWSFailDebug(@"Couldn't build protobuf."); - return nil; - } - - SSKProtoContentBuilder *contentBuilder = SSKProtoContent.builder; - [contentBuilder setDataMessage:dataMessage]; - - return contentBuilder; -} - -- (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient -{ - SSKProtoContentBuilder *contentBuilder = [self prepareCustomContentBuilder:recipient]; - - NSError *error; - NSData *_Nullable contentData = [contentBuilder buildSerializedDataAndReturnError:&error]; - if (error != nil || contentData == nil) { - OWSFailDebug(@"Couldn't serialize protobuf due to error: %@.", error); - return nil; - } - - return contentData; -} - -- (BOOL)shouldSyncTranscript -{ - return YES; -} - -- (NSString *)statusDescription -{ - NSMutableString *result = [NSMutableString new]; - [result appendFormat:@"[status: %@", NSStringForOutgoingMessageState(self.messageState)]; - for (NSString *recipientId in self.recipientStateMap) { - TSOutgoingMessageRecipientState *recipientState = self.recipientStateMap[recipientId]; - [result appendFormat:@", %@: %@", recipientId, NSStringForOutgoingMessageRecipientState(recipientState.state)]; - } - [result appendString:@"]"]; - return [result copy]; -} - -- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeRegular]; } - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h deleted file mode 100644 index cdedc4f90..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h +++ /dev/null @@ -1,108 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class SSKProtoDataMessage; -@class TSAttachment; -@class TSAttachmentStream; -@class TSQuotedMessage; -@class TSThread; -@class YapDatabaseReadWriteTransaction; - -@interface OWSAttachmentInfo : MTLModel - -@property (nonatomic, readonly, nullable) NSString *contentType; -@property (nonatomic, readonly, nullable) NSString *sourceFilename; - -// This is only set when sending a new attachment so we have a way -// to reference the original attachment when generating a thumbnail. -// We don't want to do this until the message is saved, when the user sends -// the message so as not to end up with an orphaned file. -@property (nonatomic, readonly, nullable) NSString *attachmentId; - -// References a yet-to-be downloaded thumbnail file -@property (atomic, nullable) NSString *thumbnailAttachmentPointerId; - -// References an already downloaded or locally generated thumbnail file -@property (atomic, nullable) NSString *thumbnailAttachmentStreamId; - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithAttachmentId:(nullable NSString *)attachmentId - contentType:(NSString *)contentType - sourceFilename:(NSString *)sourceFilename NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithAttachmentStream:(TSAttachmentStream *)attachmentStream; - -@end - -typedef NS_ENUM(NSUInteger, TSQuotedMessageContentSource) { - TSQuotedMessageContentSourceUnknown, - TSQuotedMessageContentSourceLocal, - TSQuotedMessageContentSourceRemote -}; - -@interface TSQuotedMessage : MTLModel - -@property (nonatomic, readonly) uint64_t timestamp; -@property (nonatomic, readonly) NSString *authorId; -@property (nonatomic, readonly) TSQuotedMessageContentSource bodySource; - -// This property should be set IFF we are quoting a text message -// or attachment with caption. -@property (nullable, nonatomic, readonly) NSString *body; - -#pragma mark - Attachments - -// This is a MIME type. -// -// This property should be set IFF we are quoting an attachment message. -- (nullable NSString *)contentType; -- (nullable NSString *)sourceFilename; - -// References a yet-to-be downloaded thumbnail file -- (nullable NSString *)thumbnailAttachmentPointerId; - -// References an already downloaded or locally generated thumbnail file -- (nullable NSString *)thumbnailAttachmentStreamId; -- (void)setThumbnailAttachmentStream:(TSAttachment *)thumbnailAttachmentStream; - -// currently only used by orphan attachment cleaner -- (NSArray *)thumbnailAttachmentStreamIds; - -@property (atomic, readonly) NSArray *quotedAttachments; - -// Before sending, persist a thumbnail attachment derived from the quoted attachment -- (NSArray *)createThumbnailAttachmentsIfNecessaryWithTransaction: - (YapDatabaseReadWriteTransaction *)transaction; - -- (instancetype)init NS_UNAVAILABLE; - -// used when receiving quoted messages -- (instancetype)initWithTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - body:(NSString *_Nullable)body - bodySource:(TSQuotedMessageContentSource)bodySource - receivedQuotedAttachmentInfos:(NSArray *)attachmentInfos; - -// used when sending quoted messages -- (instancetype)initWithTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - body:(NSString *_Nullable)body - quotedAttachmentsForSending:(NSArray *)attachments; - - -+ (nullable instancetype)quotedMessageForDataMessage:(SSKProtoDataMessage *)dataMessage - thread:(TSThread *)thread - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -@end - -#pragma mark - - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m deleted file mode 100644 index 0668cc70f..000000000 --- a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m +++ /dev/null @@ -1,378 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSQuotedMessage.h" -#import "TSAccountManager.h" -#import "TSAttachment.h" -#import "TSAttachmentPointer.h" -#import "TSAttachmentStream.h" -#import "TSIncomingMessage.h" -#import "TSInteraction.h" -#import "TSOutgoingMessage.h" -#import "TSThread.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSAttachmentInfo - -- (instancetype)initWithAttachmentStream:(TSAttachmentStream *)attachmentStream; -{ - OWSAssertDebug([attachmentStream isKindOfClass:[TSAttachmentStream class]]); - OWSAssertDebug(attachmentStream.uniqueId); - OWSAssertDebug(attachmentStream.contentType); - - return [self initWithAttachmentId:attachmentStream.uniqueId - contentType:attachmentStream.contentType - sourceFilename:attachmentStream.sourceFilename]; -} - -- (instancetype)initWithAttachmentId:(nullable NSString *)attachmentId - contentType:(NSString *)contentType - sourceFilename:(NSString *)sourceFilename -{ - self = [super init]; - if (!self) { - return self; - } - - _attachmentId = attachmentId; - _contentType = contentType; - _sourceFilename = sourceFilename; - - return self; -} - -@end - -@interface TSQuotedMessage () - -@property (atomic) NSArray *quotedAttachments; -@property (atomic) NSArray *quotedAttachmentsForSending; - -@end - -@implementation TSQuotedMessage - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - body:(NSString *_Nullable)body - bodySource:(TSQuotedMessageContentSource)bodySource - receivedQuotedAttachmentInfos:(NSArray *)attachmentInfos -{ - OWSAssertDebug(timestamp > 0); - OWSAssertDebug(authorId.length > 0); - - self = [super init]; - if (!self) { - return nil; - } - - _timestamp = timestamp; - _authorId = authorId; - _body = body; - _bodySource = bodySource; - _quotedAttachments = attachmentInfos; - - return self; -} - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - body:(NSString *_Nullable)body - quotedAttachmentsForSending:(NSArray *)attachments -{ - OWSAssertDebug(timestamp > 0); - OWSAssertDebug(authorId.length > 0); - - self = [super init]; - if (!self) { - return nil; - } - - _timestamp = timestamp; - _authorId = authorId; - _body = body; - _bodySource = TSQuotedMessageContentSourceLocal; - - NSMutableArray *attachmentInfos = [NSMutableArray new]; - for (TSAttachmentStream *attachmentStream in attachments) { - [attachmentInfos addObject:[[OWSAttachmentInfo alloc] initWithAttachmentStream:attachmentStream]]; - } - _quotedAttachments = [attachmentInfos copy]; - - return self; -} - -+ (TSQuotedMessage *_Nullable)quotedMessageForDataMessage:(SSKProtoDataMessage *)dataMessage - thread:(TSThread *)thread - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(dataMessage); - - if (!dataMessage.quote) { - return nil; - } - - SSKProtoDataMessageQuote *quoteProto = [dataMessage quote]; - - if (quoteProto.id == 0) { - OWSFailDebug(@"quoted message missing id"); - return nil; - } - uint64_t timestamp = [quoteProto id]; - - if (quoteProto.author.length == 0) { - OWSFailDebug(@"quoted message missing author"); - return nil; - } - // TODO: We could verify that this is a valid e164 value. - NSString *authorId = [quoteProto author]; - - NSString *_Nullable body = nil; - BOOL hasAttachment = NO; - TSQuotedMessageContentSource bodySource = TSQuotedMessageContentSourceUnknown; - - // Prefer to generate the text snippet locally if available. - TSMessage *_Nullable quotedMessage = [self findQuotedMessageWithTimestamp:timestamp - threadId:thread.uniqueId - authorId:authorId - transaction:transaction]; - - if (quotedMessage) { - bodySource = TSQuotedMessageContentSourceLocal; - - NSString *localText = [quotedMessage bodyTextWithTransaction:transaction]; - if (localText.length > 0) { - body = localText; - } - } - - if (body.length == 0) { - if (quoteProto.text.length > 0) { - bodySource = TSQuotedMessageContentSourceRemote; - body = quoteProto.text; - } - } - - NSMutableArray *attachmentInfos = [NSMutableArray new]; - for (SSKProtoDataMessageQuoteQuotedAttachment *quotedAttachment in quoteProto.attachments) { - hasAttachment = YES; - OWSAttachmentInfo *attachmentInfo = [[OWSAttachmentInfo alloc] initWithAttachmentId:nil - contentType:quotedAttachment.contentType - sourceFilename:quotedAttachment.fileName]; - - // We prefer deriving any thumbnail locally rather than fetching one from the network. - TSAttachmentStream *_Nullable localThumbnail = - [self tryToDeriveLocalThumbnailWithTimestamp:timestamp - threadId:thread.uniqueId - authorId:authorId - contentType:quotedAttachment.contentType - transaction:transaction]; - - if (localThumbnail) { - OWSLogDebug(@"Generated local thumbnail for quoted quoted message: %@:%lu", - thread.uniqueId, - (unsigned long)timestamp); - - [localThumbnail saveWithTransaction:transaction]; - - attachmentInfo.thumbnailAttachmentStreamId = localThumbnail.uniqueId; - } else if (quotedAttachment.thumbnail) { - OWSLogDebug(@"Saving reference for fetching remote thumbnail for quoted message: %@:%lu", - thread.uniqueId, - (unsigned long)timestamp); - - SSKProtoAttachmentPointer *thumbnailAttachmentProto = quotedAttachment.thumbnail; - TSAttachmentPointer *_Nullable thumbnailPointer = - [TSAttachmentPointer attachmentPointerFromProto:thumbnailAttachmentProto albumMessage:nil]; - if (thumbnailPointer) { - [thumbnailPointer saveWithTransaction:transaction]; - - attachmentInfo.thumbnailAttachmentPointerId = thumbnailPointer.uniqueId; - } else { - OWSFailDebug(@"Invalid thumbnail attachment."); - } - } else { - OWSLogDebug(@"No thumbnail for quoted message: %@:%lu", thread.uniqueId, (unsigned long)timestamp); - } - - [attachmentInfos addObject:attachmentInfo]; - - // For now, only support a single quoted attachment. - break; - } - - if (body.length == 0 && !hasAttachment) { - return nil; - } - - // Legit usage of senderTimestamp - this class references the message it is quoting by it's sender timestamp - return [[TSQuotedMessage alloc] initWithTimestamp:timestamp - authorId:authorId - body:body - bodySource:bodySource - receivedQuotedAttachmentInfos:attachmentInfos]; -} - -+ (nullable TSAttachmentStream *)tryToDeriveLocalThumbnailWithTimestamp:(uint64_t)timestamp - threadId:(NSString *)threadId - authorId:(NSString *)authorId - contentType:(NSString *)contentType - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - TSMessage *_Nullable quotedMessage = - [self findQuotedMessageWithTimestamp:timestamp threadId:threadId authorId:authorId transaction:transaction]; - if (!quotedMessage) { - return nil; - } - - TSAttachment *_Nullable attachmentToQuote = nil; - if (quotedMessage.attachmentIds.count > 0) { - attachmentToQuote = [quotedMessage attachmentsWithTransaction:transaction].firstObject; - } else if (quotedMessage.linkPreview && quotedMessage.linkPreview.imageAttachmentId.length > 0) { - attachmentToQuote = - [TSAttachment fetchObjectWithUniqueID:quotedMessage.linkPreview.imageAttachmentId transaction:transaction]; - } - if (![attachmentToQuote isKindOfClass:[TSAttachmentStream class]]) { - return nil; - } - if (![TSAttachmentStream hasThumbnailForMimeType:contentType]) { - return nil; - } - TSAttachmentStream *sourceStream = (TSAttachmentStream *)attachmentToQuote; - return [sourceStream cloneAsThumbnail]; -} - -+ (nullable TSMessage *)findQuotedMessageWithTimestamp:(uint64_t)timestamp - threadId:(NSString *)threadId - authorId:(NSString *)authorId - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - if (timestamp <= 0) { - OWSFailDebug(@"Invalid timestamp: %llu", timestamp); - return nil; - } - if (threadId.length <= 0) { - OWSFailDebug(@"Invalid thread."); - return nil; - } - if (authorId.length <= 0) { - OWSFailDebug(@"Invalid authorId: %@", authorId); - return nil; - } - - for (TSMessage *message in - [TSInteraction interactionsWithTimestamp:timestamp ofClass:TSMessage.class withTransaction:transaction]) { - if (![message.uniqueThreadId isEqualToString:threadId]) { - continue; - } - if ([message isKindOfClass:[TSIncomingMessage class]]) { - TSIncomingMessage *incomingMessage = (TSIncomingMessage *)message; - if (![authorId isEqual:incomingMessage.authorId]) { - continue; - } - } else if ([message isKindOfClass:[TSOutgoingMessage class]]) { - if (![authorId isEqual:[TSAccountManager localNumber]]) { - continue; - } - } - - return message; - } - OWSLogWarn(@"Could not find quoted message: %llu", timestamp); - return nil; -} - -#pragma mark - Attachment (not necessarily with a thumbnail) - -- (nullable OWSAttachmentInfo *)firstAttachmentInfo -{ - OWSAssertDebug(self.quotedAttachments.count <= 1); - return self.quotedAttachments.firstObject; -} - -- (nullable NSString *)contentType -{ - OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo; - - return firstAttachment.contentType; -} - -- (nullable NSString *)sourceFilename -{ - OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo; - - return firstAttachment.sourceFilename; -} - -- (nullable NSString *)thumbnailAttachmentPointerId -{ - OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo; - - return firstAttachment.thumbnailAttachmentPointerId; -} - -- (nullable NSString *)thumbnailAttachmentStreamId -{ - OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo; - - return firstAttachment.thumbnailAttachmentStreamId; -} - -- (void)setThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream -{ - OWSAssertDebug([attachmentStream isKindOfClass:[TSAttachmentStream class]]); - OWSAssertDebug(self.quotedAttachments.count == 1); - - OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo; - firstAttachment.thumbnailAttachmentStreamId = attachmentStream.uniqueId; -} - -- (NSArray *)thumbnailAttachmentStreamIds -{ - NSMutableArray *streamIds = [NSMutableArray new]; - for (OWSAttachmentInfo *info in self.quotedAttachments) { - if (info.thumbnailAttachmentStreamId) { - [streamIds addObject:info.thumbnailAttachmentStreamId]; - } - } - - return [streamIds copy]; -} - -// Before sending, persist a thumbnail attachment derived from the quoted attachment -- (NSArray *)createThumbnailAttachmentsIfNecessaryWithTransaction: - (YapDatabaseReadWriteTransaction *)transaction -{ - NSMutableArray *thumbnailAttachments = [NSMutableArray new]; - - for (OWSAttachmentInfo *info in self.quotedAttachments) { - - OWSAssertDebug(info.attachmentId); - TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:info.attachmentId transaction:transaction]; - if (![attachment isKindOfClass:[TSAttachmentStream class]]) { - continue; - } - TSAttachmentStream *sourceStream = (TSAttachmentStream *)attachment; - - TSAttachmentStream *_Nullable thumbnailStream = [sourceStream cloneAsThumbnail]; - if (!thumbnailStream) { - continue; - } - - [thumbnailStream saveWithTransaction:transaction]; - info.thumbnailAttachmentStreamId = thumbnailStream.uniqueId; - [thumbnailAttachments addObject:thumbnailStream]; - } - - return [thumbnailAttachments copy]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.h b/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.h deleted file mode 100644 index 2f4f5ff08..000000000 --- a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSErrorMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class OWSFingerprint; - -@interface TSInvalidIdentityKeyErrorMessage : TSErrorMessage - -- (void)throws_acceptNewIdentityKey NS_SWIFT_UNAVAILABLE("throws objc exceptions"); -- (nullable NSData *)throws_newIdentityKey NS_SWIFT_UNAVAILABLE("throws objc exceptions"); -- (NSString *)theirSignalId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.m b/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.m deleted file mode 100644 index 239461875..000000000 --- a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyErrorMessage.m +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSInvalidIdentityKeyErrorMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation TSInvalidIdentityKeyErrorMessage - -- (void)throws_acceptNewIdentityKey -{ - OWSAbstractMethod(); -} - -- (nullable NSData *)throws_newIdentityKey -{ - OWSAbstractMethod(); - return nil; -} - -- (NSString *)theirSignalId -{ - OWSAbstractMethod(); - return nil; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyReceivingErrorMessage.h b/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyReceivingErrorMessage.h deleted file mode 100644 index 9bb4fe456..000000000 --- a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyReceivingErrorMessage.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSInvalidIdentityKeyErrorMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class SSKProtoEnvelope; - -// DEPRECATED - we no longer create new instances of this class (as of mid-2017); However, existing instances may -// exist, so we should keep this class around to honor their old behavior. -__attribute__((deprecated)) @interface TSInvalidIdentityKeyReceivingErrorMessage : TSInvalidIdentityKeyErrorMessage - -#ifdef DEBUG -+ (nullable instancetype)untrustedKeyWithEnvelope:(SSKProtoEnvelope *)envelope - withTransaction:(YapDatabaseReadWriteTransaction *)transaction; -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyReceivingErrorMessage.m b/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyReceivingErrorMessage.m deleted file mode 100644 index f0946bc5c..000000000 --- a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeyReceivingErrorMessage.m +++ /dev/null @@ -1,154 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSInvalidIdentityKeyReceivingErrorMessage.h" -#import "OWSFingerprint.h" -#import "OWSIdentityManager.h" -#import "OWSMessageManager.h" -#import "OWSMessageReceiver.h" -#import "OWSPrimaryStorage+SessionStore.h" -#import "OWSPrimaryStorage.h" -#import "SSKEnvironment.h" -#import "TSContactThread.h" -#import "TSDatabaseView.h" -#import "TSErrorMessage_privateConstructor.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -__attribute__((deprecated)) @interface TSInvalidIdentityKeyReceivingErrorMessage() - -@property (nonatomic, readonly, copy) NSString *authorId; - -@end - -@implementation TSInvalidIdentityKeyReceivingErrorMessage { - // Not using a property declaration in order to exclude from DB serialization - SSKProtoEnvelope *_Nullable _envelope; -} - -@synthesize envelopeData = _envelopeData; - -#ifdef DEBUG -// We no longer create these messages, but they might exist on legacy clients so it's useful to be able to -// create them with the debug UI -+ (nullable instancetype)untrustedKeyWithEnvelope:(SSKProtoEnvelope *)envelope - withTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - TSContactThread *contactThread = - [TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction]; - - // Legit usage of senderTimestamp, references message which failed to decrypt - TSInvalidIdentityKeyReceivingErrorMessage *errorMessage = - [[self alloc] initForUnknownIdentityKeyWithTimestamp:envelope.timestamp - inThread:contactThread - incomingEnvelope:envelope]; - return errorMessage; -} - -- (nullable instancetype)initForUnknownIdentityKeyWithTimestamp:(uint64_t)timestamp - inThread:(TSThread *)thread - incomingEnvelope:(SSKProtoEnvelope *)envelope -{ - self = [self initWithTimestamp:timestamp inThread:thread failedMessageType:TSErrorMessageWrongTrustedIdentityKey]; - if (!self) { - return self; - } - - NSError *error; - _envelopeData = [envelope serializedDataAndReturnError:&error]; - if (!_envelopeData || error != nil) { - OWSFailDebug(@"failure: envelope data failed with error: %@", error); - return nil; - } - - _authorId = envelope.source; - - return self; -} -#endif - -- (nullable SSKProtoEnvelope *)envelope -{ - if (!_envelope) { - NSError *error; - SSKProtoEnvelope *_Nullable envelope = [SSKProtoEnvelope parseData:self.envelopeData error:&error]; - if (error || envelope == nil) { - OWSFailDebug(@"Could not parse proto: %@", error); - } else { - _envelope = envelope; - } - } - return _envelope; -} - -- (void)throws_acceptNewIdentityKey -{ - OWSAssertIsOnMainThread(); - - if (self.errorType != TSErrorMessageWrongTrustedIdentityKey) { - OWSLogError(@"Refusing to accept identity key for anything but a Key error."); - return; - } - - NSData *_Nullable newKey = [self throws_newIdentityKey]; - if (!newKey) { - OWSFailDebug(@"Couldn't extract identity key to accept"); - return; - } - - [[OWSIdentityManager sharedManager] saveRemoteIdentity:newKey recipientId:self.envelope.source]; - - // Decrypt this and any old messages for the newly accepted key - NSArray *messagesToDecrypt = - [self.thread receivedMessagesForInvalidKey:newKey]; - - for (TSInvalidIdentityKeyReceivingErrorMessage *errorMessage in messagesToDecrypt) { - [SSKEnvironment.shared.messageReceiver handleReceivedEnvelopeData:errorMessage.envelopeData]; - - // Here we remove the existing error message because handleReceivedEnvelope will either - // 1.) succeed and create a new successful message in the thread or... - // 2.) fail and create a new identical error message in the thread. - [errorMessage remove]; - } -} - -- (nullable NSData *)throws_newIdentityKey -{ - if (!self.envelope) { - OWSLogError(@"Error message had no envelope data to extract key from"); - return nil; - } - - if (self.envelope.type != SSKProtoEnvelopeTypePrekeyBundle) { - OWSLogError(@"Refusing to attempt key extraction from an envelope which isn't a prekey bundle"); - return nil; - } - - NSData *pkwmData = self.envelope.content; - if (!pkwmData) { - OWSLogError(@"Ignoring acceptNewIdentityKey for empty message"); - return nil; - } - - PreKeyWhisperMessage *message = [[PreKeyWhisperMessage alloc] init_throws_withData:pkwmData]; - return [message.identityKey throws_removeKeyType]; -} - -- (NSString *)theirSignalId -{ - if (self.authorId) { - return self.authorId; - } else { - // for existing messages before we were storing author id. - return self.envelope.source; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeySendingErrorMessage.h b/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeySendingErrorMessage.h deleted file mode 100644 index 8b86e43c7..000000000 --- a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeySendingErrorMessage.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSInvalidIdentityKeyErrorMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class PreKeyBundle; -@class TSOutgoingMessage; -@class TSThread; - -extern NSString *TSInvalidPreKeyBundleKey; -extern NSString *TSInvalidRecipientKey; - -// DEPRECATED - we no longer create new instances of this class (as of mid-2017); However, existing instances may -// exist, so we should keep this class around to honor their old behavior. -__attribute__((deprecated)) @interface TSInvalidIdentityKeySendingErrorMessage : TSInvalidIdentityKeyErrorMessage - -@property (nonatomic, readonly) NSString *messageId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeySendingErrorMessage.m b/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeySendingErrorMessage.m deleted file mode 100644 index cbb522ef9..000000000 --- a/SignalServiceKit/src/Messages/InvalidKeyMessages/TSInvalidIdentityKeySendingErrorMessage.m +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSInvalidIdentityKeySendingErrorMessage.h" -#import "OWSFingerprint.h" -#import "OWSIdentityManager.h" -#import "OWSPrimaryStorage+SessionStore.h" -#import "OWSPrimaryStorage.h" -#import "PreKeyBundle+jsonDict.h" -#import "TSContactThread.h" -#import "TSErrorMessage_privateConstructor.h" -#import "TSOutgoingMessage.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *TSInvalidPreKeyBundleKey = @"TSInvalidPreKeyBundleKey"; -NSString *TSInvalidRecipientKey = @"TSInvalidRecipientKey"; - -@interface TSInvalidIdentityKeySendingErrorMessage () - -@property (nonatomic, readonly) PreKeyBundle *preKeyBundle; - -@end - -// DEPRECATED - we no longer create new instances of this class (as of mid-2017); However, existing instances may -// exist, so we should keep this class around to honor their old behavior. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -@implementation TSInvalidIdentityKeySendingErrorMessage -#pragma clang diagnostic pop - -- (void)throws_acceptNewIdentityKey -{ - // Shouldn't really get here, since we're no longer creating blocking SN changes. - // But there may still be some old unaccepted SN errors in the wild that need to be accepted. - OWSFailDebug(@"accepting new identity key is deprecated."); - - NSData *_Nullable newIdentityKey = [self throws_newIdentityKey]; - if (!newIdentityKey) { - OWSFailDebug(@"newIdentityKey is unexpectedly nil. Bad Prekey bundle?: %@", self.preKeyBundle); - return; - } - - [[OWSIdentityManager sharedManager] saveRemoteIdentity:newIdentityKey recipientId:self.recipientId]; -} - -- (nullable NSData *)throws_newIdentityKey -{ - return [self.preKeyBundle.identityKey throws_removeKeyType]; -} - -- (NSString *)theirSignalId -{ - return self.recipientId; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSAddToContactsOfferMessage.h b/SignalServiceKit/src/Messages/OWSAddToContactsOfferMessage.h deleted file mode 100644 index 15a2af9d9..000000000 --- a/SignalServiceKit/src/Messages/OWSAddToContactsOfferMessage.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSInfoMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -// This is a deprecated class, we're keeping it around to avoid YapDB serialization errors -// TODO - remove this class, clean up existing instances, ensure any missed ones don't explode (UnknownDBObject) -__attribute__((deprecated)) @interface OWSAddToContactsOfferMessage : TSInfoMessage - -+ (instancetype)addToContactsOfferMessageWithTimestamp:(uint64_t)timestamp - thread:(TSThread *)thread - contactId:(NSString *)contactId; - -@property (nonatomic, readonly) NSString *contactId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSAddToContactsOfferMessage.m b/SignalServiceKit/src/Messages/OWSAddToContactsOfferMessage.m deleted file mode 100644 index af1b648c8..000000000 --- a/SignalServiceKit/src/Messages/OWSAddToContactsOfferMessage.m +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSAddToContactsOfferMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSAddToContactsOfferMessage () - -@property (nonatomic) NSString *contactId; - -@end - -#pragma mark - - -// This is a deprecated class, we're keeping it around to avoid YapDB serialization errors -// TODO - remove this class, clean up existing instances, ensure any missed ones don't explode (UnknownDBObject) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -@implementation OWSAddToContactsOfferMessage -#pragma clang diagnostic pop - -+ (instancetype)addToContactsOfferMessageWithTimestamp:(uint64_t)timestamp - thread:(TSThread *)thread - contactId:(NSString *)contactId -{ - return [[OWSAddToContactsOfferMessage alloc] initWithTimestamp:timestamp thread:thread contactId:contactId]; -} - -- (instancetype)initWithTimestamp:(uint64_t)timestamp thread:(TSThread *)thread contactId:(NSString *)contactId -{ - self = [super initWithTimestamp:timestamp inThread:thread messageType:TSInfoMessageAddToContactsOffer]; - - if (self) { - _contactId = contactId; - } - - return self; -} - -- (BOOL)shouldUseReceiptDateForSorting -{ - // Use the timestamp, not the "received at" timestamp to sort, - // since we're creating these interactions after the fact and back-dating them. - return NO; -} - -- (BOOL)isDynamicInteraction -{ - return YES; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.h b/SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.h deleted file mode 100644 index 368c93085..000000000 --- a/SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSInfoMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -// This is a deprecated class, we're keeping it around to avoid YapDB serialization errors -// TODO - remove this class, clean up existing instances, ensure any missed ones don't explode (UnknownDBObject) -__attribute__((deprecated)) @interface OWSAddToProfileWhitelistOfferMessage : TSInfoMessage - -+ (instancetype)addToProfileWhitelistOfferMessageWithTimestamp:(uint64_t)timestamp thread:(TSThread *)thread; - -@property (nonatomic, readonly) NSString *contactId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.m b/SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.m deleted file mode 100644 index 0ca611409..000000000 --- a/SignalServiceKit/src/Messages/OWSAddToProfileWhitelistOfferMessage.m +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSAddToProfileWhitelistOfferMessage.h" -#import "TSThread.h" - -NS_ASSUME_NONNULL_BEGIN - -// This is a deprecated class, we're keeping it around to avoid YapDB serialization errors -// TODO - remove this class, clean up existing instances, ensure any missed ones don't explode (UnknownDBObject) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -@implementation OWSAddToProfileWhitelistOfferMessage -#pragma clang diagnostic pop - -+ (instancetype)addToProfileWhitelistOfferMessageWithTimestamp:(uint64_t)timestamp thread:(TSThread *)thread -{ - return [[OWSAddToProfileWhitelistOfferMessage alloc] - initWithTimestamp:timestamp - inThread:thread - messageType:(thread.isGroupThread ? TSInfoMessageAddGroupToProfileWhitelistOffer - : TSInfoMessageAddUserToProfileWhitelistOffer)]; -} - -- (BOOL)shouldUseReceiptDateForSorting -{ - // Use the timestamp, not the "received at" timestamp to sort, - // since we're creating these interactions after the fact and back-dating them. - return NO; -} - -- (BOOL)isDynamicInteraction -{ - return YES; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.h b/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.h deleted file mode 100644 index fef3b85ca..000000000 --- a/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class OWSStorage; -@class SSKProtoEnvelope; -@class YapDatabaseReadWriteTransaction; - -@interface OWSMessageContentQueue : NSObject - -- (dispatch_queue_t)serialQueue; - -@end - -// This class is used to write incoming (decrypted, unprocessed) -// messages to a durable queue and then process them in batches, -// in the order in which they were received. -@interface OWSBatchMessageProcessor : NSObject - -@property (nonatomic, readonly) OWSMessageContentQueue *processingQueue; - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -+ (NSString *)databaseExtensionName; -+ (void)asyncRegisterDatabaseExtension:(OWSStorage *)storage; - -- (void)enqueueEnvelopeData:(NSData *)envelopeData - plaintextData:(NSData *_Nullable)plaintextData - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m b/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m deleted file mode 100644 index c078ec2ed..000000000 --- a/SignalServiceKit/src/Messages/OWSBatchMessageProcessor.m +++ /dev/null @@ -1,545 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSBatchMessageProcessor.h" -#import "AppContext.h" -#import "AppReadiness.h" -#import "NSArray+OWS.h" -#import "NotificationsProtocol.h" -#import "OWSBackgroundTask.h" -#import "OWSMessageManager.h" -#import "OWSPrimaryStorage+SessionStore.h" -#import "OWSPrimaryStorage.h" -#import "OWSQueues.h" -#import "OWSStorage.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSDatabaseView.h" -#import "TSErrorMessage.h" -#import "TSYapDatabaseObject.h" -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#pragma mark - Persisted data model - -@interface OWSMessageContentJob : TSYapDatabaseObject - -@property (nonatomic, readonly) NSDate *createdAt; -@property (nonatomic, readonly) NSData *envelopeData; -@property (nonatomic, readonly, nullable) NSData *plaintextData; -@property (nonatomic, readonly) BOOL wasReceivedByUD; - -- (instancetype)initWithEnvelopeData:(NSData *)envelopeData - plaintextData:(NSData *_Nullable)plaintextData - wasReceivedByUD:(BOOL)wasReceivedByUD NS_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; -- (instancetype)initWithUniqueId:(NSString *_Nullable)uniqueId NS_UNAVAILABLE; - -@property (nonatomic, readonly, nullable) SSKProtoEnvelope *envelope; - -@end - -#pragma mark - - -@implementation OWSMessageContentJob - -+ (NSString *)collection -{ - return @"OWSBatchMessageProcessingJob"; -} - -- (instancetype)initWithEnvelopeData:(NSData *)envelopeData - plaintextData:(NSData *_Nullable)plaintextData - wasReceivedByUD:(BOOL)wasReceivedByUD -{ - OWSAssertDebug(envelopeData); - - self = [super initWithUniqueId:[NSUUID new].UUIDString]; - - if (!self) { - return self; - } - - _envelopeData = envelopeData; - _plaintextData = plaintextData; - _wasReceivedByUD = wasReceivedByUD; - _createdAt = [NSDate new]; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (nullable SSKProtoEnvelope *)envelope -{ - NSError *error; - SSKProtoEnvelope *_Nullable result = [SSKProtoEnvelope parseData:self.envelopeData error:&error]; - - if (error) { - OWSFailDebug(@"paring SSKProtoEnvelope failed with error: %@", error); - return nil; - } - - return result; -} - -@end - -#pragma mark - Finder - -NSString *const OWSMessageContentJobFinderExtensionName = @"OWSMessageContentJobFinderExtensionName2"; -NSString *const OWSMessageContentJobFinderExtensionGroup = @"OWSMessageContentJobFinderExtensionGroup2"; - -@interface OWSMessageContentJobFinder : NSObject - -@end - -#pragma mark - - -@interface OWSMessageContentJobFinder () - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@end - -#pragma mark - - -@implementation OWSMessageContentJobFinder - -- (instancetype)initWithDBConnection:(YapDatabaseConnection *)dbConnection -{ - OWSSingletonAssert(); - - self = [super init]; - if (!self) { - return self; - } - - _dbConnection = dbConnection; - - return self; -} - -- (NSArray *)nextJobsForBatchSize:(NSUInteger)maxBatchSize -{ - NSMutableArray *jobs = [NSMutableArray new]; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - YapDatabaseViewTransaction *viewTransaction = [transaction ext:OWSMessageContentJobFinderExtensionName]; - OWSAssertDebug(viewTransaction != nil); - [viewTransaction enumerateKeysAndObjectsInGroup:OWSMessageContentJobFinderExtensionGroup - usingBlock:^(NSString *_Nonnull collection, - NSString *_Nonnull key, - id _Nonnull object, - NSUInteger index, - BOOL *_Nonnull stop) { - OWSMessageContentJob *job = object; - [jobs addObject:job]; - if (jobs.count >= maxBatchSize) { - *stop = YES; - } - }]; - }]; - - return [jobs copy]; -} - -- (void)addJobWithEnvelopeData:(NSData *)envelopeData - plaintextData:(NSData *_Nullable)plaintextData - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(envelopeData); - OWSAssertDebug(transaction); - - OWSMessageContentJob *job = [[OWSMessageContentJob alloc] initWithEnvelopeData:envelopeData - plaintextData:plaintextData - wasReceivedByUD:wasReceivedByUD]; - [job saveWithTransaction:transaction]; -} - -- (void)removeJobsWithIds:(NSArray *)uniqueIds -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [transaction removeObjectsForKeys:uniqueIds inCollection:[OWSMessageContentJob collection]]; - }]; -} - -+ (YapDatabaseView *)databaseExtension -{ - YapDatabaseViewSorting *sorting = - [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, - NSString *group, - NSString *collection1, - NSString *key1, - id object1, - NSString *collection2, - NSString *key2, - id object2) { - - if (![object1 isKindOfClass:[OWSMessageContentJob class]]) { - OWSFailDebug(@"Unexpected object: %@ in collection: %@", [object1 class], collection1); - return NSOrderedSame; - } - OWSMessageContentJob *job1 = (OWSMessageContentJob *)object1; - - if (![object2 isKindOfClass:[OWSMessageContentJob class]]) { - OWSFailDebug(@"Unexpected object: %@ in collection: %@", [object2 class], collection2); - return NSOrderedSame; - } - OWSMessageContentJob *job2 = (OWSMessageContentJob *)object2; - - return [job1.createdAt compare:job2.createdAt]; - }]; - - YapDatabaseViewGrouping *grouping = - [YapDatabaseViewGrouping withObjectBlock:^NSString *_Nullable(YapDatabaseReadTransaction *_Nonnull transaction, - NSString *_Nonnull collection, - NSString *_Nonnull key, - id _Nonnull object) { - if (![object isKindOfClass:[OWSMessageContentJob class]]) { - OWSFailDebug(@"Unexpected object: %@ in collection: %@", object, collection); - return nil; - } - - // Arbitrary string - all in the same group. We're only using the view for sorting. - return OWSMessageContentJobFinderExtensionGroup; - }]; - - YapDatabaseViewOptions *options = [YapDatabaseViewOptions new]; - options.allowedCollections = - [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[OWSMessageContentJob collection]]]; - - return [[YapDatabaseAutoView alloc] initWithGrouping:grouping sorting:sorting versionTag:@"1" options:options]; -} - - -+ (void)asyncRegisterDatabaseExtension:(OWSStorage *)storage -{ - YapDatabaseView *existingView = [storage registeredExtension:OWSMessageContentJobFinderExtensionName]; - if (existingView) { - OWSFailDebug(@"%@ was already initialized.", OWSMessageContentJobFinderExtensionName); - // already initialized - return; - } - [storage asyncRegisterExtension:[self databaseExtension] withName:OWSMessageContentJobFinderExtensionName]; -} - -@end - -#pragma mark - Queue Processing - -@interface OWSMessageContentQueue () - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (nonatomic, readonly) OWSMessageContentJobFinder *finder; -@property (nonatomic) BOOL isDrainingQueue; -@property (atomic) BOOL isAppInBackground; - -- (instancetype)initWithDBConnection:(YapDatabaseConnection *)dbConnection - finder:(OWSMessageContentJobFinder *)finder NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -@end - -#pragma mark - - -@implementation OWSMessageContentQueue - -- (instancetype)initWithDBConnection:(YapDatabaseConnection *)dbConnection finder:(OWSMessageContentJobFinder *)finder -{ - OWSSingletonAssert(); - - self = [super init]; - - if (!self) { - return self; - } - - _dbConnection = dbConnection; - _finder = finder; - _isDrainingQueue = NO; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillEnterForeground:) - name:OWSApplicationWillEnterForegroundNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidEnterBackground:) - name:OWSApplicationDidEnterBackgroundNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(registrationStateDidChange:) - name:RegistrationStateDidChangeNotification - object:nil]; - - // Start processing. - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - if (CurrentAppContext().isMainApp) { - [self drainQueue]; - } - }]; - - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -#pragma mark - Singletons - -- (OWSMessageManager *)messageManager -{ - OWSAssertDebug(SSKEnvironment.shared.messageManager); - - return SSKEnvironment.shared.messageManager; -} - -- (TSAccountManager *)tsAccountManager -{ - OWSAssertDebug(SSKEnvironment.shared.tsAccountManager); - - return SSKEnvironment.shared.tsAccountManager; -} - -#pragma mark - Notifications - -- (void)applicationWillEnterForeground:(NSNotification *)notification -{ - self.isAppInBackground = NO; -} - -- (void)applicationDidEnterBackground:(NSNotification *)notification -{ - self.isAppInBackground = YES; -} - -- (void)registrationStateDidChange:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - if (CurrentAppContext().isMainApp) { - [self drainQueue]; - } - }]; -} - -#pragma mark - instance methods - -- (dispatch_queue_t)serialQueue -{ - static dispatch_queue_t queue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - queue = dispatch_queue_create("org.whispersystems.message.process", DISPATCH_QUEUE_SERIAL); - }); - return queue; -} - -- (void)enqueueEnvelopeData:(NSData *)envelopeData - plaintextData:(NSData *_Nullable)plaintextData - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(envelopeData); - OWSAssertDebug(transaction); - - // We need to persist the decrypted envelope data ASAP to prevent data loss. - [self.finder addJobWithEnvelopeData:envelopeData - plaintextData:plaintextData - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; -} - -- (void)drainQueue -{ - OWSAssertDebug(AppReadiness.isAppReady); - - if (!CurrentAppContext().isMainApp) { return; } - if (!self.tsAccountManager.isRegisteredAndReady) { return; } - - dispatch_async(self.serialQueue, ^{ - if (self.isDrainingQueue) { return; } - self.isDrainingQueue = YES; - [self drainQueueWorkStep]; - }); -} - -- (void)drainQueueWorkStep -{ - AssertOnDispatchQueue(self.serialQueue); - - // We want a value that is just high enough to yield performance benefits - const NSUInteger kIncomingMessageBatchSize = 32; - - NSArray *batchJobs = [self.finder nextJobsForBatchSize:kIncomingMessageBatchSize]; - OWSAssertDebug(batchJobs); - if (batchJobs.count < 1) { - self.isDrainingQueue = NO; - OWSLogVerbose(@"Queue is drained"); - return; - } - - OWSBackgroundTask *_Nullable backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - - NSArray *processedJobs = [self processJobs:batchJobs]; - - [self.finder removeJobsWithIds:processedJobs.uniqueIds]; - - OWSAssertDebug(backgroundTask); - backgroundTask = nil; - - OWSLogVerbose(@"completed %lu/%lu jobs. %lu jobs left.", - (unsigned long)processedJobs.count, - (unsigned long)batchJobs.count, - (unsigned long)[OWSMessageContentJob numberOfKeysInCollection]); - - // Wait a bit in hopes of increasing the batch size. - // This delay won't affect the first message to arrive when this queue is idle, - // so by definition we're receiving more than one message and can benefit from - // batching. - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5f * NSEC_PER_SEC)), self.serialQueue, ^{ - [self drainQueueWorkStep]; - }); -} - -- (NSArray *)processJobs:(NSArray *)jobs -{ - AssertOnDispatchQueue(self.serialQueue); - - NSMutableArray *processedJobs = [NSMutableArray new]; - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - for (OWSMessageContentJob *job in jobs) { - - void (^reportFailure)(YapDatabaseReadWriteTransaction *transaction) = ^( - YapDatabaseReadWriteTransaction *transaction) { - TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread]; - [SSKEnvironment.shared.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage transaction:transaction]; - }; - - @try { - SSKProtoEnvelope *_Nullable envelope = job.envelope; - if (!envelope) { - reportFailure(transaction); - } else { - [self.messageManager throws_processEnvelope:envelope - plaintextData:job.plaintextData - wasReceivedByUD:job.wasReceivedByUD - transaction:transaction - serverID:0]; - } - } @catch (NSException *exception) { - reportFailure(transaction); - } - - [processedJobs addObject:job]; - - if (self.isAppInBackground) { - // If the app is in the background, stop processing this batch. - // - // Since this check is done after processing jobs, we'll continue - // to process jobs in batches of 1. This reduces the cost of - // being interrupted and rolled back if app is suspended. - break; - } - } - }]; - - return processedJobs; -} - -@end - -#pragma mark - OWSBatchMessageProcessor - -@interface OWSBatchMessageProcessor () - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@end - -#pragma mark - - -@implementation OWSBatchMessageProcessor - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - OWSSingletonAssert(); - - self = [super init]; - if (!self) { - return self; - } - - // For coherency we use the same dbConnection to persist and read the unprocessed envelopes - YapDatabaseConnection *dbConnection = [primaryStorage newDatabaseConnection]; - OWSMessageContentJobFinder *finder = [[OWSMessageContentJobFinder alloc] initWithDBConnection:dbConnection]; - OWSMessageContentQueue *processingQueue = - [[OWSMessageContentQueue alloc] initWithDBConnection:dbConnection finder:finder]; - - _processingQueue = processingQueue; - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - if (CurrentAppContext().isMainApp) { - [self.processingQueue drainQueue]; - } - }]; - - return self; -} - -#pragma mark - class methods - -+ (NSString *)databaseExtensionName -{ - return OWSMessageContentJobFinderExtensionName; -} - -+ (void)asyncRegisterDatabaseExtension:(OWSStorage *)storage -{ - [OWSMessageContentJobFinder asyncRegisterDatabaseExtension:storage]; -} - -#pragma mark - instance methods - -- (void)enqueueEnvelopeData:(NSData *)envelopeData - plaintextData:(NSData *_Nullable)plaintextData - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (envelopeData.length < 1) { - OWSFailDebug(@"Received an empty envelope."); - return; - } - OWSAssert(transaction); - - // We need to persist the decrypted envelope data ASAP to prevent data loss. - [self.processingQueue enqueueEnvelopeData:envelopeData - plaintextData:plaintextData - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - - // The new envelope won't be visible to the finder until this transaction commits, - // so drainQueue in the transaction completion. - [transaction addCompletionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) - completionBlock:^{ - [self.processingQueue drainQueue]; - }]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSBlockingManager.h b/SignalServiceKit/src/Messages/OWSBlockingManager.h deleted file mode 100644 index 40b81f865..000000000 --- a/SignalServiceKit/src/Messages/OWSBlockingManager.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class TSGroupModel; -@class TSThread; - -extern NSString *const kNSNotificationName_BlockListDidChange; - -extern NSString *const kOWSBlockingManager_BlockListCollection; - -// This class can be safely accessed and used from any thread. -@interface OWSBlockingManager : NSObject - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -+ (instancetype)sharedManager; - -- (void)addBlockedPhoneNumber:(NSString *)phoneNumber; - -- (void)removeBlockedPhoneNumber:(NSString *)phoneNumber; - -// When updating the block list from a sync message, we don't -// want to fire a sync message. -- (void)setBlockedPhoneNumbers:(NSArray *)blockedPhoneNumbers sendSyncMessage:(BOOL)sendSyncMessage; - -// TODO convert to property -- (NSArray *)blockedPhoneNumbers; - -@property (readonly) NSArray *blockedGroupIds; -@property (readonly) NSArray *blockedGroups; - -- (void)addBlockedGroup:(TSGroupModel *)group; -- (void)removeBlockedGroupId:(NSData *)groupId; -- (nullable TSGroupModel *)cachedGroupDetailsWithGroupId:(NSData *)groupId; - -- (BOOL)isRecipientIdBlocked:(NSString *)recipientId; -- (BOOL)isGroupIdBlocked:(NSData *)groupId; -- (BOOL)isThreadBlocked:(TSThread *)thread; - -- (void)syncBlockList; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSBlockingManager.m b/SignalServiceKit/src/Messages/OWSBlockingManager.m deleted file mode 100644 index 5a432cc44..000000000 --- a/SignalServiceKit/src/Messages/OWSBlockingManager.m +++ /dev/null @@ -1,442 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSBlockingManager.h" -#import "AppContext.h" -#import "AppReadiness.h" -#import "NSNotificationCenter+OWS.h" -#import "OWSBlockedPhoneNumbersMessage.h" -#import "OWSMessageSender.h" -#import "OWSPrimaryStorage.h" -#import "SSKEnvironment.h" -#import "TSContactThread.h" -#import "TSGroupThread.h" -#import "YapDatabaseConnection+OWS.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const kNSNotificationName_BlockListDidChange = @"kNSNotificationName_BlockListDidChange"; - -NSString *const kOWSBlockingManager_BlockListCollection = @"kOWSBlockingManager_BlockedPhoneNumbersCollection"; - -// These keys are used to persist the current local "block list" state. -NSString *const kOWSBlockingManager_BlockedPhoneNumbersKey = @"kOWSBlockingManager_BlockedPhoneNumbersKey"; -NSString *const kOWSBlockingManager_BlockedGroupMapKey = @"kOWSBlockingManager_BlockedGroupMapKey"; - -// These keys are used to persist the most recently synced remote "block list" state. -NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockingManager_SyncedBlockedPhoneNumbersKey"; -NSString *const kOWSBlockingManager_SyncedBlockedGroupIdsKey = @"kOWSBlockingManager_SyncedBlockedGroupIdsKey"; - -@interface OWSBlockingManager () - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -// We don't store the phone numbers as instances of PhoneNumber to avoid -// consistency issues between clients, but these should all be valid e164 -// phone numbers. -@property (atomic, readonly) NSMutableSet *blockedPhoneNumberSet; -@property (atomic, readonly) NSMutableDictionary *blockedGroupMap; - -@end - -#pragma mark - - -@implementation OWSBlockingManager - -+ (instancetype)sharedManager -{ - OWSAssertDebug(SSKEnvironment.shared.blockingManager); - - return SSKEnvironment.shared.blockingManager; -} - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - - if (!self) { - return self; - } - - OWSAssertDebug(primaryStorage); - - _dbConnection = primaryStorage.newDatabaseConnection; - - OWSSingletonAssert(); - - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)observeNotifications -{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidBecomeActive:) - name:OWSApplicationDidBecomeActiveNotification - object:nil]; -} - -- (OWSMessageSender *)messageSender -{ - OWSAssertDebug(SSKEnvironment.shared.messageSender); - - return SSKEnvironment.shared.messageSender; -} - -#pragma mark - - -- (BOOL)isThreadBlocked:(TSThread *)thread -{ - if ([thread isKindOfClass:[TSContactThread class]]) { - TSContactThread *contactThread = (TSContactThread *)thread; - return [self isRecipientIdBlocked:contactThread.contactIdentifier]; - } else if ([thread isKindOfClass:[TSGroupThread class]]) { - TSGroupThread *groupThread = (TSGroupThread *)thread; - return [self isGroupIdBlocked:groupThread.groupModel.groupId]; - } else { - OWSFailDebug(@"%@ failure unexpected thread type", self.logTag); - return NO; - } -} - -#pragma mark - Contact Blocking - -- (void)addBlockedPhoneNumber:(NSString *)phoneNumber -{ - OWSAssertDebug(phoneNumber.length > 0); - - OWSLogInfo(@"addBlockedPhoneNumber: %@", phoneNumber); - - @synchronized(self) - { - [self ensureLazyInitialization]; - - if ([_blockedPhoneNumberSet containsObject:phoneNumber]) { - // Ignore redundant changes. - return; - } - - [_blockedPhoneNumberSet addObject:phoneNumber]; - } - - [self handleUpdate]; -} - -- (void)removeBlockedPhoneNumber:(NSString *)phoneNumber -{ - OWSAssertDebug(phoneNumber.length > 0); - - OWSLogInfo(@"removeBlockedPhoneNumber: %@", phoneNumber); - - @synchronized(self) - { - [self ensureLazyInitialization]; - - if (![_blockedPhoneNumberSet containsObject:phoneNumber]) { - // Ignore redundant changes. - return; - } - - [_blockedPhoneNumberSet removeObject:phoneNumber]; - } - - [self handleUpdate]; -} - -- (void)setBlockedPhoneNumbers:(NSArray *)blockedPhoneNumbers sendSyncMessage:(BOOL)sendSyncMessage -{ - OWSAssertDebug(blockedPhoneNumbers != nil); - - OWSLogInfo(@"setBlockedPhoneNumbers: %d", (int)blockedPhoneNumbers.count); - - @synchronized(self) - { - [self ensureLazyInitialization]; - - NSSet *newSet = [NSSet setWithArray:blockedPhoneNumbers]; - if ([_blockedPhoneNumberSet isEqualToSet:newSet]) { - return; - } - - _blockedPhoneNumberSet = [newSet mutableCopy]; - } - - [self handleUpdate:sendSyncMessage]; -} - -- (NSArray *)blockedPhoneNumbers -{ - @synchronized(self) - { - [self ensureLazyInitialization]; - - return [_blockedPhoneNumberSet.allObjects sortedArrayUsingSelector:@selector(compare:)]; - } -} - -- (BOOL)isRecipientIdBlocked:(NSString *)recipientId -{ - __block NSString *masterPublicKey; - [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { - masterPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:recipientId in:transaction] ?: recipientId; - }]; - return [self.blockedPhoneNumbers containsObject:masterPublicKey]; -} - -#pragma mark - Group Blocking - -- (NSArray *)blockedGroupIds -{ - @synchronized(self) { - [self ensureLazyInitialization]; - return self.blockedGroupMap.allKeys; - } -} - -- (NSArray *)blockedGroups -{ - @synchronized(self) { - [self ensureLazyInitialization]; - return self.blockedGroupMap.allValues; - } -} - -- (BOOL)isGroupIdBlocked:(NSData *)groupId -{ - return self.blockedGroupMap[groupId] != nil; -} - -- (nullable TSGroupModel *)cachedGroupDetailsWithGroupId:(NSData *)groupId -{ - @synchronized(self) { - return self.blockedGroupMap[groupId]; - } -} - -- (void)addBlockedGroup:(TSGroupModel *)groupModel -{ - NSData *groupId = groupModel.groupId; - OWSAssertDebug(groupId.length > 0); - - OWSLogInfo(@"groupId: %@", groupId); - - @synchronized(self) { - [self ensureLazyInitialization]; - - if ([self isGroupIdBlocked:groupId]) { - // Ignore redundant changes. - return; - } - self.blockedGroupMap[groupId] = groupModel; - } - - [self handleUpdate]; -} - -- (void)removeBlockedGroupId:(NSData *)groupId -{ - OWSAssertDebug(groupId.length > 0); - - OWSLogInfo(@"groupId: %@", groupId); - - @synchronized(self) { - [self ensureLazyInitialization]; - - if (![self isGroupIdBlocked:groupId]) { - // Ignore redundant changes. - return; - } - - [self.blockedGroupMap removeObjectForKey:groupId]; - } - - [self handleUpdate]; -} - - -#pragma mark - Updates - -// This should be called every time the block list changes. - -- (void)handleUpdate -{ - // By default, always send a sync message when the block list changes. - [self handleUpdate:YES]; -} - -// TODO label the `sendSyncMessage` param -- (void)handleUpdate:(BOOL)sendSyncMessage -{ - NSArray *blockedPhoneNumbers = [self blockedPhoneNumbers]; - - [self.dbConnection setObject:blockedPhoneNumbers - forKey:kOWSBlockingManager_BlockedPhoneNumbersKey - inCollection:kOWSBlockingManager_BlockListCollection]; - - NSDictionary *blockedGroupMap; - @synchronized(self) { - blockedGroupMap = [self.blockedGroupMap copy]; - } - NSArray *blockedGroupIds = blockedGroupMap.allKeys; - - [self.dbConnection setObject:blockedGroupMap - forKey:kOWSBlockingManager_BlockedGroupMapKey - inCollection:kOWSBlockingManager_BlockListCollection]; - - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - if (sendSyncMessage) { - [self sendBlockListSyncMessageWithPhoneNumbers:blockedPhoneNumbers groupIds:blockedGroupIds]; - } else { - // If this update came from an incoming block list sync message, - // update the "synced blocked list" state immediately, - // since we're now in sync. - // - // There could be data loss if both clients modify the block list - // at the same time, but: - // - // a) Block list changes will be rare. - // b) Conflicting block list changes will be even rarer. - // c) It's unlikely a user will make conflicting changes on two - // devices around the same time. - // d) There isn't a good way to avoid this. - [self saveSyncedBlockListWithPhoneNumbers:blockedPhoneNumbers groupIds:blockedGroupIds]; - } - - [[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotificationName_BlockListDidChange - object:nil - userInfo:nil]; - }); -} - -// This method should only be called from within a synchronized block. -- (void)ensureLazyInitialization -{ - if (_blockedPhoneNumberSet) { - OWSAssertDebug(_blockedGroupMap); - - // already loaded - return; - } - - NSArray *blockedPhoneNumbers = - [self.dbConnection objectForKey:kOWSBlockingManager_BlockedPhoneNumbersKey - inCollection:kOWSBlockingManager_BlockListCollection]; - _blockedPhoneNumberSet = [[NSMutableSet alloc] initWithArray:(blockedPhoneNumbers ?: [NSArray new])]; - - NSDictionary *storedBlockedGroupMap = - [self.dbConnection objectForKey:kOWSBlockingManager_BlockedGroupMapKey - inCollection:kOWSBlockingManager_BlockListCollection]; - if ([storedBlockedGroupMap isKindOfClass:[NSDictionary class]]) { - _blockedGroupMap = [storedBlockedGroupMap mutableCopy]; - } else { - _blockedGroupMap = [NSMutableDictionary new]; - } - - [self syncBlockListIfNecessary]; - [self observeNotifications]; -} - -- (void)syncBlockList -{ - OWSAssertDebug(_blockedPhoneNumberSet); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self sendBlockListSyncMessageWithPhoneNumbers:self.blockedPhoneNumbers groupIds:self.blockedGroupIds]; - }); -} - -// This method should only be called from within a synchronized block. -- (void)syncBlockListIfNecessary -{ - /* - OWSAssertDebug(_blockedPhoneNumberSet); - - // If we haven't yet successfully synced the current "block list" changes, - // try again to sync now. - NSArray *syncedBlockedPhoneNumbers = - [self.dbConnection objectForKey:kOWSBlockingManager_SyncedBlockedPhoneNumbersKey - inCollection:kOWSBlockingManager_BlockListCollection]; - NSSet *syncedBlockedPhoneNumberSet = - [[NSSet alloc] initWithArray:(syncedBlockedPhoneNumbers ?: [NSArray new])]; - - NSArray *syncedBlockedGroupIds = - [self.dbConnection objectForKey:kOWSBlockingManager_SyncedBlockedGroupIdsKey - inCollection:kOWSBlockingManager_BlockListCollection]; - NSSet *syncedBlockedGroupIdSet = [[NSSet alloc] initWithArray:(syncedBlockedGroupIds ?: [NSArray new])]; - - NSArray *localBlockedGroupIds = self.blockedGroupIds; - NSSet *localBlockedGroupIdSet = [[NSSet alloc] initWithArray:localBlockedGroupIds]; - - if ([self.blockedPhoneNumberSet isEqualToSet:syncedBlockedPhoneNumberSet] && - [localBlockedGroupIdSet isEqualToSet:syncedBlockedGroupIdSet]) { - OWSLogVerbose(@"Ignoring redundant block list sync"); - return; - } - - OWSLogInfo(@"retrying sync of block list"); - [self sendBlockListSyncMessageWithPhoneNumbers:self.blockedPhoneNumbers groupIds:localBlockedGroupIds]; - */ -} - -- (void)sendBlockListSyncMessageWithPhoneNumbers:(NSArray *)blockedPhoneNumbers - groupIds:(NSArray *)blockedGroupIds -{ - OWSAssertDebug(blockedPhoneNumbers); - OWSAssertDebug(blockedGroupIds); - - OWSBlockedPhoneNumbersMessage *message = - [[OWSBlockedPhoneNumbersMessage alloc] initWithPhoneNumbers:blockedPhoneNumbers groupIds:blockedGroupIds]; - - [self.messageSender sendMessage:message - success:^{ - OWSLogInfo(@"Successfully sent blocked phone numbers sync message"); - - // DURABLE CLEANUP - we could replace the custom durability logic in this class - // with a durable JobQueue. - [self saveSyncedBlockListWithPhoneNumbers:blockedPhoneNumbers groupIds:blockedGroupIds]; - } - failure:^(NSError *error) { - OWSLogError(@"Failed to send blocked phone numbers sync message with error: %@", error); - }]; -} - -/// Records the last block list which we successfully synced. -- (void)saveSyncedBlockListWithPhoneNumbers:(NSArray *)blockedPhoneNumbers - groupIds:(NSArray *)blockedGroupIds -{ - OWSAssertDebug(blockedPhoneNumbers); - OWSAssertDebug(blockedGroupIds); - - [self.dbConnection setObject:blockedPhoneNumbers - forKey:kOWSBlockingManager_SyncedBlockedPhoneNumbersKey - inCollection:kOWSBlockingManager_BlockListCollection]; - - [self.dbConnection setObject:blockedGroupIds - forKey:kOWSBlockingManager_SyncedBlockedGroupIdsKey - inCollection:kOWSBlockingManager_BlockListCollection]; -} - -#pragma mark - Notifications - -- (void)applicationDidBecomeActive:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - @synchronized(self) - { - [self syncBlockListIfNecessary]; - } - }]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSDisappearingMessagesFinder.h b/SignalServiceKit/src/Messages/OWSDisappearingMessagesFinder.h deleted file mode 100644 index 339e504c9..000000000 --- a/SignalServiceKit/src/Messages/OWSDisappearingMessagesFinder.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class OWSStorage; -@class TSMessage; -@class TSThread; -@class YapDatabaseReadTransaction; - -@interface OWSDisappearingMessagesFinder : NSObject - -- (void)enumerateExpiredMessagesWithBlock:(void (^_Nonnull)(TSMessage *message))block - transaction:(YapDatabaseReadTransaction *)transaction; - -- (void)enumerateUnstartedExpiringMessagesInThread:(TSThread *)thread - block:(void (^_Nonnull)(TSMessage *message))block - transaction:(YapDatabaseReadTransaction *)transaction; - -- (void)enumerateMessagesWhichFailedToStartExpiringWithBlock:(void (^_Nonnull)(TSMessage *message))block - transaction:(YapDatabaseReadTransaction *)transaction; - -/** - * @return - * uint64_t millisecond timestamp wrapped in a number. Retrieve with `unsignedLongLongvalue`. - * or nil if there are no upcoming expired messages - */ -- (nullable NSNumber *)nextExpirationTimestampWithTransaction:(YapDatabaseReadTransaction *_Nonnull)transaction; - -+ (NSString *)databaseExtensionName; - -+ (void)asyncRegisterDatabaseExtensions:(OWSStorage *)storage; - -#ifdef DEBUG -/** - * Only use the sync version for testing, generally we'll want to register extensions async - */ -+ (void)blockingRegisterDatabaseExtensions:(OWSStorage *)storage; -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSDisappearingMessagesFinder.m b/SignalServiceKit/src/Messages/OWSDisappearingMessagesFinder.m deleted file mode 100644 index acc2bad88..000000000 --- a/SignalServiceKit/src/Messages/OWSDisappearingMessagesFinder.m +++ /dev/null @@ -1,269 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDisappearingMessagesFinder.h" -#import "OWSPrimaryStorage.h" -#import "TSIncomingMessage.h" -#import "TSMessage.h" -#import "TSOutgoingMessage.h" -#import "TSThread.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const OWSDisappearingMessageFinderThreadIdColumn = @"thread_id"; -static NSString *const OWSDisappearingMessageFinderExpiresAtColumn = @"expires_at"; -static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_messages_on_expires_at_and_thread_id_v2"; - -@implementation OWSDisappearingMessagesFinder - -- (NSArray *)fetchUnstartedExpiringMessageIdsInThread:(TSThread *)thread - transaction:(YapDatabaseReadTransaction *_Nonnull)transaction -{ - OWSAssertDebug(transaction); - - NSMutableArray *messageIds = [NSMutableArray new]; - NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ = 0 AND %@ = \"%@\"", - OWSDisappearingMessageFinderExpiresAtColumn, - OWSDisappearingMessageFinderThreadIdColumn, - thread.uniqueId]; - - YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; - [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] - enumerateKeysMatchingQuery:query - usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { - [messageIds addObject:key]; - }]; - - return [messageIds copy]; -} - -- (NSArray *)fetchMessageIdsWhichFailedToStartExpiring:(YapDatabaseReadTransaction *_Nonnull)transaction -{ - OWSAssertDebug(transaction); - - NSMutableArray *messageIds = [NSMutableArray new]; - NSString *formattedString = - [NSString stringWithFormat:@"WHERE %@ = 0", OWSDisappearingMessageFinderExpiresAtColumn]; - - YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; - [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] - enumerateKeysAndObjectsMatchingQuery:query - usingBlock:^void(NSString *collection, NSString *key, id object, BOOL *stop) { - if (![object isKindOfClass:[TSMessage class]]) { - OWSFailDebug(@"Object was unexpected class: %@", [object class]); - return; - } - - // We'll need to update if we ever support expiring other message types - OWSAssertDebug([object isKindOfClass:[TSOutgoingMessage class]] || [object isKindOfClass:[TSIncomingMessage class]]); - - TSMessage *message = (TSMessage *)object; - if ([message shouldStartExpireTimerWithTransaction:transaction]) { - if ([message isKindOfClass:[TSIncomingMessage class]]) { - TSIncomingMessage *incomingMessage = (TSIncomingMessage *)message; - if (!incomingMessage.wasRead) { - return; - } - } - [messageIds addObject:key]; - } - }]; - - return [messageIds copy]; -} - -- (NSArray *)fetchExpiredMessageIdsWithTransaction:(YapDatabaseReadTransaction *_Nonnull)transaction -{ - OWSAssertDebug(transaction); - - NSMutableArray *messageIds = [NSMutableArray new]; - - uint64_t now = [NSDate ows_millisecondTimeStamp]; - // When (expiresAt == 0) the message SHOULD NOT expire. Careful ;) - NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ > 0 AND %@ <= %lld", - OWSDisappearingMessageFinderExpiresAtColumn, - OWSDisappearingMessageFinderExpiresAtColumn, - now]; - YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; - [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] - enumerateKeysMatchingQuery:query - usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { - [messageIds addObject:key]; - }]; - - return [messageIds copy]; -} - -- (nullable NSNumber *)nextExpirationTimestampWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ > 0 ORDER BY %@ ASC", - OWSDisappearingMessageFinderExpiresAtColumn, - OWSDisappearingMessageFinderExpiresAtColumn]; - YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; - - __block TSMessage *firstMessage; - [[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex] - enumerateKeysAndObjectsMatchingQuery:query - usingBlock:^void(NSString *collection, NSString *key, id object, BOOL *stop) { - firstMessage = (TSMessage *)object; - *stop = YES; - }]; - - if (firstMessage && firstMessage.expiresAt > 0) { - return [NSNumber numberWithUnsignedLongLong:firstMessage.expiresAt]; - } - - return nil; -} - -- (void)enumerateUnstartedExpiringMessagesInThread:(TSThread *)thread - block:(void (^_Nonnull)(TSMessage *message))block - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - for (NSString *expiringMessageId in - [self fetchUnstartedExpiringMessageIdsInThread:thread transaction:transaction]) { - TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiringMessageId transaction:transaction]; - if ([message isKindOfClass:[TSMessage class]]) { - block(message); - } else { - OWSFailDebug(@"unexpected object: %@", [message class]); - } - } -} - -- (void)enumerateMessagesWhichFailedToStartExpiringWithBlock:(void (^_Nonnull)(TSMessage *message))block - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - for (NSString *expiringMessageId in [self fetchMessageIdsWhichFailedToStartExpiring:transaction]) { - - TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiringMessageId transaction:transaction]; - if (![message isKindOfClass:[TSMessage class]]) { - OWSFailDebug(@"unexpected object: %@", [message class]); - continue; - } - - if (![message shouldStartExpireTimerWithTransaction:transaction]) { - OWSFailDebug(@"object: %@ shouldn't expire.", message); - continue; - } - - block(message); - } -} - -/** - * Don't use this in production. Useful for testing. - * We don't want to instantiate potentially many messages at once. - */ -- (NSArray *)fetchUnstartedExpiringMessagesInThread:(TSThread *)thread - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - NSMutableArray *messages = [NSMutableArray new]; - [self enumerateUnstartedExpiringMessagesInThread:thread - block:^(TSMessage *message) { - [messages addObject:message]; - } - transaction:transaction]; - - return [messages copy]; -} - - -- (void)enumerateExpiredMessagesWithBlock:(void (^_Nonnull)(TSMessage *message))block - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - // Since we can't directly mutate the enumerated expired messages, we store only their ids in hopes of saving a - // little memory and then enumerate the (larger) TSMessage objects one at a time. - for (NSString *expiredMessageId in [self fetchExpiredMessageIdsWithTransaction:transaction]) { - TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiredMessageId transaction:transaction]; - if ([message isKindOfClass:[TSMessage class]]) { - block(message); - } else { - OWSLogError(@"unexpected object: %@", message); - } - } -} - -/** - * Don't use this in production. Useful for testing. - * We don't want to instantiate potentially many messages at once. - */ -- (NSArray *)fetchExpiredMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - NSMutableArray *messages = [NSMutableArray new]; - [self enumerateExpiredMessagesWithBlock:^(TSMessage *message) { - [messages addObject:message]; - } - transaction:transaction]; - - return [messages copy]; -} - -#pragma mark - YapDatabaseExtension - -+ (YapDatabaseSecondaryIndex *)indexDatabaseExtension -{ - YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new]; - [setup addColumn:OWSDisappearingMessageFinderExpiresAtColumn withType:YapDatabaseSecondaryIndexTypeInteger]; - [setup addColumn:OWSDisappearingMessageFinderThreadIdColumn withType:YapDatabaseSecondaryIndexTypeText]; - - YapDatabaseSecondaryIndexHandler *handler = - [YapDatabaseSecondaryIndexHandler withObjectBlock:^(YapDatabaseReadTransaction *transaction, - NSMutableDictionary *dict, - NSString *collection, - NSString *key, - id object) { - if (![object isKindOfClass:[TSMessage class]]) { - return; - } - TSMessage *message = (TSMessage *)object; - - if (![message shouldStartExpireTimerWithTransaction:transaction]) { - return; - } - - dict[OWSDisappearingMessageFinderExpiresAtColumn] = @(message.expiresAt); - dict[OWSDisappearingMessageFinderThreadIdColumn] = message.uniqueThreadId; - }]; - - return [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:handler versionTag:@"1"]; -} - -#ifdef DEBUG -// Useful for tests, don't use in app startup path because it's slow. -+ (void)blockingRegisterDatabaseExtensions:(OWSStorage *)storage -{ - [storage registerExtension:[self indexDatabaseExtension] withName:OWSDisappearingMessageFinderExpiresAtIndex]; -} -#endif - -+ (NSString *)databaseExtensionName -{ - return OWSDisappearingMessageFinderExpiresAtIndex; -} - -+ (void)asyncRegisterDatabaseExtensions:(OWSStorage *)storage -{ - [storage asyncRegisterExtension:[self indexDatabaseExtension] withName:OWSDisappearingMessageFinderExpiresAtIndex]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSDisappearingMessagesJob.h b/SignalServiceKit/src/Messages/OWSDisappearingMessagesJob.h deleted file mode 100644 index 48e4cf95f..000000000 --- a/SignalServiceKit/src/Messages/OWSDisappearingMessagesJob.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class TSMessage; -@class TSThread; -@class YapDatabaseReadWriteTransaction; - -@protocol ContactsManagerProtocol; - -@interface OWSDisappearingMessagesJob : NSObject - -+ (instancetype)sharedJob; - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -- (void)startAnyExpirationForMessage:(TSMessage *)message - expirationStartedAt:(uint64_t)expirationStartedAt - transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction; - -/** - * Synchronize our disappearing messages settings with that of the given message. Useful so we can - * become eventually consistent with remote senders. - * - * @param duration - * Can be 0, indicating a non-expiring message, or greater, indicating an expiring message. We match the expiration - * timer of the message, including disabling expiring messages if the message is not an expiring message. - * - * @param remoteRecipientId - * nil for outgoing messages, otherwise the recipientId of the sender - * - * @param createdInExistingGroup - * YES when being added to a group which already has DM enabled, otherwise NO - */ -- (void)becomeConsistentWithDisappearingDuration:(uint32_t)duration - thread:(TSThread *)thread - createdByRemoteRecipientId:(nullable NSString *)remoteRecipientId - createdInExistingGroup:(BOOL)createdInExistingGroup - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -// Clean up any messages that expired since last launch immediately -// and continue cleaning in the background. -- (void)startIfNecessary; - -- (void)cleanupMessagesWhichFailedToStartExpiringWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSDisappearingMessagesJob.m b/SignalServiceKit/src/Messages/OWSDisappearingMessagesJob.m deleted file mode 100644 index 2e048ac13..000000000 --- a/SignalServiceKit/src/Messages/OWSDisappearingMessagesJob.m +++ /dev/null @@ -1,422 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDisappearingMessagesJob.h" -#import "AppContext.h" -#import "AppReadiness.h" -#import "ContactsManagerProtocol.h" -#import "NSTimer+OWS.h" -#import "OWSBackgroundTask.h" -#import "OWSDisappearingConfigurationUpdateInfoMessage.h" -#import "OWSDisappearingMessagesConfiguration.h" -#import "OWSDisappearingMessagesFinder.h" -#import "OWSPrimaryStorage.h" -#import "SSKEnvironment.h" -#import "TSIncomingMessage.h" -#import "TSMessage.h" -#import "TSThread.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -// Can we move to Signal-iOS? -@interface OWSDisappearingMessagesJob () - -@property (nonatomic, readonly) YapDatabaseConnection *databaseConnection; - -@property (nonatomic, readonly) OWSDisappearingMessagesFinder *disappearingMessagesFinder; - -+ (dispatch_queue_t)serialQueue; - -// These three properties should only be accessed on the main thread. -@property (nonatomic) BOOL hasStarted; -@property (nonatomic, nullable) NSTimer *nextDisappearanceTimer; -@property (nonatomic, nullable) NSDate *nextDisappearanceDate; -@property (nonatomic, nullable) NSTimer *fallbackTimer; - -@end - -void AssertIsOnDisappearingMessagesQueue() -{ -#ifdef DEBUG - if (@available(iOS 10.0, *)) { - dispatch_assert_queue(OWSDisappearingMessagesJob.serialQueue); - } -#endif -} - -#pragma mark - - -@implementation OWSDisappearingMessagesJob - -+ (instancetype)sharedJob -{ - OWSAssertDebug(SSKEnvironment.shared.disappearingMessagesJob); - - return SSKEnvironment.shared.disappearingMessagesJob; -} - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - if (!self) { - return self; - } - - _databaseConnection = primaryStorage.newDatabaseConnection; - _disappearingMessagesFinder = [OWSDisappearingMessagesFinder new]; - - // suspenders in case a deletion schedule is missed. - NSTimeInterval kFallBackTimerInterval = 5 * kMinuteInterval; - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - if (CurrentAppContext().isMainApp) { - self.fallbackTimer = [NSTimer weakScheduledTimerWithTimeInterval:kFallBackTimerInterval - target:self - selector:@selector(fallbackTimerDidFire) - userInfo:nil - repeats:YES]; - } - }]; - - OWSSingletonAssert(); - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidBecomeActive:) - name:OWSApplicationDidBecomeActiveNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillResignActive:) - name:OWSApplicationWillResignActiveNotification - object:nil]; - - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -+ (dispatch_queue_t)serialQueue -{ - static dispatch_queue_t queue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - queue = dispatch_queue_create("org.whispersystems.disappearing.messages", DISPATCH_QUEUE_SERIAL); - }); - return queue; -} - -#pragma mark - Dependencies - -- (id)contactsManager -{ - return SSKEnvironment.shared.contactsManager; -} - -#pragma mark - - -- (NSUInteger)deleteExpiredMessages -{ - AssertIsOnDisappearingMessagesQueue(); - - uint64_t now = [NSDate ows_millisecondTimeStamp]; - - OWSBackgroundTask *_Nullable backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - - __block NSUInteger expirationCount = 0; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [self.disappearingMessagesFinder enumerateExpiredMessagesWithBlock:^(TSMessage *message) { - // sanity check - if (message.expiresAt > now) { - OWSFailDebug(@"Refusing to remove message which doesn't expire until: %lld", message.expiresAt); - return; - } - - OWSLogInfo(@"Removing message which expired at: %lld", message.expiresAt); - [message removeWithTransaction:transaction]; - expirationCount++; - } - transaction:transaction]; - }]; - - OWSLogDebug(@"Removed %lu expired messages", (unsigned long)expirationCount); - - OWSAssertDebug(backgroundTask); - backgroundTask = nil; - return expirationCount; -} - -// deletes any expired messages and schedules the next run. -- (NSUInteger)runLoop -{ - OWSLogVerbose(@"in runLoop"); - AssertIsOnDisappearingMessagesQueue(); - - NSUInteger deletedCount = [self deleteExpiredMessages]; - - __block NSNumber *nextExpirationTimestampNumber; - [self.databaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - nextExpirationTimestampNumber = - [self.disappearingMessagesFinder nextExpirationTimestampWithTransaction:transaction]; - }]; - - if (!nextExpirationTimestampNumber) { - OWSLogDebug(@"No more expiring messages."); - return deletedCount; - } - - uint64_t nextExpirationAt = nextExpirationTimestampNumber.unsignedLongLongValue; - NSDate *nextEpirationDate = [NSDate ows_dateWithMillisecondsSince1970:nextExpirationAt]; - [self scheduleRunByDate:nextEpirationDate]; - - return deletedCount; -} - -- (void)startAnyExpirationForMessage:(TSMessage *)message - expirationStartedAt:(uint64_t)expirationStartedAt - transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction -{ - OWSAssertDebug(transaction); - - if (!message.isExpiringMessage) { - return; - } - - NSTimeInterval startedSecondsAgo = ([NSDate ows_millisecondTimeStamp] - expirationStartedAt) / 1000.0; - OWSLogDebug(@"Starting expiration for message read %f seconds ago", startedSecondsAgo); - - // Don't clobber if multiple actions simultaneously triggered expiration. - if (message.expireStartedAt == 0 || message.expireStartedAt > expirationStartedAt) { - [message updateWithExpireStartedAt:expirationStartedAt transaction:transaction]; - } - - [transaction addCompletionQueue:nil - completionBlock:^{ - // Necessary that the async expiration run happens *after* the message is saved with it's new - // expiration configuration. - [self scheduleRunByDate:[NSDate ows_dateWithMillisecondsSince1970:message.expiresAt]]; - }]; -} - -#pragma mark - Apply Remote Configuration - -- (void)becomeConsistentWithDisappearingDuration:(uint32_t)duration - thread:(TSThread *)thread - createdByRemoteRecipientId:(nullable NSString *)remoteRecipientId - createdInExistingGroup:(BOOL)createdInExistingGroup - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(thread); - OWSAssertDebug(transaction); - - OWSBackgroundTask *_Nullable backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - - NSString *_Nullable remoteContactName = nil; - if (remoteRecipientId) { - remoteContactName = [self.contactsManager displayNameForPhoneIdentifier:remoteRecipientId - transaction:transaction]; - } - - // Become eventually consistent in the case that the remote changed their settings at the same time. - // Also in case remote doesn't support expiring messages - OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration = - [thread disappearingMessagesConfigurationWithTransaction:transaction]; - - if (duration == 0) { - disappearingMessagesConfiguration.enabled = NO; - } else { - disappearingMessagesConfiguration.enabled = YES; - disappearingMessagesConfiguration.durationSeconds = duration; - } - - if (!disappearingMessagesConfiguration.dictionaryValueDidChange) { - return; - } - - OWSLogInfo(@"becoming consistent with disappearing message configuration: %@", - disappearingMessagesConfiguration.dictionaryValue); - - [disappearingMessagesConfiguration saveWithTransaction:transaction]; - - // MJK TODO - should be safe to remove this senderTimestamp - OWSDisappearingConfigurationUpdateInfoMessage *infoMessage = - [[OWSDisappearingConfigurationUpdateInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - thread:thread - configuration:disappearingMessagesConfiguration - createdByRemoteName:remoteContactName - createdInExistingGroup:createdInExistingGroup]; - [infoMessage saveWithTransaction:transaction]; - - OWSAssertDebug(backgroundTask); - backgroundTask = nil; -} - -#pragma mark - - -- (void)startIfNecessary -{ - dispatch_async(dispatch_get_main_queue(), ^{ - if (self.hasStarted) { - return; - } - self.hasStarted = YES; - - dispatch_async(OWSDisappearingMessagesJob.serialQueue, ^{ - // Theoretically this shouldn't be necessary, but there was a race condition when receiving a backlog - // of messages across timer changes which could cause a disappearing message's timer to never be started. - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [self cleanupMessagesWhichFailedToStartExpiringWithTransaction:transaction]; - }]; - - [self runLoop]; - }); - }); -} - -- (NSDateFormatter *)dateFormatter -{ - static NSDateFormatter *dateFormatter; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - dateFormatter = [NSDateFormatter new]; - dateFormatter.dateStyle = NSDateFormatterNoStyle; - dateFormatter.timeStyle = kCFDateFormatterMediumStyle; - dateFormatter.locale = [NSLocale systemLocale]; - }); - - return dateFormatter; -} - -- (void)scheduleRunByDate:(NSDate *)date -{ - OWSAssertDebug(date); - - dispatch_async(dispatch_get_main_queue(), ^{ - if (!CurrentAppContext().isMainAppAndActive) { - // Don't schedule run when inactive or not in main app. - return; - } - - // Don't run more often than once per second. - const NSTimeInterval kMinDelaySeconds = 1.0; - NSTimeInterval delaySeconds = MAX(kMinDelaySeconds, date.timeIntervalSinceNow); - NSDate *newTimerScheduleDate = [NSDate dateWithTimeIntervalSinceNow:delaySeconds]; - if (self.nextDisappearanceDate && [self.nextDisappearanceDate isBeforeDate:newTimerScheduleDate]) { - OWSLogVerbose(@"Request to run at %@ (%d sec.) ignored due to earlier scheduled run at %@ (%d sec.)", - [self.dateFormatter stringFromDate:date], - (int)round(MAX(0, [date timeIntervalSinceDate:[NSDate new]])), - [self.dateFormatter stringFromDate:self.nextDisappearanceDate], - (int)round(MAX(0, [self.nextDisappearanceDate timeIntervalSinceDate:[NSDate new]]))); - return; - } - - // Update Schedule - OWSLogVerbose(@"Scheduled run at %@ (%d sec.)", - [self.dateFormatter stringFromDate:newTimerScheduleDate], - (int)round(MAX(0, [newTimerScheduleDate timeIntervalSinceDate:[NSDate new]]))); - [self resetNextDisappearanceTimer]; - self.nextDisappearanceDate = newTimerScheduleDate; - self.nextDisappearanceTimer = [NSTimer weakScheduledTimerWithTimeInterval:delaySeconds - target:self - selector:@selector(disappearanceTimerDidFire) - userInfo:nil - repeats:NO]; - }); -} - -- (void)disappearanceTimerDidFire -{ - OWSAssertIsOnMainThread(); - OWSLogDebug(@""); - - if (!CurrentAppContext().isMainAppAndActive) { - // Don't schedule run when inactive or not in main app. - OWSFailDebug(@"Disappearing messages job timer fired while main app inactive."); - return; - } - - [self resetNextDisappearanceTimer]; - - dispatch_async(OWSDisappearingMessagesJob.serialQueue, ^{ - [self runLoop]; - }); -} - -- (void)fallbackTimerDidFire -{ - OWSAssertIsOnMainThread(); - OWSLogDebug(@""); - - BOOL recentlyScheduledDisappearanceTimer = NO; - if (fabs(self.nextDisappearanceDate.timeIntervalSinceNow) < 1.0) { - recentlyScheduledDisappearanceTimer = YES; - } - - if (!CurrentAppContext().isMainAppAndActive) { - OWSLogInfo(@"Ignoring fallbacktimer for app which is not main and active."); - return; - } - - dispatch_async(OWSDisappearingMessagesJob.serialQueue, ^{ - NSUInteger deletedCount = [self runLoop]; - - // Normally deletions should happen via the disappearanceTimer, to make sure that they're prompt. - // So, if we're deleting something via this fallback timer, something may have gone wrong. The - // exception is if we're in close proximity to the disappearanceTimer, in which case a race condition - // is inevitable. - if (!recentlyScheduledDisappearanceTimer && deletedCount > 0) { - OWSFailDebug(@"unexpectedly deleted disappearing messages via fallback timer."); - } - }); -} - -- (void)resetNextDisappearanceTimer -{ - OWSAssertIsOnMainThread(); - - [self.nextDisappearanceTimer invalidate]; - self.nextDisappearanceTimer = nil; - self.nextDisappearanceDate = nil; -} - -#pragma mark - Cleanup - -- (void)cleanupMessagesWhichFailedToStartExpiringWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [self.disappearingMessagesFinder - enumerateMessagesWhichFailedToStartExpiringWithBlock:^(TSMessage *_Nonnull message) { - OWSFailDebug(@"starting old timer for message timestamp: %lu", (unsigned long)message.timestamp); - - // We don't know when it was actually read, so assume it was read as soon as it was received. - uint64_t readTimeBestGuess = message.receivedAtTimestamp; - [self startAnyExpirationForMessage:message expirationStartedAt:readTimeBestGuess transaction:transaction]; - } - transaction:transaction]; -} - -#pragma mark - Notifications - -- (void)applicationDidBecomeActive:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - dispatch_async(OWSDisappearingMessagesJob.serialQueue, ^{ - [self runLoop]; - }); - }]; -} - -- (void)applicationWillResignActive:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [self resetNextDisappearanceTimer]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.h b/SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.h deleted file mode 100644 index e858a49f6..000000000 --- a/SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class OWSStorage; - -@interface OWSFailedAttachmentDownloadsJob : NSObject - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -- (void)run; - -+ (NSString *)databaseExtensionName; -+ (void)asyncRegisterDatabaseExtensionsWithPrimaryStorage:(OWSStorage *)storage; - -#ifdef DEBUG -/** - * Only use the sync version for testing, generally we'll want to register extensions async - */ -- (void)blockingRegisterDatabaseExtensions; -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.m b/SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.m deleted file mode 100644 index f4d2a092d..000000000 --- a/SignalServiceKit/src/Messages/OWSFailedAttachmentDownloadsJob.m +++ /dev/null @@ -1,142 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSFailedAttachmentDownloadsJob.h" -#import "OWSPrimaryStorage.h" -#import "TSAttachmentPointer.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const OWSFailedAttachmentDownloadsJobAttachmentStateColumn = @"state"; -static NSString *const OWSFailedAttachmentDownloadsJobAttachmentStateIndex = @"index_attachment_downloads_on_state"; - -@interface OWSFailedAttachmentDownloadsJob () - -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; - -@end - -#pragma mark - - -@implementation OWSFailedAttachmentDownloadsJob - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - if (!self) { - return self; - } - - _primaryStorage = primaryStorage; - - return self; -} - -- (NSArray *)fetchAttemptingOutAttachmentIdsWithTransaction: - (YapDatabaseReadWriteTransaction *_Nonnull)transaction -{ - OWSAssertDebug(transaction); - - NSMutableArray *attachmentIds = [NSMutableArray new]; - - NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ != %d", - OWSFailedAttachmentDownloadsJobAttachmentStateColumn, - (int)TSAttachmentPointerStateFailed]; - YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; - [[transaction ext:OWSFailedAttachmentDownloadsJobAttachmentStateIndex] - enumerateKeysMatchingQuery:query - usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { - [attachmentIds addObject:key]; - }]; - - return [attachmentIds copy]; -} - -- (void)enumerateAttemptingOutAttachmentsWithBlock:(void (^_Nonnull)(TSAttachmentPointer *attachment))block - transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction -{ - OWSAssertDebug(transaction); - - // Since we can't directly mutate the enumerated attachments, we store only their ids in hopes - // of saving a little memory and then enumerate the (larger) TSAttachment objects one at a time. - for (NSString *attachmentId in [self fetchAttemptingOutAttachmentIdsWithTransaction:transaction]) { - TSAttachmentPointer *_Nullable attachment = - [TSAttachmentPointer fetchObjectWithUniqueID:attachmentId transaction:transaction]; - if ([attachment isKindOfClass:[TSAttachmentPointer class]]) { - block(attachment); - } else { - OWSLogError(@"unexpected object: %@", attachment); - } - } -} - -- (void)run -{ - __block uint count = 0; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [self enumerateAttemptingOutAttachmentsWithBlock:^(TSAttachmentPointer *attachment) { - // sanity check - if (attachment.state != TSAttachmentPointerStateFailed) { - attachment.state = TSAttachmentPointerStateFailed; - [attachment saveWithTransaction:transaction]; - count++; - } - } - transaction:transaction]; - }]; - - OWSLogDebug(@"Marked %u attachments as unsent", count); -} - -#pragma mark - YapDatabaseExtension - -+ (YapDatabaseSecondaryIndex *)indexDatabaseExtension -{ - YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new]; - [setup addColumn:OWSFailedAttachmentDownloadsJobAttachmentStateColumn - withType:YapDatabaseSecondaryIndexTypeInteger]; - - YapDatabaseSecondaryIndexHandler *handler = - [YapDatabaseSecondaryIndexHandler withObjectBlock:^(YapDatabaseReadTransaction *transaction, - NSMutableDictionary *dict, - NSString *collection, - NSString *key, - id object) { - if (![object isKindOfClass:[TSAttachmentPointer class]]) { - return; - } - TSAttachmentPointer *attachment = (TSAttachmentPointer *)object; - dict[OWSFailedAttachmentDownloadsJobAttachmentStateColumn] = @(attachment.state); - }]; - - return [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:handler versionTag:nil]; -} - -#ifdef DEBUG -// Useful for tests, don't use in app startup path because it's slow. -- (void)blockingRegisterDatabaseExtensions -{ - [self.primaryStorage registerExtension:[self.class indexDatabaseExtension] - withName:OWSFailedAttachmentDownloadsJobAttachmentStateIndex]; -} -#endif - -+ (NSString *)databaseExtensionName -{ - return OWSFailedAttachmentDownloadsJobAttachmentStateIndex; -} - -+ (void)asyncRegisterDatabaseExtensionsWithPrimaryStorage:(OWSStorage *)storage -{ - [storage asyncRegisterExtension:[self indexDatabaseExtension] - withName:OWSFailedAttachmentDownloadsJobAttachmentStateIndex]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSFailedMessagesJob.h b/SignalServiceKit/src/Messages/OWSFailedMessagesJob.h deleted file mode 100644 index eea3d0822..000000000 --- a/SignalServiceKit/src/Messages/OWSFailedMessagesJob.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class OWSStorage; - -@interface OWSFailedMessagesJob : NSObject - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -- (void)run; - -+ (NSString *)databaseExtensionName; -+ (void)asyncRegisterDatabaseExtensionsWithPrimaryStorage:(OWSStorage *)storage; - -#ifdef DEBUG -/** - * Only use the sync version for testing, generally we'll want to register extensions async - */ -- (void)blockingRegisterDatabaseExtensions; -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSFailedMessagesJob.m b/SignalServiceKit/src/Messages/OWSFailedMessagesJob.m deleted file mode 100644 index e851564bf..000000000 --- a/SignalServiceKit/src/Messages/OWSFailedMessagesJob.m +++ /dev/null @@ -1,149 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSFailedMessagesJob.h" -#import "OWSPrimaryStorage.h" -#import "TSMessage.h" -#import "TSOutgoingMessage.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const OWSFailedMessagesJobMessageStateColumn = @"message_state"; -static NSString *const OWSFailedMessagesJobMessageStateIndex = @"index_outoing_messages_on_message_state"; - -@interface OWSFailedMessagesJob () - -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; - -@end - -#pragma mark - - -@implementation OWSFailedMessagesJob - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - if (!self) { - return self; - } - - _primaryStorage = primaryStorage; - - return self; -} - -- (NSArray *)fetchAttemptingOutMessageIdsWithTransaction: - (YapDatabaseReadWriteTransaction *_Nonnull)transaction -{ - OWSAssertDebug(transaction); - - NSMutableArray *messageIds = [NSMutableArray new]; - - NSString *formattedString = [NSString - stringWithFormat:@"WHERE %@ == %d", OWSFailedMessagesJobMessageStateColumn, (int)TSOutgoingMessageStateSending]; - YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; - [[transaction ext:OWSFailedMessagesJobMessageStateIndex] - enumerateKeysMatchingQuery:query - usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { - if (key == nil) { return; } - [messageIds addObject:key]; - }]; - - return [messageIds copy]; -} - -- (void)enumerateAttemptingOutMessagesWithBlock:(void (^_Nonnull)(TSOutgoingMessage *message))block - transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction -{ - OWSAssertDebug(transaction); - - // Since we can't directly mutate the enumerated "attempting out" expired messages, we store only their ids in hopes - // of saving a little memory and then enumerate the (larger) TSMessage objects one at a time. - for (NSString *expiredMessageId in [self fetchAttemptingOutMessageIdsWithTransaction:transaction]) { - TSOutgoingMessage *_Nullable message = - [TSOutgoingMessage fetchObjectWithUniqueID:expiredMessageId transaction:transaction]; - if ([message isKindOfClass:[TSOutgoingMessage class]]) { - block(message); - } else { - OWSLogError(@"unexpected object: %@", message); - } - } -} - -- (void)run -{ - __block uint count = 0; - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [self enumerateAttemptingOutMessagesWithBlock:^(TSOutgoingMessage *message) { - // sanity check - OWSAssertDebug(message.messageState == TSOutgoingMessageStateSending); - if (message.messageState != TSOutgoingMessageStateSending) { - OWSLogError(@"Refusing to mark as unsent message with state: %d", (int)message.messageState); - return; - } - - OWSLogDebug(@"marking message as unsent: %@", message.uniqueId); - [message updateWithAllSendingRecipientsMarkedAsFailedWithTansaction:transaction]; - OWSAssertDebug(message.messageState == TSOutgoingMessageStateFailed); - - count++; - } - transaction:transaction]; - }]; - - OWSLogDebug(@"Marked %u messages as unsent", count); -} - -#pragma mark - YapDatabaseExtension - -+ (YapDatabaseSecondaryIndex *)indexDatabaseExtension -{ - YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new]; - [setup addColumn:OWSFailedMessagesJobMessageStateColumn withType:YapDatabaseSecondaryIndexTypeInteger]; - - YapDatabaseSecondaryIndexHandler *handler = - [YapDatabaseSecondaryIndexHandler withObjectBlock:^(YapDatabaseReadTransaction *transaction, - NSMutableDictionary *dict, - NSString *collection, - NSString *key, - id object) { - if (![object isKindOfClass:[TSOutgoingMessage class]]) { - return; - } - TSOutgoingMessage *message = (TSOutgoingMessage *)object; - - dict[OWSFailedMessagesJobMessageStateColumn] = @(message.messageState); - }]; - - return [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:handler versionTag:nil]; -} - -#ifdef DEBUG -// Useful for tests, don't use in app startup path because it's slow. -- (void)blockingRegisterDatabaseExtensions -{ - [self.primaryStorage registerExtension:[self.class indexDatabaseExtension] - withName:OWSFailedMessagesJobMessageStateIndex]; -} -#endif - -+ (NSString *)databaseExtensionName -{ - return OWSFailedMessagesJobMessageStateIndex; -} - -+ (void)asyncRegisterDatabaseExtensionsWithPrimaryStorage:(OWSStorage *)storage -{ - [storage asyncRegisterExtension:[self indexDatabaseExtension] withName:OWSFailedMessagesJobMessageStateIndex]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSIdentityManager.h b/SignalServiceKit/src/Messages/OWSIdentityManager.h deleted file mode 100644 index 838cd25fe..000000000 --- a/SignalServiceKit/src/Messages/OWSIdentityManager.h +++ /dev/null @@ -1,92 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSRecipientIdentity.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const OWSPrimaryStorageIdentityKeyStoreIdentityKey; -extern NSString *const LKSeedKey; -extern NSString *const LKED25519SecretKey; -extern NSString *const LKED25519PublicKey; -extern NSString *const OWSPrimaryStorageIdentityKeyStoreCollection; - -extern NSString *const OWSPrimaryStorageTrustedKeysCollection; - -// This notification will be fired whenever identities are created -// or their verification state changes. -extern NSString *const kNSNotificationName_IdentityStateDidChange; - -// number of bytes in a signal identity key, excluding the key-type byte. -extern const NSUInteger kIdentityKeyLength; - -#ifdef DEBUG -extern const NSUInteger kStoredIdentityKeyLength; -#endif - -@class OWSRecipientIdentity; -@class OWSStorage; -@class SSKProtoVerified; -@class YapDatabaseReadWriteTransaction; - -// This class can be safely accessed and used from any thread. -@interface OWSIdentityManager : NSObject - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -+ (instancetype)sharedManager; - -- (void)generateNewIdentityKeyPair; -- (void)generateNewIdentityKeyPairFromSeed:(NSData *)seed; -- (void)clearIdentityKey; - -- (void)setVerificationState:(OWSVerificationState)verificationState - identityKey:(NSData *)identityKey - recipientId:(NSString *)recipientId - isUserInitiatedChange:(BOOL)isUserInitiatedChange - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (OWSVerificationState)verificationStateForRecipientId:(NSString *)recipientId; -- (OWSVerificationState)verificationStateForRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadTransaction *)transaction; - -- (void)setVerificationState:(OWSVerificationState)verificationState - identityKey:(NSData *)identityKey - recipientId:(NSString *)recipientId - isUserInitiatedChange:(BOOL)isUserInitiatedChange; - -- (nullable OWSRecipientIdentity *)recipientIdentityForRecipientId:(NSString *)recipientId; - -/** - * @param recipientId unique stable identifier for the recipient, e.g. e164 phone number - * @returns nil if the recipient does not exist, or is trusted for sending - * else returns the untrusted recipient. - */ -- (nullable OWSRecipientIdentity *)untrustedIdentityForSendingToRecipientId:(NSString *)recipientId; - -// This method can be called from any thread. -- (void)throws_processIncomingSyncMessage:(SSKProtoVerified *)verified - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (BOOL)saveRemoteIdentity:(NSData *)identityKey recipientId:(NSString *)recipientId; - -- (nullable ECKeyPair *)identityKeyPair; - -#pragma mark - Debug - -#if DEBUG -// Clears everything except the local identity key. -- (void)clearIdentityState:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)snapshotIdentityState:(YapDatabaseReadWriteTransaction *)transaction; -- (void)restoreIdentityState:(YapDatabaseReadWriteTransaction *)transaction; -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSIdentityManager.m b/SignalServiceKit/src/Messages/OWSIdentityManager.m deleted file mode 100644 index 09c516b9d..000000000 --- a/SignalServiceKit/src/Messages/OWSIdentityManager.m +++ /dev/null @@ -1,976 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSIdentityManager.h" -#import "AppContext.h" -#import "AppReadiness.h" -#import "NSNotificationCenter+OWS.h" -#import "NotificationsProtocol.h" -#import "OWSError.h" -#import "OWSFileSystem.h" -#import "OWSMessageSender.h" -#import "OWSOutgoingNullMessage.h" -#import "OWSPrimaryStorage+sessionStore.h" -#import "OWSPrimaryStorage.h" -#import "OWSRecipientIdentity.h" -#import "OWSVerificationStateChangeMessage.h" -#import "OWSVerificationStateSyncMessage.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSContactThread.h" -#import "TSErrorMessage.h" -#import "TSGroupThread.h" -#import "YapDatabaseConnection+OWS.h" -#import "YapDatabaseTransaction+OWS.h" -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -// Storing our own identity key -NSString *const OWSPrimaryStorageIdentityKeyStoreIdentityKey = @"TSStorageManagerIdentityKeyStoreIdentityKey"; -NSString *const LKSeedKey = @"LKLokiSeed"; -NSString *const LKED25519SecretKey = @"LKED25519SecretKey"; -NSString *const LKED25519PublicKey = @"LKED25519PublicKey"; -NSString *const OWSPrimaryStorageIdentityKeyStoreCollection = @"TSStorageManagerIdentityKeyStoreCollection"; - -// Storing recipients identity keys -NSString *const OWSPrimaryStorageTrustedKeysCollection = @"TSStorageManagerTrustedKeysCollection"; - -NSString *const OWSIdentityManager_QueuedVerificationStateSyncMessages = - @"OWSIdentityManager_QueuedVerificationStateSyncMessages"; - -// Don't trust an identity for sending to unless they've been around for at least this long -const NSTimeInterval kIdentityKeyStoreNonBlockingSecondsThreshold = 5.0; - -// The canonical key includes 32 bytes of identity material plus one byte specifying the key type -const NSUInteger kIdentityKeyLength = 33; - -// Cryptographic operations do not use the "type" byte of the identity key, so, for legacy reasons we store just -// the identity material. -// TODO: migrate to storing the full 33 byte representation. -const NSUInteger kStoredIdentityKeyLength = 32; - -NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationName_IdentityStateDidChange"; - -@interface OWSIdentityManager () - -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; - -@end - -#pragma mark - - -@implementation OWSIdentityManager - -+ (instancetype)sharedManager -{ - OWSAssertDebug(SSKEnvironment.shared.identityManager); - - return SSKEnvironment.shared.identityManager; -} - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - - if (!self) { - return self; - } - - OWSAssertDebug(primaryStorage); - - _primaryStorage = primaryStorage; - _dbConnection = primaryStorage.newDatabaseConnection; - self.dbConnection.objectCacheEnabled = NO; - - OWSSingletonAssert(); - - [self observeNotifications]; - - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -#pragma mark - Dependencies - -- (OWSMessageSender *)messageSender -{ - OWSAssertDebug(SSKEnvironment.shared.messageSender); - - return SSKEnvironment.shared.messageSender; -} - -#pragma mark - - -- (void)observeNotifications -{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidBecomeActive:) - name:UIApplicationDidBecomeActiveNotification - object:nil]; -} - -- (void)generateNewIdentityKeyPair -{ - ECKeyPair *keyPair = [Curve25519 generateKeyPair]; - [self.dbConnection setObject:keyPair forKey:OWSPrimaryStorageIdentityKeyStoreIdentityKey inCollection:OWSPrimaryStorageIdentityKeyStoreCollection]; -} - -- (void)generateNewIdentityKeyPairFromSeed:(NSData *)seed -{ - ECKeyPair *keyPair = [Curve25519 generateKeyPairFromSeed:seed]; - [self.dbConnection setObject:keyPair forKey:OWSPrimaryStorageIdentityKeyStoreIdentityKey inCollection:OWSPrimaryStorageIdentityKeyStoreCollection]; -} - -- (void)clearIdentityKey -{ - [self.dbConnection removeObjectForKey:OWSPrimaryStorageIdentityKeyStoreIdentityKey - inCollection:OWSPrimaryStorageIdentityKeyStoreCollection]; -} - -- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId -{ - __block NSData *_Nullable result = nil; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - result = [self identityKeyForRecipientId:recipientId transaction:transaction]; - }]; - return result; -} - -- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId protocolContext:(nullable id)protocolContext -{ - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadTransaction class]]); - - YapDatabaseReadTransaction *transaction = protocolContext; - - return [self identityKeyForRecipientId:recipientId transaction:transaction]; -} - -- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - return [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction].identityKey; -} - -- (nullable ECKeyPair *)identityKeyPair -{ - __block ECKeyPair *_Nullable identityKeyPair = nil; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - identityKeyPair = [self identityKeyPairWithTransaction:transaction]; - }]; - return identityKeyPair; -} - -// This method should only be called from SignalProtocolKit, which doesn't know about YapDatabaseTransactions. -// Whenever possible, prefer to call the strongly typed variant: `identityKeyPairWithTransaction:`. -- (nullable ECKeyPair *)identityKeyPair:(nullable id)protocolContext -{ - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadTransaction class]]); - - YapDatabaseReadTransaction *transaction = protocolContext; - - return [self identityKeyPairWithTransaction:transaction]; -} - -- (nullable ECKeyPair *)identityKeyPairWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - ECKeyPair *_Nullable identityKeyPair = [transaction keyPairForKey:OWSPrimaryStorageIdentityKeyStoreIdentityKey - inCollection:OWSPrimaryStorageIdentityKeyStoreCollection]; - return identityKeyPair; -} - -- (int)localRegistrationId:(nullable id)protocolContext -{ - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); - - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - return (int)[TSAccountManager getOrGenerateRegistrationId:transaction]; -} - -- (BOOL)saveRemoteIdentity:(NSData *)identityKey recipientId:(NSString *)recipientId -{ - OWSAssertDebug(identityKey.length == kStoredIdentityKeyLength); - OWSAssertDebug(recipientId.length > 0); - - __block BOOL result; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - result = [self saveRemoteIdentity:identityKey recipientId:recipientId protocolContext:transaction]; - }]; - - return result; -} - -- (BOOL)saveRemoteIdentity:(NSData *)identityKey - recipientId:(NSString *)recipientId - protocolContext:(nullable id)protocolContext -{ - OWSAssertDebug(identityKey.length == kStoredIdentityKeyLength); - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); - - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - // Deprecated. We actually no longer use the OWSPrimaryStorageTrustedKeysCollection for trust - // decisions, but it's desirable to try to keep it up to date with our trusted identitys - // while we're switching between versions, e.g. so we don't get into a state where we have a - // session for an identity not in our key store. - [transaction setObject:identityKey forKey:recipientId inCollection:OWSPrimaryStorageTrustedKeysCollection]; - - OWSRecipientIdentity *existingIdentity = - [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction]; - - if (existingIdentity == nil) { - OWSLogInfo(@"saving first use identity for recipient: %@", recipientId); - [[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId - identityKey:identityKey - isFirstKnownKey:YES - createdAt:[NSDate new] - verificationState:OWSVerificationStateDefault] - saveWithTransaction:transaction]; - - // Cancel any pending verification state sync messages for this recipient. - [self clearSyncMessageForRecipientId:recipientId transaction:transaction]; - - [self fireIdentityStateChangeNotification]; - - return NO; - } - - if (![existingIdentity.identityKey isEqual:identityKey]) { - OWSVerificationState verificationState; - switch (existingIdentity.verificationState) { - case OWSVerificationStateDefault: - verificationState = OWSVerificationStateDefault; - break; - case OWSVerificationStateVerified: - case OWSVerificationStateNoLongerVerified: - verificationState = OWSVerificationStateNoLongerVerified; - break; - } - - OWSLogInfo(@"replacing identity for existing recipient: %@ (%@ -> %@)", - recipientId, - OWSVerificationStateToString(existingIdentity.verificationState), - OWSVerificationStateToString(verificationState)); - [self createIdentityChangeInfoMessageForRecipientId:recipientId transaction:transaction]; - - [[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId - identityKey:identityKey - isFirstKnownKey:NO - createdAt:[NSDate new] - verificationState:verificationState] saveWithTransaction:transaction]; - - [self.primaryStorage archiveAllSessionsForContact:recipientId protocolContext:protocolContext]; - - // Cancel any pending verification state sync messages for this recipient. - [self clearSyncMessageForRecipientId:recipientId transaction:transaction]; - - [self fireIdentityStateChangeNotification]; - - return YES; - } - - return NO; -} - -- (void)setVerificationState:(OWSVerificationState)verificationState - identityKey:(NSData *)identityKey - recipientId:(NSString *)recipientId - isUserInitiatedChange:(BOOL)isUserInitiatedChange -{ - OWSAssertDebug(identityKey.length == kStoredIdentityKeyLength); - OWSAssertDebug(recipientId.length > 0); - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [self setVerificationState:verificationState - identityKey:identityKey - recipientId:recipientId - isUserInitiatedChange:isUserInitiatedChange - transaction:transaction]; - }]; -} - -- (void)setVerificationState:(OWSVerificationState)verificationState - identityKey:(NSData *)identityKey - recipientId:(NSString *)recipientId - isUserInitiatedChange:(BOOL)isUserInitiatedChange - protocolContext:(nullable id)protocolContext -{ - OWSAssertDebug(identityKey.length == kStoredIdentityKeyLength); - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); - - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - [self setVerificationState:verificationState - identityKey:identityKey - recipientId:recipientId - isUserInitiatedChange:isUserInitiatedChange - transaction:transaction]; -} - -- (void)setVerificationState:(OWSVerificationState)verificationState - identityKey:(NSData *)identityKey - recipientId:(NSString *)recipientId - isUserInitiatedChange:(BOOL)isUserInitiatedChange - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(identityKey.length == kStoredIdentityKeyLength); - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - // Ensure a remote identity exists for this key. We may be learning about - // it for the first time. - [self saveRemoteIdentity:identityKey recipientId:recipientId protocolContext:transaction]; - - OWSRecipientIdentity *recipientIdentity = - [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction]; - - if (recipientIdentity == nil) { - OWSFailDebug(@"Missing expected identity: %@", recipientId); - return; - } - - if (recipientIdentity.verificationState == verificationState) { - return; - } - - OWSLogInfo(@"setVerificationState: %@ (%@ -> %@)", - recipientId, - OWSVerificationStateToString(recipientIdentity.verificationState), - OWSVerificationStateToString(verificationState)); - - [recipientIdentity updateWithVerificationState:verificationState transaction:transaction]; - - if (isUserInitiatedChange) { - [self saveChangeMessagesForRecipientId:recipientId - verificationState:verificationState - isLocalChange:YES - transaction:transaction]; - [self enqueueSyncMessageForVerificationStateForRecipientId:recipientId transaction:transaction]; - } else { - // Cancel any pending verification state sync messages for this recipient. - [self clearSyncMessageForRecipientId:recipientId transaction:transaction]; - } - - [self fireIdentityStateChangeNotification]; -} - -- (OWSVerificationState)verificationStateForRecipientId:(NSString *)recipientId -{ - __block OWSVerificationState result; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - result = [self verificationStateForRecipientId:recipientId transaction:transaction]; - }]; - return result; -} - -- (OWSVerificationState)verificationStateForRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - OWSRecipientIdentity *_Nullable currentIdentity = - [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction]; - - if (!currentIdentity) { - // We might not know the identity for this recipient yet. - return OWSVerificationStateDefault; - } - - return currentIdentity.verificationState; -} - -- (nullable OWSRecipientIdentity *)recipientIdentityForRecipientId:(NSString *)recipientId -{ - OWSAssertDebug(recipientId.length > 0); - - __block OWSRecipientIdentity *_Nullable result; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - result = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction]; - }]; - return result; -} - -- (nullable OWSRecipientIdentity *)untrustedIdentityForSendingToRecipientId:(NSString *)recipientId -{ - OWSAssertDebug(recipientId.length > 0); - - __block OWSRecipientIdentity *_Nullable result; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - OWSRecipientIdentity *_Nullable recipientIdentity = - [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction]; - - if (recipientIdentity == nil) { - // trust on first use - return; - } - - BOOL isTrusted = [self isTrustedIdentityKey:recipientIdentity.identityKey - recipientId:recipientId - direction:TSMessageDirectionOutgoing - transaction:transaction]; - if (isTrusted) { - return; - } else { - result = recipientIdentity; - } - }]; - return result; -} - -- (void)fireIdentityStateChangeNotification -{ - [[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotificationName_IdentityStateDidChange - object:nil - userInfo:nil]; -} - -- (BOOL)isTrustedIdentityKey:(NSData *)identityKey - recipientId:(NSString *)recipientId - direction:(TSMessageDirection)direction - protocolContext:(nullable id)protocolContext -{ - OWSAssertDebug(identityKey.length == kStoredIdentityKeyLength); - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(direction != TSMessageDirectionUnknown); - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); - - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - return [self isTrustedIdentityKey:identityKey recipientId:recipientId direction:direction transaction:transaction]; -} - -- (BOOL)isTrustedIdentityKey:(NSData *)identityKey - recipientId:(NSString *)recipientId - direction:(TSMessageDirection)direction - transaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(identityKey.length == kStoredIdentityKeyLength); - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(direction != TSMessageDirectionUnknown); - OWSAssertDebug(transaction); - - if ([[TSAccountManager localNumber] isEqualToString:recipientId]) { - ECKeyPair *_Nullable localIdentityKeyPair = [self identityKeyPairWithTransaction:transaction]; - - if ([localIdentityKeyPair.publicKey isEqualToData:identityKey]) { - return YES; - } else { - OWSFailDebug(@"Wrong identity: %@ for local key: %@, recipientId: %@", - identityKey, - localIdentityKeyPair.publicKey, - recipientId); - return NO; - } - } - - switch (direction) { - case TSMessageDirectionIncoming: { - return YES; - } - case TSMessageDirectionOutgoing: { - OWSRecipientIdentity *existingIdentity = - [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction]; - return [self isTrustedKey:identityKey forSendingToIdentity:existingIdentity]; - } - default: { - OWSFailDebug(@"unexpected message direction: %ld", (long)direction); - return NO; - } - } -} - -- (BOOL)isTrustedKey:(NSData *)identityKey forSendingToIdentity:(nullable OWSRecipientIdentity *)recipientIdentity -{ - OWSAssertDebug(identityKey.length == kStoredIdentityKeyLength); - - if (recipientIdentity == nil) { - return YES; - } - - OWSAssertDebug(recipientIdentity.identityKey.length == kStoredIdentityKeyLength); - if (![recipientIdentity.identityKey isEqualToData:identityKey]) { - OWSLogWarn(@"key mismatch for recipient: %@", recipientIdentity.recipientId); - return NO; - } - - if ([recipientIdentity isFirstKnownKey]) { - return YES; - } - - switch (recipientIdentity.verificationState) { - case OWSVerificationStateDefault: { - BOOL isNew = (fabs([recipientIdentity.createdAt timeIntervalSinceNow]) - < kIdentityKeyStoreNonBlockingSecondsThreshold); - if (isNew) { - OWSLogWarn(@"not trusting new identity for recipient: %@", recipientIdentity.recipientId); - return NO; - } else { - return YES; - } - } - case OWSVerificationStateVerified: - return YES; - case OWSVerificationStateNoLongerVerified: - OWSLogWarn(@"not trusting no longer verified identity for recipient: %@", recipientIdentity.recipientId); - return NO; - } -} - -- (void)createIdentityChangeInfoMessageForRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - NSMutableArray *messages = [NSMutableArray new]; - - TSContactThread *contactThread = - [TSContactThread getOrCreateThreadWithContactId:recipientId transaction:transaction]; - OWSAssertDebug(contactThread != nil); - - TSErrorMessage *errorMessage = - [TSErrorMessage nonblockingIdentityChangeInThread:contactThread recipientId:recipientId]; - [messages addObject:errorMessage]; - - for (TSGroupThread *groupThread in [TSGroupThread groupThreadsWithRecipientId:recipientId transaction:transaction]) { - [messages addObject:[TSErrorMessage nonblockingIdentityChangeInThread:groupThread recipientId:recipientId]]; - } - - // MJK TODO - why not save immediately, why build up this array? - for (TSMessage *message in messages) { - [message saveWithTransaction:transaction]; - } - - [SSKEnvironment.shared.notificationsManager notifyUserForErrorMessage:errorMessage - thread:contactThread - transaction:transaction]; -} - -- (void)enqueueSyncMessageForVerificationStateForRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - [transaction setObject:recipientId - forKey:recipientId - inCollection:OWSIdentityManager_QueuedVerificationStateSyncMessages]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [self tryToSyncQueuedVerificationStates]; - }); -} - -- (void)tryToSyncQueuedVerificationStates -{ - OWSAssertIsOnMainThread(); - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - [self syncQueuedVerificationStates]; - }]; -} - -- (void)syncQueuedVerificationStates -{ - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSMutableArray *recipientIds = [NSMutableArray new]; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - [transaction - enumerateKeysAndObjectsInCollection:OWSIdentityManager_QueuedVerificationStateSyncMessages - usingBlock:^( - NSString *_Nonnull recipientId, id _Nonnull object, BOOL *_Nonnull stop) { - [recipientIds addObject:recipientId]; - }]; - }]; - - NSMutableArray *messages = [NSMutableArray new]; - for (NSString *recipientId in recipientIds) { - OWSRecipientIdentity *recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId]; - if (!recipientIdentity) { - OWSFailDebug(@"Could not load recipient identity for recipientId: %@", recipientId); - continue; - } - if (recipientIdentity.recipientId.length < 1) { - OWSFailDebug(@"Invalid recipient identity for recipientId: %@", recipientId); - continue; - } - - // Prepend key type for transit. - // TODO we should just be storing the key type so we don't have to juggle re-adding it. - NSData *identityKey = [recipientIdentity.identityKey prependKeyType]; - if (identityKey.length != kIdentityKeyLength) { - OWSFailDebug(@"Invalid recipient identitykey for recipientId: %@ key: %@", recipientId, identityKey); - continue; - } - if (recipientIdentity.verificationState == OWSVerificationStateNoLongerVerified) { - // We don't want to sync "no longer verified" state. Other clients can - // figure this out from the /profile/ endpoint, and this can cause data - // loss as a user's devices overwrite each other's verification. - OWSFailDebug(@"Queue verification state had unexpected value: %@ recipientId: %@", - OWSVerificationStateToString(recipientIdentity.verificationState), - recipientId); - continue; - } - OWSVerificationStateSyncMessage *message = - [[OWSVerificationStateSyncMessage alloc] initWithVerificationState:recipientIdentity.verificationState - identityKey:identityKey - verificationForRecipientId:recipientIdentity.recipientId]; - [messages addObject:message]; - } - if (messages.count > 0) { - for (OWSVerificationStateSyncMessage *message in messages) { - [self sendSyncVerificationStateMessage:message]; - } - } - }); -} - -- (void)sendSyncVerificationStateMessage:(OWSVerificationStateSyncMessage *)message -{ - OWSAssertDebug(message); - OWSAssertDebug(message.verificationForRecipientId.length > 0); - - TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:message.verificationForRecipientId]; - - // Send null message to appear as though we're sending a normal message to cover the sync messsage sent - // subsequently - OWSOutgoingNullMessage *nullMessage = [[OWSOutgoingNullMessage alloc] initWithContactThread:contactThread - verificationStateSyncMessage:message]; - - // DURABLE CLEANUP - we could replace the custom durability logic in this class - // with a durable JobQueue. - [self.messageSender sendMessage:nullMessage - success:^{ - OWSLogInfo(@"Successfully sent verification state NullMessage"); - [self.messageSender sendMessage:message - success:^{ - OWSLogInfo(@"Successfully sent verification state sync message"); - - // Record that this verification state was successfully synced. - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self clearSyncMessageForRecipientId:message.verificationForRecipientId - transaction:transaction]; - }]; - } - failure:^(NSError *error) { - OWSLogError(@"Failed to send verification state sync message with error: %@", error); - }]; - } - failure:^(NSError *_Nonnull error) { - OWSLogError(@"Failed to send verification state NullMessage with error: %@", error); - if (error.code == OWSErrorCodeNoSuchSignalRecipient) { - OWSLogInfo(@"Removing retries for syncing verification state, since user is no longer registered: %@", - message.verificationForRecipientId); - // Otherwise this will fail forever. - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self clearSyncMessageForRecipientId:message.verificationForRecipientId transaction:transaction]; - }]; - } - }]; -} - -- (void)clearSyncMessageForRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - [transaction removeObjectForKey:recipientId inCollection:OWSIdentityManager_QueuedVerificationStateSyncMessages]; -} - -- (void)throws_processIncomingSyncMessage:(SSKProtoVerified *)verified - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(verified); - OWSAssertDebug(transaction); - - NSString *recipientId = verified.destination; - if (recipientId.length < 1) { - OWSFailDebug(@"Verification state sync message missing recipientId."); - return; - } - NSData *rawIdentityKey = verified.identityKey; - if (rawIdentityKey.length != kIdentityKeyLength) { - OWSFailDebug(@"Verification state sync message for recipient: %@ with malformed identityKey: %@", - recipientId, - rawIdentityKey); - return; - } - NSData *identityKey = [rawIdentityKey throws_removeKeyType]; - - switch (verified.state) { - case SSKProtoVerifiedStateDefault: - [self tryToApplyVerificationStateFromSyncMessage:OWSVerificationStateDefault - recipientId:recipientId - identityKey:identityKey - overwriteOnConflict:NO - transaction:transaction]; - break; - case SSKProtoVerifiedStateVerified: - [self tryToApplyVerificationStateFromSyncMessage:OWSVerificationStateVerified - recipientId:recipientId - identityKey:identityKey - overwriteOnConflict:YES - transaction:transaction]; - break; - case SSKProtoVerifiedStateUnverified: - OWSFailDebug(@"Verification state sync message for recipientId: %@ has unexpected value: %@.", - recipientId, - OWSVerificationStateToString(OWSVerificationStateNoLongerVerified)); - return; - } - - [self fireIdentityStateChangeNotification]; -} - -- (void)tryToApplyVerificationStateFromSyncMessage:(OWSVerificationState)verificationState - recipientId:(NSString *)recipientId - identityKey:(NSData *)identityKey - overwriteOnConflict:(BOOL)overwriteOnConflict - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - if (recipientId.length < 1) { - OWSFailDebug(@"Verification state sync message missing recipientId."); - return; - } - - if (identityKey.length != kStoredIdentityKeyLength) { - OWSFailDebug(@"Verification state sync message missing identityKey: %@", recipientId); - return; - } - - OWSRecipientIdentity *_Nullable recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId - transaction:transaction]; - if (!recipientIdentity) { - // There's no existing recipient identity for this recipient. - // We should probably create one. - - if (verificationState == OWSVerificationStateDefault) { - // There's no point in creating a new recipient identity just to - // set its verification state to default. - return; - } - - // Ensure a remote identity exists for this key. We may be learning about - // it for the first time. - [self saveRemoteIdentity:identityKey recipientId:recipientId protocolContext:transaction]; - - recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId - transaction:transaction]; - - if (recipientIdentity == nil) { - OWSFailDebug(@"Missing expected identity: %@", recipientId); - return; - } - - if (![recipientIdentity.recipientId isEqualToString:recipientId]) { - OWSFailDebug(@"recipientIdentity has unexpected recipientId: %@", recipientId); - return; - } - - if (![recipientIdentity.identityKey isEqualToData:identityKey]) { - OWSFailDebug(@"recipientIdentity has unexpected identityKey: %@", recipientId); - return; - } - - if (recipientIdentity.verificationState == verificationState) { - return; - } - - OWSLogInfo(@"setVerificationState: %@ (%@ -> %@)", - recipientId, - OWSVerificationStateToString(recipientIdentity.verificationState), - OWSVerificationStateToString(verificationState)); - - [recipientIdentity updateWithVerificationState:verificationState - transaction:transaction]; - - // No need to call [saveChangeMessagesForRecipientId:..] since this is - // a new recipient. - } else { - // There's an existing recipient identity for this recipient. - // We should update it. - if (![recipientIdentity.recipientId isEqualToString:recipientId]) { - OWSFailDebug(@"recipientIdentity has unexpected recipientId: %@", recipientId); - return; - } - - if (![recipientIdentity.identityKey isEqualToData:identityKey]) { - // The conflict case where we receive a verification sync message - // whose identity key disagrees with the local identity key for - // this recipient. - if (!overwriteOnConflict) { - OWSLogWarn(@"recipientIdentity has non-matching identityKey: %@", recipientId); - return; - } - - OWSLogWarn(@"recipientIdentity has non-matching identityKey; overwriting: %@", recipientId); - [self saveRemoteIdentity:identityKey recipientId:recipientId protocolContext:transaction]; - - recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId - transaction:transaction]; - - if (recipientIdentity == nil) { - OWSFailDebug(@"Missing expected identity: %@", recipientId); - return; - } - - if (![recipientIdentity.recipientId isEqualToString:recipientId]) { - OWSFailDebug(@"recipientIdentity has unexpected recipientId: %@", recipientId); - return; - } - - if (![recipientIdentity.identityKey isEqualToData:identityKey]) { - OWSFailDebug(@"recipientIdentity has unexpected identityKey: %@", recipientId); - return; - } - } - - if (recipientIdentity.verificationState == verificationState) { - return; - } - - [recipientIdentity updateWithVerificationState:verificationState - transaction:transaction]; - - [self saveChangeMessagesForRecipientId:recipientId - verificationState:verificationState - isLocalChange:NO - transaction:transaction]; - } -} - -// We only want to create change messages in response to user activity, -// on any of their devices. -- (void)saveChangeMessagesForRecipientId:(NSString *)recipientId - verificationState:(OWSVerificationState)verificationState - isLocalChange:(BOOL)isLocalChange - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(transaction); - - NSMutableArray *messages = [NSMutableArray new]; - - TSContactThread *contactThread = - [TSContactThread getOrCreateThreadWithContactId:recipientId transaction:transaction]; - OWSAssertDebug(contactThread); - // MJK TODO - should be safe to remove senderTimestamp - [messages addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - thread:contactThread - recipientId:recipientId - verificationState:verificationState - isLocalChange:isLocalChange]]; - - for (TSGroupThread *groupThread in - [TSGroupThread groupThreadsWithRecipientId:recipientId transaction:transaction]) { - // MJK TODO - should be safe to remove senderTimestamp - [messages - addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - thread:groupThread - recipientId:recipientId - verificationState:verificationState - isLocalChange:isLocalChange]]; - } - - // MJK TODO - why not save in-line, vs storing in an array and saving the array? - for (TSMessage *message in messages) { - [message saveWithTransaction:transaction]; - } -} - -#pragma mark - Debug - -#if DEBUG -- (void)clearIdentityState:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - NSMutableArray *identityKeysToRemove = [NSMutableArray new]; - [transaction enumerateKeysInCollection:OWSPrimaryStorageIdentityKeyStoreCollection - usingBlock:^(NSString *_Nonnull key, BOOL *_Nonnull stop) { - if ([key isEqualToString:OWSPrimaryStorageIdentityKeyStoreIdentityKey]) { - // Don't delete our own key. - return; - } - [identityKeysToRemove addObject:key]; - }]; - for (NSString *key in identityKeysToRemove) { - [transaction removeObjectForKey:key inCollection:OWSPrimaryStorageIdentityKeyStoreCollection]; - } - [transaction removeAllObjectsInCollection:OWSPrimaryStorageTrustedKeysCollection]; -} - -- (NSString *)identityKeySnapshotFilePath -{ - // Prefix name with period "." so that backups will ignore these snapshots. - NSString *dirPath = [OWSFileSystem appDocumentDirectoryPath]; - return [dirPath stringByAppendingPathComponent:@".identity-key-snapshot"]; -} - -- (NSString *)trustedKeySnapshotFilePath -{ - // Prefix name with period "." so that backups will ignore these snapshots. - NSString *dirPath = [OWSFileSystem appDocumentDirectoryPath]; - return [dirPath stringByAppendingPathComponent:@".trusted-key-snapshot"]; -} - -- (void)snapshotIdentityState:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - [transaction snapshotCollection:OWSPrimaryStorageIdentityKeyStoreCollection - snapshotFilePath:self.identityKeySnapshotFilePath]; - [transaction snapshotCollection:OWSPrimaryStorageTrustedKeysCollection - snapshotFilePath:self.trustedKeySnapshotFilePath]; -} - -- (void)restoreIdentityState:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - [transaction restoreSnapshotOfCollection:OWSPrimaryStorageIdentityKeyStoreCollection - snapshotFilePath:self.identityKeySnapshotFilePath]; - [transaction restoreSnapshotOfCollection:OWSPrimaryStorageTrustedKeysCollection - snapshotFilePath:self.trustedKeySnapshotFilePath]; -} - -#endif - -#pragma mark - Notifications - -- (void)applicationDidBecomeActive:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - // We want to defer this so that we never call this method until - // [UIApplicationDelegate applicationDidBecomeActive:] is complete. - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [self tryToSyncQueuedVerificationStates]; - }); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.h b/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.h deleted file mode 100644 index 76c401a44..000000000 --- a/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class OWSStorage; - -@interface OWSIncompleteCallsJob : NSObject - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -- (void)run; - -+ (NSString *)databaseExtensionName; -+ (void)asyncRegisterDatabaseExtensionsWithPrimaryStorage:(OWSStorage *)storage; - -#ifdef DEBUG -/** - * Only use the sync version for testing, generally we'll want to register extensions async - */ -- (void)blockingRegisterDatabaseExtensions; -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.m b/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.m deleted file mode 100644 index 556c5fe73..000000000 --- a/SignalServiceKit/src/Messages/OWSIncompleteCallsJob.m +++ /dev/null @@ -1,160 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSIncompleteCallsJob.h" -#import "AppContext.h" -#import "OWSPrimaryStorage.h" -#import "TSCall.h" -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const OWSIncompleteCallsJobCallTypeColumn = @"call_type"; -static NSString *const OWSIncompleteCallsJobCallTypeIndex = @"index_calls_on_call_type"; - -@interface OWSIncompleteCallsJob () - -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; - -@end - -#pragma mark - - -@implementation OWSIncompleteCallsJob - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - if (!self) { - return self; - } - - _primaryStorage = primaryStorage; - - return self; -} - -- (NSArray *)fetchIncompleteCallIdsWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - NSMutableArray *messageIds = [NSMutableArray new]; - - NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ == %d OR %@ == %d", - OWSIncompleteCallsJobCallTypeColumn, - (int)RPRecentCallTypeOutgoingIncomplete, - OWSIncompleteCallsJobCallTypeColumn, - (int)RPRecentCallTypeIncomingIncomplete]; - YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; - [[transaction ext:OWSIncompleteCallsJobCallTypeIndex] - enumerateKeysMatchingQuery:query - usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { - [messageIds addObject:key]; - }]; - - return [messageIds copy]; -} - -- (void)enumerateIncompleteCallsWithBlock:(void (^)(TSCall *call))block - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - // Since we can't directly mutate the enumerated "incomplete" calls, we store only their ids in hopes - // of saving a little memory and then enumerate the (larger) TSCall objects one at a time. - for (NSString *callId in [self fetchIncompleteCallIdsWithTransaction:transaction]) { - TSCall *_Nullable call = [TSCall fetchObjectWithUniqueID:callId transaction:transaction]; - if ([call isKindOfClass:[TSCall class]]) { - block(call); - } else { - OWSLogError(@"unexpected object: %@", call); - } - } -} - -- (void)run -{ - __block uint count = 0; - - OWSAssertDebug(CurrentAppContext().appLaunchTime); - uint64_t cutoffTimestamp = [NSDate ows_millisecondsSince1970ForDate:CurrentAppContext().appLaunchTime]; - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self - enumerateIncompleteCallsWithBlock:^(TSCall *call) { - if (call.timestamp <= cutoffTimestamp) { - OWSLogInfo(@"ignoring new call: %@", call.uniqueId); - return; - } - - if (call.callType == RPRecentCallTypeOutgoingIncomplete) { - OWSLogDebug(@"marking call as missed: %@", call.uniqueId); - [call updateCallType:RPRecentCallTypeOutgoingMissed transaction:transaction]; - OWSAssertDebug(call.callType == RPRecentCallTypeOutgoingMissed); - } else if (call.callType == RPRecentCallTypeIncomingIncomplete) { - OWSLogDebug(@"marking call as missed: %@", call.uniqueId); - [call updateCallType:RPRecentCallTypeIncomingMissed transaction:transaction]; - OWSAssertDebug(call.callType == RPRecentCallTypeIncomingMissed); - } else { - OWSFailDebug(@"call has unexpected call type: %@", NSStringFromCallType(call.callType)); - return; - } - count++; - } - transaction:transaction]; - }]; - - OWSLogInfo(@"Marked %u calls as missed", count); -} - -#pragma mark - YapDatabaseExtension - -+ (YapDatabaseSecondaryIndex *)indexDatabaseExtension -{ - YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new]; - [setup addColumn:OWSIncompleteCallsJobCallTypeColumn withType:YapDatabaseSecondaryIndexTypeInteger]; - - YapDatabaseSecondaryIndexHandler *handler = - [YapDatabaseSecondaryIndexHandler withObjectBlock:^(YapDatabaseReadTransaction *transaction, - NSMutableDictionary *dict, - NSString *collection, - NSString *key, - id object) { - if (![object isKindOfClass:[TSCall class]]) { - return; - } - TSCall *call = (TSCall *)object; - - dict[OWSIncompleteCallsJobCallTypeColumn] = @(call.callType); - }]; - - return [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:handler versionTag:nil]; -} - -#ifdef DEBUG -// Useful for tests, don't use in app startup path because it's slow. -- (void)blockingRegisterDatabaseExtensions -{ - [self.primaryStorage registerExtension:[self.class indexDatabaseExtension] - withName:OWSIncompleteCallsJobCallTypeIndex]; -} -#endif - -+ (NSString *)databaseExtensionName -{ - return OWSIncompleteCallsJobCallTypeIndex; -} - -+ (void)asyncRegisterDatabaseExtensionsWithPrimaryStorage:(OWSStorage *)storage -{ - [storage asyncRegisterExtension:[self indexDatabaseExtension] withName:OWSIncompleteCallsJobCallTypeIndex]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageDecrypter.h b/SignalServiceKit/src/Messages/OWSMessageDecrypter.h deleted file mode 100644 index dda173286..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageDecrypter.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageHandler.h" - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class SSKProtoEnvelope; -@class YapDatabaseReadWriteTransaction; - -@interface OWSMessageDecryptResult : NSObject - -@property (nonatomic, readonly) NSData *envelopeData; -@property (nonatomic, readonly, nullable) NSData *plaintextData; -@property (nonatomic, readonly) NSString *source; -@property (nonatomic, readonly) UInt32 sourceDevice; -@property (nonatomic, readonly) BOOL isUDMessage; - -@end - -#pragma mark - - -// Decryption result includes the envelope since the envelope -// may be altered by the decryption process. -typedef void (^DecryptSuccessBlock)(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction); -typedef void (^DecryptFailureBlock)(void); - -@interface OWSMessageDecrypter : OWSMessageHandler - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -// decryptEnvelope: can be called from any thread. -// successBlock & failureBlock will be called an arbitrary thread. -// -// Exactly one of successBlock & failureBlock will be called, -// once. -- (void)decryptEnvelope:(SSKProtoEnvelope *)envelope - envelopeData:(NSData *)envelopeData - successBlock:(DecryptSuccessBlock)successBlock - failureBlock:(DecryptFailureBlock)failureBlock; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageDecrypter.m b/SignalServiceKit/src/Messages/OWSMessageDecrypter.m deleted file mode 100644 index 554d28a76..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageDecrypter.m +++ /dev/null @@ -1,686 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageDecrypter.h" -#import "NSData+messagePadding.h" -#import "NSString+SSK.h" -#import "NotificationsProtocol.h" -#import "OWSAnalytics.h" -#import "OWSBlockingManager.h" -#import "OWSDevice.h" -#import "OWSError.h" -#import "OWSIdentityManager.h" -#import "OWSPrimaryStorage+PreKeyStore.h" -#import "OWSPrimaryStorage+SessionStore.h" -#import "OWSPrimaryStorage+SignedPreKeyStore.h" -#import "OWSPrimaryStorage.h" -#import "SSKEnvironment.h" -#import "SignalRecipient.h" -#import "TSAccountManager.h" -#import "TSContactThread.h" -#import "TSErrorMessage.h" -#import "TSPreKeyManager.h" -#import -#import -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDescription) -{ - if (error) { - return error; - } - OWSCFailDebug(@"Caller should provide specific error"); - return OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptUDMessage, fallbackErrorDescription); -} - -#pragma mark - - -@interface OWSMessageDecryptResult () - -@property (nonatomic) NSData *envelopeData; -@property (nonatomic, nullable) NSData *plaintextData; -@property (nonatomic) NSString *source; -@property (nonatomic) UInt32 sourceDevice; -@property (nonatomic) BOOL isUDMessage; - -@end - -#pragma mark - - -@implementation OWSMessageDecryptResult - -+ (OWSMessageDecryptResult *)resultWithEnvelopeData:(NSData *)envelopeData - plaintextData:(nullable NSData *)plaintextData - source:(NSString *)source - sourceDevice:(UInt32)sourceDevice - isUDMessage:(BOOL)isUDMessage -{ - OWSAssertDebug(envelopeData); - OWSAssertDebug(source.length > 0); - OWSAssertDebug(sourceDevice > 0); - - OWSMessageDecryptResult *result = [OWSMessageDecryptResult new]; - result.envelopeData = envelopeData; - result.plaintextData = plaintextData; - result.source = source; - result.sourceDevice = sourceDevice; - result.isUDMessage = isUDMessage; - return result; -} - -@end - -#pragma mark - - -@interface OWSMessageDecrypter () - -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; -@property (nonatomic, readonly) LKSessionResetImplementation *sessionResetImplementation; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@end - -#pragma mark - - -@implementation OWSMessageDecrypter - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - - if (!self) { - return self; - } - - _primaryStorage = primaryStorage; - _sessionResetImplementation = [LKSessionResetImplementation new]; - _dbConnection = primaryStorage.newDatabaseConnection; - - OWSSingletonAssert(); - - return self; -} - -#pragma mark - Dependencies - -- (OWSBlockingManager *)blockingManager -{ - OWSAssertDebug(SSKEnvironment.shared.blockingManager); - - return SSKEnvironment.shared.blockingManager; -} - -- (OWSIdentityManager *)identityManager -{ - OWSAssertDebug(SSKEnvironment.shared.identityManager); - - return SSKEnvironment.shared.identityManager; -} - -- (id)udManager -{ - OWSAssertDebug(SSKEnvironment.shared.udManager); - - return SSKEnvironment.shared.udManager; -} - -- (TSAccountManager *)tsAccountManager -{ - OWSAssertDebug(SSKEnvironment.shared.tsAccountManager); - - return SSKEnvironment.shared.tsAccountManager; -} - -#pragma mark - Blocking - -- (BOOL)isEnvelopeSenderBlocked:(SSKProtoEnvelope *)envelope -{ - OWSAssertDebug(envelope); - - return [self.blockingManager.blockedPhoneNumbers containsObject:envelope.source]; -} - -#pragma mark - Decryption - -- (void)decryptEnvelope:(SSKProtoEnvelope *)envelope - envelopeData:(NSData *)envelopeData - successBlock:(DecryptSuccessBlock)successBlockParameter - failureBlock:(DecryptFailureBlock)failureBlockParameter -{ - OWSAssertDebug(envelope); - OWSAssertDebug(envelopeData); - OWSAssertDebug(successBlockParameter); - OWSAssertDebug(failureBlockParameter); - OWSAssertDebug([self.tsAccountManager isRegistered]); - - // successBlock is called synchronously so that we can avail ourselves of - // the transaction. - // - // Ensure that failureBlock is called on a worker queue. - DecryptFailureBlock failureBlock = ^() { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - failureBlockParameter(); - }); - }; - - NSString *localRecipientId = self.tsAccountManager.localNumber; - uint32_t localDeviceId = OWSDevicePrimaryDeviceId; - DecryptSuccessBlock successBlock = ^( - OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) { - // Ensure all blocked messages are discarded. - if ([self isEnvelopeSenderBlocked:envelope]) { - OWSLogInfo(@"Ignoring blocked envelope from: %@.", envelope.source); - return failureBlock(); - } - - if ([result.source isEqualToString:localRecipientId] && result.sourceDevice == localDeviceId) { - // Self-sent messages should be discarded during the decryption process. - OWSFailDebug(@"Unexpected self-sent sync message."); - return failureBlock(); - } - - // Having received a valid (decryptable) message from this user, - // make note of the fact that they have a valid Signal account. - [SignalRecipient markRecipientAsRegistered:result.source deviceId:result.sourceDevice transaction:transaction]; - - successBlockParameter(result, transaction); - }; - - @try { - OWSLogInfo(@"Decrypting envelope: %@.", [self descriptionForEnvelope:envelope]); - - if (envelope.type != SSKProtoEnvelopeTypeUnidentifiedSender) { - if (!envelope.hasSource || envelope.source.length < 1 || ![ECKeyPair isValidHexEncodedPublicKeyWithCandidate:envelope.source]) { - OWSFailDebug(@"Incoming envelope with invalid source."); - return failureBlock(); - } - if (!envelope.hasSourceDevice || envelope.sourceDevice < 1) { - OWSFailDebug(@"Incoming envelope with invalid source device."); - return failureBlock(); - } - - // We block UD messages later, after they are decrypted. - if ([self isEnvelopeSenderBlocked:envelope]) { - OWSLogInfo(@"Ignoring blocked envelope from: %@.", envelope.source); - return failureBlock(); - } - } - - switch (envelope.type) { - case SSKProtoEnvelopeTypeCiphertext: { - [self throws_decryptSecureMessage:envelope - envelopeData:envelopeData - successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) { - OWSLogDebug(@"Decrypted secure message."); - successBlock(result, transaction); - } - failureBlock:^(NSError *_Nullable error) { - OWSLogError(@"Decrypting secure message from: %@ failed with error: %@.", - envelopeAddress(envelope), - error); - OWSProdError([OWSAnalyticsEvents messageManagerErrorCouldNotHandleSecureMessage]); - failureBlock(); - }]; - // Return to avoid double-acknowledging. - return; - } - case SSKProtoEnvelopeTypePrekeyBundle: { - [self throws_decryptPreKeyBundle:envelope - envelopeData:envelopeData - successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) { - OWSLogDebug(@"Decrypted pre key bundle message."); - successBlock(result, transaction); - } - failureBlock:^(NSError *_Nullable error) { - OWSLogError(@"Decrypting pre key bundle message from: %@ failed with error: %@.", - envelopeAddress(envelope), - error); - OWSProdError([OWSAnalyticsEvents messageManagerErrorCouldNotHandlePrekeyBundle]); - failureBlock(); - }]; - // Return to avoid double-acknowledging. - return; - } - // These message types don't have a payload to decrypt. - case SSKProtoEnvelopeTypeReceipt: - case SSKProtoEnvelopeTypeKeyExchange: - case SSKProtoEnvelopeTypeUnknown: { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - OWSMessageDecryptResult *result = - [OWSMessageDecryptResult resultWithEnvelopeData:envelopeData - plaintextData:nil - source:envelope.source - sourceDevice:envelope.sourceDevice - isUDMessage:NO]; - successBlock(result, transaction); - }]; - // Return to avoid double-acknowledging. - return; - } - case SSKProtoEnvelopeTypeClosedGroupCiphertext: { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - NSError *error = nil; - NSArray *plaintextAndSenderPublicKey = [LKClosedGroupUtilities decryptEnvelope:envelope transaction:transaction error:&error]; - if (error != nil) { return failureBlock(); } - NSData *plaintext = plaintextAndSenderPublicKey[0]; - NSString *senderPublicKey = plaintextAndSenderPublicKey[1]; - SSKProtoEnvelopeBuilder *newEnvelope = [envelope asBuilder]; - [newEnvelope setSource:senderPublicKey]; - NSData *newEnvelopeAsData = [newEnvelope buildSerializedDataAndReturnError:&error]; - if (error != nil) { return failureBlock(); } - NSString *userPublicKey = [OWSIdentityManager.sharedManager.identityKeyPair hexEncodedPublicKey]; - if ([senderPublicKey isEqual:userPublicKey]) { return failureBlock(); } - OWSMessageDecryptResult *result = [OWSMessageDecryptResult resultWithEnvelopeData:newEnvelopeAsData - plaintextData:[plaintext removePadding] - source:senderPublicKey - sourceDevice:OWSDevicePrimaryDeviceId - isUDMessage:NO]; - successBlock(result, transaction); - }]; - return; - } - case SSKProtoEnvelopeTypeUnidentifiedSender: { - [self decryptUnidentifiedSender:envelope - successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) { - OWSLogDebug(@"Decrypted unidentified sender message."); - successBlock(result, transaction); - } - failureBlock:^(NSError *_Nullable error) { - OWSLogError(@"Decrypting unidentified sender message from: %@ failed with error: %@.", - envelopeAddress(envelope), - error); - OWSProdError([OWSAnalyticsEvents messageManagerErrorCouldNotHandleUnidentifiedSenderMessage]); - failureBlock(); - }]; - // Return to avoid double-acknowledging. - return; - } - default: - OWSLogWarn(@"Received unhandled envelope type: %d.", (int)envelope.type); - break; - } - } @catch (NSException *exception) { - OWSFailDebug(@"Received an invalid envelope: %@.", exception.debugDescription); - OWSProdFail([OWSAnalyticsEvents messageManagerErrorInvalidProtocolMessage]); - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread]; - [SSKEnvironment.shared.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage - transaction:transaction]; - }]; - } - - failureBlock(); -} - -- (void)throws_decryptSecureMessage:(SSKProtoEnvelope *)envelope - envelopeData:(NSData *)envelopeData - successBlock:(DecryptSuccessBlock)successBlock - failureBlock:(void (^)(NSError *_Nullable error))failureBlock -{ - OWSAssertDebug(envelope); - OWSAssertDebug(envelopeData); - OWSAssertDebug(successBlock); - OWSAssertDebug(failureBlock); - - [self decryptEnvelope:envelope - envelopeData:envelopeData - cipherTypeName:@"Secure Message" - cipherMessageBlock:^(NSData *encryptedData) { - return [[WhisperMessage alloc] init_throws_withData:encryptedData]; - } - successBlock:successBlock - failureBlock:failureBlock]; -} - -- (void)throws_decryptPreKeyBundle:(SSKProtoEnvelope *)envelope - envelopeData:(NSData *)envelopeData - successBlock:(DecryptSuccessBlock)successBlock - failureBlock:(void (^)(NSError *_Nullable error))failureBlock -{ - OWSAssertDebug(envelope); - OWSAssertDebug(envelopeData); - OWSAssertDebug(successBlock); - OWSAssertDebug(failureBlock); - - // Check whether we need to refresh our PreKeys every time we receive a PreKeyWhisperMessage. - [TSPreKeyManager checkPreKeys]; - - [self decryptEnvelope:envelope - envelopeData:envelopeData - cipherTypeName:@"PreKey Bundle" - cipherMessageBlock:^(NSData *encryptedData) { - return [[PreKeyWhisperMessage alloc] init_throws_withData:encryptedData]; - } - successBlock:successBlock - failureBlock:failureBlock]; -} - -- (void)decryptEnvelope:(SSKProtoEnvelope *)envelope - envelopeData:(NSData *)envelopeData - cipherTypeName:(NSString *)cipherTypeName - cipherMessageBlock:(id (^_Nonnull)(NSData *))cipherMessageBlock - successBlock:(DecryptSuccessBlock)successBlock - failureBlock:(void (^)(NSError *_Nullable error))failureBlock -{ - OWSAssertDebug(envelope); - OWSAssertDebug(envelopeData); - OWSAssertDebug(cipherTypeName.length > 0); - OWSAssertDebug(cipherMessageBlock); - OWSAssertDebug(successBlock); - OWSAssertDebug(failureBlock); - - NSString *recipientId = envelope.source; - int deviceId = envelope.sourceDevice; - - // DEPRECATED - Remove `legacyMessage` after all clients have been upgraded. - NSData *encryptedData = envelope.content ?: envelope.legacyMessage; - if (!encryptedData) { - OWSProdFail([OWSAnalyticsEvents messageManagerErrorMessageEnvelopeHasNoContent]); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, @"Envelope has no content."); - return failureBlock(error); - } - - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - @try { - id cipherMessage = cipherMessageBlock(encryptedData); - LKSessionCipher *cipher = [[LKSessionCipher alloc] - initWithSessionResetImplementation:self.sessionResetImplementation - sessionStore:self.primaryStorage - preKeyStore:self.primaryStorage - signedPreKeyStore:self.primaryStorage - identityKeyStore:self.identityManager - recipientID:recipientId - deviceID:deviceId]; - - // plaintextData may be nil for some envelope types. - NSError *error = nil; - NSData *_Nullable decryptedData = [cipher decrypt:cipherMessage protocolContext:transaction error:&error]; - // Throw if we got an error - SCKRaiseIfExceptionWrapperError(error); - NSData *_Nullable plaintextData = decryptedData != nil ? [decryptedData removePadding] : nil; - - OWSMessageDecryptResult *result = [OWSMessageDecryptResult resultWithEnvelopeData:envelopeData - plaintextData:plaintextData - source:envelope.source - sourceDevice:envelope.sourceDevice - isUDMessage:NO]; - successBlock(result, transaction); - } @catch (NSException *exception) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self processException:exception envelope:envelope]; - NSString *errorDescription = [NSString - stringWithFormat:@"Exception while decrypting %@: %@.", cipherTypeName, exception.description]; - OWSLogError(@"%@", errorDescription); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, errorDescription); - failureBlock(error); - }); - } - }]; -} - -- (void)decryptUnidentifiedSender:(SSKProtoEnvelope *)envelope - successBlock:(DecryptSuccessBlock)successBlock - failureBlock:(void (^)(NSError *_Nullable error))failureBlock -{ - OWSAssertDebug(envelope); - OWSAssertDebug(successBlock); - OWSAssertDebug(failureBlock); - - // NOTE: We don't need to bother with `legacyMessage` for UD messages. - NSData *encryptedData = envelope.content; - if (!encryptedData) { - NSString *errorDescription = @"UD Envelope is missing content."; - OWSFailDebug(@"%@", errorDescription); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptUDMessage, errorDescription); - return failureBlock(error); - } - - UInt64 serverTimestamp = envelope.timestamp; - - id certificateValidator = - [[SMKCertificateDefaultValidator alloc] initWithTrustRoot:self.udManager.trustRoot]; - - NSString *localRecipientId = self.tsAccountManager.localNumber; - uint32_t localDeviceId = OWSDevicePrimaryDeviceId; - - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - NSError *cipherError; - SMKSecretSessionCipher *_Nullable cipher = - [[SMKSecretSessionCipher alloc] initWithSessionResetImplementation:self.sessionResetImplementation - sessionStore:self.primaryStorage - preKeyStore:self.primaryStorage - signedPreKeyStore:self.primaryStorage - identityStore:self.identityManager - error:&cipherError]; - - if (cipherError || !cipher) { - OWSFailDebug(@"Could not create secret session cipher: %@.", cipherError); - cipherError = EnsureDecryptError(cipherError, @"Could not create secret session cipher."); - return failureBlock(cipherError); - } - - NSError *decryptError; - SMKDecryptResult *_Nullable decryptResult = - [cipher throwswrapped_decryptMessageWithCertificateValidator:certificateValidator - cipherTextData:encryptedData - timestamp:serverTimestamp - localRecipientId:localRecipientId - localDeviceId:localDeviceId - protocolContext:transaction - error:&decryptError]; - - if (!decryptResult) { - if (!decryptError) { - OWSFailDebug(@"Caller should provide specific error."); - NSError *error = OWSErrorWithCodeDescription( - OWSErrorCodeFailedToDecryptUDMessage, @"Could not decrypt UD message."); - return failureBlock(error); - } - - // Decrypt Failure Part 1: Unwrap failure details - - NSError *_Nullable underlyingError; - SSKProtoEnvelope *_Nullable identifiedEnvelope; - - if (![decryptError.domain isEqualToString:@"SessionMetadataKit.SecretSessionKnownSenderError"]) { - underlyingError = decryptError; - identifiedEnvelope = envelope; - } else { - underlyingError = decryptError.userInfo[NSUnderlyingErrorKey]; - - NSString *senderRecipientId - = decryptError.userInfo[SecretSessionKnownSenderError.kSenderRecipientIdKey]; - OWSAssert(senderRecipientId); - - NSNumber *senderDeviceId = decryptError.userInfo[SecretSessionKnownSenderError.kSenderDeviceIdKey]; - OWSAssert(senderDeviceId); - - SSKProtoEnvelopeBuilder *identifiedEnvelopeBuilder = envelope.asBuilder; - identifiedEnvelopeBuilder.source = senderRecipientId; - identifiedEnvelopeBuilder.sourceDevice = senderDeviceId.unsignedIntValue; - NSError *identifiedEnvelopeBuilderError; - - identifiedEnvelope = [identifiedEnvelopeBuilder buildAndReturnError:&identifiedEnvelopeBuilderError]; - if (identifiedEnvelopeBuilderError) { - OWSFailDebug(@"identifiedEnvelopeBuilderError: %@", identifiedEnvelopeBuilderError); - } - } - OWSAssert(underlyingError); - OWSAssert(identifiedEnvelope); - - NSException *_Nullable underlyingException; - if ([underlyingError.domain isEqualToString:SCKExceptionWrapperErrorDomain] - && underlyingError.code == SCKExceptionWrapperErrorThrown) { - - underlyingException = underlyingError.userInfo[SCKExceptionWrapperUnderlyingExceptionKey]; - OWSAssert(underlyingException); - } - - // Decrypt Failure Part 2: Handle unwrapped failure details - - if (underlyingException) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self processException:underlyingException envelope:identifiedEnvelope]; - NSString *errorDescription = [NSString - stringWithFormat:@"Exception while decrypting UD message: %@.", underlyingException.description]; - OWSLogError(@"%@", errorDescription); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, errorDescription); - failureBlock(error); - }); - return; - } - - if ([underlyingError.domain isEqualToString:@"SessionMetadataKit.SMKSecretSessionCipherError"] - && underlyingError.code == SMKSecretSessionCipherErrorSelfSentMessage) { - // Self-sent messages can be safely discarded. - failureBlock(underlyingError); - return; - } - - // Attempt to recover automatically - if ([decryptError userInfo][NSUnderlyingErrorKey] != nil) { - NSDictionary *underlyingErrorUserInfo = [[decryptError userInfo][NSUnderlyingErrorKey] userInfo]; - if (underlyingErrorUserInfo[SCKExceptionWrapperUnderlyingExceptionKey] != nil) { - NSException *underlyingUnderlyingError = underlyingErrorUserInfo[SCKExceptionWrapperUnderlyingExceptionKey]; - if ([[underlyingUnderlyingError reason] hasPrefix:@"Bad Mac!"]) { - if ([underlyingError userInfo][@"kSenderRecipientIdKey"] != nil) { - NSString *senderPublicKey = [underlyingError userInfo][@"kSenderRecipientIdKey"]; - TSContactThread *thread = [TSContactThread getThreadWithContactId:senderPublicKey transaction:transaction]; - if (thread != nil) { - [thread addSessionRestoreDevice:senderPublicKey transaction:transaction]; - [LKSessionManagementProtocol startSessionResetInThread:thread transaction:transaction]; - } - } - } - } - } - - failureBlock(underlyingError); - return; - } - - if (decryptResult.messageType == SMKMessageTypePrekey) { - [TSPreKeyManager checkPreKeys]; - } - - NSString *source = decryptResult.senderRecipientId; - if (source.length < 1) { - NSString *errorDescription = @"Invalid UD sender."; - OWSFailDebug(@"%@", errorDescription); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptUDMessage, errorDescription); - return failureBlock(error); - } - - long sourceDeviceId = decryptResult.senderDeviceId; - if (sourceDeviceId < 1 || sourceDeviceId > UINT32_MAX) { - NSString *errorDescription = @"Invalid UD sender device ID."; - OWSFailDebug(@"%@", errorDescription); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptUDMessage, errorDescription); - return failureBlock(error); - } - NSData *plaintextData = [decryptResult.paddedPayload removePadding]; - - SSKProtoEnvelopeBuilder *envelopeBuilder = [envelope asBuilder]; - [envelopeBuilder setSource:source]; - [envelopeBuilder setSourceDevice:(uint32_t)sourceDeviceId]; - if (decryptResult.messageType == SMKMessageTypeFallback) { - [envelopeBuilder setType:SSKProtoEnvelopeTypeFallbackMessage]; - OWSLogInfo(@"SMKMessageTypeFallback"); - } - NSError *envelopeBuilderError; - NSData *_Nullable newEnvelopeData = [envelopeBuilder buildSerializedDataAndReturnError:&envelopeBuilderError]; - if (envelopeBuilderError || !newEnvelopeData) { - OWSFailDebug(@"Could not update UD envelope data: %@", envelopeBuilderError); - NSError *error = EnsureDecryptError(envelopeBuilderError, @"Could not update UD envelope data"); - return failureBlock(error); - } - - OWSMessageDecryptResult *result = [OWSMessageDecryptResult resultWithEnvelopeData:newEnvelopeData - plaintextData:plaintextData - source:source - sourceDevice:(uint32_t)sourceDeviceId - isUDMessage:YES]; - successBlock(result, transaction); - }]; -} - -- (void)processException:(NSException *)exception envelope:(SSKProtoEnvelope *)envelope -{ - OWSLogError( - @"Got exception: %@ of type: %@ with reason: %@", exception.description, exception.name, exception.reason); - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - TSErrorMessage *errorMessage; - - if (envelope.source.length == 0) { - TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread]; - [SSKEnvironment.shared.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage - transaction:transaction]; - return; - } - - if ([exception.name isEqualToString:NoSessionException]) { - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorNoSession], envelope); - errorMessage = [TSErrorMessage missingSessionWithEnvelope:envelope withTransaction:transaction]; - } else if ([exception.name isEqualToString:InvalidKeyException]) { - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorInvalidKey], envelope); - errorMessage = [TSErrorMessage invalidKeyExceptionWithEnvelope:envelope withTransaction:transaction]; - } else if ([exception.name isEqualToString:InvalidKeyIdException]) { - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorInvalidKeyId], envelope); - errorMessage = [TSErrorMessage invalidKeyExceptionWithEnvelope:envelope withTransaction:transaction]; - } else if ([exception.name isEqualToString:DuplicateMessageException]) { - // Duplicate messages are silently discarded. - return; - } else if ([exception.name isEqualToString:InvalidVersionException]) { - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorInvalidMessageVersion], envelope); - errorMessage = [TSErrorMessage invalidVersionWithEnvelope:envelope withTransaction:transaction]; - } else if ([exception.name isEqualToString:UntrustedIdentityKeyException]) { - // Should no longer get here, since we now record the new identity for incoming messages. - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorUntrustedIdentityKeyException], envelope); - OWSFailDebug(@"Failed to trust identity on incoming message from: %@", envelopeAddress(envelope)); - return; - } else { - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorCorruptMessage], envelope); - errorMessage = [TSErrorMessage corruptedMessageWithEnvelope:envelope withTransaction:transaction]; - } - - OWSAssertDebug(errorMessage); - if (errorMessage != nil) { - [LKSessionManagementProtocol handleDecryptionError:errorMessage forPublicKey:envelope.source transaction:transaction]; - if (![LKSessionMetaProtocol isErrorMessageFromBeforeRestoration:errorMessage]) { - [errorMessage saveWithTransaction:transaction]; - [self notifyUserForErrorMessage:errorMessage envelope:envelope transaction:transaction]; - } else { - // Show the thread if it exists before restoration - NSString *masterPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source; - TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:masterPublicKey transaction:transaction]; - contactThread.shouldThreadBeVisible = true; - [contactThread saveWithTransaction:transaction]; - } - } - }]; -} - -- (void)notifyUserForErrorMessage:(TSErrorMessage *)errorMessage - envelope:(SSKProtoEnvelope *)envelope - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - NSString *masterPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source; - TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:masterPublicKey transaction:transaction]; - [SSKEnvironment.shared.notificationsManager notifyUserForErrorMessage:errorMessage - thread:contactThread - transaction:transaction]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageHandler.h b/SignalServiceKit/src/Messages/OWSMessageHandler.h deleted file mode 100644 index 8544dfefe..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageHandler.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class SSKProtoContent; -@class SSKProtoDataMessage; -@class SSKProtoEnvelope; - -NSString *envelopeAddress(SSKProtoEnvelope *envelope); - -@interface OWSMessageHandler : NSObject - -- (NSString *)descriptionForEnvelopeType:(SSKProtoEnvelope *)envelope; -- (NSString *)descriptionForEnvelope:(SSKProtoEnvelope *)envelope; -- (NSString *)descriptionForContent:(SSKProtoContent *)content; -- (NSString *)descriptionForDataMessage:(SSKProtoDataMessage *)dataMessage; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageHandler.m b/SignalServiceKit/src/Messages/OWSMessageHandler.m deleted file mode 100644 index 377e250cb..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageHandler.m +++ /dev/null @@ -1,186 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageHandler.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -// used in log formatting -NSString *envelopeAddress(SSKProtoEnvelope *envelope) -{ - return [NSString stringWithFormat:@"%@.%d", envelope.source, (unsigned int)envelope.sourceDevice]; -} - -@implementation OWSMessageHandler - -- (NSString *)descriptionForEnvelopeType:(SSKProtoEnvelope *)envelope -{ - OWSAssertDebug(envelope != nil); - - switch (envelope.type) { - case SSKProtoEnvelopeTypeReceipt: - return @"DeliveryReceipt"; - case SSKProtoEnvelopeTypeUnknown: - // Shouldn't happen - OWSProdFail([OWSAnalyticsEvents messageManagerErrorEnvelopeTypeUnknown]); - return @"Unknown"; - case SSKProtoEnvelopeTypeCiphertext: - return @"SignalEncryptedMessage"; - case SSKProtoEnvelopeTypeKeyExchange: - // Unsupported - OWSProdFail([OWSAnalyticsEvents messageManagerErrorEnvelopeTypeKeyExchange]); - return @"KeyExchange"; - case SSKProtoEnvelopeTypePrekeyBundle: - return @"PreKeyEncryptedMessage"; - case SSKProtoEnvelopeTypeUnidentifiedSender: - return @"UnidentifiedSender"; - case SSKProtoEnvelopeTypeFallbackMessage: - return @"FallbackMessage"; - case SSKProtoEnvelopeTypeClosedGroupCiphertext: - return @"ClosedGroupCiphertext"; - default: - // Shouldn't happen - OWSProdFail([OWSAnalyticsEvents messageManagerErrorEnvelopeTypeOther]); - return @"Other"; - } -} - -- (NSString *)descriptionForEnvelope:(SSKProtoEnvelope *)envelope -{ - OWSAssertDebug(envelope != nil); - - return [NSString stringWithFormat:@"", - [self descriptionForEnvelopeType:envelope], - envelopeAddress(envelope), - envelope.timestamp, - (unsigned long)envelope.content.length]; -} - -/** - * We don't want to just log `content.description` because we'd potentially log message bodies for dataMesssages and - * sync transcripts - */ -- (NSString *)descriptionForContent:(SSKProtoContent *)content -{ - if (content.syncMessage) { - return [NSString stringWithFormat:@"", [self descriptionForSyncMessage:content.syncMessage]]; - } else if (content.dataMessage) { - return [NSString stringWithFormat:@"", [self descriptionForDataMessage:content.dataMessage]]; - } else if (content.callMessage) { - NSString *callMessageDescription = [self descriptionForCallMessage:content.callMessage]; - return [NSString stringWithFormat:@"", callMessageDescription]; - } else if (content.nullMessage) { - return [NSString stringWithFormat:@"", content.nullMessage]; - } else if (content.receiptMessage) { - return [NSString stringWithFormat:@"", content.receiptMessage]; - } else if (content.typingMessage) { - return [NSString stringWithFormat:@"", content.typingMessage]; - } else { - // Don't fire an analytics event; if we ever add a new content type, we'd generate a ton of - // analytics traffic. - return @"UnknownContent"; - } -} - -- (NSString *)descriptionForCallMessage:(SSKProtoCallMessage *)callMessage -{ - NSString *messageType; - UInt64 callId; - - if (callMessage.offer) { - messageType = @"Offer"; - callId = callMessage.offer.id; - } else if (callMessage.busy) { - messageType = @"Busy"; - callId = callMessage.busy.id; - } else if (callMessage.answer) { - messageType = @"Answer"; - callId = callMessage.answer.id; - } else if (callMessage.hangup) { - messageType = @"Hangup"; - callId = callMessage.hangup.id; - } else if (callMessage.iceUpdate.count > 0) { - messageType = [NSString stringWithFormat:@"Ice Updates (%lu)", (unsigned long)callMessage.iceUpdate.count]; - callId = callMessage.iceUpdate.firstObject.id; - } else { - OWSFailDebug(@"failure: unexpected call message type: %@", callMessage); - messageType = @"Unknown"; - callId = 0; - } - - return [NSString stringWithFormat:@"type: %@, id: %llu", messageType, callId]; -} - -/** - * We don't want to just log `dataMessage.description` because we'd potentially log message contents - */ -- (NSString *)descriptionForDataMessage:(SSKProtoDataMessage *)dataMessage -{ - NSMutableString *description = [NSMutableString new]; - - if (dataMessage.group) { - [description appendString:@"(Group:YES) "]; - } - - if ((dataMessage.flags & SSKProtoDataMessageFlagsEndSession) != 0) { - [description appendString:@"EndSession"]; - } else if ((dataMessage.flags & SSKProtoDataMessageFlagsExpirationTimerUpdate) != 0) { - [description appendString:@"ExpirationTimerUpdate"]; - } else if ((dataMessage.flags & SSKProtoDataMessageFlagsProfileKeyUpdate) != 0) { - [description appendString:@"ProfileKey"]; - } else if (dataMessage.attachments.count > 0) { - [description appendString:@"MessageWithAttachment"]; - } else { - [description appendString:@"Plain"]; - } - - return [NSString stringWithFormat:@"<%@ />", description]; -} - -/** - * We don't want to just log `syncMessage.description` because we'd potentially log message contents in sent transcripts - */ -- (NSString *)descriptionForSyncMessage:(SSKProtoSyncMessage *)syncMessage -{ - NSMutableString *description = [NSMutableString new]; - if (syncMessage.sent) { - [description appendString:@"SentTranscript"]; - } else if (syncMessage.request) { - if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeContacts) { - [description appendString:@"ContactRequest"]; - } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeGroups) { - [description appendString:@"GroupRequest"]; - } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeBlocked) { - [description appendString:@"BlockedRequest"]; - } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeConfiguration) { - [description appendString:@"ConfigurationRequest"]; - } else { - OWSFailDebug(@"Unknown sync message request type"); - [description appendString:@"UnknownRequest"]; - } - } else if (syncMessage.blocked) { - [description appendString:@"Blocked"]; - } else if (syncMessage.read.count > 0) { - [description appendString:@"ReadReceipt"]; - } else if (syncMessage.verified) { - NSString *verifiedString = - [NSString stringWithFormat:@"Verification for: %@", syncMessage.verified.destination]; - [description appendString:verifiedString]; - } else if (syncMessage.contacts) { - [description appendString:@"Contacts"]; - } else if (syncMessage.groups) { - [description appendString:@"ClosedGroups"]; - } else if (syncMessage.openGroups) { - [description appendString:@"OpenGroups"]; - } else { - [description appendString:@"Unknown"]; - } - - return description; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.h b/SignalServiceKit/src/Messages/OWSMessageManager.h deleted file mode 100644 index b6c2817bc..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageManager.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageHandler.h" - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class SSKProtoEnvelope; -@class TSThread; -@class YapDatabaseReadWriteTransaction; - -@interface OWSMessageManager : OWSMessageHandler - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)sharedManager; - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -// processEnvelope: can be called from any thread. -- (void)throws_processEnvelope:(SSKProtoEnvelope *)envelope - plaintextData:(NSData *_Nullable)plaintextData - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction - serverID:(uint64_t)serverID; - -// This should be invoked by the main app when the app is ready. -- (void)startObserving; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m deleted file mode 100644 index 713d999f8..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ /dev/null @@ -1,1736 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageManager.h" -#import "AppContext.h" -#import "AppReadiness.h" -#import "ContactsManagerProtocol.h" -#import "MimeTypeUtil.h" -#import "NSNotificationCenter+OWS.h" -#import "NSString+SSK.h" -#import "NotificationsProtocol.h" -#import "OWSAttachmentDownloads.h" -#import "OWSBlockingManager.h" -#import "OWSCallMessageHandler.h" -#import "OWSContact.h" -#import "OWSDevice.h" -#import "OWSDevicesService.h" -#import "OWSDisappearingConfigurationUpdateInfoMessage.h" -#import "OWSDisappearingMessagesConfiguration.h" -#import "OWSDisappearingMessagesJob.h" -#import "LKDeviceLinkMessage.h" -#import "OWSIdentityManager.h" -#import "OWSIncomingMessageFinder.h" -#import "OWSIncomingSentMessageTranscript.h" -#import "OWSMessageSender.h" -#import "OWSMessageUtils.h" -#import "OWSOutgoingNullMessage.h" -#import "OWSOutgoingReceiptManager.h" -#import "OWSPrimaryStorage+SessionStore.h" -#import "OWSPrimaryStorage+Loki.h" -#import "OWSPrimaryStorage.h" -#import "OWSReadReceiptManager.h" -#import "OWSRecordTranscriptJob.h" -#import "OWSSyncGroupsMessage.h" -#import "OWSSyncGroupsRequestMessage.h" -#import "ProfileManagerProtocol.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSAttachment.h" -#import "TSAttachmentPointer.h" -#import "TSAttachmentStream.h" -#import "TSContactThread.h" -#import "TSDatabaseView.h" -#import "TSGroupModel.h" -#import "TSGroupThread.h" -#import "TSIncomingMessage.h" -#import "TSInfoMessage.h" -#import "TSNetworkManager.h" -#import "TSOutgoingMessage.h" -#import "TSQuotedMessage.h" -#import -#import -#import -#import -#import -#import -#import -#import "OWSDispatch.h" -#import "OWSBatchMessageProcessor.h" -#import "OWSQueues.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSMessageManager () - -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (nonatomic, readonly) OWSIncomingMessageFinder *incomingMessageFinder; - -@end - -#pragma mark - - -@implementation OWSMessageManager - -+ (instancetype)sharedManager -{ - OWSAssertDebug(SSKEnvironment.shared.messageManager); - - return SSKEnvironment.shared.messageManager; -} - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - - if (!self) { - return self; - } - - _primaryStorage = primaryStorage; - _dbConnection = primaryStorage.newDatabaseConnection; - _incomingMessageFinder = [[OWSIncomingMessageFinder alloc] initWithPrimaryStorage:primaryStorage]; - - OWSSingletonAssert(); - - return self; -} - -- (void)dealloc { - [NSNotificationCenter.defaultCenter removeObserver:self]; -} - -#pragma mark - - -- (id)callMessageHandler -{ - OWSAssertDebug(SSKEnvironment.shared.callMessageHandler); - - return SSKEnvironment.shared.callMessageHandler; -} - -- (id)contactsManager -{ - OWSAssertDebug(SSKEnvironment.shared.contactsManager); - - return SSKEnvironment.shared.contactsManager; -} - -- (SSKMessageSenderJobQueue *)messageSenderJobQueue -{ - return SSKEnvironment.shared.messageSenderJobQueue; -} - -- (OWSBlockingManager *)blockingManager -{ - OWSAssertDebug(SSKEnvironment.shared.blockingManager); - - return SSKEnvironment.shared.blockingManager; -} - -- (OWSIdentityManager *)identityManager -{ - OWSAssertDebug(SSKEnvironment.shared.identityManager); - - return SSKEnvironment.shared.identityManager; -} - -- (TSNetworkManager *)networkManager -{ - OWSAssertDebug(SSKEnvironment.shared.networkManager); - - return SSKEnvironment.shared.networkManager; -} - -- (OWSOutgoingReceiptManager *)outgoingReceiptManager -{ - OWSAssertDebug(SSKEnvironment.shared.outgoingReceiptManager); - - return SSKEnvironment.shared.outgoingReceiptManager; -} - -- (id)syncManager -{ - OWSAssertDebug(SSKEnvironment.shared.syncManager); - - return SSKEnvironment.shared.syncManager; -} - -- (TSAccountManager *)tsAccountManager -{ - OWSAssertDebug(SSKEnvironment.shared.tsAccountManager); - - return SSKEnvironment.shared.tsAccountManager; -} - -- (id)profileManager -{ - return SSKEnvironment.shared.profileManager; -} - -- (id)typingIndicators -{ - return SSKEnvironment.shared.typingIndicators; -} - -- (OWSAttachmentDownloads *)attachmentDownloads -{ - return SSKEnvironment.shared.attachmentDownloads; -} - -#pragma mark - - -- (void)startObserving -{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(yapDatabaseModified:) - name:YapDatabaseModifiedNotification - object:OWSPrimaryStorage.sharedManager.dbNotificationObject]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(yapDatabaseModified:) - name:YapDatabaseModifiedExternallyNotification - object:nil]; -} - -- (void)yapDatabaseModified:(NSNotification *)notification -{ - if (AppReadiness.isAppReady) { - [OWSMessageUtils.sharedManager updateApplicationBadgeCount]; - } else { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - [OWSMessageUtils.sharedManager updateApplicationBadgeCount]; - }]; - }); - } -} - -#pragma mark - - -- (BOOL)isEnvelopeSenderBlocked:(SSKProtoEnvelope *)envelope -{ - OWSAssertDebug(envelope); - - return [self.blockingManager isRecipientIdBlocked:envelope.source]; -} - -- (BOOL)isDataMessageBlocked:(SSKProtoDataMessage *)dataMessage envelope:(SSKProtoEnvelope *)envelope -{ - OWSAssertDebug(dataMessage); - OWSAssertDebug(envelope); - - if (dataMessage.group) { - return [self.blockingManager isGroupIdBlocked:dataMessage.group.id]; - } else { - BOOL senderBlocked = [self isEnvelopeSenderBlocked:envelope]; - - // If the envelopeSender was blocked, we never should have gotten as far as decrypting the dataMessage. - OWSAssertDebug(!senderBlocked); - - return senderBlocked; - } -} - -#pragma mark - - -- (void)throws_processEnvelope:(SSKProtoEnvelope *)envelope - plaintextData:(NSData *_Nullable)plaintextData - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction - serverID:(uint64_t)serverID -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - if (!self.tsAccountManager.isRegistered) { - OWSFailDebug(@"Not registered."); - return; - } - - OWSLogInfo(@"Handling decrypted envelope: %@.", [self descriptionForEnvelope:envelope]); - - if (!envelope.hasSource || envelope.source.length < 1) { - OWSFailDebug(@"Incoming envelope with invalid source."); - return; - } - if (!envelope.hasSourceDevice || envelope.sourceDevice < 1) { - OWSFailDebug(@"Incoming envelope with invalid source device."); - return; - } - - if ([self isEnvelopeSenderBlocked:envelope]) { - return; - } - - [self checkForUnknownLinkedDevice:envelope transaction:transaction]; - - switch (envelope.type) { - case SSKProtoEnvelopeTypeFallbackMessage: - case SSKProtoEnvelopeTypeCiphertext: - case SSKProtoEnvelopeTypePrekeyBundle: - case SSKProtoEnvelopeTypeClosedGroupCiphertext: - case SSKProtoEnvelopeTypeUnidentifiedSender: - if (!plaintextData) { - OWSFailDebug(@"missing decrypted data for envelope: %@", [self descriptionForEnvelope:envelope]); - return; - } - [self throws_handleEnvelope:envelope - plaintextData:plaintextData - wasReceivedByUD:wasReceivedByUD - transaction:transaction - serverID:serverID]; - break; - case SSKProtoEnvelopeTypeReceipt: - OWSAssertDebug(!plaintextData); - [self handleDeliveryReceipt:envelope transaction:transaction]; - break; - // Other messages are just dismissed for now. - case SSKProtoEnvelopeTypeKeyExchange: - OWSLogWarn(@"Received Key Exchange Message, not supported"); - break; - case SSKProtoEnvelopeTypeUnknown: - OWSLogWarn(@"Received an unknown message type"); - break; - default: - OWSLogWarn(@"Received unhandled envelope type: %d", (int)envelope.type); - break; - } -} - -- (void)handleDeliveryReceipt:(SSKProtoEnvelope *)envelope transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - // Old-style delivery notices don't include a "delivery timestamp". - [self processDeliveryReceiptsFromRecipientId:envelope.source - sentTimestamps:@[ - @(envelope.timestamp), - ] - deliveryTimestamp:nil - transaction:transaction]; -} - -// deliveryTimestamp is an optional parameter, since legacy -// delivery receipts don't have a "delivery timestamp". Those -// messages repurpose the "timestamp" field to indicate when the -// corresponding message was originally sent. -- (void)processDeliveryReceiptsFromRecipientId:(NSString *)recipientId - sentTimestamps:(NSArray *)sentTimestamps - deliveryTimestamp:(NSNumber *_Nullable)deliveryTimestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (recipientId.length < 1) { - OWSFailDebug(@"Empty recipientId."); - return; - } - if (sentTimestamps.count < 1) { - OWSFailDebug(@"Missing sentTimestamps."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - for (NSNumber *nsTimestamp in sentTimestamps) { - uint64_t timestamp = [nsTimestamp unsignedLongLongValue]; - - NSArray *messages - = (NSArray *)[TSInteraction interactionsWithTimestamp:timestamp - ofClass:[TSOutgoingMessage class] - withTransaction:transaction]; - if (messages.count < 1) { - // The service sends delivery receipts for "unpersisted" messages - // like group updates, so these errors are expected to a certain extent. - // - // TODO: persist "early" delivery receipts. - OWSLogInfo(@"Missing message for delivery receipt: %llu", timestamp); - } else { - if (messages.count > 1) { - OWSLogInfo(@"More than one message (%lu) for delivery receipt: %llu", - (unsigned long)messages.count, - timestamp); - } - for (TSOutgoingMessage *outgoingMessage in messages) { - [outgoingMessage updateWithDeliveredRecipient:recipientId - deliveryTimestamp:deliveryTimestamp - transaction:transaction]; - } - } - } -} - -- (void)throws_handleEnvelope:(SSKProtoEnvelope *)envelope - plaintextData:(NSData *)plaintextData - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction - serverID:(uint64_t)serverID -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!plaintextData) { - OWSFailDebug(@"Missing plaintextData."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - if (envelope.timestamp < 1) { - OWSFailDebug(@"Invalid timestamp."); - return; - } - if (envelope.source.length < 1) { - OWSFailDebug(@"Missing source."); - return; - } - if (envelope.sourceDevice < 1) { - OWSFailDebug(@"Invalid source device."); - return; - } - - OWSPrimaryStorage *storage = OWSPrimaryStorage.sharedManager; - __block NSSet *senderLinkedDevices; - [storage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - senderLinkedDevices = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:envelope.source in:transaction]; - }]; - - BOOL duplicateEnvelope = NO; - for (NSString *publicKey in senderLinkedDevices) { - duplicateEnvelope = duplicateEnvelope - || [self.incomingMessageFinder existsMessageWithTimestamp:envelope.timestamp - sourceId:publicKey - sourceDeviceId:envelope.sourceDevice - transaction:transaction]; - } - - if (duplicateEnvelope) { - OWSLogInfo(@"Ignoring previously received envelope from: %@ with timestamp: %llu.", - envelopeAddress(envelope), - envelope.timestamp); - return; - } - - if (envelope.content != nil) { - NSError *error; - SSKProtoContent *_Nullable contentProto = [SSKProtoContent parseData:plaintextData error:&error]; - if (error != nil || contentProto == nil) { - OWSFailDebug(@"Couldn't parse proto due to error: %@.", error); - return; - } - OWSLogInfo(@"Handling content: .", [self descriptionForContent:contentProto]); - - if ([LKSyncMessagesProtocol isDuplicateSyncMessage:contentProto fromPublicKey:envelope.source]) { - [LKLogger print:@"[Loki] Ignoring duplicate sync transcript."]; - return; - } - - [LKSessionManagementProtocol handlePreKeyBundleMessageIfNeeded:contentProto wrappedIn:envelope transaction:transaction]; - - if (contentProto.lokiDeviceLinkMessage != nil) { - [LKMultiDeviceProtocol handleDeviceLinkMessageIfNeeded:contentProto wrappedIn:envelope transaction:transaction]; - } else if (contentProto.syncMessage) { - [self throws_handleIncomingEnvelope:envelope - withSyncMessage:contentProto.syncMessage - transaction:transaction - serverID:serverID]; - - [[OWSDeviceManager sharedManager] setHasReceivedSyncMessage]; - } else if (contentProto.dataMessage) { - [self handleIncomingEnvelope:envelope - withDataMessage:contentProto.dataMessage - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - } else if (contentProto.callMessage) { - [self handleIncomingEnvelope:envelope withCallMessage:contentProto.callMessage]; - } else if (contentProto.typingMessage) { - [self handleIncomingEnvelope:envelope withTypingMessage:contentProto.typingMessage transaction:transaction]; - } else if (contentProto.receiptMessage) { - [self handleIncomingEnvelope:envelope - withReceiptMessage:contentProto.receiptMessage - transaction:transaction]; - } else { - OWSLogWarn(@"Ignoring envelope. Content with no known payload"); - } - } else if (envelope.legacyMessage != nil) { // DEPRECATED - Remove after all clients have been upgraded. - NSError *error; - SSKProtoDataMessage *_Nullable dataMessageProto = [SSKProtoDataMessage parseData:plaintextData error:&error]; - if (error || !dataMessageProto) { - OWSFailDebug(@"could not parse proto: %@", error); - return; - } - OWSLogInfo(@"handling message: ", [self descriptionForDataMessage:dataMessageProto]); - - [self handleIncomingEnvelope:envelope - withDataMessage:dataMessageProto - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - } else { - OWSProdInfoWEnvelope([OWSAnalyticsEvents messageManagerErrorEnvelopeNoActionablePayload], envelope); - } -} - -- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)envelope - withDataMessage:(SSKProtoDataMessage *)dataMessage - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - if ([self isDataMessageBlocked:dataMessage envelope:envelope]) { - NSString *logMessage = [NSString stringWithFormat:@"Ignoring blocked message from sender: %@.", envelope.source]; - if (dataMessage.group) { - logMessage = [logMessage stringByAppendingFormat:@" in group: %@", dataMessage.group.id]; - } - OWSLogError(@"%@", logMessage); - return; - } - - if (dataMessage.hasTimestamp) { - if (dataMessage.timestamp <= 0) { - OWSFailDebug(@"Ignoring data message with invalid timestamp: %@.", envelope.source); - return; - } - // This prevents replay attacks by the service. - if (dataMessage.timestamp != envelope.timestamp) { - OWSFailDebug(@"Ignoring data message with non-matching timestamp: %@.", envelope.source); - return; - } - } - - [LKClosedGroupsProtocol handleSharedSenderKeysUpdateIfNeeded:dataMessage from:envelope.source transaction:transaction]; - - if (dataMessage.group) { - TSGroupThread *_Nullable groupThread = - [TSGroupThread threadWithGroupId:dataMessage.group.id transaction:transaction]; - - if (groupThread) { - if (groupThread.groupModel.groupType == closedGroup) { - if ([LKClosedGroupsProtocol shouldIgnoreClosedGroupMessage:dataMessage inThread:groupThread wrappedIn:envelope]) { return; } - } - - if (dataMessage.group.type != SSKProtoGroupContextTypeUpdate) { - if (![groupThread isCurrentUserInGroupWithTransaction:transaction]) { - OWSLogInfo(@"Ignoring messages for left group."); - return; - } - } - } else { - // Unknown group - if (dataMessage.group.type == SSKProtoGroupContextTypeUpdate) { - // Accept group updates for unknown groups - } else if (dataMessage.group.type == SSKProtoGroupContextTypeDeliver) { - [self sendGroupInfoRequest:dataMessage.group.id envelope:envelope transaction:transaction]; - return; - } else { - OWSLogInfo(@"Ignoring group message for unknown group from: %@.", envelope.source); - return; - } - } - } - - if ((dataMessage.flags & SSKProtoDataMessageFlagsEndSession) != 0) { - [self handleEndSessionMessageWithEnvelope:envelope dataMessage:dataMessage transaction:transaction]; - } else if ((dataMessage.flags & SSKProtoDataMessageFlagsExpirationTimerUpdate) != 0) { - [self handleExpirationTimerUpdateMessageWithEnvelope:envelope dataMessage:dataMessage transaction:transaction]; - } else if ((dataMessage.flags & SSKProtoDataMessageFlagsProfileKeyUpdate) != 0) { - [self handleProfileKeyMessageWithEnvelope:envelope dataMessage:dataMessage]; - } else if ([LKMultiDeviceProtocol isUnlinkDeviceMessage:dataMessage]) { - [LKMultiDeviceProtocol handleUnlinkDeviceMessage:dataMessage wrappedIn:envelope transaction:transaction]; - } else if (dataMessage.attachments.count > 0) { - [self handleReceivedMediaWithEnvelope:envelope - dataMessage:dataMessage - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - } else { - [self handleReceivedTextMessageWithEnvelope:envelope - dataMessage:dataMessage - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - - if ([self isDataMessageGroupAvatarUpdate:dataMessage]) { - OWSLogVerbose(@"Data message had group avatar attachment"); - [self handleReceivedGroupAvatarUpdateWithEnvelope:envelope dataMessage:dataMessage transaction:transaction]; - } - } - - // Send delivery receipts for "valid data" messages received via UD. - if (wasReceivedByUD) { - [self.outgoingReceiptManager enqueueDeliveryReceiptForEnvelope:envelope]; - } -} - -- (void)sendGroupInfoRequest:(NSData *)groupId - envelope:(SSKProtoEnvelope *)envelope - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - if (groupId.length < 1) { - OWSFailDebug(@"Invalid groupId."); - return; - } - - // FIXME: https://github.com/signalapp/Signal-iOS/issues/1340 - OWSLogInfo(@"Sending group info request: %@", envelopeAddress(envelope)); - - NSString *recipientId = envelope.source; - - TSThread *thread = [TSContactThread getOrCreateThreadWithContactId:recipientId transaction:transaction]; - - OWSSyncGroupsRequestMessage *syncGroupsRequestMessage = - [[OWSSyncGroupsRequestMessage alloc] initWithThread:thread groupId:groupId]; - - [self.messageSenderJobQueue addMessage:syncGroupsRequestMessage transaction:transaction]; -} - -- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)envelope - withReceiptMessage:(SSKProtoReceiptMessage *)receiptMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!receiptMessage) { - OWSFailDebug(@"Missing receiptMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - NSArray *sentTimestamps = receiptMessage.timestamp; - - switch (receiptMessage.type) { - case SSKProtoReceiptMessageTypeDelivery: - OWSLogVerbose(@"Processing receipt message with delivery receipts."); - [self processDeliveryReceiptsFromRecipientId:envelope.source - sentTimestamps:sentTimestamps - deliveryTimestamp:@(envelope.timestamp) - transaction:transaction]; - return; - case SSKProtoReceiptMessageTypeRead: - OWSLogVerbose(@"Processing receipt message with read receipts."); - [OWSReadReceiptManager.sharedManager processReadReceiptsFromRecipientId:envelope.source - sentTimestamps:sentTimestamps - readTimestamp:envelope.timestamp]; - break; - default: - OWSLogInfo(@"Ignoring receipt message of unknown type: %d.", (int)receiptMessage.type); - return; - } -} - -- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)envelope - withCallMessage:(SSKProtoCallMessage *)callMessage -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!callMessage) { - OWSFailDebug(@"Missing callMessage."); - return; - } - - if ([callMessage hasProfileKey]) { - NSData *profileKey = [callMessage profileKey]; - NSString *recipientId = envelope.source; - [self.profileManager setProfileKeyData:profileKey forRecipientId:recipientId]; - } - - // By dispatching async, we introduce the possibility that these messages might be lost - // if the app exits before this block is executed. This is fine, since the call by - // definition will end if the app exits. - dispatch_async(dispatch_get_main_queue(), ^{ - if (callMessage.offer) { - [self.callMessageHandler receivedOffer:callMessage.offer fromCallerId:envelope.source]; - } else if (callMessage.answer) { - [self.callMessageHandler receivedAnswer:callMessage.answer fromCallerId:envelope.source]; - } else if (callMessage.iceUpdate.count > 0) { - for (SSKProtoCallMessageIceUpdate *iceUpdate in callMessage.iceUpdate) { - [self.callMessageHandler receivedIceUpdate:iceUpdate fromCallerId:envelope.source]; - } - } else if (callMessage.hangup) { - OWSLogVerbose(@"Received CallMessage with Hangup."); - [self.callMessageHandler receivedHangup:callMessage.hangup fromCallerId:envelope.source]; - } else if (callMessage.busy) { - [self.callMessageHandler receivedBusy:callMessage.busy fromCallerId:envelope.source]; - } else { - OWSProdInfoWEnvelope([OWSAnalyticsEvents messageManagerErrorCallMessageNoActionablePayload], envelope); - } - }); -} - -- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)envelope - withTypingMessage:(SSKProtoTypingMessage *)typingMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!typingMessage) { - OWSFailDebug(@"Missing typingMessage."); - return; - } - if (typingMessage.timestamp != envelope.timestamp) { - OWSFailDebug(@"typingMessage has invalid timestamp."); - return; - } - NSString *localNumber = self.tsAccountManager.localNumber; - if ([localNumber isEqualToString:envelope.source]) { - OWSLogVerbose(@"Ignoring typing indicators from self or linked device."); - return; - } else if ([self.blockingManager isRecipientIdBlocked:envelope.source] - || (typingMessage.hasGroupID && [self.blockingManager isGroupIdBlocked:typingMessage.groupID])) { - NSString *logMessage = [NSString stringWithFormat:@"Ignoring blocked message from sender: %@", envelope.source]; - if (typingMessage.hasGroupID) { - logMessage = [logMessage stringByAppendingFormat:@" in group: %@", typingMessage.groupID]; - } - OWSLogError(@"%@", logMessage); - return; - } - - TSThread *_Nullable thread; - if (typingMessage.hasGroupID) { - TSGroupThread *groupThread = [TSGroupThread threadWithGroupId:typingMessage.groupID transaction:transaction]; - - if (![groupThread isCurrentUserInGroupWithTransaction:transaction]) { - OWSLogInfo(@"Ignoring messages for left group."); - return; - } - - thread = groupThread; - } else { - thread = [TSContactThread getThreadWithContactId:envelope.source transaction:transaction]; - } - - if (!thread) { - // This isn't neccesarily an error. We might not yet know about the thread, - // in which case we don't need to display the typing indicators. - OWSLogWarn(@"Could not locate thread for typingMessage."); - return; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - switch (typingMessage.action) { - case SSKProtoTypingMessageActionStarted: - [self.typingIndicators didReceiveTypingStartedMessageInThread:thread - recipientId:envelope.source - deviceId:envelope.sourceDevice]; - break; - case SSKProtoTypingMessageActionStopped: - [self.typingIndicators didReceiveTypingStoppedMessageInThread:thread - recipientId:envelope.source - deviceId:envelope.sourceDevice]; - break; - default: - OWSFailDebug(@"Typing message has unexpected action."); - break; - } - }); -} - -- (void)handleReceivedGroupAvatarUpdateWithEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - TSGroupThread *_Nullable groupThread = - [TSGroupThread threadWithGroupId:dataMessage.group.id transaction:transaction]; - if (!groupThread) { - OWSFailDebug(@"Missing group for group avatar update"); - return; - } - - TSAttachmentPointer *_Nullable avatarPointer = - [TSAttachmentPointer attachmentPointerFromProto:dataMessage.group.avatar albumMessage:nil]; - - if (!avatarPointer) { - OWSLogWarn(@"received unsupported group avatar envelope"); - return; - } - [self.attachmentDownloads downloadAttachmentPointer:avatarPointer - success:^(NSArray *attachmentStreams) { - OWSAssertDebug(attachmentStreams.count == 1); - TSAttachmentStream *attachmentStream = attachmentStreams.firstObject; - [groupThread updateAvatarWithAttachmentStream:attachmentStream]; - } - failure:^(NSError *error) { - OWSLogError(@"failed to fetch attachments for group avatar sent at: %llu. with error: %@", - envelope.timestamp, - error); - }]; -} - -- (void)handleReceivedMediaWithEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - TSThread *_Nullable thread = [self threadForEnvelope:envelope dataMessage:dataMessage transaction:transaction]; - - if (!thread) { - OWSFailDebug(@"Ignoring media message for unknown group."); - return; - } - - TSIncomingMessage *_Nullable message = [self handleReceivedEnvelope:envelope - withDataMessage:dataMessage - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - - if (!message) { - return; - } - - [message saveWithTransaction:transaction]; - - OWSLogDebug(@"Incoming attachment message: %@.", message.debugDescription); - - [self.attachmentDownloads downloadAttachmentsForMessage:message - transaction:transaction - success:^(NSArray *attachmentStreams) { - OWSLogDebug(@"Successfully fetched attachments: %lu for message: %@.", - (unsigned long)attachmentStreams.count, - message); - } - failure:^(NSError *error) { - OWSLogError(@"Failed to fetch attachments for message: %@ with error: %@.", message, error); - }]; -} - -- (void)throws_handleIncomingEnvelope:(SSKProtoEnvelope *)envelope - withSyncMessage:(SSKProtoSyncMessage *)syncMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction - serverID:(uint64_t)serverID -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!syncMessage) { - OWSFailDebug(@"Missing syncMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - if (![LKSyncMessagesProtocol isValidSyncMessage:envelope transaction:transaction]) { - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorSyncMessageFromUnknownSource], envelope); - return; - } - - if (syncMessage.sent) { - OWSIncomingSentMessageTranscript *transcript = - [[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent transaction:transaction]; - - SSKProtoDataMessage *_Nullable dataMessage = syncMessage.sent.message; - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - - // Loki: Update profile if needed (i.e. if the sync message came from the master device) - [LKSyncMessagesProtocol updateProfileFromSyncMessageIfNeeded:dataMessage wrappedIn:envelope transaction:transaction]; - - NSString *destination = syncMessage.sent.destination; - if (dataMessage && destination.length > 0 && dataMessage.hasProfileKey) { - // If we observe a linked device sending our profile key to another - // user, we can infer that that user belongs in our profile whitelist. - if (dataMessage.group) { - [self.profileManager addGroupIdToProfileWhitelist:dataMessage.group.id]; - } else { - [self.profileManager addUserToProfileWhitelist:destination]; - } - } - - if ([self isDataMessageGroupAvatarUpdate:syncMessage.sent.message] && !syncMessage.sent.isRecipientUpdate) { - [OWSRecordTranscriptJob - processIncomingSentMessageTranscript:transcript - serverID:0 - serverTimestamp:0 - attachmentHandler:^(NSArray *attachmentStreams) { - OWSAssertDebug(attachmentStreams.count == 1); - TSAttachmentStream *attachmentStream = attachmentStreams.firstObject; - [LKStorage writeSyncWithBlock:^( - YapDatabaseReadWriteTransaction *transaction) { - TSGroupThread *_Nullable groupThread = - [TSGroupThread threadWithGroupId:dataMessage.group.id - transaction:transaction]; - if (!groupThread) { - OWSFailDebug(@"ignoring sync group avatar update for unknown group."); - return; - } - - [groupThread updateAvatarWithAttachmentStream:attachmentStream - transaction:transaction]; - }]; - } - transaction:transaction - ]; - } else { - if (transcript.isGroupUpdate) { - [LKSyncMessagesProtocol handleClosedGroupUpdateSyncMessageIfNeeded:transcript wrappedIn:envelope transaction:transaction]; - } else if (transcript.isGroupQuit) { - [LKSyncMessagesProtocol handleClosedGroupQuitSyncMessageIfNeeded:transcript wrappedIn:envelope transaction:transaction]; - } else { - [OWSRecordTranscriptJob - processIncomingSentMessageTranscript:transcript - serverID:(serverID ?: 0) - serverTimestamp:(uint64_t)envelope.serverTimestamp - attachmentHandler:^(NSArray *attachmentStreams) { - OWSLogDebug(@"successfully fetched transcript attachments: %lu", - (unsigned long)attachmentStreams.count); - } - transaction:transaction]; - } - } - } else if (syncMessage.request) { - if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeContacts) { - // We respond asynchronously because populating the sync message will - // create transactions and it's not practical (due to locking in the OWSIdentityManager) - // to plumb our transaction through. - // - // In rare cases this means we won't respond to the sync request, but that's - // acceptable. - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [[self.syncManager syncAllContacts] retainUntilComplete]; - }); - } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeGroups) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [[self.syncManager syncAllGroups] retainUntilComplete]; - }); - } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeBlocked) { - OWSLogInfo(@"Received request for block list"); - [self.blockingManager syncBlockList]; - } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeConfiguration) { - [SSKEnvironment.shared.syncManager sendConfigurationSyncMessage]; - } else { - OWSLogWarn(@"ignoring unsupported sync request message"); - } - } else if (syncMessage.blocked) { - NSArray *blockedPhoneNumbers = [syncMessage.blocked.numbers copy]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self.blockingManager setBlockedPhoneNumbers:blockedPhoneNumbers sendSyncMessage:NO]; - dispatch_async(dispatch_get_main_queue(), ^{ - [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.blockedContactsUpdated object:nil]; - }); - }); - } else if (syncMessage.read.count > 0) { - OWSLogInfo(@"Received %lu read receipt(s)", (unsigned long)syncMessage.read.count); - [OWSReadReceiptManager.sharedManager processReadReceiptsFromLinkedDevice:syncMessage.read - readTimestamp:envelope.timestamp - transaction:transaction]; - } else if (syncMessage.verified) { - OWSLogInfo(@"Received verification state for %@", syncMessage.verified.destination); - [self.identityManager throws_processIncomingSyncMessage:syncMessage.verified transaction:transaction]; - } else if (syncMessage.contacts != nil) { - [LKSyncMessagesProtocol handleContactSyncMessageIfNeeded:syncMessage wrappedIn:envelope transaction:transaction]; - } else if (syncMessage.groups != nil) { - [LKSyncMessagesProtocol handleClosedGroupSyncMessageIfNeeded:syncMessage wrappedIn:envelope transaction:transaction]; - } else if (syncMessage.openGroups != nil) { - [LKSyncMessagesProtocol handleOpenGroupSyncMessageIfNeeded:syncMessage wrappedIn:envelope transaction:transaction]; - } else { - OWSLogWarn(@"Ignoring unsupported sync message."); - } -} - -- (void)handleEndSessionMessageWithEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction]; - [LKSessionManagementProtocol handleEndSessionMessageReceivedInThread:thread using:transaction]; -} - -- (void)handleExpirationTimerUpdateMessageWithEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - TSThread *_Nullable thread = [self threadForEnvelope:envelope dataMessage:dataMessage transaction:transaction]; - if (!thread) { - OWSFailDebug(@"Ignoring expiring messages update for unknown group."); - return; - } - - OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration; - if (dataMessage.hasExpireTimer && dataMessage.expireTimer > 0) { - OWSLogInfo( - @"Expiring messages duration turned to %u for thread %@", (unsigned int)dataMessage.expireTimer, thread); - disappearingMessagesConfiguration = - [[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId - enabled:YES - durationSeconds:dataMessage.expireTimer]; - } else { - OWSLogInfo(@"Expiring messages have been turned off for thread %@", thread); - disappearingMessagesConfiguration = [[OWSDisappearingMessagesConfiguration alloc] - initWithThreadId:thread.uniqueId - enabled:NO - durationSeconds:OWSDisappearingMessagesConfigurationDefaultExpirationDuration]; - } - OWSAssertDebug(disappearingMessagesConfiguration); - [disappearingMessagesConfiguration saveWithTransaction:transaction]; - NSString *name = [dataMessage.profile displayName] ?: [SSKEnvironment.shared.profileManager profileNameForRecipientWithID:envelope.source transaction:transaction] ?: envelope.source; - - // MJK TODO - safe to remove senderTimestamp - OWSDisappearingConfigurationUpdateInfoMessage *message = - [[OWSDisappearingConfigurationUpdateInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - thread:thread - configuration:disappearingMessagesConfiguration - createdByRemoteName:name - createdInExistingGroup:NO]; - [message saveWithTransaction:transaction]; -} - -- (void)handleProfileKeyMessageWithEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - - NSString *recipientId = envelope.source; - if (!dataMessage.hasProfileKey) { - OWSFailDebug(@"Received a profile key message without a profile key from: %@.", envelopeAddress(envelope)); - return; - } - - NSData *profileKey = dataMessage.profileKey; - if (profileKey.length != kAES256_KeyByteLength) { - OWSFailDebug(@"received profile key of unexpected length: %lu, from: %@", - (unsigned long)profileKey.length, - envelopeAddress(envelope)); - return; - } - - if (dataMessage.profile == nil) { - OWSFailDebug(@"received profile key message without loki profile attached from: %@", envelopeAddress(envelope)); - return; - } - - id profileManager = SSKEnvironment.shared.profileManager; - [profileManager setProfileKeyData:profileKey forRecipientId:recipientId avatarURL:dataMessage.profile.profilePicture]; -} - -- (void)handleReceivedTextMessageWithEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - [self handleReceivedEnvelope:envelope - withDataMessage:dataMessage - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; -} - -- (void)handleGroupInfoRequest:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - if (dataMessage.group.type != SSKProtoGroupContextTypeRequestInfo) { - OWSFailDebug(@"Unexpected group message type."); - return; - } - - NSData *groupId = dataMessage.group ? dataMessage.group.id : nil; - if (!groupId) { - OWSFailDebug(@"Group info request is missing group id."); - return; - } - - OWSLogInfo(@"Received 'Request Group Info' message for group: %@ from: %@", groupId, envelope.source); - - TSGroupThread *_Nullable gThread = [TSGroupThread threadWithGroupId:dataMessage.group.id transaction:transaction]; - if (!gThread) { - OWSLogWarn(@"Unknown group: %@", groupId); - return; - } - - // Ensure sender is in the group. - if (![gThread isUserMemberInGroup:envelope.source transaction:transaction]) { - OWSLogWarn(@"Ignoring 'Request Group Info' message for non-member of group. %@ not in %@", - envelope.source, - gThread.groupModel.groupMemberIds); - return; - } - - // Ensure we are in the group. - if (![gThread isCurrentUserInGroupWithTransaction:transaction]) { - OWSLogWarn(@"Ignoring 'Request Group Info' message for group we no longer belong to."); - return; - } - - NSString *updateGroupInfo = - [gThread.groupModel getInfoStringAboutUpdateTo:gThread.groupModel contactsManager:self.contactsManager]; - - uint32_t expiresInSeconds = [gThread disappearingMessagesDurationWithTransaction:transaction]; - TSOutgoingMessage *message = [TSOutgoingMessage outgoingMessageInThread:gThread - groupMetaMessage:TSGroupMetaMessageUpdate - expiresInSeconds:expiresInSeconds]; - - [message updateWithCustomMessage:updateGroupInfo transaction:transaction]; - // Only send this group update to the requester. - [message updateWithSendingToSingleGroupRecipient:envelope.source transaction:transaction]; - - if (gThread.groupModel.groupImage) { - NSData *_Nullable data = UIImagePNGRepresentation(gThread.groupModel.groupImage); - OWSAssertDebug(data); - if (data) { - DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithData:data fileExtension:@"png"]; - [self.messageSenderJobQueue addMediaMessage:message - dataSource:dataSource - contentType:OWSMimeTypeImagePng - sourceFilename:nil - caption:nil - albumMessageId:nil - isTemporaryAttachment:YES]; - } - } else { - [self.messageSenderJobQueue addMessage:message transaction:transaction]; - } -} - -- (TSIncomingMessage *_Nullable)handleReceivedEnvelope:(SSKProtoEnvelope *)envelope - withDataMessage:(SSKProtoDataMessage *)dataMessage - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return nil; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return nil; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return nil; - } - - uint64_t timestamp = envelope.timestamp; - NSString *body = dataMessage.body; - NSData *groupId = dataMessage.group ? dataMessage.group.id : nil; - OWSContact *_Nullable contact = [OWSContacts contactForDataMessage:dataMessage transaction:transaction]; - NSNumber *_Nullable serverTimestamp = (envelope.hasServerTimestamp ? @(envelope.serverTimestamp) : nil); - - if (dataMessage.group.type == SSKProtoGroupContextTypeRequestInfo) { - [self handleGroupInfoRequest:envelope dataMessage:dataMessage transaction:transaction]; - return nil; - } - - /* - // Loki: Update device links in a blocking way - // FIXME: This is horrible for performance - // FIXME: ======== - // The envelope source is set during UD decryption - if ([ECKeyPair isValidHexEncodedPublicKeyWithCandidate:envelope.source] && dataMessage.publicChatInfo == nil // Handled in LokiPublicChatPoller for open group messages - && envelope.type != SSKProtoEnvelopeTypeClosedGroupCiphertext) { - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - [[LKMultiDeviceProtocol updateDeviceLinksIfNeededForPublicKey:envelope.source transaction:transaction].ensureOn(queue, ^() { - dispatch_semaphore_signal(semaphore); - }).catchOn(queue, ^(NSError *error) { - dispatch_semaphore_signal(semaphore); - }) retainUntilComplete]; - dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)); - } - // FIXME: ======== - */ - - if (groupId.length > 0) { - NSMutableSet *newMemberIds = [NSMutableSet setWithArray:dataMessage.group.members]; - NSMutableSet *removedMemberIds = [NSMutableSet new]; - for (NSString *recipientId in newMemberIds) { - if (![ECKeyPair isValidHexEncodedPublicKeyWithCandidate:recipientId]) { - OWSLogVerbose(@"Incoming group update has invalid group member: %@", [self descriptionForEnvelope:envelope]); - OWSFailDebug(@"Incoming group update has invalid group member"); - return nil; - } - } - - NSString *senderMasterPublicKey = ([LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source); - NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - NSString *userMasterPublicKey = ([LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userPublicKey in:transaction] ?: userPublicKey); - - // Group messages create the group if it doesn't already exist. - // - // We distinguish between the old group state (if any) and the new group state. - TSGroupThread *_Nullable oldGroupThread = [TSGroupThread threadWithGroupId:groupId transaction:transaction]; - if (oldGroupThread) { - // Loki: Determine removed members - removedMemberIds = [NSMutableSet setWithArray:oldGroupThread.groupModel.groupMemberIds]; - [removedMemberIds minusSet:newMemberIds]; - // TODO: Below is the original code. Is it safe that we modified it like this? - // ======== - // Don't trust other clients; ensure all known group members remain in the - // group unless it is a "quit" message in which case we should only remove - // the quiting member below. -// [newMemberIds addObjectsFromArray:oldGroupThread.groupModel.groupMemberIds]; - // ======== - } - - [LKSessionMetaProtocol updateProfileKeyIfNeededForPublicKey:senderMasterPublicKey using:dataMessage]; - - [LKSessionMetaProtocol updateDisplayNameIfNeededForPublicKey:senderMasterPublicKey using:dataMessage transaction:transaction]; - - switch (dataMessage.group.type) { - case SSKProtoGroupContextTypeUpdate: { - if (oldGroupThread != nil && oldGroupThread.groupModel.groupType == closedGroup - && [LKClosedGroupsProtocol shouldIgnoreClosedGroupUpdateMessage:dataMessage inThread:oldGroupThread wrappedIn:envelope]) { - return nil; - } - // Ensures that the thread exists but don't update it. - TSGroupThread *newGroupThread = - [TSGroupThread getOrCreateThreadWithGroupId:groupId groupType:oldGroupThread.groupModel.groupType transaction:transaction]; - - TSGroupModel *newGroupModel = [[TSGroupModel alloc] initWithTitle:dataMessage.group.name - memberIds:newMemberIds.allObjects - image:oldGroupThread.groupModel.groupImage - groupId:dataMessage.group.id - groupType:oldGroupThread.groupModel.groupType - adminIds:dataMessage.group.admins]; - newGroupModel.removedMembers = removedMemberIds; - NSString *updateGroupInfo = [newGroupThread.groupModel getInfoStringAboutUpdateTo:newGroupModel - contactsManager:self.contactsManager]; - - [newGroupThread setGroupModel:newGroupModel withTransaction:transaction]; - - BOOL wasCurrentUserRemovedFromGroup = [removedMemberIds containsObject:userMasterPublicKey]; - if (!wasCurrentUserRemovedFromGroup) { - [LKClosedGroupsProtocol establishSessionsIfNeededWithClosedGroupMembers:newMemberIds.allObjects transaction:transaction]; - } - - [[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:dataMessage.expireTimer - thread:newGroupThread - createdByRemoteRecipientId:nil - createdInExistingGroup:YES - transaction:transaction]; - - // MJK TODO - should be safe to remove senderTimestamp - TSInfoMessage *infoMessage = [[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:newGroupThread - messageType:TSInfoMessageTypeGroupUpdate - customMessage:updateGroupInfo]; - [infoMessage saveWithTransaction:transaction]; - - // If we were the one that was removed then we need to leave the group - if (wasCurrentUserRemovedFromGroup) { - [newGroupThread leaveGroupWithTransaction:transaction]; - } - - return nil; - } - case SSKProtoGroupContextTypeQuit: { - if (!oldGroupThread) { - OWSLogWarn(@"Ignoring quit group message from unknown group."); - return nil; - } - newMemberIds = [NSMutableSet setWithArray:oldGroupThread.groupModel.groupMemberIds]; - [newMemberIds removeObject:senderMasterPublicKey]; - oldGroupThread.groupModel.groupMemberIds = [newMemberIds.allObjects mutableCopy]; - [oldGroupThread saveWithTransaction:transaction]; - - NSString *nameString = [SSKEnvironment.shared.profileManager profileNameForRecipientWithID:senderMasterPublicKey transaction:transaction] ?: - [self.contactsManager displayNameForPhoneIdentifier:senderMasterPublicKey transaction:transaction]; - NSString *updateGroupInfo = - [NSString stringWithFormat:NSLocalizedString(@"GROUP_MEMBER_LEFT", @""), nameString]; - // MJK TODO - should be safe to remove senderTimestamp - [[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:oldGroupThread - messageType:TSInfoMessageTypeGroupUpdate - customMessage:updateGroupInfo] saveWithTransaction:transaction]; - - // If we were the one that quit then we need to leave the group (only relevant for slave - // devices in a multi device context) - // TODO: This needs more documentation - if (![newMemberIds containsObject:userMasterPublicKey]) { - [oldGroupThread leaveGroupWithTransaction:transaction]; - } - - return nil; - } - case SSKProtoGroupContextTypeDeliver: { - if (!oldGroupThread) { - OWSFailDebug(@"Ignoring deliver group message from unknown group."); - return nil; - } - - [[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:dataMessage.expireTimer - thread:oldGroupThread - createdByRemoteRecipientId:senderMasterPublicKey - createdInExistingGroup:NO - transaction:transaction]; - - TSQuotedMessage *_Nullable quotedMessage = [TSQuotedMessage quotedMessageForDataMessage:dataMessage - thread:oldGroupThread - transaction:transaction]; - - NSError *linkPreviewError; - OWSLinkPreview *_Nullable linkPreview = - [OWSLinkPreview buildValidatedLinkPreviewWithDataMessage:dataMessage - body:body - transaction:transaction - error:&linkPreviewError]; - if (linkPreviewError && ![OWSLinkPreview isNoPreviewError:linkPreviewError]) { - OWSLogError(@"linkPreviewError: %@", linkPreviewError); - } - - OWSLogDebug(@"Incoming message from: %@ for group: %@ with timestamp: %lu", - envelopeAddress(envelope), - groupId, - (unsigned long)timestamp); - - // Legit usage of senderTimestamp when creating an incoming group message record - TSIncomingMessage *incomingMessage = - [[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:timestamp - inThread:oldGroupThread - authorId:senderMasterPublicKey - sourceDeviceId:envelope.sourceDevice - messageBody:body - attachmentIds:@[] - expiresInSeconds:dataMessage.expireTimer - quotedMessage:quotedMessage - contactShare:contact - linkPreview:linkPreview - serverTimestamp:serverTimestamp - wasReceivedByUD:wasReceivedByUD]; - - // For open group messages, use the server timestamp as the received timestamp - if (oldGroupThread.isPublicChat) { - [incomingMessage setServerTimestampToReceivedTimestamp:(uint64_t)envelope.serverTimestamp]; - } - - // Loki: Set open group server ID if needed - if (dataMessage.publicChatInfo != nil && dataMessage.publicChatInfo.hasServerID) { - incomingMessage.openGroupServerMessageID = dataMessage.publicChatInfo.serverID; - } - - NSArray *attachmentPointers = - [TSAttachmentPointer attachmentPointersFromProtos:dataMessage.attachments - albumMessage:incomingMessage]; - for (TSAttachmentPointer *pointer in attachmentPointers) { - [pointer saveWithTransaction:transaction]; - [incomingMessage.attachmentIds addObject:pointer.uniqueId]; - } - - if (body.length == 0 && attachmentPointers.count < 1 && !contact) { - OWSLogWarn(@"Ignoring empty incoming message from: %@ for group: %@ with timestamp: %lu.", - senderMasterPublicKey, - groupId, - (unsigned long)timestamp); - return nil; - } - - // Loki: Cache the user public key (for mentions) - dispatch_async(dispatch_get_main_queue(), ^{ - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - [LKMentionsManager populateUserPublicKeyCacheIfNeededFor:oldGroupThread.uniqueId in:transaction]; - [LKMentionsManager cache:incomingMessage.authorId for:oldGroupThread.uniqueId]; - }]; - }); - - [self finalizeIncomingMessage:incomingMessage - thread:oldGroupThread - masterThread:oldGroupThread - envelope:envelope - transaction:transaction]; - - // Loki: Map the message ID to the message server ID if needed - if (dataMessage.publicChatInfo != nil && dataMessage.publicChatInfo.hasServerID) { - [self.primaryStorage setIDForMessageWithServerID:dataMessage.publicChatInfo.serverID to:incomingMessage.uniqueId in:transaction]; - } - - return incomingMessage; - } - default: { - OWSLogWarn(@"Ignoring unknown group message type: %d.", (int)dataMessage.group.type); - return nil; - } - } - } else { - - // Loki: A message from a slave device should appear as if it came from the master device; the underlying - // friend request logic, however, should still be specific to the slave device. - - NSString *publicKey = envelope.source; - NSString *masterPublicKey = ([LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source); - TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:publicKey transaction:transaction]; - TSContactThread *masterThread = [TSContactThread getOrCreateThreadWithContactId:masterPublicKey transaction:transaction]; - - OWSLogDebug(@"Incoming message from: %@ with timestamp: %lu.", publicKey, (unsigned long)timestamp); - - [[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:dataMessage.expireTimer - thread:masterThread - createdByRemoteRecipientId:publicKey - createdInExistingGroup:NO - transaction:transaction]; - - TSQuotedMessage *_Nullable quotedMessage = [TSQuotedMessage quotedMessageForDataMessage:dataMessage - thread:masterThread - transaction:transaction]; - - NSError *linkPreviewError; - OWSLinkPreview *_Nullable linkPreview = - [OWSLinkPreview buildValidatedLinkPreviewWithDataMessage:dataMessage - body:body - transaction:transaction - error:&linkPreviewError]; - if (linkPreviewError && ![OWSLinkPreview isNoPreviewError:linkPreviewError]) { - OWSLogError(@"linkPreviewError: %@", linkPreviewError); - } - - // Legit usage of senderTimestamp when creating incoming message from received envelope - TSIncomingMessage *incomingMessage = - [[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:timestamp - inThread:masterThread - authorId:masterThread.contactIdentifier - sourceDeviceId:envelope.sourceDevice - messageBody:body - attachmentIds:@[] - expiresInSeconds:dataMessage.expireTimer - quotedMessage:quotedMessage - contactShare:contact - linkPreview:linkPreview - serverTimestamp:serverTimestamp - wasReceivedByUD:wasReceivedByUD]; - - [LKSessionMetaProtocol updateProfileKeyIfNeededForPublicKey:masterPublicKey using:dataMessage]; - - [LKSessionMetaProtocol updateDisplayNameIfNeededForPublicKey:masterPublicKey using:dataMessage transaction:transaction]; - - NSArray *attachmentPointers = - [TSAttachmentPointer attachmentPointersFromProtos:dataMessage.attachments albumMessage:incomingMessage]; - for (TSAttachmentPointer *pointer in attachmentPointers) { - [pointer saveWithTransaction:transaction]; - [incomingMessage.attachmentIds addObject:pointer.uniqueId]; - } - - if (body.length == 0 && attachmentPointers.count < 1 && !contact) { return nil; } - - [self finalizeIncomingMessage:incomingMessage - thread:thread - masterThread:thread - envelope:envelope - transaction:transaction]; - - return incomingMessage; - } -} - -- (void)finalizeIncomingMessage:(TSIncomingMessage *)incomingMessage - thread:(TSThread *)thread - masterThread:(TSThread *)masterThread - envelope:(SSKProtoEnvelope *)envelope - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!thread) { - OWSFailDebug(@"Missing thread."); - return; - } - if (!incomingMessage) { - OWSFailDebug(@"Missing incomingMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - [incomingMessage saveWithTransaction:transaction]; - - // Any messages sent from the current user - from this device or another - should be automatically marked as read. - if ([(masterThread.contactIdentifier ?: envelope.source) isEqualToString:self.tsAccountManager.localNumber]) { - // Don't send a read receipt for messages sent by ourselves. - [incomingMessage markAsReadAtTimestamp:envelope.timestamp sendReadReceipt:NO transaction:transaction]; - } - - // Download the "non-message body" attachments. - NSMutableArray *otherAttachmentIds = [incomingMessage.allAttachmentIds mutableCopy]; - if (incomingMessage.attachmentIds) { - [otherAttachmentIds removeObjectsInArray:incomingMessage.attachmentIds]; - } - for (NSString *attachmentId in otherAttachmentIds) { - TSAttachment *_Nullable attachment = - [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction]; - if (![attachment isKindOfClass:[TSAttachmentPointer class]]) { - OWSLogInfo(@"Skipping attachment stream."); - continue; - } - TSAttachmentPointer *_Nullable attachmentPointer = (TSAttachmentPointer *)attachment; - - OWSLogDebug(@"Downloading attachment for message: %lu", (unsigned long)incomingMessage.timestamp); - - // Use a separate download for each attachment so that: - // - // * We update the message as each comes in. - // * Failures don't interfere with successes. - [self.attachmentDownloads downloadAttachmentPointer:attachmentPointer - success:^(NSArray *attachmentStreams) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - TSAttachmentStream *_Nullable attachmentStream = attachmentStreams.firstObject; - OWSAssertDebug(attachmentStream); - if (attachmentStream && incomingMessage.quotedMessage.thumbnailAttachmentPointerId.length > 0 && - [attachmentStream.uniqueId - isEqualToString:incomingMessage.quotedMessage.thumbnailAttachmentPointerId]) { - [incomingMessage setQuotedMessageThumbnailAttachmentStream:attachmentStream]; - [incomingMessage saveWithTransaction:transaction]; - } else { - // We touch the message to trigger redraw of any views displaying it, - // since the attachment might be a contact avatar, etc. - [incomingMessage touchWithTransaction:transaction]; - } - }]; - } - failure:^(NSError *error) { - OWSLogWarn(@"Failed to download attachment for message: %lu with error: %@.", - (unsigned long)incomingMessage.timestamp, - error); - }]; - } - - // In case we already have a read receipt for this new message (this happens sometimes). - [OWSReadReceiptManager.sharedManager applyEarlyReadReceiptsForIncomingMessage:incomingMessage - transaction:transaction]; - - // Update thread preview in inbox - [masterThread touchWithTransaction:transaction]; - - if (CurrentAppContext().isMainApp) { - [SSKEnvironment.shared.notificationsManager notifyUserForIncomingMessage:incomingMessage inThread:masterThread transaction:transaction]; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - [self.typingIndicators didReceiveIncomingMessageInThread:masterThread - recipientId:(masterThread.contactIdentifier ?: envelope.source) - deviceId:envelope.sourceDevice]; - }); -} - -#pragma mark - helpers - -- (BOOL)isDataMessageGroupAvatarUpdate:(SSKProtoDataMessage *)dataMessage -{ - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return NO; - } - - return (dataMessage.group != nil && dataMessage.group.type == SSKProtoGroupContextTypeUpdate - && dataMessage.group.avatar != nil); -} - -/** - * @returns - * Group or Contact thread for message, creating a new contact thread if necessary, - * but never creating a new group thread. - */ -- (nullable TSThread *)threadForEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return nil; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return nil; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return nil; - } - - if (dataMessage.group) { - NSData *groupId = dataMessage.group.id; - OWSAssertDebug(groupId.length > 0); - TSGroupThread *_Nullable groupThread = [TSGroupThread threadWithGroupId:groupId transaction:transaction]; - // This method should only be called from a code path that has already verified - // that this is a "known" group. - OWSAssertDebug(groupThread); - return groupThread; - } else { - return [TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction]; - } -} - -#pragma mark - - -- (void)checkForUnknownLinkedDevice:(SSKProtoEnvelope *)envelope - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(envelope); - OWSAssertDebug(transaction); - - NSString *localNumber = self.tsAccountManager.localNumber; - if (![localNumber isEqualToString:envelope.source]) { - return; - } - - // Consult the device list cache we use for message sending - // whether or not we know about this linked device. - SignalRecipient *_Nullable recipient = - [SignalRecipient registeredRecipientForRecipientId:localNumber mustHaveDevices:NO transaction:transaction]; - if (!recipient) { - - } else { - BOOL isRecipientDevice = [recipient.devices containsObject:@(envelope.sourceDevice)]; - if (!isRecipientDevice) { - OWSLogInfo(@"Message received from unknown linked device; adding to local SignalRecipient: %lu.", - (unsigned long) envelope.sourceDevice); - - [recipient updateRegisteredRecipientWithDevicesToAdd:@[ @(envelope.sourceDevice) ] - devicesToRemove:nil - transaction:transaction]; - } - } - - // Consult the device list cache we use for the "linked device" UI - // whether or not we know about this linked device. - NSMutableSet *deviceIdSet = [NSMutableSet new]; - for (OWSDevice *device in [OWSDevice currentDevicesWithTransaction:transaction]) { - [deviceIdSet addObject:@(device.deviceId)]; - } - BOOL isInDeviceList = [deviceIdSet containsObject:@(envelope.sourceDevice)]; - if (!isInDeviceList) { - OWSLogInfo(@"Message received from unknown linked device; refreshing device list: %lu.", - (unsigned long) envelope.sourceDevice); - - [OWSDevicesService refreshDevices]; - dispatch_async(dispatch_get_main_queue(), ^{ - [self.profileManager fetchLocalUsersProfile]; - }); - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageReceiver.h b/SignalServiceKit/src/Messages/OWSMessageReceiver.h deleted file mode 100644 index b9c103090..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageReceiver.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class OWSStorage; - -// This class is used to write incoming (encrypted, unprocessed) -// messages to a durable queue and then decrypt them in the order -// in which they were received. Successfully decrypted messages -// are forwarded to OWSBatchMessageProcessor. -@interface OWSMessageReceiver : NSObject - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -+ (NSString *)databaseExtensionName; -+ (void)asyncRegisterDatabaseExtension:(OWSStorage *)storage; - -- (void)handleReceivedEnvelopeData:(NSData *)envelopeData; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageReceiver.m b/SignalServiceKit/src/Messages/OWSMessageReceiver.m deleted file mode 100644 index 0852dd03f..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageReceiver.m +++ /dev/null @@ -1,514 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageReceiver.h" -#import "AppContext.h" -#import "AppReadiness.h" -#import "NSArray+OWS.h" -#import "NotificationsProtocol.h" -#import "OWSBackgroundTask.h" -#import "OWSBatchMessageProcessor.h" -#import "OWSMessageDecrypter.h" -#import "OWSPrimaryStorage+Loki.h" -#import "OWSQueues.h" -#import "OWSStorage.h" -#import "OWSIdentityManager.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSDatabaseView.h" -#import "TSErrorMessage.h" -#import "TSYapDatabaseObject.h" -#import -#import -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSMessageDecryptJob : TSYapDatabaseObject - -@property (nonatomic, readonly) NSDate *createdAt; -@property (nonatomic, readonly) NSData *envelopeData; -@property (nonatomic, readonly, nullable) SSKProtoEnvelope *envelopeProto; - -- (instancetype)initWithEnvelopeData:(NSData *)envelopeData NS_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; -- (instancetype)initWithUniqueId:(NSString *_Nullable)uniqueId NS_UNAVAILABLE; - -@end - -#pragma mark - - -@implementation OWSMessageDecryptJob - -+ (NSString *)collection -{ - return @"OWSMessageProcessingJob"; -} - -- (instancetype)initWithEnvelopeData:(NSData *)envelopeData -{ - OWSAssertDebug(envelopeData); - - self = [super initWithUniqueId:[NSUUID new].UUIDString]; - if (!self) { - return self; - } - - _envelopeData = envelopeData; - _createdAt = [NSDate new]; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (nullable SSKProtoEnvelope *)envelopeProto -{ - NSError *error; - SSKProtoEnvelope *_Nullable envelope = [SSKProtoEnvelope parseData:self.envelopeData error:&error]; - if (error || envelope == nil) { - OWSFailDebug(@"failed to parse envelope with error: %@", error); - return nil; - } - - return envelope; -} - -@end - -#pragma mark - Finder - -NSString *const OWSMessageDecryptJobFinderExtensionName = @"OWSMessageProcessingJobFinderExtensionName2"; -NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessingJobFinderExtensionGroup2"; - -@interface OWSMessageDecryptJobFinder : NSObject - -@end - -#pragma mark - - -@interface OWSMessageDecryptJobFinder () - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@end - -#pragma mark - - -@implementation OWSMessageDecryptJobFinder - -- (instancetype)initWithDBConnection:(YapDatabaseConnection *)dbConnection -{ - OWSSingletonAssert(); - - self = [super init]; - if (!self) { - return self; - } - - _dbConnection = dbConnection; - - [OWSMessageDecryptJobFinder registerLegacyClasses]; - - return self; -} - -- (OWSMessageDecryptJob *_Nullable)nextJob -{ - __block OWSMessageDecryptJob *_Nullable job = nil; - - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - YapDatabaseViewTransaction *viewTransaction = [transaction ext:OWSMessageDecryptJobFinderExtensionName]; - OWSAssertDebug(viewTransaction != nil); - job = [viewTransaction firstObjectInGroup:OWSMessageDecryptJobFinderExtensionGroup]; - }]; - - return job; -} - -- (void)addJobForEnvelopeData:(NSData *)envelopeData -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - OWSMessageDecryptJob *job = [[OWSMessageDecryptJob alloc] initWithEnvelopeData:envelopeData]; - [job saveWithTransaction:transaction]; - }]; -} - -- (void)removeJobWithId:(NSString *)uniqueId -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [transaction removeObjectForKey:uniqueId inCollection:[OWSMessageDecryptJob collection]]; - }]; -} - -+ (YapDatabaseView *)databaseExtension -{ - YapDatabaseViewSorting *sorting = - [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, - NSString *group, - NSString *collection1, - NSString *key1, - id object1, - NSString *collection2, - NSString *key2, - id object2) { - - if (![object1 isKindOfClass:[OWSMessageDecryptJob class]]) { - OWSFailDebug(@"Unexpected object: %@ in collection: %@", [object1 class], collection1); - return NSOrderedSame; - } - OWSMessageDecryptJob *job1 = (OWSMessageDecryptJob *)object1; - - if (![object2 isKindOfClass:[OWSMessageDecryptJob class]]) { - OWSFailDebug(@"Unexpected object: %@ in collection: %@", [object2 class], collection2); - return NSOrderedSame; - } - OWSMessageDecryptJob *job2 = (OWSMessageDecryptJob *)object2; - - return [job1.createdAt compare:job2.createdAt]; - }]; - - YapDatabaseViewGrouping *grouping = - [YapDatabaseViewGrouping withObjectBlock:^NSString *_Nullable(YapDatabaseReadTransaction *_Nonnull transaction, - NSString *_Nonnull collection, - NSString *_Nonnull key, - id _Nonnull object) { - if (![object isKindOfClass:[OWSMessageDecryptJob class]]) { - OWSFailDebug(@"Unexpected object: %@ in collection: %@", object, collection); - return nil; - } - - // Arbitrary string - all in the same group. We're only using the view for sorting. - return OWSMessageDecryptJobFinderExtensionGroup; - }]; - - YapDatabaseViewOptions *options = [YapDatabaseViewOptions new]; - options.allowedCollections = - [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[OWSMessageDecryptJob collection]]]; - - return [[YapDatabaseAutoView alloc] initWithGrouping:grouping sorting:sorting versionTag:@"1" options:options]; -} - -+ (void)registerLegacyClasses -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // We've renamed OWSMessageProcessingJob to OWSMessageDecryptJob. - [NSKeyedUnarchiver setClass:[OWSMessageDecryptJob class] forClassName:[OWSMessageDecryptJob collection]]; - }); -} - -+ (void)asyncRegisterDatabaseExtension:(OWSStorage *)storage -{ - [self registerLegacyClasses]; - - YapDatabaseView *existingView = [storage registeredExtension:OWSMessageDecryptJobFinderExtensionName]; - if (existingView) { - OWSFailDebug(@"%@ was already initialized.", OWSMessageDecryptJobFinderExtensionName); - // already initialized - return; - } - [storage asyncRegisterExtension:[self databaseExtension] withName:OWSMessageDecryptJobFinderExtensionName]; -} - -@end - -#pragma mark - Queue Processing - -@interface OWSMessageDecryptQueue : NSObject - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (nonatomic, readonly) OWSMessageDecryptJobFinder *finder; -@property (nonatomic) BOOL isDrainingQueue; - -- (instancetype)initWithDBConnection:(YapDatabaseConnection *)dbConnection - finder:(OWSMessageDecryptJobFinder *)finder NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -@end - -#pragma mark - - -@implementation OWSMessageDecryptQueue - -- (instancetype)initWithDBConnection:(YapDatabaseConnection *)dbConnection finder:(OWSMessageDecryptJobFinder *)finder -{ - OWSSingletonAssert(); - - self = [super init]; - if (!self) { - return self; - } - - _dbConnection = dbConnection; - _finder = finder; - _isDrainingQueue = NO; - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - if (CurrentAppContext().isMainApp) { - [self drainQueue]; - } - }]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(registrationStateDidChange:) - name:RegistrationStateDidChangeNotification - object:nil]; - - return self; -} - -#pragma mark - Singletons - -- (OWSMessageDecrypter *)messageDecrypter -{ - OWSAssertDebug(SSKEnvironment.shared.messageDecrypter); - - return SSKEnvironment.shared.messageDecrypter; -} - -- (OWSBatchMessageProcessor *)batchMessageProcessor -{ - OWSAssertDebug(SSKEnvironment.shared.batchMessageProcessor); - - return SSKEnvironment.shared.batchMessageProcessor; -} - -- (TSAccountManager *)tsAccountManager -{ - OWSAssertDebug(SSKEnvironment.shared.tsAccountManager); - - return SSKEnvironment.shared.tsAccountManager; -} - -#pragma mark - Notifications - -- (void)registrationStateDidChange:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - if (CurrentAppContext().isMainApp) { - [self drainQueue]; - } - }]; -} - -#pragma mark - Instance methods - -- (dispatch_queue_t)serialQueue -{ - static dispatch_queue_t queue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - queue = dispatch_queue_create("org.whispersystems.message.decrypt", DISPATCH_QUEUE_SERIAL); - }); - return queue; -} - -- (void)enqueueEnvelopeData:(NSData *)envelopeData -{ - [self.finder addJobForEnvelopeData:envelopeData]; -} - -- (void)drainQueue -{ - OWSAssertDebug(AppReadiness.isAppReady); - - if (!CurrentAppContext().isMainApp) { return; } - if (!self.tsAccountManager.isRegisteredAndReady) { return; } - - dispatch_async(self.serialQueue, ^{ - if (self.isDrainingQueue) { return; } - self.isDrainingQueue = YES; - [self drainQueueWorkStep]; - }); -} - -- (void)drainQueueWorkStep -{ - AssertOnDispatchQueue(self.serialQueue); - - OWSMessageDecryptJob *_Nullable job = [self.finder nextJob]; - - if (!job) { - self.isDrainingQueue = NO; - OWSLogVerbose(@"Queue is drained."); - return; - } - - __block OWSBackgroundTask *_Nullable backgroundTask = - [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - - [self processJob:job - completion:^(BOOL success) { - [self.finder removeJobWithId:job.uniqueId]; - OWSLogVerbose(@"%@ job. %lu jobs left.", - success ? @"decrypted" : @"failed to decrypt", - (unsigned long)[OWSMessageDecryptJob numberOfKeysInCollection]); - [self drainQueueWorkStep]; - OWSAssertDebug(backgroundTask); - backgroundTask = nil; - }]; -} - -- (BOOL)wasReceivedByUD:(SSKProtoEnvelope *)envelope -{ - return (envelope.type == SSKProtoEnvelopeTypeUnidentifiedSender && (!envelope.hasSource || envelope.source.length < 1)); -} - -- (void)processJob:(OWSMessageDecryptJob *)job completion:(void (^)(BOOL))completion -{ - AssertOnDispatchQueue(self.serialQueue); - OWSAssertDebug(job); - - SSKProtoEnvelope *_Nullable envelope = job.envelopeProto; - - if (!envelope) { - OWSFailDebug(@"Couldn't parse proto."); - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread]; - [SSKEnvironment.shared.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage - transaction:transaction]; - }]; - - dispatch_async(self.serialQueue, ^{ - completion(NO); - }); - - return; - } - - // We use the original envelope for this check; - // the decryption process might rewrite the envelope. - BOOL wasReceivedByUD = [self wasReceivedByUD:envelope]; - - [self.messageDecrypter decryptEnvelope:envelope - envelopeData:job.envelopeData - successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) { - OWSAssertDebug(transaction); - - if ([LKSessionMetaProtocol shouldSkipMessageDecryptResult:result wrappedIn:envelope]) { - dispatch_async(self.serialQueue, ^{ - completion(YES); - }); - return; - } - - // We persist the decrypted envelope data in the same transaction within which - // it was decrypted to prevent data loss. If the new job isn't persisted, - // the session state side effects of its decryption are also rolled back. - // - // NOTE: We use envelopeData from the decrypt result, not job.envelopeData, - // since the envelope may be altered by the decryption process in the UD case. - [self.batchMessageProcessor enqueueEnvelopeData:result.envelopeData - plaintextData:result.plaintextData - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - - dispatch_async(self.serialQueue, ^{ - completion(YES); - }); - } - failureBlock:^{ - dispatch_async(self.serialQueue, ^{ - completion(NO); - }); - }]; -} - -@end - -#pragma mark - OWSMessageReceiver - -@interface OWSMessageReceiver () - -@property (nonatomic, readonly) OWSMessageDecryptQueue *processingQueue; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@end - -#pragma mark - - -@implementation OWSMessageReceiver - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - OWSSingletonAssert(); - - self = [super init]; - - if (!self) { - return self; - } - - // For coherency we use the same dbConnection to persist and read the unprocessed envelopes - YapDatabaseConnection *dbConnection = [primaryStorage newDatabaseConnection]; - OWSMessageDecryptJobFinder *finder = [[OWSMessageDecryptJobFinder alloc] initWithDBConnection:dbConnection]; - OWSMessageDecryptQueue *processingQueue = [[OWSMessageDecryptQueue alloc] initWithDBConnection:dbConnection finder:finder]; - - _processingQueue = processingQueue; - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - if (CurrentAppContext().isMainApp) { - [self.processingQueue drainQueue]; - } - }]; - - return self; -} - -#pragma mark - class methods - -+ (NSString *)databaseExtensionName -{ - return OWSMessageDecryptJobFinderExtensionName; -} - -+ (void)asyncRegisterDatabaseExtension:(OWSStorage *)storage -{ - [OWSMessageDecryptJobFinder asyncRegisterDatabaseExtension:storage]; -} - -#pragma mark - instance methods - -- (void)handleReceivedEnvelopeData:(NSData *)envelopeData -{ - if (envelopeData.length < 1) { - OWSFailDebug(@"Received an empty envelope."); - return; - } - - // Drop any too-large messages on the floor. Well behaving clients should never send them. - NSUInteger kMaxEnvelopeByteCount = 250 * 1024; - if (envelopeData.length > kMaxEnvelopeByteCount) { - OWSProdError([OWSAnalyticsEvents messageReceiverErrorOversizeMessage]); - OWSFailDebug(@"Received an oversized message."); - return; - } - - // Take note of any messages larger than we expect, but still process them. - // This likely indicates a misbehaving sending client. - NSUInteger kLargeEnvelopeWarningByteCount = 25 * 1024; - if (envelopeData.length > kLargeEnvelopeWarningByteCount) { - OWSProdError([OWSAnalyticsEvents messageReceiverErrorLargeMessage]); - OWSFailDebug(@"Received an unexpectedly large message."); - } - - [self.processingQueue enqueueEnvelopeData:envelopeData]; - [self.processingQueue drainQueue]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageSend.swift b/SignalServiceKit/src/Messages/OWSMessageSend.swift deleted file mode 100644 index b9324851f..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageSend.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import SessionMetadataKit - -// Corresponds to a single effort to send a message to a given recipient, -// which may span multiple attempts. Note that group messages may be sent -// to multiple recipients and therefore require multiple instances of -// OWSMessageSend. -@objc -public class OWSMessageSend: NSObject { - @objc - public let message: TSOutgoingMessage - - // thread may be nil if message is an OWSOutgoingSyncMessage. - @objc - public let thread: TSThread? - - @objc - public let recipient: SignalRecipient - - private static let kMaxRetriesPerRecipient: Int = 1 // Loki: We have our own retrying - - @objc - public var remainingAttempts = OWSMessageSend.kMaxRetriesPerRecipient - - // We "fail over" to REST sends after _any_ error sending - // via the web socket. - @objc - public var hasWebsocketSendFailed = false - - @objc - public var udAccess: OWSUDAccess? - - @objc - public var senderCertificate: SMKSenderCertificate? - - @objc - public let localNumber: String - - @objc - public let isLocalNumber: Bool - - @objc - public let success: () -> Void - - @objc - public let failure: (Error) -> Void - - @objc - public init(message: TSOutgoingMessage, - thread: TSThread?, - recipient: SignalRecipient, - senderCertificate: SMKSenderCertificate?, - udAccess: OWSUDAccess?, - localNumber: String, - success: @escaping () -> Void, - failure: @escaping (Error) -> Void) { - self.message = message - self.thread = thread - self.recipient = recipient - self.localNumber = localNumber - self.senderCertificate = senderCertificate - self.udAccess = udAccess - - if let recipientId = recipient.uniqueId { - self.isLocalNumber = localNumber == recipientId - } else { - owsFailDebug("SignalRecipient missing recipientId") - self.isLocalNumber = false - } - - self.success = success - self.failure = failure - } - - @objc - public var isUDSend: Bool { - return udAccess != nil && senderCertificate != nil - } - - @objc - public func disableUD() { - Logger.verbose("\(recipient.recipientId)") - udAccess = nil - } - - @objc - public func setHasUDAuthFailed() { - Logger.verbose("\(recipient.recipientId)") - // We "fail over" to non-UD sends after auth errors sending via UD. - disableUD() - } -} diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.h b/SignalServiceKit/src/Messages/OWSMessageSender.h deleted file mode 100644 index 374056f9d..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageSender.h +++ /dev/null @@ -1,121 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "DataSource.h" -#import "TSContactThread.h" - -NS_ASSUME_NONNULL_BEGIN - -extern const NSUInteger kOversizeTextMessageSizeThreshold; - -@class OWSBlockingManager; -@class OWSPrimaryStorage; -@class TSAttachmentStream; -@class TSInvalidIdentityKeySendingErrorMessage; -@class TSNetworkManager; -@class TSOutgoingMessage; -@class TSThread; -@class YapDatabaseReadWriteTransaction; -@class OWSMessageSend; - -@protocol ContactsManagerProtocol; - -/** - * Useful for when you *sometimes* want to retry before giving up and calling the failure handler - * but *sometimes* we don't want to retry when we know it's a terminal failure, so we allow the - * caller to indicate this with isRetryable=NO. - */ -typedef void (^RetryableFailureHandler)(NSError *_Nonnull error); - -// Message send error handling is slightly different for contact and group messages. -// -// For example, If one member of a group deletes their account, the group should -// ignore errors when trying to send messages to this ex-member. - -#pragma mark - - -NS_SWIFT_NAME(OutgoingAttachmentInfo) -@interface OWSOutgoingAttachmentInfo : NSObject - -@property (nonatomic, readonly) DataSource *dataSource; -@property (nonatomic, readonly) NSString *contentType; -@property (nonatomic, readonly, nullable) NSString *sourceFilename; -@property (nonatomic, readonly, nullable) NSString *caption; -@property (nonatomic, readonly, nullable) NSString *albumMessageId; - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithDataSource:(DataSource *)dataSource - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId NS_DESIGNATED_INITIALIZER; - -@end - -#pragma mark - - -NS_SWIFT_NAME(MessageSender) -@interface OWSMessageSender : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -/** - * Send and resend text messages or resend messages with existing attachments. - * If you haven't yet created the attachment, see the `sendAttachment:` variants. - */ -- (void)sendMessage:(TSOutgoingMessage *)message - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler; - -/** - * Takes care of allocating and uploading the attachment, then sends the message. - * Only necessary to call once. If sending fails, retry with `sendMessage:`. - */ -- (void)sendAttachment:(DataSource *)dataSource - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - albumMessageId:(nullable NSString *)albumMessageId - inMessage:(TSOutgoingMessage *)outgoingMessage - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler; - -- (void)sendAttachments:(NSArray *)attachmentInfos - inMessage:(TSOutgoingMessage *)message - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler; - -/** - * Same as `sendAttachment:`, but deletes the local copy of the attachment after sending. - * Used for sending sync request data, not for user visible attachments. - */ -- (void)sendTemporaryAttachment:(DataSource *)dataSource - contentType:(NSString *)contentType - inMessage:(TSOutgoingMessage *)outgoingMessage - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler; - -- (void)sendMessage:(OWSMessageSend *)messageSend; - -@end - -#pragma mark - - -@interface OutgoingMessagePreparer : NSObject - -/// Persists all necessary data to disk before sending, e.g. generate thumbnails -+ (NSArray *)prepareMessageForSending:(TSOutgoingMessage *)message - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -/// Writes attachment to disk and applies original filename to message attributes -+ (void)prepareAttachments:(NSArray *)attachmentInfos - inMessage:(TSOutgoingMessage *)outgoingMessage - completionHandler:(void (^)(NSError *_Nullable error))completionHandler - NS_SWIFT_NAME(prepareAttachments(_:inMessage:completionHandler:)); - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m deleted file mode 100644 index 04c0b10b1..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ /dev/null @@ -1,1764 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageSender.h" -#import "AppContext.h" -#import "NSData+keyVersionByte.h" -#import "NSData+messagePadding.h" -#import "NSError+MessageSending.h" -#import "OWSBackgroundTask.h" -#import "OWSBlockingManager.h" -#import "OWSContact.h" -#import "OWSDevice.h" -#import "OWSDisappearingMessagesJob.h" -#import "OWSDispatch.h" -#import "OWSEndSessionMessage.h" -#import "OWSError.h" -#import "OWSIdentityManager.h" -#import "OWSMessageServiceParams.h" -#import "OWSOperation.h" -#import "OWSOutgoingSentMessageTranscript.h" -#import "OWSOutgoingSyncMessage.h" -#import "OWSPrimaryStorage+PreKeyStore.h" -#import "OWSPrimaryStorage+SignedPreKeyStore.h" -#import "OWSPrimaryStorage+sessionStore.h" -#import "OWSPrimaryStorage+Loki.h" -#import "OWSPrimaryStorage.h" -#import "OWSRequestFactory.h" -#import "OWSUploadOperation.h" -#import "PreKeyBundle+jsonDict.h" -#import "SSKEnvironment.h" -#import "SignalRecipient.h" -#import "TSAccountManager.h" -#import "TSAttachmentStream.h" -#import "TSContactThread.h" -#import "TSGroupThread.h" -#import "TSIncomingMessage.h" -#import "TSInfoMessage.h" -#import "TSInvalidIdentityKeySendingErrorMessage.h" -#import "TSNetworkManager.h" -#import "TSOutgoingMessage.h" -#import "TSPreKeyManager.h" -#import "TSQuotedMessage.h" -#import "TSRequest.h" -#import "TSSocketManager.h" -#import "TSThread.h" -#import "TSContactThread.h" -#import "LKDeviceLinkMessage.h" -#import "LKUnlinkDeviceMessage.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *NoSessionForTransientMessageException = @"NoSessionForTransientMessageException"; - -const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024; - -NSError *SSKEnsureError(NSError *_Nullable error, OWSErrorCode fallbackCode, NSString *fallbackErrorDescription) -{ - if (error) { - return error; - } - OWSCFailDebug(@"Using fallback error."); - return OWSErrorWithCodeDescription(fallbackCode, fallbackErrorDescription); -} - -#pragma mark - - -void AssertIsOnSendingQueue() -{ -#ifdef DEBUG - if (@available(iOS 10.0, *)) { - dispatch_assert_queue([OWSDispatch sendingQueue]); - } // else, skip assert as it's a development convenience. -#endif -} - -#pragma mark - - -@implementation OWSOutgoingAttachmentInfo - -- (instancetype)initWithDataSource:(DataSource *)dataSource - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId -{ - self = [super init]; - if (!self) { - return self; - } - - _dataSource = dataSource; - _contentType = contentType; - _sourceFilename = sourceFilename; - _caption = caption; - _albumMessageId = albumMessageId; - - return self; -} - -@end - -#pragma mark - - -/** - * OWSSendMessageOperation encapsulates all the work associated with sending a message, e.g. uploading attachments, - * getting proper keys, and retrying upon failure. - * - * Used by `OWSMessageSender` to serialize message sending, ensuring that messages are emitted in the order they - * were sent. - */ -@interface OWSSendMessageOperation : OWSOperation - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithMessage:(TSOutgoingMessage *)message - messageSender:(OWSMessageSender *)messageSender - dbConnection:(YapDatabaseConnection *)dbConnection - success:(void (^)(void))aSuccessHandler - failure:(void (^)(NSError * error))aFailureHandler NS_DESIGNATED_INITIALIZER; - -@end - -#pragma mark - - -@interface OWSMessageSender (OWSSendMessageOperation) - -- (void)sendMessageToService:(TSOutgoingMessage *)message - success:(void (^)(void))successHandler - failure:(RetryableFailureHandler)failureHandler; - -@end - -#pragma mark - - -@interface OWSSendMessageOperation () - -@property (nonatomic, readonly) TSOutgoingMessage *message; -@property (nonatomic, readonly) OWSMessageSender *messageSender; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (nonatomic, readonly) void (^successHandler)(void); -@property (nonatomic, readonly) void (^failureHandler)(NSError *error); - -@end - -#pragma mark - - -@implementation OWSSendMessageOperation - -- (instancetype)initWithMessage:(TSOutgoingMessage *)message - messageSender:(OWSMessageSender *)messageSender - dbConnection:(YapDatabaseConnection *)dbConnection - success:(void (^)(void))successHandler - failure:(void (^)(NSError * error))failureHandler -{ - self = [super init]; - - if (!self) { - return self; - } - - _message = message; - _messageSender = messageSender; - _dbConnection = dbConnection; - _successHandler = successHandler; - _failureHandler = failureHandler; - - return self; -} - -#pragma mark - OWSOperation overrides - -- (nullable NSError *)checkForPreconditionError -{ - __block NSError *_Nullable error = [super checkForPreconditionError]; - if (error) { return error; } - - if (self.message.hasAttachments) { - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - for (TSAttachment *attachment in [self.message attachmentsWithTransaction:transaction]) { - if (![attachment isKindOfClass:[TSAttachmentStream class]]) { - error = OWSErrorMakeFailedToSendOutgoingMessageError(); - break; - } - - TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; - OWSAssertDebug(attachmentStream); - OWSAssertDebug(attachmentStream.serverId); - OWSAssertDebug(attachmentStream.isUploaded); - } - }]; - } - - return error; -} - -- (void)run -{ - if (self.message.shouldBeSaved && ![TSOutgoingMessage fetchObjectWithUniqueID:self.message.uniqueId]) { - OWSLogInfo(@"Aborting message send; message deleted."); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageDeletedBeforeSent, @"Message was deleted before it could be sent."); - error.isFatal = YES; - [self reportError:error]; - return; - } - - [self.messageSender sendMessageToService:self.message - success:^{ - [self reportSuccess]; - } - failure:^(NSError *error) { - [self reportError:error]; - }]; -} - -- (void)didSucceed -{ - if (self.message.messageState != TSOutgoingMessageStateSent) { - [LKLogger print:@"[Loki] Succeeded with sending a message, but the message state isn't TSOutgoingMessageStateSent."]; - } - - self.successHandler(); -} - -- (void)didFailWithError:(NSError *)error -{ - OWSLogError(@"Message failed to send due to error: %@.", error); - self.failureHandler(error); -} - -@end - -#pragma mark - - -NSString *const OWSMessageSenderInvalidDeviceException = @"InvalidDeviceException"; -NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; - -@interface OWSMessageSender () - -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (atomic, readonly) NSMutableDictionary *sendingQueueMap; - -@end - -#pragma mark - - -@implementation OWSMessageSender - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - if (!self) { - return self; - } - - _primaryStorage = primaryStorage; - _sendingQueueMap = [NSMutableDictionary new]; - _dbConnection = primaryStorage.newDatabaseConnection; - - OWSSingletonAssert(); - - return self; -} - -#pragma mark - Dependencies - -- (id)contactsManager -{ - OWSAssertDebug(SSKEnvironment.shared.contactsManager); - - return SSKEnvironment.shared.contactsManager; -} - -- (OWSBlockingManager *)blockingManager -{ - OWSAssertDebug(SSKEnvironment.shared.blockingManager); - - return SSKEnvironment.shared.blockingManager; -} - -- (TSNetworkManager *)networkManager -{ - OWSAssertDebug(SSKEnvironment.shared.networkManager); - - return SSKEnvironment.shared.networkManager; -} - -- (id)udManager -{ - OWSAssertDebug(SSKEnvironment.shared.udManager); - - return SSKEnvironment.shared.udManager; -} - -- (TSAccountManager *)tsAccountManager -{ - return TSAccountManager.sharedInstance; -} - -- (OWSIdentityManager *)identityManager -{ - return SSKEnvironment.shared.identityManager; -} - -#pragma mark - - -- (NSOperationQueue *)sendingQueueForMessage:(TSOutgoingMessage *)message -{ - OWSAssertDebug(message); - - - NSString *kDefaultQueueKey = @"kDefaultQueueKey"; - NSString *queueKey = message.uniqueThreadId ?: kDefaultQueueKey; - OWSAssertDebug(queueKey.length > 0); - - if ([kDefaultQueueKey isEqualToString:queueKey]) { - // when do we get here? - OWSLogDebug(@"using default message queue"); - } - - @synchronized(self) - { - NSOperationQueue *sendingQueue = self.sendingQueueMap[queueKey]; - - if (!sendingQueue) { - sendingQueue = [NSOperationQueue new]; - sendingQueue.qualityOfService = NSOperationQualityOfServiceUserInitiated; - sendingQueue.maxConcurrentOperationCount = 1; - sendingQueue.name = [NSString stringWithFormat:@"%@:%@", self.logTag, queueKey]; - self.sendingQueueMap[queueKey] = sendingQueue; - } - - return sendingQueue; - } -} - -- (void)sendMessage:(TSOutgoingMessage *)message - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - OWSAssertDebug(message); - - if (message.body.length > 0) { - OWSAssertDebug([message.body lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= kOversizeTextMessageSizeThreshold); - } - - if (message.shouldBeSaved && !message.thread.isGroupThread && ![LKSessionMetaProtocol isThreadNoteToSelf:message.thread]) { - // Loki: Not strictly true but nice from a UI point of view - [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.calculatingPoW object:[[NSNumber alloc] initWithUnsignedLongLong:message.timestamp]]; - } - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - NSMutableArray *allAttachmentIds = [NSMutableArray new]; - - // This method will use a read/write transaction. This transaction - // will block until any open read/write transactions are complete. - // - // That's key - we don't want to send any messages in response - // to an incoming message until processing of that batch of messages - // is complete. For example, we wouldn't want to auto-reply to a - // group info request before that group info request's batch was - // finished processing. Otherwise, we might receive a delivery - // notice for a group update we hadn't yet saved to the database. - // - // So we're using YDB behavior to ensure this invariant, which is a bit - // unorthodox. - if (message.allAttachmentIds.count > 0) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [allAttachmentIds addObjectsFromArray:[OutgoingMessagePreparer prepareMessageForSending:message transaction:transaction]]; - }]; - } - - NSOperationQueue *sendingQueue = [self sendingQueueForMessage:message]; - - OWSSendMessageOperation *sendMessageOperation = - [[OWSSendMessageOperation alloc] initWithMessage:message - messageSender:self - dbConnection:self.dbConnection - success:successHandler - failure:failureHandler]; - - for (NSString *attachmentId in allAttachmentIds) { - OWSUploadOperation *uploadAttachmentOperation = - [[OWSUploadOperation alloc] initWithAttachmentId:attachmentId - threadID:message.thread.uniqueId - dbConnection:self.dbConnection]; - - [sendMessageOperation addDependency:uploadAttachmentOperation]; - [sendingQueue addOperation:uploadAttachmentOperation]; - } - - [sendingQueue addOperation:sendMessageOperation]; - }); -} - -- (void)sendTemporaryAttachment:(DataSource *)dataSource - contentType:(NSString *)contentType - inMessage:(TSOutgoingMessage *)message - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - OWSAssertDebug(dataSource); - - void (^successWithDeleteHandler)(void) = ^() { - successHandler(); - - OWSLogDebug(@"Removing successful temporary attachment message with attachment ids: %@", message.attachmentIds); - [message remove]; - }; - - void (^failureWithDeleteHandler)(NSError *error) = ^(NSError *error) { - failureHandler(error); - - OWSLogDebug(@"Removing failed temporary attachment message with attachment ids: %@", message.attachmentIds); - [message remove]; - }; - - [self sendAttachment:dataSource - contentType:contentType - sourceFilename:nil - albumMessageId:nil - inMessage:message - success:successWithDeleteHandler - failure:failureWithDeleteHandler]; -} - -- (void)sendAttachment:(DataSource *)dataSource - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - albumMessageId:(nullable NSString *)albumMessageId - inMessage:(TSOutgoingMessage *)message - success:(void (^)(void))success - failure:(void (^)(NSError *error))failure -{ - OWSAssertDebug(dataSource); - - OWSOutgoingAttachmentInfo *attachmentInfo = [[OWSOutgoingAttachmentInfo alloc] initWithDataSource:dataSource - contentType:contentType - sourceFilename:sourceFilename - caption:nil - albumMessageId:albumMessageId]; - [self sendAttachments:@[ attachmentInfo, ] - inMessage:message - success:success - failure:failure]; -} - -- (void)sendAttachments:(NSArray *)attachmentInfos - inMessage:(TSOutgoingMessage *)message - success:(void (^)(void))success - failure:(void (^)(NSError *error))failure -{ - OWSAssertDebug(attachmentInfos.count > 0); - - [OutgoingMessagePreparer prepareAttachments:attachmentInfos - inMessage:message - completionHandler:^(NSError *_Nullable error) { - if (error) { - failure(error); - return; - } - [self sendMessage:message success:success failure:failure]; - }]; -} - -- (void)sendMessageToService:(TSOutgoingMessage *)message - success:(void (^)(void))success - failure:(RetryableFailureHandler)failure -{ - [self.udManager ensureSenderCertificateWithSuccess:^(SMKSenderCertificate *senderCertificate) { - OWSAssertDebug(senderCertificate != nil); - dispatch_async(OWSDispatch.sendingQueue, ^{ - [self sendMessageToService:message senderCertificate:senderCertificate success:success failure:failure]; - }); - } - failure:^(NSError *error) { // Should never occur - dispatch_async(OWSDispatch.sendingQueue, ^{ - [self sendMessageToService:message senderCertificate:nil success:success failure:failure]; - }); - }]; -} - -- (nullable NSArray *)unsentRecipientsForMessage:(TSOutgoingMessage *)message - thread:(nullable TSThread *)thread - error:(NSError **)errorHandle -{ - OWSAssertDebug(message); - OWSAssertDebug(errorHandle); - - NSString *userPublicKey = self.tsAccountManager.localNumber; - - __block NSMutableSet *recipientIds = [NSMutableSet new]; - if ([message isKindOfClass:OWSOutgoingSyncMessage.class]) { - recipientIds = [LKSessionMetaProtocol getDestinationsForOutgoingSyncMessage]; - } else if (thread.isGroupThread) { - TSGroupThread *groupThread = (TSGroupThread *)thread; - recipientIds = [LKSessionMetaProtocol getDestinationsForOutgoingGroupMessage:message inThread:thread]; - __block NSString *userMasterPublicKey; - [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - userMasterPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userPublicKey in:transaction] ?: userPublicKey; - }]; - if ([recipientIds containsObject:userMasterPublicKey]) { - OWSFailDebug(@"Message send recipients should not include self."); - } - } else if ([thread isKindOfClass:TSContactThread.class]) { - NSString *recipientContactId = ((TSContactThread *)thread).contactIdentifier; - - // Treat 1:1 sends to blocked contacts as failures. - // If we block a user, don't send 1:1 messages to them. The UI - // should prevent this from occurring, but in some edge cases - // you might, for example, have a pending outgoing message when - // you block them. - OWSAssertDebug(recipientContactId.length > 0); - if ([self.blockingManager isRecipientIdBlocked:recipientContactId]) { - OWSLogInfo(@"Skipping 1:1 send to blocked contact: %@", recipientContactId); - NSError *error = OWSErrorMakeMessageSendFailedDueToBlockListError(); - [error setIsRetryable:NO]; - *errorHandle = error; - return nil; - } - - [recipientIds addObject:recipientContactId]; - } else { - OWSFailDebug(@"Unknown message type: %@", [message class]); - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - [error setIsRetryable:NO]; - *errorHandle = error; - return nil; - } - - [recipientIds minusSet:[NSSet setWithArray:self.blockingManager.blockedPhoneNumbers]]; - return recipientIds.allObjects; -} - -- (NSArray *)recipientsForRecipientIds:(NSArray *)recipientIds -{ - OWSAssertDebug(recipientIds.count > 0); - - NSMutableArray *recipients = [NSMutableArray new]; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - for (NSString *recipientId in recipientIds) { - SignalRecipient *recipient = - [SignalRecipient getOrBuildUnsavedRecipientForRecipientId:recipientId transaction:transaction]; - [recipients addObject:recipient]; - } - }]; - return [recipients copy]; -} - -- (AnyPromise *)sendPromiseForRecipients:(NSArray *)recipients - message:(TSOutgoingMessage *)message - thread:(nullable TSThread *)thread - senderCertificate:(nullable SMKSenderCertificate *)senderCertificate - sendErrors:(NSMutableArray *)sendErrors -{ - OWSAssertDebug(recipients.count > 0); - OWSAssertDebug(message); - OWSAssertDebug(sendErrors); - - NSMutableArray *sendPromises = [NSMutableArray array]; - - for (SignalRecipient *recipient in recipients) { - AnyPromise *sendPromise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) { - NSString *localNumber = self.tsAccountManager.localNumber; - - OWSUDAccess *_Nullable theirUDAccess; - if (senderCertificate != nil && ![recipient.recipientId isEqualToString:localNumber]) { - theirUDAccess = [self.udManager udAccessForRecipientId:recipient.recipientId requireSyncAccess:YES]; - } - - OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:message - thread:thread - recipient:recipient - senderCertificate:senderCertificate - udAccess:theirUDAccess - localNumber:self.tsAccountManager.localNumber - success:^{ - // The value doesn't matter, we just need any non-NSError value. - resolve(@(1)); - } - failure:^(NSError *error) { - @synchronized(sendErrors) { - [sendErrors addObject:error]; - } - resolve(error); - }]; - -// NSString *publicKey = recipients.firstObject.recipientId; -// if ([LKMultiDeviceProtocol isMultiDeviceRequiredForMessage:message toPublicKey:publicKey]) { // Avoid the write transaction if possible -// [self.primaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { -// [LKMultiDeviceProtocol sendMessageToDestinationAndLinkedDevices:messageSend transaction:transaction]; -// }]; -// } else { - [self sendMessage:messageSend]; -// } - }]; - [sendPromises addObject:sendPromise]; - } - - // We use PMKJoin(), not PMKWhen(), because we don't want the - // completion promise to execute until _all_ send promises - // have either succeeded or failed. PMKWhen() executes as - // soon as any of its input promises fail. - return PMKJoin(sendPromises); -} - -- (void)sendMessageToService:(TSOutgoingMessage *)message - senderCertificate:(nullable SMKSenderCertificate *)senderCertificate - success:(void (^)(void))successHandlerParam - failure:(RetryableFailureHandler)failureHandlerParam -{ - AssertIsOnSendingQueue(); - OWSAssert(senderCertificate); - - void (^successHandler)(void) = ^() { - dispatch_async(OWSDispatch.sendingQueue, ^{ - [self handleMessageSentLocally:message - success:^{ - successHandlerParam(); - } - failure:^(NSError *error) { - OWSLogError(@"Error sending sync message for message: %@ timestamp: %llu.", - message.class, - message.timestamp); - - failureHandlerParam(error); - }]; - }); - }; - void (^failureHandler)(NSError *) = ^(NSError *error) { - if (message.wasSentToAnyRecipient) { - dispatch_async(OWSDispatch.sendingQueue, ^{ - [self handleMessageSentLocally:message - success:^{ - failureHandlerParam(error); - } - failure:^(NSError *syncError) { - OWSLogError(@"Error sending sync message for message: %@ timestamp: %llu, %@.", - message.class, - message.timestamp, - syncError); - - // Discard the sync message error in favor of the original error - failureHandlerParam(error); - }]; - }); - return; - } - - failureHandlerParam(error); - }; - - TSThread *_Nullable thread = message.thread; - - BOOL isSyncMessage = [message isKindOfClass:[OWSOutgoingSyncMessage class]]; - if (thread == nil && !isSyncMessage) { - - // The thread has been deleted since the message was enqueued. - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageSendNoValidRecipients, - NSLocalizedString(@"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS", @"Error indicating that an outgoing message had no valid recipients.")); - [error setIsRetryable:NO]; - return failureHandler(error); - } - - // In the "self-send" special case, we ony need to send a sync message with a delivery receipt - // Loki: Take into account multi device - if ([LKSessionMetaProtocol isThreadNoteToSelf:thread] - && !([message isKindOfClass:LKDeviceLinkMessage.class]) && !([message isKindOfClass:LKClosedGroupUpdateMessage.class])) { - // Don't mark self-sent messages as read (or sent) until the sync transcript is sent - successHandler(); - return; - } - - if (thread.isGroupThread) { - [self saveInfoMessageForGroupMessage:message inThread:thread]; - } - - NSError *error; - NSArray *_Nullable recipientIds = [self unsentRecipientsForMessage:message thread:thread error:&error]; - if (error || !recipientIds) { - error = SSKEnsureError( - error, OWSErrorCodeMessageSendNoValidRecipients, @"Couldn't build recipient list for message."); - [error setIsRetryable:NO]; - return failureHandler(error); - } - - // Mark skipped recipients as such. We skip because: - // - // * Recipient is no longer in the group. - // * Recipient is blocked. - // - // Elsewhere, we skip recipient if their Signal account has been deactivated. - NSMutableSet *obsoleteRecipientIds = [NSMutableSet setWithArray:message.sendingRecipientIds]; - [obsoleteRecipientIds minusSet:[NSSet setWithArray:recipientIds]]; - if (obsoleteRecipientIds.count > 0) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - for (NSString *recipientId in obsoleteRecipientIds) { - [message updateWithSkippedRecipient:recipientId transaction:transaction]; - } - }]; - } - - if (recipientIds.count < 1) { - successHandler(); - return; - } - - NSArray *recipients = [self recipientsForRecipientIds:recipientIds]; - - BOOL isGroupSend = (thread && thread.isGroupThread); - NSMutableArray *sendErrors = [NSMutableArray array]; - AnyPromise *sendPromise = [self sendPromiseForRecipients:recipients - message:message - thread:thread - senderCertificate:senderCertificate - sendErrors:sendErrors] - .then(^(id value) { - successHandler(); - }); - - sendPromise.catch(^(id failure) { - NSError *firstRetryableError = nil; - NSError *firstNonRetryableError = nil; - - NSArray *sendErrorsCopy; - @synchronized(sendErrors) { - sendErrorsCopy = [sendErrors copy]; - } - - for (NSError *error in sendErrorsCopy) { - // Some errors should be ignored when sending messages - // to groups. See discussion on - // NSError (OWSMessageSender) category. - if (isGroupSend && error.shouldBeIgnoredForGroups) { - continue; - } - - // Some errors should never be retried, in order to avoid - // hitting rate limits, for example. Unfortunately, since - // group send retry is all-or-nothing, we need to fail - // immediately even if some of the other recipients had - // retryable errors. - if (error.isFatal) { - failureHandler(error); - return; - } - - if ([error isRetryable] && !firstRetryableError) { - firstRetryableError = error; - } else if (![error isRetryable] && !firstNonRetryableError) { - firstNonRetryableError = error; - } - } - - // If any of the send errors are retryable, we want to retry. - // Therefore, prefer to propagate a retryable error. - if (firstRetryableError) { - return failureHandler(firstRetryableError); - } else if (firstNonRetryableError) { - return failureHandler(firstNonRetryableError); - } else { - // If we only received errors that we should ignore, - // consider this send a success, unless the message could - // not be sent to any recipient. - if (message.sentRecipientsCount == 0) { - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageSendNoValidRecipients, - NSLocalizedString(@"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS", @"Error indicating that an outgoing message had no valid recipients.")); - [error setIsRetryable:NO]; - failureHandler(error); - } else { - successHandler(); - } - } - }); - - [sendPromise retainUntilComplete]; -} - -- (nullable NSArray *)deviceMessagesForMessageSend:(OWSMessageSend *)messageSend - error:(NSError **)errorHandle -{ - OWSAssertDebug(messageSend); - OWSAssertDebug(errorHandle); - AssertIsOnSendingQueue(); - - SignalRecipient *recipient = messageSend.recipient; - - NSArray *deviceMessages; - @try { - deviceMessages = [self throws_deviceMessagesForMessageSend:messageSend]; - } @catch (NSException *exception) { - if ([exception.name isEqualToString:NoSessionForTransientMessageException]) { - // When users re-register, we don't want transient messages (like typing - // indicators) to cause users to hit the prekey fetch rate limit. So - // we silently discard these message if there is no pre-existing session - // for the recipient. - NSError *error = OWSErrorWithCodeDescription( - OWSErrorCodeNoSessionForTransientMessage, @"No session for transient message."); - [error setIsRetryable:NO]; - [error setIsFatal:YES]; - *errorHandle = error; - return nil; - } else if ([exception.name isEqualToString:UntrustedIdentityKeyException]) { - // This *can* happen under normal usage, but it should happen relatively rarely. - // We expect it to happen whenever Bob reinstalls, and Alice messages Bob before - // she can pull down his latest identity. - // If it's happening a lot, we should rethink our profile fetching strategy. - OWSProdInfo([OWSAnalyticsEvents messageSendErrorFailedDueToUntrustedKey]); - - NSString *localizedErrorDescriptionFormat - = NSLocalizedString(@"FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_KEY", - @"action sheet header when re-sending message which failed because of untrusted identity keys"); - - NSString *localizedErrorDescription = - [NSString stringWithFormat:localizedErrorDescriptionFormat, - [self.contactsManager displayNameForPhoneIdentifier:recipient.recipientId]]; - NSError *error = OWSErrorMakeUntrustedIdentityError(localizedErrorDescription, recipient.recipientId); - - // Key will continue to be unaccepted, so no need to retry. It'll only cause us to hit the Pre-Key request - // rate limit - [error setIsRetryable:NO]; - // Avoid the "Too many failures with this contact" error rate limiting. - [error setIsFatal:YES]; - *errorHandle = error; - - PreKeyBundle *_Nullable newKeyBundle = exception.userInfo[TSInvalidPreKeyBundleKey]; - if (newKeyBundle == nil) { - OWSProdFail([OWSAnalyticsEvents messageSenderErrorMissingNewPreKeyBundle]); - return nil; - } - - if (![newKeyBundle isKindOfClass:[PreKeyBundle class]]) { - OWSProdFail([OWSAnalyticsEvents messageSenderErrorUnexpectedKeyBundle]); - return nil; - } - - NSData *newIdentityKeyWithVersion = newKeyBundle.identityKey; - - if (![newIdentityKeyWithVersion isKindOfClass:[NSData class]]) { - OWSProdFail([OWSAnalyticsEvents messageSenderErrorInvalidIdentityKeyType]); - return nil; - } - - // TODO migrate to storing the full 33 byte representation of the identity key. - if (newIdentityKeyWithVersion.length != kIdentityKeyLength) { - OWSProdFail([OWSAnalyticsEvents messageSenderErrorInvalidIdentityKeyLength]); - return nil; - } - - NSData *newIdentityKey = [newIdentityKeyWithVersion throws_removeKeyType]; - [self.identityManager saveRemoteIdentity:newIdentityKey recipientId:recipient.recipientId]; - - return nil; - } - - if ([exception.name isEqualToString:OWSMessageSenderRateLimitedException]) { - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceRateLimited, - NSLocalizedString(@"FAILED_SENDING_BECAUSE_RATE_LIMIT", - @"action sheet header when re-sending message which failed because of too many attempts")); - // We're already rate-limited. No need to exacerbate the problem. - [error setIsRetryable:NO]; - // Avoid exacerbating the rate limiting. - [error setIsFatal:YES]; - *errorHandle = error; - return nil; - } - - OWSLogWarn(@"Could not build device messages: %@", exception); - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - [error setIsRetryable:YES]; - *errorHandle = error; - return nil; - } - - return deviceMessages; -} - -- (void)sendMessage:(OWSMessageSend *)messageSend -{ - OWSAssertDebug(messageSend); - OWSAssertDebug(messageSend.thread || [messageSend.message isKindOfClass:[OWSOutgoingSyncMessage class]]); - NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - if (!messageSend.isUDSend && ![messageSend.recipient.recipientId isEqual:userPublicKey]) { - [LKLogger print:@"[Loki] Non-UD send"]; - } - - TSOutgoingMessage *message = messageSend.message; - SignalRecipient *recipient = messageSend.recipient; - - BOOL notifyPNServer = ((message.body != nil && message.body.length > 0) || message.hasAttachments); - - OWSLogInfo(@"Attempting to send message: %@, timestamp: %llu, recipient: %@.", - message.class, - message.timestamp, - recipient.uniqueId); - - AssertIsOnSendingQueue(); - - if ([TSPreKeyManager isAppLockedDueToPreKeyUpdateFailures]) { - OWSProdError([OWSAnalyticsEvents messageSendErrorFailedDueToPrekeyUpdateFailures]); - - // Retry pre key update every time user tries to send a message while the app - // is disabled due to pre key update failures. - // - // Only try to update the signed pre key; updating it is sufficient to - // re-enable message sending. - [TSPreKeyManager - rotateSignedPreKeyWithSuccess:^{ - OWSLogInfo(@"New pre keys registered with server."); - NSError *error = OWSErrorMakeMessageSendDisabledDueToPreKeyUpdateFailuresError(); - [error setIsRetryable:YES]; - return messageSend.failure(error); - } - failure:^(NSError *error) { - OWSLogWarn(@"Failed to update pre keys with the server due to error: %@.", error); - return messageSend.failure(error); - }]; - } - - if (messageSend.remainingAttempts <= 0) { - // We should always fail with a specific error. - OWSProdFail([OWSAnalyticsEvents messageSenderErrorGenericSendFailure]); - - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - [error setIsRetryable:YES]; - return messageSend.failure(error); - } - - // Consume an attempt. - messageSend.remainingAttempts = messageSend.remainingAttempts - 1; - - // We need to disable UD for sync messages before we build the device messages, - // since we don't want to build a device message for the local device in the - // non-UD auth case. - if ([message isKindOfClass:[OWSOutgoingSyncMessage class]] - && ![message isKindOfClass:[OWSOutgoingSentMessageTranscript class]]) { - [messageSend disableUD]; - } - - NSError *deviceMessagesError; - NSArray *_Nullable deviceMessages; - if (message.thread.isGroupThread && ((TSGroupThread *)message.thread).isPublicChat) { - deviceMessages = @{}; - } else { - deviceMessages = [self deviceMessagesForMessageSend:messageSend error:&deviceMessagesError]; - - // Loki: Remove this when we have shared sender keys - // ======== - if (deviceMessages.count == 0) { - return messageSend.success(); - } - // ======== - } - - if (deviceMessagesError || !deviceMessages) { - OWSAssertDebug(deviceMessagesError); - return messageSend.failure(deviceMessagesError); - } - - for (NSDictionary *deviceMessage in deviceMessages) { - NSNumber *_Nullable messageType = deviceMessage[@"type"]; - OWSAssertDebug(messageType); - BOOL hasValidMessageType; - if (messageSend.isUDSend) { - hasValidMessageType = [messageType isEqualToNumber:@(TSUnidentifiedSenderMessageType)]; - } else { - NSArray *validMessageTypes = @[ @(TSEncryptedWhisperMessageType), @(TSPreKeyWhisperMessageType), @(TSFallbackMessageType), @(TSClosedGroupCiphertextMessageType) ]; - hasValidMessageType = [validMessageTypes containsObject:messageType]; - } - - if (!hasValidMessageType) { - OWSFailDebug(@"Invalid message type: %@.", messageType); - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - [error setIsRetryable:NO]; - return messageSend.failure(error); - } - } - - if (deviceMessages.count == 0 && !(message.thread.isGroupThread && ((TSGroupThread *)message.thread).isPublicChat)) { - // This might happen: - // - // * The first (after upgrading?) time we send a sync message to our linked devices. - // * After unlinking all linked devices. - // * After trying and failing to link a device. - // * The first time we send a message to a user, if they don't have their - // default device. For example, if they have unregistered - // their primary but still have a linked device. Or later, when they re-register. - // - // When we're not sure if we have linked devices, we need to try - // to send self-sync messages even if they have no device messages - // so that we can learn from the service whether or not there are - // linked devices that we don't know about. - OWSLogWarn(@"Sending a message with no device messages."); - - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - [error setIsRetryable:NO]; - return messageSend.failure(error); - } - - void (^failedMessageSend)(NSError *error) = ^(NSError *error) { - NSUInteger statusCode = 0; - NSData *_Nullable responseData = nil; - if ([error.domain isEqualToString:TSNetworkManagerErrorDomain]) { - statusCode = error.code; - NSError *_Nullable underlyingError = error.userInfo[NSUnderlyingErrorKey]; - if (underlyingError) { - responseData = underlyingError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]; - } else { - OWSFailDebug(@"Missing underlying error: %@.", error); - } - } - [self messageSendDidFail:messageSend deviceMessages:deviceMessages statusCode:statusCode error:error responseData:responseData]; - }; - - __block LKPublicChat *publicChat; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - publicChat = [LKDatabaseUtilities getPublicChatForThreadID:message.uniqueThreadId transaction: transaction]; - }]; - if (publicChat != nil) { - NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - NSString *displayName = SSKEnvironment.shared.profileManager.localProfileName; - if (displayName == nil) { displayName = @"Anonymous"; } - TSQuotedMessage *quote = message.quotedMessage; - uint64_t quoteID = quote.timestamp; - NSString *quoteePublicKey = quote.authorId; - __block uint64_t quotedMessageServerID = 0; - if (quoteID != 0) { - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - quotedMessageServerID = [LKDatabaseUtilities getServerIDForQuoteWithID:quoteID quoteeHexEncodedPublicKey:quoteePublicKey threadID:messageSend.thread.uniqueId transaction:transaction]; - }]; - } - NSString *body = (message.body != nil && message.body.length > 0) ? message.body : [NSString stringWithFormat:@"%@", @(message.timestamp)]; // Workaround for the fact that the back-end doesn't accept messages without a body - LKPublicChatMessage *groupMessage = [[LKPublicChatMessage alloc] initWithSenderPublicKey:userPublicKey displayName:displayName body:body type:LKPublicChatAPI.publicChatMessageType - timestamp:message.timestamp quotedMessageTimestamp:quoteID quoteePublicKey:quoteePublicKey quotedMessageBody:quote.body quotedMessageServerID:quotedMessageServerID signatureData:nil signatureVersion:0 serverTimestamp:0]; - OWSLinkPreview *linkPreview = message.linkPreview; - if (linkPreview != nil) { - TSAttachmentStream *attachment = [TSAttachmentStream fetchObjectWithUniqueID:linkPreview.imageAttachmentId]; - if (attachment != nil) { - [groupMessage addAttachmentWithKind:@"preview" server:publicChat.server serverID:attachment.serverId contentType:attachment.contentType size:attachment.byteCount fileName:attachment.sourceFilename flags:0 width:@(attachment.imageSize.width).unsignedIntegerValue height:@(attachment.imageSize.height).unsignedIntegerValue caption:attachment.caption url:attachment.downloadURL linkPreviewURL:linkPreview.urlString linkPreviewTitle:linkPreview.title]; - } - } - for (NSString *attachmentID in message.attachmentIds) { - TSAttachmentStream *attachment = [TSAttachmentStream fetchObjectWithUniqueID:attachmentID]; - if (attachment == nil) { continue; } - NSUInteger width = attachment.shouldHaveImageSize ? @(attachment.imageSize.width).unsignedIntegerValue : 0; - NSUInteger height = attachment.shouldHaveImageSize ? @(attachment.imageSize.height).unsignedIntegerValue : 0; - [groupMessage addAttachmentWithKind:@"attachment" server:publicChat.server serverID:attachment.serverId contentType:attachment.contentType size:attachment.byteCount fileName:attachment.sourceFilename flags:0 width:width height:height caption:attachment.caption url:attachment.downloadURL linkPreviewURL:nil linkPreviewTitle:nil]; - } - message.actualSenderHexEncodedPublicKey = userPublicKey; - [[LKPublicChatAPI sendMessage:groupMessage toGroup:publicChat.channel onServer:publicChat.server] - .thenOn(OWSDispatch.sendingQueue, ^(LKPublicChatMessage *groupMessage) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [message saveOpenGroupServerMessageID:groupMessage.serverID in:transaction]; - [self.primaryStorage setIDForMessageWithServerID:groupMessage.serverID to:message.uniqueId in:transaction]; - }]; - [self messageSendDidSucceed:messageSend deviceMessages:deviceMessages wasSentByUD:messageSend.isUDSend wasSentByWebsocket:false]; - }) - .catchOn(OWSDispatch.sendingQueue, ^(NSError *error) { - failedMessageSend(error); - }) retainUntilComplete]; - } else { - NSString *targetPublicKey = recipient.recipientId; - NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - __block BOOL isUserLinkedDevice; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - isUserLinkedDevice = [LKDatabaseUtilities isUserLinkedDevice:targetPublicKey in:transaction]; - }]; - BOOL isSSKBasedClosedGroup = [messageSend.thread isKindOfClass:TSGroupThread.class] && ((TSGroupThread *)messageSend.thread).usesSharedSenderKeys; - if (isSSKBasedClosedGroup) { - [LKLogger print:[NSString stringWithFormat:@"[Loki] Sending %@ to SSK based closed group.", message.class]]; - } else if ([targetPublicKey isEqual:userPublicKey]) { - [LKLogger print:[NSString stringWithFormat:@"[Loki] Sending %@ to self.", message.class]]; - } else if (isUserLinkedDevice) { - [LKLogger print:[NSString stringWithFormat:@"[Loki] Sending %@ to %@ (one of the current user's linked devices).", message.class, recipient.recipientId]]; - } else { - [LKLogger print:[NSString stringWithFormat:@"[Loki] Sending %@ to %@.", message.class, recipient.recipientId]]; - } - NSDictionary *signalMessageInfo = deviceMessages.firstObject; - SSKProtoEnvelopeType type = ((NSNumber *)signalMessageInfo[@"type"]).integerValue; - uint32_t senderDeviceID = (type == SSKProtoEnvelopeTypeUnidentifiedSender) ? 0 : OWSDevicePrimaryDeviceId; - NSString *content = signalMessageInfo[@"content"]; - NSString *recipientID = signalMessageInfo[@"destination"]; - uint64_t ttl = ((NSNumber *)signalMessageInfo[@"ttl"]).unsignedIntegerValue; - BOOL isPing = ((NSNumber *)signalMessageInfo[@"isPing"]).boolValue; - uint64_t timestamp = message.timestamp; - NSString *senderID; - if (type == SSKProtoEnvelopeTypeClosedGroupCiphertext) { - senderID = recipientID; - } else if (type == SSKProtoEnvelopeTypeUnidentifiedSender) { - senderID = @""; - } else { - senderID = userPublicKey; - [LKLogger print:@"[Loki] Non-UD send"]; - } - LKSignalMessage *signalMessage = [[LKSignalMessage alloc] initWithType:type timestamp:timestamp senderID:senderID senderDeviceID:senderDeviceID content:content recipientID:recipientID ttl:ttl isPing:isPing]; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - if (!message.skipSave) { - // Update the PoW calculation status - [message saveIsCalculatingProofOfWork:YES withTransaction:transaction]; - } - }]; - // Convenience - void (^handleError)(NSError *error) = ^(NSError *error) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - if (!message.skipSave) { - // Update the PoW calculation status - [message saveIsCalculatingProofOfWork:NO withTransaction:transaction]; - } - }]; - // Handle the error - failedMessageSend(error); - }; - // Send the message - [[LKSnodeAPI sendSignalMessage:signalMessage] - .thenOn(OWSDispatch.sendingQueue, ^(id result) { - NSSet *promises = (NSSet *)result; - __block BOOL isSuccess = NO; - NSUInteger promiseCount = promises.count; - __block NSUInteger errorCount = 0; - for (AnyPromise *promise in promises) { - [promise - .thenOn(OWSDispatch.sendingQueue, ^(id result) { - if (isSuccess) { return; } // Succeed as soon as the first promise succeeds - [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.messageSent object:[[NSNumber alloc] initWithUnsignedLongLong:signalMessage.timestamp]]; - isSuccess = YES; - if (notifyPNServer) { - [LKPushNotificationManager notifyForMessage:signalMessage]; - } - [self messageSendDidSucceed:messageSend deviceMessages:deviceMessages wasSentByUD:messageSend.isUDSend wasSentByWebsocket:false]; - }) - .catchOn(OWSDispatch.sendingQueue, ^(NSError *error) { - errorCount += 1; - if (errorCount != promiseCount) { return; } // Only error out if all promises failed - [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.messageFailed object:[[NSNumber alloc] initWithUnsignedLongLong:signalMessage.timestamp]]; - handleError(error); - }) retainUntilComplete]; - } - }) - .catchOn(OWSDispatch.sendingQueue, ^(NSError *error) { - handleError(error); - }) retainUntilComplete]; - } -} - -- (void)messageSendDidSucceed:(OWSMessageSend *)messageSend - deviceMessages:(NSArray *)deviceMessages - wasSentByUD:(BOOL)wasSentByUD - wasSentByWebsocket:(BOOL)wasSentByWebsocket -{ - OWSAssertDebug(messageSend); - OWSAssertDebug(deviceMessages); - - SignalRecipient *recipient = messageSend.recipient; - - OWSLogInfo(@"Successfully sent message: %@ timestamp: %llu, wasSentByUD: %d.", - messageSend.message.class, messageSend.message.timestamp, wasSentByUD); - - if (messageSend.isLocalNumber && deviceMessages.count == 0) { - OWSLogInfo(@"Sent a message with no device messages; clearing 'mayHaveLinkedDevices'."); - // In order to avoid skipping necessary sync messages, the default value - // for mayHaveLinkedDevices is YES. Once we've successfully sent a - // sync message with no device messages (e.g. the service has confirmed - // that we have no linked devices), we can set mayHaveLinkedDevices to NO - // to avoid unnecessary message sends for sync messages until we learn - // of a linked device (e.g. through the device linking UI or by receiving - // a sync message, etc.). - [OWSDeviceManager.sharedManager clearMayHaveLinkedDevices]; - } - - dispatch_async(OWSDispatch.sendingQueue, ^{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [messageSend.message updateWithSentRecipient:messageSend.recipient.uniqueId - wasSentByUD:wasSentByUD - transaction:transaction]; - - // If we've just delivered a message to a user, we know they - // have a valid Signal account. - [SignalRecipient markRecipientAsRegisteredAndGet:recipient.recipientId transaction:transaction]; - }]; - - messageSend.success(); - }); -} - -- (void)messageSendDidFail:(OWSMessageSend *)messageSend - deviceMessages:(NSArray *)deviceMessages - statusCode:(NSInteger)statusCode - error:(NSError *)responseError - responseData:(nullable NSData *)responseData -{ - OWSAssertDebug(messageSend); - OWSAssertDebug(messageSend.thread || [messageSend.message isKindOfClass:[OWSOutgoingSyncMessage class]]); - OWSAssertDebug(deviceMessages); - OWSAssertDebug(responseError); - - TSOutgoingMessage *message = messageSend.message; - SignalRecipient *recipient = messageSend.recipient; - - OWSLogInfo(@"Failed to send message: %@, timestamp: %llu, to recipient: %@.", - message.class, - message.timestamp, - recipient.uniqueId); - - void (^retrySend)(void) = ^void() { - if (messageSend.remainingAttempts <= 0) { - return messageSend.failure(responseError); - } - - dispatch_async(OWSDispatch.sendingQueue, ^{ - OWSLogDebug(@"Retrying: %@.", message.debugDescription); - [self sendMessage:messageSend]; - }); - }; - - switch (statusCode) { - case 0: { // Loki - NSError *error; - if ([responseError isKindOfClass:LKSnodeAPIError.class] || [responseError isKindOfClass:LKDotNetAPIError.class] - || [responseError isKindOfClass:DiffieHellmanError.class]) { - error = responseError; - } else { - error = OWSErrorMakeFailedToSendOutgoingMessageError(); - } - [error setIsRetryable:NO]; - return messageSend.failure(error); - } - case 401: { - OWSLogWarn(@"Unable to send due to invalid credentials. Did the user's client get de-authed by " - @"registering elsewhere?"); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceFailure, - NSLocalizedString(@"ERROR_DESCRIPTION_SENDING_UNAUTHORIZED", @"Error message when attempting to send message")); - // No need to retry if we've been de-authed. - [error setIsRetryable:NO]; - return messageSend.failure(error); - } - default: - retrySend(); - break; - } -} - -- (void)handleMessageSentLocally:(TSOutgoingMessage *)message - success:(void (^)(void))successParam - failure:(RetryableFailureHandler)failure -{ - dispatch_block_t success = ^{ - // Don't mark self-sent messages as read (or sent) until the sync transcript is sent - // Loki: Take into account multi device - BOOL isNoteToSelf = [LKSessionMetaProtocol isThreadNoteToSelf:message.thread]; - if (isNoteToSelf && !([message isKindOfClass:LKDeviceLinkMessage.class]) - && ![message isKindOfClass:LKClosedGroupUpdateMessage.class]) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - for (NSString *recipientId in message.sendingRecipientIds) { - [message updateWithReadRecipientId:recipientId readTimestamp:message.timestamp transaction:transaction]; - } - }]; - } - - successParam(); - }; - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [[OWSDisappearingMessagesJob sharedJob] startAnyExpirationForMessage:message - expirationStartedAt:[NSDate ows_millisecondTimeStamp] - transaction:transaction]; - }]; - - if (!message.shouldSyncTranscript) { - return success(); - } - - BOOL shouldSendTranscript = [LKSessionMetaProtocol shouldSendTranscriptForMessage:message inThread:message.thread]; - if (!shouldSendTranscript) { - return success(); - } - - BOOL isRecipientUpdate = message.hasSyncedTranscript; - [self - sendSyncTranscriptForMessage:message - isRecipientUpdate:isRecipientUpdate - success:^{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [message updateWithHasSyncedTranscript:YES transaction:transaction]; - }]; - - success(); - } - failure:failure]; -} - -- (void)sendSyncTranscriptForMessage:(TSOutgoingMessage *)message - isRecipientUpdate:(BOOL)isRecipientUpdate - success:(void (^)(void))success - failure:(RetryableFailureHandler)failure -{ - OWSOutgoingSentMessageTranscript *sentMessageTranscript = - [[OWSOutgoingSentMessageTranscript alloc] initWithOutgoingMessage:message isRecipientUpdate:isRecipientUpdate]; - - NSString *userPublicKey = self.tsAccountManager.localNumber; - - // Loki: Send to the user's other device - __block NSSet *userLinkedDevices; - [self.primaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - userLinkedDevices = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:userPublicKey in:transaction]; - }]; - NSString *otherUserDevice; - for (NSString *device in userLinkedDevices) { - if (![device isEqual:userPublicKey]) { - otherUserDevice = device; - break; - } - } - - NSString *recipientId = otherUserDevice ?: userPublicKey; - __block SignalRecipient *recipient; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - recipient = [SignalRecipient markRecipientAsRegisteredAndGet:recipientId transaction:transaction]; - }]; - - SMKSenderCertificate *senderCertificate = [self.udManager getSenderCertificate]; - OWSUDAccess *recipientUDAccess = nil; - if (senderCertificate != nil) { - recipientUDAccess = [self.udManager udAccessForRecipientId:recipient.recipientId requireSyncAccess:YES]; - } - - // Loki: If the message was aimed at an SSK based closed group, aim the sync transcript at - // the contact thread with the other device rather than also sending it to the group. - __block TSThread *thread = message.thread; - if ([thread isKindOfClass:TSGroupThread.class] && ((TSGroupThread *)thread).usesSharedSenderKeys) { - [LKStorage readWithBlock:^(YapDatabaseReadTransaction *transaction) { - thread = [TSContactThread getThreadWithContactId:otherUserDevice transaction:transaction]; - }]; - } - - OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:sentMessageTranscript - thread:thread - recipient:recipient - senderCertificate:senderCertificate - udAccess:recipientUDAccess - localNumber:self.tsAccountManager.localNumber - success:^{ - OWSLogInfo(@"Successfully sent sync transcript."); - - success(); - } - failure:^(NSError *error) { - OWSLogInfo(@"Failed to send sync transcript: %@ (isRetryable: %d).", error, error.isRetryable); - - failure(error); - }]; - - [self sendMessage:messageSend]; -} - -- (NSArray *)throws_deviceMessagesForMessageSend:(OWSMessageSend *)messageSend -{ - // Loki: Multi device is handled elsewhere so just send to the provided recipient ID (Signal used - // to send to each of the recipient's devices here) - OWSAssertDebug(messageSend.message != nil); - OWSAssertDebug(messageSend.recipient != nil); - - SignalRecipient *recipient = messageSend.recipient; - NSMutableArray *messagesArray = [NSMutableArray new]; - - NSData *_Nullable plainText = [messageSend.message buildPlainTextData:recipient]; - if (!plainText) { - OWSRaiseException(InvalidMessageException, @"Failed to build message proto."); - } - OWSLogDebug(@"Built message: %@ plainTextData.length: %lu", [messageSend.message class], (unsigned long)plainText.length); - - NSArray *recipientID = recipient.recipientId; - - OWSLogVerbose(@"Building device messages for: %@ %@ (isLocalNumber: %d, isUDSend: %d).", - recipientID, - recipient.devices, - messageSend.isLocalNumber, - messageSend.isUDSend); - - @try { - __block BOOL isSessionRequired; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - isSessionRequired = [LKSessionManagementProtocol isSessionRequiredForMessage:messageSend.message recipientID:recipientID transaction:transaction]; - }]; - if (isSessionRequired) { - BOOL hasSession = [self throws_ensureRecipientHasSessionForMessageSend:messageSend recipientID:recipientID deviceId:@(OWSDevicePrimaryDeviceId)]; - - // Loki: Remove this when shared sender keys has been widely rolled out - // ======== - if (!hasSession && [LKSessionManagementProtocol shouldIgnoreMissingPreKeyBundleExceptionForMessage:messageSend.message to:recipientID]) { - return [NSDictionary new]; - } - // ======== - } - - __block NSDictionary *_Nullable messageDict; - __block NSException *encryptionException; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - @try { - messageDict = [self throws_encryptedMessageForMessageSend:messageSend - recipientID:recipientID - plainText:plainText - transaction:transaction]; - } @catch (NSException *exception) { - encryptionException = exception; - } - }]; - - if (encryptionException) { - OWSLogInfo(@"Exception during encryption: %@.", encryptionException); - @throw encryptionException; - } - - if (messageDict) { - [messagesArray addObject:messageDict]; - } else { - OWSRaiseException(InvalidMessageException, @"Failed to encrypt message."); - } - } @catch (NSException *exception) { - if ([exception.name isEqualToString:OWSMessageSenderInvalidDeviceException]) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [recipient updateRegisteredRecipientWithDevicesToAdd:nil - devicesToRemove:@[ @(OWSDevicePrimaryDeviceId) ] - transaction:transaction]; - }]; - } else { - @throw exception; - } - } - - return [messagesArray copy]; -} - -- (BOOL)throws_ensureRecipientHasSessionForMessageSend:(OWSMessageSend *)messageSend recipientID:(NSString *)recipientID deviceId:(NSNumber *)deviceId -{ - OWSAssertDebug(messageSend); - OWSAssertDebug(deviceId); - - OWSPrimaryStorage *storage = self.primaryStorage; - SignalRecipient *recipient = messageSend.recipient; - OWSAssertDebug(recipientID.length > 0); - - // Discard "typing indicator" messages if there is no existing session with the user. - BOOL canSafelyBeDiscarded = messageSend.message.isOnline; - if (canSafelyBeDiscarded) { - OWSRaiseException(NoSessionForTransientMessageException, @"No session for transient message."); - } - - PreKeyBundle *_Nullable bundle = [storage getPreKeyBundleForContact:recipientID]; - __block NSException *exception; - - if (!bundle) { - __block BOOL hasSession; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - hasSession = [storage containsSession:recipientID deviceId:[deviceId intValue] protocolContext:transaction]; - }]; - if (hasSession) { return YES; } - - TSOutgoingMessage *message = messageSend.message; - // Loki: Remove this when we have shared sender keys - // ======== - if ([LKSessionManagementProtocol shouldIgnoreMissingPreKeyBundleExceptionForMessage:message to:recipientID]) { return NO; } - // ======== - NSString *missingPrekeyBundleException = @"missingPrekeyBundleException"; - OWSRaiseException(missingPrekeyBundleException, @"Missing pre key bundle for: %@.", recipientID); - } else { - SessionBuilder *builder = [[SessionBuilder alloc] initWithSessionStore:storage - preKeyStore:storage - signedPreKeyStore:storage - identityKeyStore:self.identityManager - recipientId:recipientID - deviceId:[deviceId intValue]]; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - @try { - [builder throws_processPrekeyBundle:bundle protocolContext:transaction]; - - // Loki: Discard the pre key bundle as the session has now been established - [storage removePreKeyBundleForContact:recipientID transaction:transaction]; - } @catch (NSException *caughtException) { - exception = caughtException; - } - }]; - if (exception) { - if ([exception.name isEqualToString:UntrustedIdentityKeyException]) { - OWSRaiseExceptionWithUserInfo(UntrustedIdentityKeyException, (@{ TSInvalidPreKeyBundleKey : bundle, TSInvalidRecipientKey : recipientID }), @""); - } - @throw exception; - } - return YES; - } -} - -- (nullable NSDictionary *)throws_encryptedMessageForMessageSend:(OWSMessageSend *)messageSend - recipientID:(NSString *)recipientID - plainText:(NSData *)plainText - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(messageSend); - OWSAssertDebug(recipientID); - OWSAssertDebug(plainText); - OWSAssertDebug(transaction); - - OWSPrimaryStorage *storage = self.primaryStorage; - TSOutgoingMessage *message = messageSend.message; - - SessionCipher *cipher = [[SessionCipher alloc] initWithSessionStore:storage - preKeyStore:storage - signedPreKeyStore:storage - identityKeyStore:self.identityManager - recipientId:recipientID - deviceId:@(OWSDevicePrimaryDeviceId).intValue]; - - NSData *_Nullable serializedMessage; - TSWhisperMessageType messageType; - if ([LKSharedSenderKeysImplementation.shared isClosedGroup:recipientID]) { - NSError *error; - serializedMessage = [LKClosedGroupUtilities encryptData:plainText.paddedMessageBody usingGroupPublicKey:recipientID transaction:transaction error:&error]; - - if (error != nil) { - OWSFailDebug(@"Couldn't encrypt message for SSK based closed group due to error: %@.", error); - return nil; - } - - messageType = TSClosedGroupCiphertextMessageType; - - messageSend.udAccess = nil; - } else if (messageSend.isUDSend) { - NSError *error; - LKSessionResetImplementation *sessionResetImplementation = [LKSessionResetImplementation new]; - - SMKSecretSessionCipher *_Nullable secretCipher = - [[SMKSecretSessionCipher alloc] initWithSessionResetImplementation:sessionResetImplementation - sessionStore:self.primaryStorage - preKeyStore:self.primaryStorage - signedPreKeyStore:self.primaryStorage - identityStore:self.identityManager - error:&error]; - if (error || !secretCipher) { - OWSRaiseException(@"SecretSessionCipherFailure", @"Can't create secret session cipher."); - } - - // Loki: The way this works is: - // • Alice sends a session request (i.e. a pre key bundle) to Bob using fallback encryption. - // • She may send any number of subsequent messages also encrypted using fallback encryption. - // • When Bob receives the session request, he sets up his Signal cipher session locally and sends back a null message, - // now encrypted using Signal encryption. - // • Alice receives this, sets up her Signal cipher session locally, and sends any subsequent messages - // using Signal encryption. - - BOOL shouldUseFallbackEncryption = [LKSessionManagementProtocol shouldUseFallbackEncryptionForMessage:message recipientID:recipientID transaction:transaction]; - - if (shouldUseFallbackEncryption) { - [LKLogger print:@"[Loki] Using fallback encryption"]; - } else { - [LKLogger print:@"[Loki] Using Signal Encryption"]; - } - - serializedMessage = [secretCipher throwswrapped_encryptMessageWithRecipientPublicKey:recipientID - deviceID:@(OWSDevicePrimaryDeviceId).intValue - paddedPlaintext:plainText.paddedMessageBody - senderCertificate:messageSend.senderCertificate - protocolContext:transaction - useFallbackSessionCipher:shouldUseFallbackEncryption - error:&error]; - - SCKRaiseIfExceptionWrapperError(error); - if (serializedMessage == nil || error != nil) { - OWSFailDebug(@"Error while UD encrypting message: %@.", error); - return nil; - } - messageType = TSUnidentifiedSenderMessageType; - } else { - id encryptedMessage = - [cipher throws_encryptMessage:[plainText paddedMessageBody] protocolContext:transaction]; - serializedMessage = encryptedMessage.serialized; - messageType = [self messageTypeForCipherMessage:encryptedMessage]; - } - - BOOL isSilent = message.isSilent; - BOOL isOnline = message.isOnline; - - OWSMessageServiceParams *messageParams = - [[OWSMessageServiceParams alloc] initWithType:messageType - recipientId:recipientID - device:@(OWSDevicePrimaryDeviceId).intValue - content:serializedMessage - isSilent:isSilent - isOnline:isOnline - registrationId:[cipher throws_remoteRegistrationId:transaction] - ttl:message.ttl - isPing:NO]; - - NSError *error; - NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:messageParams error:&error]; - - if (error != nil) { - OWSProdError([OWSAnalyticsEvents messageSendErrorCouldNotSerializeMessageJson]); - return nil; - } - - return jsonDict; -} - -- (TSWhisperMessageType)messageTypeForCipherMessage:(id)cipherMessage -{ - switch (cipherMessage.cipherMessageType) { - case CipherMessageType_Whisper: - return TSEncryptedWhisperMessageType; - case CipherMessageType_Prekey: - return TSPreKeyWhisperMessageType; - default: - return TSUnknownMessageType; - } -} - -- (void)saveInfoMessageForGroupMessage:(TSOutgoingMessage *)message inThread:(TSThread *)thread -{ - OWSAssertDebug(message); - OWSAssertDebug(thread); - - if (message.groupMetaMessage == TSGroupMetaMessageDeliver) { - // TODO: Why is this necessary? - [message save]; - } else if (message.groupMetaMessage == TSGroupMetaMessageQuit) { - // MJK TODO - remove senderTimestamp - [[[TSInfoMessage alloc] initWithTimestamp:message.timestamp - inThread:thread - messageType:TSInfoMessageTypeGroupQuit - customMessage:message.customMessage] save]; - } else { - // MJK TODO - remove senderTimestamp - [[[TSInfoMessage alloc] initWithTimestamp:message.timestamp - inThread:thread - messageType:TSInfoMessageTypeGroupUpdate - customMessage:message.customMessage] save]; - } -} - -@end - -@implementation OutgoingMessagePreparer - -#pragma mark - Dependencies - -+ (YapDatabaseConnection *)dbConnection -{ - return SSKEnvironment.shared.primaryStorage.dbReadWriteConnection; -} - -#pragma mark - - -+ (NSArray *)prepareMessageForSending:(TSOutgoingMessage *)message - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(message); - OWSAssertDebug(transaction); - - NSMutableArray *attachmentIds = [NSMutableArray new]; - - if (message.attachmentIds) { - [attachmentIds addObjectsFromArray:message.attachmentIds]; - } - - if (message.quotedMessage) { - // Though we currently only ever expect at most one thumbnail, the proto data model - // suggests this could change. The logic is intended to work with multiple, but - // if we ever actually want to send multiple, we should do more testing. - NSArray *quotedThumbnailAttachments = - [message.quotedMessage createThumbnailAttachmentsIfNecessaryWithTransaction:transaction]; - for (TSAttachmentStream *attachment in quotedThumbnailAttachments) { - [attachmentIds addObject:attachment.uniqueId]; - } - } - - if (message.contactShare.avatarAttachmentId != nil) { - TSAttachment *attachment = [message.contactShare avatarAttachmentWithTransaction:transaction]; - if ([attachment isKindOfClass:[TSAttachmentStream class]]) { - [attachmentIds addObject:attachment.uniqueId]; - } else { - OWSFailDebug(@"Unexpected avatarAttachment: %@.", attachment); - } - } - - if (message.linkPreview.imageAttachmentId != nil) { - TSAttachment *attachment = - [TSAttachment fetchObjectWithUniqueID:message.linkPreview.imageAttachmentId transaction:transaction]; - if ([attachment isKindOfClass:[TSAttachmentStream class]]) { - [attachmentIds addObject:attachment.uniqueId]; - } else { - OWSFailDebug(@"Unexpected attachment: %@.", attachment); - } - } - - // All outgoing messages should be saved at the time they are enqueued. - [message saveWithTransaction:transaction]; - // When we start a message send, all "failed" recipients should be marked as "sending". - [message updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:transaction]; - - return attachmentIds; -} - -+ (void)prepareAttachments:(NSArray *)attachmentInfos - inMessage:(TSOutgoingMessage *)outgoingMessage - completionHandler:(void (^)(NSError *_Nullable error))completionHandler -{ - OWSAssertDebug(attachmentInfos.count > 0); - OWSAssertDebug(outgoingMessage); - - dispatch_async([OWSDispatch attachmentsQueue], ^{ - NSMutableArray *attachmentStreams = [NSMutableArray new]; - for (OWSOutgoingAttachmentInfo *attachmentInfo in attachmentInfos) { - TSAttachmentStream *attachmentStream = - [[TSAttachmentStream alloc] initWithContentType:attachmentInfo.contentType - byteCount:(UInt32)attachmentInfo.dataSource.dataLength - sourceFilename:attachmentInfo.sourceFilename - caption:attachmentInfo.caption - albumMessageId:attachmentInfo.albumMessageId]; - - if (outgoingMessage.isVoiceMessage) { - attachmentStream.attachmentType = TSAttachmentTypeVoiceMessage; - } - - if (![attachmentStream writeDataSource:attachmentInfo.dataSource]) { - OWSProdError([OWSAnalyticsEvents messageSenderErrorCouldNotWriteAttachment]); - NSError *error = OWSErrorMakeWriteAttachmentDataError(); - completionHandler(error); - return; - } - - [attachmentStreams addObject:attachmentStream]; - } - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - for (TSAttachmentStream *attachmentStream in attachmentStreams) { - [outgoingMessage.attachmentIds addObject:attachmentStream.uniqueId]; - if (attachmentStream.sourceFilename) { - outgoingMessage.attachmentFilenameMap[attachmentStream.uniqueId] = attachmentStream.sourceFilename; - } - } - [outgoingMessage saveWithTransaction:transaction]; - for (TSAttachmentStream *attachmentStream in attachmentStreams) { - [attachmentStream saveWithTransaction:transaction]; - } - }]; - - completionHandler(nil); - }); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageServiceParams.h b/SignalServiceKit/src/Messages/OWSMessageServiceParams.h deleted file mode 100644 index 83f00ce3f..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageServiceParams.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSConstants.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Contstructs the per-device-message parameters used when submitting a message to - * the Signal Web Service. - * - * See: - * https://github.com/signalapp/libsignal-service-java/blob/master/java/src/main/java/org/whispersystems/signalservice/internal/push/OutgoingPushMessage.java - */ -@interface OWSMessageServiceParams : MTLModel - -@property (nonatomic, readonly) int type; -@property (nonatomic, readonly) NSString *destination; -@property (nonatomic, readonly) int destinationDeviceId; -@property (nonatomic, readonly) int destinationRegistrationId; -@property (nonatomic, readonly) NSString *content; -@property (nonatomic, readonly) BOOL silent; -@property (nonatomic, readonly) BOOL online; - -// Loki: Message ttl -@property (nonatomic, readonly) uint ttl; - -// Loki: Wether this message is a p2p ping -@property (nonatomic, readonly) BOOL isPing; - -- (instancetype)initWithType:(TSWhisperMessageType)type - recipientId:(NSString *)destination - device:(int)deviceId - content:(NSData *)content - isSilent:(BOOL)isSilent - isOnline:(BOOL)isOnline - registrationId:(int)registrationId - ttl:(uint)ttl - isPing:(BOOL)isPing; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageServiceParams.m b/SignalServiceKit/src/Messages/OWSMessageServiceParams.m deleted file mode 100644 index 69b896bef..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageServiceParams.m +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageServiceParams.h" -#import "TSConstants.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSMessageServiceParams - -+ (NSDictionary *)JSONKeyPathsByPropertyKey -{ - return [NSDictionary mtl_identityPropertyMapWithModel:[self class]]; -} - -- (instancetype)initWithType:(TSWhisperMessageType)type - recipientId:(NSString *)destination - device:(int)deviceId - content:(NSData *)content - isSilent:(BOOL)isSilent - isOnline:(BOOL)isOnline - registrationId:(int)registrationId - ttl:(uint)ttl - isPing:(BOOL)isPing -{ - self = [super init]; - - if (!self) { - return self; - } - - _type = type; - _destination = destination; - _destinationDeviceId = deviceId; - _destinationRegistrationId = registrationId; - _content = [content base64EncodedString]; - _silent = isSilent; - _online = isOnline; - _ttl = ttl; - _isPing = isPing; - - return self; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageUtils.h b/SignalServiceKit/src/Messages/OWSMessageUtils.h deleted file mode 100644 index 031e7fc98..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageUtils.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class TSMessage; -@class TSThread; -@class YapDatabaseReadTransaction; - -@interface OWSMessageUtils : NSObject - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)sharedManager; - -- (NSUInteger)unreadMessagesCount; -- (NSUInteger)unreadMessagesCountExcept:(TSThread *)thread; - -- (void)updateApplicationBadgeCount; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageUtils.m b/SignalServiceKit/src/Messages/OWSMessageUtils.m deleted file mode 100644 index daea9cfce..000000000 --- a/SignalServiceKit/src/Messages/OWSMessageUtils.m +++ /dev/null @@ -1,124 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageUtils.h" -#import "AppContext.h" -#import "MIMETypeUtil.h" -#import "OWSMessageSender.h" -#import "OWSPrimaryStorage.h" -#import "TSAccountManager.h" -#import "TSAttachment.h" -#import "TSAttachmentStream.h" -#import "TSDatabaseView.h" -#import "TSIncomingMessage.h" -#import "TSMessage.h" -#import "TSOutgoingMessage.h" -#import "TSQuotedMessage.h" -#import "TSThread.h" -#import "UIImage+OWS.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSMessageUtils () - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@end - -#pragma mark - - -@implementation OWSMessageUtils - -+ (instancetype)sharedManager -{ - static OWSMessageUtils *sharedMyManager = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedMyManager = [[self alloc] initDefault]; - }); - return sharedMyManager; -} - -- (instancetype)initDefault -{ - OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; - - return [self initWithPrimaryStorage:primaryStorage]; -} - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - - if (!self) { - return self; - } - - _dbConnection = primaryStorage.newDatabaseConnection; - - OWSSingletonAssert(); - - return self; -} - -- (NSUInteger)unreadMessagesCount -{ - __block NSUInteger count = 0; - - [LKStorage readWithBlock:^(YapDatabaseReadTransaction *transaction) { - YapDatabaseViewTransaction *unreadMessages = [transaction ext:TSUnreadDatabaseViewExtensionName]; - NSArray *allGroups = [unreadMessages allGroups]; - for (NSString *groupID in allGroups) { - [unreadMessages enumerateKeysAndObjectsInGroup:groupID - usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { - if (![object conformsToProtocol:@protocol(OWSReadTracking)]) { - return; - } - id unread = (id)object; - if (unread.read) { - [LKLogger print:@"Found an already read message in the * unread * messages list."]; - return; - } - count += 1; - }]; - } - }]; - - return count; - - __block NSUInteger numberOfItems; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - numberOfItems = [[transaction ext:TSUnreadDatabaseViewExtensionName] numberOfItemsInAllGroups]; - }]; - - return numberOfItems; -} - -- (NSUInteger)unreadMessagesCountExcept:(TSThread *)thread -{ - __block NSUInteger numberOfItems; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - id databaseView = [transaction ext:TSUnreadDatabaseViewExtensionName]; - OWSAssertDebug(databaseView); - numberOfItems = ([databaseView numberOfItemsInAllGroups] - [databaseView numberOfItemsInGroup:thread.uniqueId]); - }]; - - return numberOfItems; -} - -- (void)updateApplicationBadgeCount -{ - if (!CurrentAppContext().isMainApp) { - return; - } - - NSUInteger numberOfItems = [self unreadMessagesCount]; - [CurrentAppContext() setMainAppBadgeNumber:numberOfItems]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.h b/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.h deleted file mode 100644 index 490f5632e..000000000 --- a/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSOutgoingMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class SSKProtoCallMessageAnswer; -@class SSKProtoCallMessageBusy; -@class SSKProtoCallMessageHangup; -@class SSKProtoCallMessageIceUpdate; -@class SSKProtoCallMessageOffer; -@class TSThread; - -/** - * WebRTC call signaling sent out of band, via the Signal Service - */ -@interface OWSOutgoingCallMessage : TSOutgoingMessage - -- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSMutableArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - isVoiceMessage:(BOOL)isVoiceMessage - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_UNAVAILABLE; - -- (instancetype)initWithThread:(TSThread *)thread offerMessage:(SSKProtoCallMessageOffer *)offerMessage; -- (instancetype)initWithThread:(TSThread *)thread answerMessage:(SSKProtoCallMessageAnswer *)answerMessage; -- (instancetype)initWithThread:(TSThread *)thread - iceUpdateMessages:(NSArray *)iceUpdateMessage; -- (instancetype)initWithThread:(TSThread *)thread hangupMessage:(SSKProtoCallMessageHangup *)hangupMessage; -- (instancetype)initWithThread:(TSThread *)thread busyMessage:(SSKProtoCallMessageBusy *)busyMessage; - -@property (nullable, nonatomic, readonly) SSKProtoCallMessageOffer *offerMessage; -@property (nullable, nonatomic, readonly) SSKProtoCallMessageAnswer *answerMessage; -@property (nullable, nonatomic, readonly) NSArray *iceUpdateMessages; -@property (nullable, nonatomic, readonly) SSKProtoCallMessageHangup *hangupMessage; -@property (nullable, nonatomic, readonly) SSKProtoCallMessageBusy *busyMessage; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.m b/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.m deleted file mode 100644 index 43eca5be5..000000000 --- a/SignalServiceKit/src/Messages/OWSOutgoingCallMessage.m +++ /dev/null @@ -1,196 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingCallMessage.h" -#import "ProtoUtils.h" -#import "SignalRecipient.h" -#import "TSContactThread.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSOutgoingCallMessage - -- (instancetype)initWithThread:(TSThread *)thread -{ - // MJK TODO - safe to remove senderTimestamp - self = [super initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - if (!self) { - return self; - } - - return self; -} - -- (instancetype)initWithThread:(TSThread *)thread offerMessage:(SSKProtoCallMessageOffer *)offerMessage -{ - self = [self initWithThread:thread]; - if (!self) { - return self; - } - - _offerMessage = offerMessage; - - return self; -} - -- (instancetype)initWithThread:(TSThread *)thread answerMessage:(SSKProtoCallMessageAnswer *)answerMessage -{ - self = [self initWithThread:thread]; - if (!self) { - return self; - } - - _answerMessage = answerMessage; - - return self; -} - -- (instancetype)initWithThread:(TSThread *)thread - iceUpdateMessages:(NSArray *)iceUpdateMessages -{ - self = [self initWithThread:thread]; - if (!self) { - return self; - } - - _iceUpdateMessages = iceUpdateMessages; - - return self; -} - -- (instancetype)initWithThread:(TSThread *)thread hangupMessage:(SSKProtoCallMessageHangup *)hangupMessage -{ - self = [self initWithThread:thread]; - if (!self) { - return self; - } - - _hangupMessage = hangupMessage; - - return self; -} - -- (instancetype)initWithThread:(TSThread *)thread busyMessage:(SSKProtoCallMessageBusy *)busyMessage -{ - self = [self initWithThread:thread]; - if (!self) { - return self; - } - - _busyMessage = busyMessage; - - return self; -} - -#pragma mark - TSOutgoingMessage overrides - -- (BOOL)shouldSyncTranscript -{ - return NO; -} - -- (BOOL)isSilent -{ - // Avoid "phantom messages" for "outgoing call messages". - - return YES; -} - -- (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient -{ - OWSAssertDebug(recipient); - - SSKProtoContentBuilder *builder = [SSKProtoContent builder]; - [builder setCallMessage:[self buildCallMessage:recipient.recipientId]]; - - NSError *error; - NSData *_Nullable data = [builder buildSerializedDataAndReturnError:&error]; - if (error || !data) { - OWSFailDebug(@"could not serialize protobuf: %@", error); - return nil; - } - return data; -} - -- (nullable SSKProtoCallMessage *)buildCallMessage:(NSString *)recipientId -{ - SSKProtoCallMessageBuilder *builder = [SSKProtoCallMessage builder]; - - if (self.offerMessage) { - [builder setOffer:self.offerMessage]; - } - - if (self.answerMessage) { - [builder setAnswer:self.answerMessage]; - } - - if (self.iceUpdateMessages.count > 0) { - [builder setIceUpdate:self.iceUpdateMessages]; - } - - if (self.hangupMessage) { - [builder setHangup:self.hangupMessage]; - } - - if (self.busyMessage) { - [builder setBusy:self.busyMessage]; - } - - [ProtoUtils addLocalProfileKeyIfNecessary:self.thread recipientId:recipientId callMessageBuilder:builder]; - - NSError *error; - SSKProtoCallMessage *_Nullable result = [builder buildAndReturnError:&error]; - if (error || !result) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - return result; -} - -#pragma mark - TSYapDatabaseObject overrides - -- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeCall]; } - -- (BOOL)shouldBeSaved -{ - return NO; -} - -- (NSString *)debugDescription -{ - NSString *className = NSStringFromClass([self class]); - - NSString *payload; - if (self.offerMessage) { - payload = @"offerMessage"; - } else if (self.answerMessage) { - payload = @"answerMessage"; - } else if (self.iceUpdateMessages.count > 0) { - payload = [NSString stringWithFormat:@"iceUpdateMessages: %lu", (unsigned long)self.iceUpdateMessages.count]; - } else if (self.hangupMessage) { - payload = @"hangupMessage"; - } else if (self.busyMessage) { - payload = @"busyMessage"; - } else { - payload = @"none"; - } - - return [NSString stringWithFormat:@"%@ with payload: %@", className, payload]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.h b/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.h deleted file mode 100644 index eb8b1c806..000000000 --- a/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSOutgoingMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class OWSVerificationStateSyncMessage; -@class TSContactThread; - -@interface OWSOutgoingNullMessage : TSOutgoingMessage - -- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSMutableArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - isVoiceMessage:(BOOL)isVoiceMessage - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview; - -- (instancetype)initWithContactThread:(TSContactThread *)contactThread - verificationStateSyncMessage:(OWSVerificationStateSyncMessage *)verificationStateSyncMessage; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.m b/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.m deleted file mode 100644 index a4b96a0f2..000000000 --- a/SignalServiceKit/src/Messages/OWSOutgoingNullMessage.m +++ /dev/null @@ -1,107 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingNullMessage.h" -#import "OWSVerificationStateSyncMessage.h" -#import "TSContactThread.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSOutgoingNullMessage () - -@property (nonatomic, readonly) OWSVerificationStateSyncMessage *verificationStateSyncMessage; - -@end - -#pragma mark - - -@implementation OWSOutgoingNullMessage - -- (instancetype)initWithContactThread:(TSContactThread *)contactThread - verificationStateSyncMessage:(OWSVerificationStateSyncMessage *)verificationStateSyncMessage -{ - // MJK TODO - remove senderTimestamp - self = [super initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:contactThread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - if (!self) { - return self; - } - - _verificationStateSyncMessage = verificationStateSyncMessage; - - return self; -} - -#pragma mark - override TSOutgoingMessage - -- (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient -{ - SSKProtoNullMessageBuilder *nullMessageBuilder = [SSKProtoNullMessage builder]; - - NSUInteger contentLength; - if (self.verificationStateSyncMessage != nil) { - contentLength = self.verificationStateSyncMessage.unpaddedVerifiedLength; - - OWSAssertDebug(self.verificationStateSyncMessage.paddingBytesLength > 0); - - // We add the same amount of padding in the VerificationStateSync message and it's coresponding NullMessage so that - // the sync message is indistinguishable from an outgoing Sent transcript corresponding to the NullMessage. We pad - // the NullMessage so as to obscure it's content. The sync message (like all sync messages) will be *additionally* - // padded by the superclass while being sent. The end result is we send a NullMessage of a non-distinct size, and a - // verification sync which is ~1-512 bytes larger then that. - contentLength += self.verificationStateSyncMessage.paddingBytesLength; - } else { - contentLength = arc4random_uniform(512); - } - - OWSAssertDebug(contentLength > 0); - - nullMessageBuilder.padding = [Cryptography generateRandomBytes:contentLength]; - - NSError *error; - SSKProtoNullMessage *_Nullable nullMessage = [nullMessageBuilder buildAndReturnError:&error]; - if (error != nil || nullMessage == nil) { - OWSFailDebug(@"Couldn't build protobuf due to error: %@.", error); - return nil; - } - - SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; - contentBuilder.nullMessage = nullMessage; - - NSData *_Nullable contentData = [contentBuilder buildSerializedDataAndReturnError:&error]; - if (error != nil || contentData == nil) { - OWSFailDebug(@"Couldn't serialize protobuf due to error: %@.", error); - return nil; - } - - return contentData; -} - -- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeEphemeral]; } - -- (BOOL)shouldSyncTranscript -{ - return NO; -} - -- (BOOL)shouldBeSaved -{ - return NO; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.h b/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.h deleted file mode 100644 index 74f75baa3..000000000 --- a/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class SSKProtoEnvelope; - -@interface OWSOutgoingReceiptManager : NSObject - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; -+ (instancetype)sharedManager; - -- (void)enqueueDeliveryReceiptForEnvelope:(SSKProtoEnvelope *)envelope; - -- (void)enqueueReadReceiptForEnvelope:(NSString *)messageAuthorId timestamp:(uint64_t)timestamp; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.m b/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.m deleted file mode 100644 index 7d4fc5e67..000000000 --- a/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.m +++ /dev/null @@ -1,312 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSOutgoingReceiptManager.h" -#import "AppReadiness.h" -#import "OWSError.h" -#import "OWSMessageSender.h" -#import "OWSPrimaryStorage.h" -#import "OWSReceiptsForSenderMessage.h" -#import "SSKEnvironment.h" -#import "TSContactThread.h" -#import "TSYapDatabaseObject.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSUInteger, OWSReceiptType) { - OWSReceiptType_Delivery, - OWSReceiptType_Read, -}; - -NSString *const kOutgoingDeliveryReceiptManagerCollection = @"kOutgoingDeliveryReceiptManagerCollection"; -NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptManagerCollection"; - -@interface OWSOutgoingReceiptManager () - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@property (nonatomic) Reachability *reachability; - -// This property should only be accessed on the serialQueue. -@property (nonatomic) BOOL isProcessing; - -@end - -#pragma mark - - -@implementation OWSOutgoingReceiptManager - -+ (instancetype)sharedManager -{ - OWSAssert(SSKEnvironment.shared.outgoingReceiptManager); - - return SSKEnvironment.shared.outgoingReceiptManager; -} - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - - if (!self) { - return self; - } - - self.reachability = [Reachability reachabilityForInternetConnection]; - - _dbConnection = primaryStorage.newDatabaseConnection; - - OWSSingletonAssert(); - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(reachabilityChanged) - name:kReachabilityChangedNotification - object:nil]; - - // Start processing. - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - [self process]; - }]; - - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -#pragma mark - Dependencies - -- (OWSMessageSender *)messageSender -{ - OWSAssertDebug(SSKEnvironment.shared.messageSender); - - return SSKEnvironment.shared.messageSender; -} - -#pragma mark - - -- (dispatch_queue_t)serialQueue -{ - static dispatch_queue_t _serialQueue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _serialQueue = dispatch_queue_create("org.whispersystems.outgoingReceipts", DISPATCH_QUEUE_SERIAL); - }); - - return _serialQueue; -} - -// Schedules a processing pass, unless one is already scheduled. -- (void)process { - OWSAssertDebug(AppReadiness.isAppReady); - - dispatch_async(self.serialQueue, ^{ - if (self.isProcessing) { - return; - } - - OWSLogVerbose(@"Processing outbound receipts."); - - self.isProcessing = YES; - - if (!self.reachability.isReachable) { - // No network availability; abort. - self.isProcessing = NO; - return; - } - - NSMutableArray *sendPromises = [NSMutableArray array]; - [sendPromises addObjectsFromArray:[self sendReceiptsForReceiptType:OWSReceiptType_Delivery]]; - [sendPromises addObjectsFromArray:[self sendReceiptsForReceiptType:OWSReceiptType_Read]]; - - if (sendPromises.count < 1) { - // No work to do; abort. - self.isProcessing = NO; - return; - } - - AnyPromise *completionPromise = PMKJoin(sendPromises); - completionPromise.ensure(^() { - // Wait N seconds before conducting another pass. - // This allows time for a batch to accumulate. - // - // We want a value high enough to allow us to effectively de-duplicate - // receipts without being so high that we incur so much latency that - // the user notices. - const CGFloat kProcessingFrequencySeconds = 3.f; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kProcessingFrequencySeconds * NSEC_PER_SEC)), - self.serialQueue, - ^{ - self.isProcessing = NO; - - [self process]; - }); - }); - [completionPromise retainUntilComplete]; - }); -} - -- (NSArray *)sendReceiptsForReceiptType:(OWSReceiptType)receiptType { - NSString *collection = [self collectionForReceiptType:receiptType]; - - NSMutableDictionary *> *queuedReceiptMap = [NSMutableDictionary new]; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - [transaction enumerateKeysAndObjectsInCollection:collection - usingBlock:^(NSString *key, id object, BOOL *stop) { - NSString *recipientId = key; - NSSet *timestamps = object; - queuedReceiptMap[recipientId] = [timestamps copy]; - }]; - }]; - - NSMutableArray *sendPromises = [NSMutableArray array]; - - for (NSString *recipientId in queuedReceiptMap) { - NSSet *timestamps = queuedReceiptMap[recipientId]; - if (timestamps.count < 1) { - OWSFailDebug(@"Missing timestamps."); - continue; - } - - TSThread *thread = [TSContactThread getOrCreateThreadWithContactId:recipientId]; - - if (![LKSessionMetaProtocol shouldSendReceiptInThread:thread]) { - continue; - } - - OWSReceiptsForSenderMessage *message; - NSString *receiptName; - switch (receiptType) { - case OWSReceiptType_Delivery: - message = - [OWSReceiptsForSenderMessage deliveryReceiptsForSenderMessageWithThread:thread - messageTimestamps:timestamps.allObjects]; - receiptName = @"Delivery"; - break; - case OWSReceiptType_Read: - message = [OWSReceiptsForSenderMessage readReceiptsForSenderMessageWithThread:thread - messageTimestamps:timestamps.allObjects]; - receiptName = @"Read"; - break; - } - - AnyPromise *sendPromise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) { - [self.messageSender sendMessage:message - success:^{ - OWSLogInfo( - @"Successfully sent %lu %@ receipts to sender.", (unsigned long)timestamps.count, receiptName); - - // DURABLE CLEANUP - we could replace the custom durability logic in this class - // with a durable JobQueue. - [self dequeueReceiptsWithRecipientId:recipientId timestamps:timestamps receiptType:receiptType]; - - // The value doesn't matter, we just need any non-NSError value. - resolve(@(1)); - } - failure:^(NSError *error) { - OWSLogError(@"Failed to send %@ receipts to sender with error: %@", receiptName, error); - - if (error.domain == OWSSignalServiceKitErrorDomain - && error.code == OWSErrorCodeNoSuchSignalRecipient) { - [self dequeueReceiptsWithRecipientId:recipientId timestamps:timestamps receiptType:receiptType]; - } - - resolve(error); - }]; - }]; - [sendPromises addObject:sendPromise]; - } - - return [sendPromises copy]; -} - -- (void)enqueueDeliveryReceiptForEnvelope:(SSKProtoEnvelope *)envelope -{ - [self enqueueReceiptWithRecipientId:envelope.source - timestamp:envelope.timestamp - receiptType:OWSReceiptType_Delivery]; -} - -- (void)enqueueReadReceiptForEnvelope:(NSString *)messageAuthorId timestamp:(uint64_t)timestamp { - [self enqueueReceiptWithRecipientId:messageAuthorId timestamp:timestamp receiptType:OWSReceiptType_Read]; -} - -- (void)enqueueReceiptWithRecipientId:(NSString *)recipientId - timestamp:(uint64_t)timestamp - receiptType:(OWSReceiptType)receiptType { - NSString *collection = [self collectionForReceiptType:receiptType]; - - if (recipientId.length < 1) { - OWSFailDebug(@"Invalid recipient id."); - return; - } - if (timestamp < 1) { - OWSFailDebug(@"Invalid timestamp."); - return; - } - dispatch_async(self.serialQueue, ^{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - NSSet *_Nullable oldTimestamps = [transaction objectForKey:recipientId inCollection:collection]; - NSMutableSet *newTimestamps - = (oldTimestamps ? [oldTimestamps mutableCopy] : [NSMutableSet new]); - [newTimestamps addObject:@(timestamp)]; - - [transaction setObject:newTimestamps forKey:recipientId inCollection:collection]; - }]; - - [self process]; - }); -} - -- (void)dequeueReceiptsWithRecipientId:(NSString *)recipientId - timestamps:(NSSet *)timestamps - receiptType:(OWSReceiptType)receiptType { - NSString *collection = [self collectionForReceiptType:receiptType]; - - if (recipientId.length < 1) { - OWSFailDebug(@"Invalid recipient id."); - return; - } - if (timestamps.count < 1) { - OWSFailDebug(@"Invalid timestamps."); - return; - } - dispatch_async(self.serialQueue, ^{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - NSSet *_Nullable oldTimestamps = [transaction objectForKey:recipientId inCollection:collection]; - NSMutableSet *newTimestamps - = (oldTimestamps ? [oldTimestamps mutableCopy] : [NSMutableSet new]); - [newTimestamps minusSet:timestamps]; - - if (newTimestamps.count > 0) { - [transaction setObject:newTimestamps forKey:recipientId inCollection:collection]; - } else { - [transaction removeObjectForKey:recipientId inCollection:collection]; - } - }]; - }); -} - -- (void)reachabilityChanged -{ - OWSAssertIsOnMainThread(); - - [self process]; -} - -- (NSString *)collectionForReceiptType:(OWSReceiptType)receiptType { - switch (receiptType) { - case OWSReceiptType_Delivery: - return kOutgoingDeliveryReceiptManagerCollection; - case OWSReceiptType_Read: - return kOutgoingReadReceiptManagerCollection; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSProfileKeyMessage.h b/SignalServiceKit/src/Messages/OWSProfileKeyMessage.h deleted file mode 100644 index c0d04bc83..000000000 --- a/SignalServiceKit/src/Messages/OWSProfileKeyMessage.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSOutgoingMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSProfileKeyMessage : TSOutgoingMessage - -- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp - inThread:(nullable TSThread *)thread - messageBody:(nullable NSString *)body - attachmentIds:(NSMutableArray *)attachmentIds - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt - isVoiceMessage:(BOOL)isVoiceMessage - groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage - quotedMessage:(nullable TSQuotedMessage *)quotedMessage - contactShare:(nullable OWSContact *)contactShare - linkPreview:(nullable OWSLinkPreview *)linkPreview NS_UNAVAILABLE; - -- (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(nullable TSThread *)thread NS_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSProfileKeyMessage.m b/SignalServiceKit/src/Messages/OWSProfileKeyMessage.m deleted file mode 100644 index 02f94d0da..000000000 --- a/SignalServiceKit/src/Messages/OWSProfileKeyMessage.m +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSProfileKeyMessage.h" -#import "ProfileManagerProtocol.h" -#import "ProtoUtils.h" -#import "SSKEnvironment.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSProfileKeyMessage - -- (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(nullable TSThread *)thread -{ - return [super initOutgoingMessageWithTimestamp:timestamp - inThread:thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeProfileKey]; } - -- (BOOL)shouldBeSaved -{ - return NO; -} - -- (BOOL)shouldSyncTranscript -{ - return NO; -} - -- (nullable SSKProtoDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId -{ - OWSAssertDebug(self.thread); - - SSKProtoDataMessageBuilder *_Nullable builder = [self dataMessageBuilder]; - if (!builder) { - OWSFailDebug(@"could not build protobuf."); - return nil; - } - [builder setTimestamp:self.timestamp]; - [ProtoUtils addLocalProfileKeyToDataMessageBuilder:builder]; - [builder setFlags:SSKProtoDataMessageFlagsProfileKeyUpdate]; - - if (recipientId.length > 0) { - // Once we've shared our profile key with a user (perhaps due to being - // a member of a whitelisted group), make sure they're whitelisted. - id profileManager = SSKEnvironment.shared.profileManager; - [profileManager addUserToProfileWhitelist:recipientId]; - } - - NSError *error; - SSKProtoDataMessage *_Nullable dataProto = [builder buildAndReturnError:&error]; - if (error || !dataProto) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - return dataProto; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSReadReceiptManager.h b/SignalServiceKit/src/Messages/OWSReadReceiptManager.h deleted file mode 100644 index 9bd2f7f03..000000000 --- a/SignalServiceKit/src/Messages/OWSReadReceiptManager.h +++ /dev/null @@ -1,86 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class SSKProtoSyncMessageRead; -@class TSIncomingMessage; -@class TSOutgoingMessage; -@class TSThread; -@class YapDatabaseReadTransaction; -@class YapDatabaseReadWriteTransaction; - -extern NSString *const kIncomingMessageMarkedAsReadNotification; - -// There are four kinds of read receipts: -// -// * Read receipts that this client sends to linked -// devices to inform them that a message has been read. -// * Read receipts that this client receives from linked -// devices that inform this client that a message has been read. -// * These read receipts are saved so that they can be applied -// if they arrive before the corresponding message. -// * Read receipts that this client sends to other users -// to inform them that a message has been read. -// * Read receipts that this client receives from other users -// that inform this client that a message has been read. -// * These read receipts are saved so that they can be applied -// if they arrive before the corresponding message. -// -// This manager is responsible for handling and emitting all four kinds. -@interface OWSReadReceiptManager : NSObject - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; -+ (instancetype)sharedManager; - -#pragma mark - Sender/Recipient Read Receipts - -// This method should be called when we receive a read receipt -// from a user to whom we have sent a message. -// -// This method can be called from any thread. -- (void)processReadReceiptsFromRecipientId:(NSString *)recipientId - sentTimestamps:(NSArray *)sentTimestamps - readTimestamp:(uint64_t)readTimestamp; - -- (void)applyEarlyReadReceiptsForOutgoingMessageFromLinkedDevice:(TSOutgoingMessage *)message - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -#pragma mark - Linked Device Read Receipts - -- (void)processReadReceiptsFromLinkedDevice:(NSArray *)readReceiptProtos - readTimestamp:(uint64_t)readTimestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)applyEarlyReadReceiptsForIncomingMessage:(TSIncomingMessage *)message - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -#pragma mark - Locally Read - -// This method cues this manager: -// -// * ...to inform the sender that this message was read (if read receipts -// are enabled). -// * ...to inform the local user's other devices that this message was read. -// -// Both types of messages are deduplicated. -// -// This method can be called from any thread. -- (void)messageWasReadLocally:(TSIncomingMessage *)message; - -- (void)markAsReadLocallyBeforeSortId:(uint64_t)sortId thread:(TSThread *)thread; - -#pragma mark - Settings - -- (void)prepareCachedValues; - -- (BOOL)areReadReceiptsEnabled; -- (BOOL)areReadReceiptsEnabledWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (void)setAreReadReceiptsEnabled:(BOOL)value; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSReadReceiptManager.m b/SignalServiceKit/src/Messages/OWSReadReceiptManager.m deleted file mode 100644 index b95671d5c..000000000 --- a/SignalServiceKit/src/Messages/OWSReadReceiptManager.m +++ /dev/null @@ -1,552 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSReadReceiptManager.h" -#import "AppReadiness.h" -#import "OWSLinkedDeviceReadReceipt.h" -#import "OWSMessageSender.h" -#import "OWSOutgoingReceiptManager.h" -#import "OWSPrimaryStorage.h" -#import "OWSReadReceiptsForLinkedDevicesMessage.h" -#import "OWSReceiptsForSenderMessage.h" -#import "OWSStorage.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSContactThread.h" -#import "TSDatabaseView.h" -#import "TSIncomingMessage.h" -#import "YapDatabaseConnection+OWS.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const kIncomingMessageMarkedAsReadNotification = @"kIncomingMessageMarkedAsReadNotification"; - -@interface TSRecipientReadReceipt : TSYapDatabaseObject - -@property (nonatomic, readonly) uint64_t sentTimestamp; -// Map of "recipient id"-to-"read timestamp". -@property (nonatomic, readonly) NSDictionary *recipientMap; - -@end - -#pragma mark - - -@implementation TSRecipientReadReceipt - -+ (NSString *)collection -{ - return @"TSRecipientReadReceipt2"; -} - -- (instancetype)initWithSentTimestamp:(uint64_t)sentTimestamp -{ - OWSAssertDebug(sentTimestamp > 0); - - self = [super initWithUniqueId:[TSRecipientReadReceipt uniqueIdForSentTimestamp:sentTimestamp]]; - - if (self) { - _sentTimestamp = sentTimestamp; - _recipientMap = [NSDictionary new]; - } - - return self; -} - -+ (NSString *)uniqueIdForSentTimestamp:(uint64_t)timestamp -{ - return [NSString stringWithFormat:@"%llu", timestamp]; -} - -- (void)addRecipientId:(NSString *)recipientId timestamp:(uint64_t)timestamp -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(timestamp > 0); - - NSMutableDictionary *recipientMapCopy = [self.recipientMap mutableCopy]; - recipientMapCopy[recipientId] = @(timestamp); - _recipientMap = [recipientMapCopy copy]; -} - -+ (void)addRecipientId:(NSString *)recipientId - sentTimestamp:(uint64_t)sentTimestamp - readTimestamp:(uint64_t)readTimestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - TSRecipientReadReceipt *_Nullable recipientReadReceipt = - [transaction objectForKey:[self uniqueIdForSentTimestamp:sentTimestamp] inCollection:[self collection]]; - if (!recipientReadReceipt) { - recipientReadReceipt = [[TSRecipientReadReceipt alloc] initWithSentTimestamp:sentTimestamp]; - } - [recipientReadReceipt addRecipientId:recipientId timestamp:readTimestamp]; - [recipientReadReceipt saveWithTransaction:transaction]; -} - -+ (nullable NSDictionary *)recipientMapForSentTimestamp:(uint64_t)sentTimestamp - transaction: - (YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - TSRecipientReadReceipt *_Nullable recipientReadReceipt = - [transaction objectForKey:[self uniqueIdForSentTimestamp:sentTimestamp] inCollection:[self collection]]; - return recipientReadReceipt.recipientMap; -} - -+ (void)removeRecipientIdsForTimestamp:(uint64_t)sentTimestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - [transaction removeObjectForKey:[self uniqueIdForSentTimestamp:sentTimestamp] inCollection:[self collection]]; -} - -@end - -#pragma mark - - -NSString *const OWSReadReceiptManagerCollection = @"OWSReadReceiptManagerCollection"; -NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsEnabled"; - -@interface OWSReadReceiptManager () - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -// A map of "thread unique id"-to-"read receipt" for read receipts that -// we will send to our linked devices. -// -// Should only be accessed while synchronized on the OWSReadReceiptManager. -@property (nonatomic, readonly) NSMutableDictionary *toLinkedDevicesReadReceiptMap; - -// Should only be accessed while synchronized on the OWSReadReceiptManager. -@property (nonatomic) BOOL isProcessing; - -@property (atomic) NSNumber *areReadReceiptsEnabledCached; - -@end - -#pragma mark - - -@implementation OWSReadReceiptManager - -+ (instancetype)sharedManager -{ - OWSAssert(SSKEnvironment.shared.readReceiptManager); - - return SSKEnvironment.shared.readReceiptManager; -} - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - - if (!self) { - return self; - } - - _dbConnection = primaryStorage.newDatabaseConnection; - - _toLinkedDevicesReadReceiptMap = [NSMutableDictionary new]; - - OWSSingletonAssert(); - - // Start processing. - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - [self scheduleProcessing]; - }]; - - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -#pragma mark - Dependencies - -- (SSKMessageSenderJobQueue *)messageSenderJobQueue -{ - return SSKEnvironment.shared.messageSenderJobQueue; -} - -- (OWSOutgoingReceiptManager *)outgoingReceiptManager -{ - OWSAssertDebug(SSKEnvironment.shared.outgoingReceiptManager); - - return SSKEnvironment.shared.outgoingReceiptManager; -} - -#pragma mark - - -// Schedules a processing pass, unless one is already scheduled. -- (void)scheduleProcessing -{ - OWSAssertDebug(AppReadiness.isAppReady); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - @synchronized(self) - { - if (self.isProcessing) { - return; - } - - self.isProcessing = YES; - - [self process]; - } - }); -} - -- (void)process -{ - @synchronized(self) - { - OWSLogVerbose(@"Processing read receipts."); - - NSArray *readReceiptsForLinkedDevices = - [self.toLinkedDevicesReadReceiptMap allValues]; - [self.toLinkedDevicesReadReceiptMap removeAllObjects]; - if (readReceiptsForLinkedDevices.count > 0) { - OWSReadReceiptsForLinkedDevicesMessage *message = - [[OWSReadReceiptsForLinkedDevicesMessage alloc] initWithReadReceipts:readReceiptsForLinkedDevices]; - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [self.messageSenderJobQueue addMessage:message transaction:transaction]; - }]; - } - - BOOL didWork = readReceiptsForLinkedDevices.count > 0; - - if (didWork) { - // Wait N seconds before processing read receipts again. - // This allows time for a batch to accumulate. - // - // We want a value high enough to allow us to effectively de-duplicate, - // read receipts without being so high that we risk not sending read - // receipts due to app exit. - const CGFloat kProcessingFrequencySeconds = 3.f; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kProcessingFrequencySeconds * NSEC_PER_SEC)), - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - ^{ - [self process]; - }); - } else { - self.isProcessing = NO; - } - } -} - -#pragma mark - Mark as Read Locally - -- (void)markAsReadLocallyBeforeSortId:(uint64_t)sortId thread:(TSThread *)thread -{ - OWSAssertDebug(thread); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self markAsReadBeforeSortId:sortId - thread:thread - readTimestamp:[NSDate ows_millisecondTimeStamp] - wasLocal:YES - transaction:transaction]; - }]; - }); -} - -- (void)messageWasReadLocally:(TSIncomingMessage *)message -{ - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - @synchronized(self) - { - NSString *threadUniqueId = message.uniqueThreadId; - OWSAssertDebug(threadUniqueId.length > 0); - - NSString *messageAuthorId = message.authorId; - OWSAssertDebug(messageAuthorId.length > 0); - - OWSLinkedDeviceReadReceipt *newReadReceipt = - [[OWSLinkedDeviceReadReceipt alloc] initWithSenderId:messageAuthorId - messageIdTimestamp:message.timestamp - readTimestamp:[NSDate ows_millisecondTimeStamp]]; - - OWSLinkedDeviceReadReceipt *_Nullable oldReadReceipt = self.toLinkedDevicesReadReceiptMap[threadUniqueId]; - if (oldReadReceipt && oldReadReceipt.messageIdTimestamp > newReadReceipt.messageIdTimestamp) { - // If there's an existing "linked device" read receipt for the same thread with - // a newer timestamp, discard this "linked device" read receipt. - OWSLogVerbose(@"Ignoring redundant read receipt for linked devices."); - } else { - OWSLogVerbose(@"Enqueuing read receipt for linked devices."); - self.toLinkedDevicesReadReceiptMap[threadUniqueId] = newReadReceipt; - } - - if (![LKSessionMetaProtocol shouldSendReceiptInThread:message.thread]) { - return; - } - - if ([self areReadReceiptsEnabled]) { - OWSLogVerbose(@"Enqueuing read receipt for sender."); - [self.outgoingReceiptManager enqueueReadReceiptForEnvelope:messageAuthorId timestamp:message.timestamp]; - } - - [self scheduleProcessing]; - } - }); -} - -#pragma mark - Read Receipts From Recipient - -- (void)processReadReceiptsFromRecipientId:(NSString *)recipientId - sentTimestamps:(NSArray *)sentTimestamps - readTimestamp:(uint64_t)readTimestamp -{ - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(sentTimestamps); - - if (![self areReadReceiptsEnabled]) { - OWSLogInfo(@"Ignoring incoming receipt message as read receipts are disabled."); - return; - } - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - for (NSNumber *nsSentTimestamp in sentTimestamps) { - UInt64 sentTimestamp = [nsSentTimestamp unsignedLongLongValue]; - - NSArray *messages - = (NSArray *)[TSInteraction interactionsWithTimestamp:sentTimestamp - ofClass:[TSOutgoingMessage class] - withTransaction:transaction]; - if (messages.count > 1) { - OWSLogError(@"More than one matching message with timestamp: %llu.", sentTimestamp); - } - if (messages.count > 0) { - // TODO: We might also need to "mark as read by recipient" any older messages - // from us in that thread. Or maybe this state should hang on the thread? - for (TSOutgoingMessage *message in messages) { - [message updateWithReadRecipientId:recipientId - readTimestamp:readTimestamp - transaction:transaction]; - } - } else { - // Persist the read receipts so that we can apply them to outgoing messages - // that we learn about later through sync messages. - [TSRecipientReadReceipt addRecipientId:recipientId - sentTimestamp:sentTimestamp - readTimestamp:readTimestamp - transaction:transaction]; - } - } - }]; - }); -} - -- (void)applyEarlyReadReceiptsForOutgoingMessageFromLinkedDevice:(TSOutgoingMessage *)message - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(message); - OWSAssertDebug(transaction); - - uint64_t sentTimestamp = message.timestamp; - NSDictionary *recipientMap = - [TSRecipientReadReceipt recipientMapForSentTimestamp:sentTimestamp transaction:transaction]; - if (!recipientMap) { - return; - } - OWSAssertDebug(recipientMap.count > 0); - for (NSString *recipientId in recipientMap) { - NSNumber *nsReadTimestamp = recipientMap[recipientId]; - OWSAssertDebug(nsReadTimestamp); - uint64_t readTimestamp = [nsReadTimestamp unsignedLongLongValue]; - - [message updateWithReadRecipientId:recipientId readTimestamp:readTimestamp transaction:transaction]; - } - [TSRecipientReadReceipt removeRecipientIdsForTimestamp:message.timestamp transaction:transaction]; -} - -#pragma mark - Linked Device Read Receipts - -- (void)applyEarlyReadReceiptsForIncomingMessage:(TSIncomingMessage *)message - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(message); - OWSAssertDebug(transaction); - - NSString *senderId = message.authorId; - uint64_t timestamp = message.timestamp; - if (senderId.length < 1 || timestamp < 1) { - OWSFailDebug(@"Invalid incoming message: %@ %llu", senderId, timestamp); - return; - } - - OWSLinkedDeviceReadReceipt *_Nullable readReceipt = - [OWSLinkedDeviceReadReceipt findLinkedDeviceReadReceiptWithSenderId:senderId - messageIdTimestamp:timestamp - transaction:transaction]; - if (!readReceipt) { - return; - } - - [message markAsReadAtTimestamp:readReceipt.readTimestamp sendReadReceipt:NO transaction:transaction]; - [readReceipt removeWithTransaction:transaction]; -} - -- (void)processReadReceiptsFromLinkedDevice:(NSArray *)readReceiptProtos - readTimestamp:(uint64_t)readTimestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(readReceiptProtos); - OWSAssertDebug(transaction); - - for (SSKProtoSyncMessageRead *readReceiptProto in readReceiptProtos) { - NSString *_Nullable senderId = readReceiptProto.sender; - uint64_t messageIdTimestamp = readReceiptProto.timestamp; - - if (senderId.length == 0) { - OWSFailDebug(@"senderId was unexpectedly nil"); - continue; - } - - if (messageIdTimestamp == 0) { - OWSFailDebug(@"messageIdTimestamp was unexpectedly 0"); - continue; - } - - NSArray *messages - = (NSArray *)[TSInteraction interactionsWithTimestamp:messageIdTimestamp - ofClass:[TSIncomingMessage class] - withTransaction:transaction]; - if (messages.count > 0) { - for (TSIncomingMessage *message in messages) { - NSTimeInterval secondsSinceRead = [NSDate new].timeIntervalSince1970 - readTimestamp / 1000; - OWSAssertDebug([message isKindOfClass:[TSIncomingMessage class]]); - OWSLogDebug(@"read on linked device %f seconds ago", secondsSinceRead); - [self markAsReadOnLinkedDevice:message readTimestamp:readTimestamp transaction:transaction]; - } - } else { - // Received read receipt for unknown incoming message. - // Persist in case we receive the incoming message later. - OWSLinkedDeviceReadReceipt *readReceipt = - [[OWSLinkedDeviceReadReceipt alloc] initWithSenderId:senderId - messageIdTimestamp:messageIdTimestamp - readTimestamp:readTimestamp]; - [readReceipt saveWithTransaction:transaction]; - } - } -} - -- (void)markAsReadOnLinkedDevice:(TSIncomingMessage *)message - readTimestamp:(uint64_t)readTimestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(message); - OWSAssertDebug(transaction); - - // Always re-mark the message as read to ensure any earlier read time is applied to disappearing messages. - [message markAsReadAtTimestamp:readTimestamp sendReadReceipt:NO transaction:transaction]; - - // Also mark any unread messages appearing earlier in the thread as read as well. - [self markAsReadBeforeSortId:message.sortId - thread:[message threadWithTransaction:transaction] - readTimestamp:readTimestamp - wasLocal:NO - transaction:transaction]; -} - -#pragma mark - Mark As Read - -- (void)markAsReadBeforeSortId:(uint64_t)sortId - thread:(TSThread *)thread - readTimestamp:(uint64_t)readTimestamp - wasLocal:(BOOL)wasLocal - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(sortId > 0); - OWSAssertDebug(thread); - OWSAssertDebug(transaction); - - NSMutableArray> *newlyReadList = [NSMutableArray new]; - - [[TSDatabaseView unseenDatabaseViewExtension:transaction] - enumerateKeysAndObjectsInGroup:thread.uniqueId - usingBlock:^(NSString *collection, NSString *key, id object, NSUInteger index, BOOL *stop) { - if (![object conformsToProtocol:@protocol(OWSReadTracking)]) { - OWSFailDebug( - @"Expected to conform to OWSReadTracking: object with class: %@ collection: %@ " - @"key: %@", - [object class], - collection, - key); - return; - } - id possiblyRead = (id)object; - if (possiblyRead.sortId > sortId) { - *stop = YES; - return; - } - - // Under normal circumstances !possiblyRead.read should always evaluate to true at this point, but - // there is a bug that can somehow cause it to be false leading to conversations permanently being - // stuck with "unread" messages. - - OWSAssertDebug(possiblyRead.expireStartedAt == 0); - if (!possiblyRead.read) { - [newlyReadList addObject:possiblyRead]; - } - }]; - - if (newlyReadList.count < 1) { - return; - } - - if (wasLocal) { - OWSLogError(@"Marking %lu messages as read locally.", (unsigned long)newlyReadList.count); - } else { - OWSLogError(@"Marking %lu messages as read by linked device.", (unsigned long)newlyReadList.count); - } - for (id readItem in newlyReadList) { - [readItem markAsReadAtTimestamp:readTimestamp sendReadReceipt:wasLocal transaction:transaction]; - } -} - -#pragma mark - Settings - -- (void)prepareCachedValues -{ - [self areReadReceiptsEnabled]; -} - -- (BOOL)areReadReceiptsEnabled -{ - // We don't need to worry about races around this cached value. - if (!self.areReadReceiptsEnabledCached) { - self.areReadReceiptsEnabledCached = @([self.dbConnection boolForKey:OWSReadReceiptManagerAreReadReceiptsEnabled - inCollection:OWSReadReceiptManagerCollection - defaultValue:NO]); - } - - return [self.areReadReceiptsEnabledCached boolValue]; -} - -- (void)setAreReadReceiptsEnabled:(BOOL)value -{ - OWSLogInfo(@"setAreReadReceiptsEnabled: %d.", value); - - [self.dbConnection setBool:value - forKey:OWSReadReceiptManagerAreReadReceiptsEnabled - inCollection:OWSReadReceiptManagerCollection]; - - [SSKEnvironment.shared.syncManager sendConfigurationSyncMessage]; - - self.areReadReceiptsEnabledCached = @(value); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSReadTracking.h b/SignalServiceKit/src/Messages/OWSReadTracking.h deleted file mode 100644 index d135837e5..000000000 --- a/SignalServiceKit/src/Messages/OWSReadTracking.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class YapDatabaseReadWriteTransaction; - -/** - * Some interactions track read/unread status. - * e.g. incoming messages and call notifications - */ -@protocol OWSReadTracking - -/** - * Has the local user seen the interaction? - */ -@property (nonatomic, readonly, getter=wasRead) BOOL read; - -@property (nonatomic, readonly) uint64_t expireStartedAt; -@property (nonatomic, readonly) uint64_t sortId; -@property (nonatomic, readonly) NSString *uniqueThreadId; - - -- (BOOL)shouldAffectUnreadCounts; - -/** - * Used both for *responding* to a remote read receipt and in response to the local user's activity. - */ -- (void)markAsReadAtTimestamp:(uint64_t)readTimestamp - sendReadReceipt:(BOOL)sendReadReceipt - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSSignalAddress.swift b/SignalServiceKit/src/Messages/OWSSignalAddress.swift deleted file mode 100644 index ce04d6013..000000000 --- a/SignalServiceKit/src/Messages/OWSSignalAddress.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -public enum OWSSignalAddressError: Error { - case assertionError(description: String) -} - -@objc -public class OWSSignalAddress: NSObject { - @objc - public let recipientId: String - - @objc - public let deviceId: UInt - - // MARK: Initializers - - @objc public init(recipientId: String, deviceId: UInt) throws { - guard recipientId.count > 0 else { - throw OWSSignalAddressError.assertionError(description: "Invalid recipient id: \(deviceId)") - } - - guard deviceId > 0 else { - throw OWSSignalAddressError.assertionError(description: "Invalid device id: \(deviceId)") - } - - self.recipientId = recipientId - self.deviceId = deviceId - } -} diff --git a/SignalServiceKit/src/Messages/OWSUnknownContactBlockOfferMessage.h b/SignalServiceKit/src/Messages/OWSUnknownContactBlockOfferMessage.h deleted file mode 100644 index 256edd2dd..000000000 --- a/SignalServiceKit/src/Messages/OWSUnknownContactBlockOfferMessage.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSErrorMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -// This is a deprecated class, we're keeping it around to avoid YapDB serialization errors -// TODO - remove this class, clean up existing instances, ensure any missed ones don't explode (UnknownDBObject) -__attribute__((deprecated)) @interface OWSUnknownContactBlockOfferMessage : TSErrorMessage - -@property (nonatomic, readonly) NSString *contactId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSUnknownContactBlockOfferMessage.m b/SignalServiceKit/src/Messages/OWSUnknownContactBlockOfferMessage.m deleted file mode 100644 index 3d11291cf..000000000 --- a/SignalServiceKit/src/Messages/OWSUnknownContactBlockOfferMessage.m +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSUnknownContactBlockOfferMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSUnknownContactBlockOfferMessage () - -@property (nonatomic) NSString *contactId; - -@end - -#pragma mark - - -// This is a deprecated class, we're keeping it around to avoid YapDB serialization errors -// TODO - remove this class, clean up existing instances, ensure any missed ones don't explode (UnknownDBObject) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -@implementation OWSUnknownContactBlockOfferMessage -#pragma clang diagnostic pop - -- (BOOL)shouldUseReceiptDateForSorting -{ - // Use the timestamp, not the "received at" timestamp to sort, - // since we're creating these interactions after the fact and back-dating them. - return NO; -} - -- (BOOL)isDynamicInteraction -{ - return YES; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/PreKeyBundle+jsonDict.h b/SignalServiceKit/src/Messages/PreKeyBundle+jsonDict.h deleted file mode 100644 index ea16793fc..000000000 --- a/SignalServiceKit/src/Messages/PreKeyBundle+jsonDict.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface PreKeyBundle (jsonDict) - -+ (nullable PreKeyBundle *)preKeyBundleFromDictionary:(NSDictionary *)dictionary forDeviceNumber:(NSNumber *)number; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/PreKeyBundle+jsonDict.m b/SignalServiceKit/src/Messages/PreKeyBundle+jsonDict.m deleted file mode 100644 index 51685ada1..000000000 --- a/SignalServiceKit/src/Messages/PreKeyBundle+jsonDict.m +++ /dev/null @@ -1,110 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "PreKeyBundle+jsonDict.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation PreKeyBundle (jsonDict) - -+ (nullable PreKeyBundle *)preKeyBundleFromDictionary:(NSDictionary *)dictionary forDeviceNumber:(NSNumber *)number -{ - PreKeyBundle *bundle = nil; - - id identityKeyObject = [dictionary objectForKey:@"identityKey"]; - if (![identityKeyObject isKindOfClass:[NSString class]]) { - OWSFailDebug(@"Unexpected identityKeyObject: %@", [identityKeyObject class]); - return nil; - } - NSString *identityKeyString = (NSString *)identityKeyObject; - - id devicesObject = [dictionary objectForKey:@"devices"]; - if (![devicesObject isKindOfClass:[NSArray class]]) { - OWSFailDebug(@"Unexpected devicesObject: %@", [devicesObject class]); - return nil; - } - NSArray *devicesArray = (NSArray *)devicesObject; - - NSData *identityKey = [NSData dataFromBase64StringNoPadding:identityKeyString]; - - for (NSDictionary *deviceDict in devicesArray) { - NSNumber *registrationIdString = [deviceDict objectForKey:@"registrationId"]; - NSNumber *deviceIdString = [deviceDict objectForKey:@"deviceId"]; - - if (!(registrationIdString && deviceIdString)) { - OWSLogError(@"Failed to get the registration id and device id"); - return nil; - } - - if (![deviceIdString isEqualToNumber:number]) { - OWSLogWarn(@"Got a keyid for another device"); - return nil; - } - - int registrationId = [registrationIdString intValue]; - int deviceId = [deviceIdString intValue]; - - NSDictionary *_Nullable preKeyDict; - id optionalPreKeyDict = [deviceDict objectForKey:@"preKey"]; - // JSON parsing might give us NSNull, so we can't simply check for non-nil. - if ([optionalPreKeyDict isKindOfClass:[NSDictionary class]]) { - preKeyDict = (NSDictionary *)optionalPreKeyDict; - } - - int prekeyId; - NSData *_Nullable preKeyPublic; - - if (!preKeyDict) { - OWSLogInfo(@"No one-time prekey included in the bundle."); - prekeyId = -1; - } else { - prekeyId = [[preKeyDict objectForKey:@"keyId"] intValue]; - NSString *preKeyPublicString = [preKeyDict objectForKey:@"publicKey"]; - preKeyPublic = [NSData dataFromBase64StringNoPadding:preKeyPublicString]; - } - - NSDictionary *signedPrekey = [deviceDict objectForKey:@"signedPreKey"]; - - if (![signedPrekey isKindOfClass:[NSDictionary class]]) { - OWSLogError(@"Device doesn't have signed prekeys registered"); - return nil; - } - - NSNumber *signedKeyIdNumber = [signedPrekey objectForKey:@"keyId"]; - NSString *signedSignatureString = [signedPrekey objectForKey:@"signature"]; - NSString *signedPublicKeyString = [signedPrekey objectForKey:@"publicKey"]; - - - if (!(signedKeyIdNumber && signedPublicKeyString && signedSignatureString)) { - OWSLogError(@"Missing signed key material"); - return nil; - } - - NSData *signedPrekeyPublic = [NSData dataFromBase64StringNoPadding:signedPublicKeyString]; - NSData *signedPreKeySignature = [NSData dataFromBase64StringNoPadding:signedSignatureString]; - - if (!(signedPrekeyPublic && signedPreKeySignature)) { - OWSLogError(@"Failed to parse signed keying material"); - return nil; - } - - int signedPreKeyId = [signedKeyIdNumber intValue]; - - bundle = [[self alloc] initWithRegistrationId:registrationId - deviceId:deviceId - preKeyId:prekeyId - preKeyPublic:preKeyPublic - signedPreKeyPublic:signedPrekeyPublic - signedPreKeyId:signedPreKeyId - signedPreKeySignature:signedPreKeySignature - identityKey:identityKey]; - } - - return bundle; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/TSCall.h b/SignalServiceKit/src/Messages/TSCall.h deleted file mode 100644 index b7c26b366..000000000 --- a/SignalServiceKit/src/Messages/TSCall.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSReadTracking.h" -#import "TSInteraction.h" - -NS_ASSUME_NONNULL_BEGIN - -@class TSContactThread; - -typedef enum { - RPRecentCallTypeIncoming = 1, - RPRecentCallTypeOutgoing, - RPRecentCallTypeIncomingMissed, - // These call types are used until the call connects. - RPRecentCallTypeOutgoingIncomplete, - RPRecentCallTypeIncomingIncomplete, - RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity, - RPRecentCallTypeIncomingDeclined, - RPRecentCallTypeOutgoingMissed, -} RPRecentCallType; - -NSString *NSStringFromCallType(RPRecentCallType callType); - -@interface TSCall : TSInteraction - -@property (nonatomic, readonly) RPRecentCallType callType; - -- (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread NS_UNAVAILABLE; - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - withCallNumber:(NSString *)contactNumber - callType:(RPRecentCallType)callType - inThread:(TSContactThread *)thread NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -- (void)updateCallType:(RPRecentCallType)callType; -- (void)updateCallType:(RPRecentCallType)callType transaction:(YapDatabaseReadWriteTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/TSCall.m b/SignalServiceKit/src/Messages/TSCall.m deleted file mode 100644 index 4b197b38c..000000000 --- a/SignalServiceKit/src/Messages/TSCall.m +++ /dev/null @@ -1,178 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSCall.h" -#import "TSContactThread.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *NSStringFromCallType(RPRecentCallType callType) -{ - switch (callType) { - case RPRecentCallTypeIncoming: - return @"RPRecentCallTypeIncoming"; - case RPRecentCallTypeOutgoing: - return @"RPRecentCallTypeOutgoing"; - case RPRecentCallTypeIncomingMissed: - return @"RPRecentCallTypeIncomingMissed"; - case RPRecentCallTypeOutgoingIncomplete: - return @"RPRecentCallTypeOutgoingIncomplete"; - case RPRecentCallTypeIncomingIncomplete: - return @"RPRecentCallTypeIncomingIncomplete"; - case RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity: - return @"RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity"; - case RPRecentCallTypeIncomingDeclined: - return @"RPRecentCallTypeIncomingDeclined"; - case RPRecentCallTypeOutgoingMissed: - return @"RPRecentCallTypeOutgoingMissed"; - } -} - -NSUInteger TSCallCurrentSchemaVersion = 1; - -@interface TSCall () - -@property (nonatomic, getter=wasRead) BOOL read; - -@property (nonatomic, readonly) NSUInteger callSchemaVersion; - -@end - -#pragma mark - - -@implementation TSCall - -- (instancetype)initWithTimestamp:(uint64_t)timestamp - withCallNumber:(NSString *)contactNumber - callType:(RPRecentCallType)callType - inThread:(TSContactThread *)thread -{ - self = [super initInteractionWithTimestamp:timestamp inThread:thread]; - - if (!self) { - return self; - } - - _callSchemaVersion = TSCallCurrentSchemaVersion; - _callType = callType; - - // Ensure users are notified of missed calls. - BOOL isIncomingMissed = (_callType == RPRecentCallTypeIncomingMissed - || _callType == RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity); - if (isIncomingMissed) { - _read = NO; - } else { - _read = YES; - } - - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - if (self.callSchemaVersion < 1) { - // Assume user has already seen any call that predate read-tracking - _read = YES; - } - - _callSchemaVersion = TSCallCurrentSchemaVersion; - - return self; -} - -- (OWSInteractionType)interactionType -{ - return OWSInteractionType_Call; -} - -- (NSString *)previewTextWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - // We don't actually use the `transaction` but other sibling classes do. - switch (_callType) { - case RPRecentCallTypeIncoming: - return NSLocalizedString(@"INCOMING_CALL", @"info message text in conversation view"); - case RPRecentCallTypeOutgoing: - return NSLocalizedString(@"OUTGOING_CALL", @"info message text in conversation view"); - case RPRecentCallTypeIncomingMissed: - return NSLocalizedString(@"MISSED_CALL", @"info message text in conversation view"); - case RPRecentCallTypeOutgoingIncomplete: - return NSLocalizedString(@"OUTGOING_INCOMPLETE_CALL", @"info message text in conversation view"); - case RPRecentCallTypeIncomingIncomplete: - return NSLocalizedString(@"INCOMING_INCOMPLETE_CALL", @"info message text in conversation view"); - case RPRecentCallTypeIncomingMissedBecauseOfChangedIdentity: - return NSLocalizedString(@"INFO_MESSAGE_MISSED_CALL_DUE_TO_CHANGED_IDENITY", @"info message text shown in conversation view"); - case RPRecentCallTypeIncomingDeclined: - return NSLocalizedString(@"INCOMING_DECLINED_CALL", - @"info message recorded in conversation history when local user declined a call"); - case RPRecentCallTypeOutgoingMissed: - return NSLocalizedString(@"OUTGOING_MISSED_CALL", - @"info message recorded in conversation history when local user tries and fails to call another user."); - } -} - -#pragma mark - OWSReadTracking - -- (uint64_t)expireStartedAt -{ - return 0; -} - -- (BOOL)shouldAffectUnreadCounts -{ - return YES; -} - -- (void)markAsReadAtTimestamp:(uint64_t)readTimestamp - sendReadReceipt:(BOOL)sendReadReceipt - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - - OWSAssertDebug(transaction); - - if (_read) { - return; - } - - OWSLogDebug(@"marking as read uniqueId: %@ which has timestamp: %llu", self.uniqueId, self.timestamp); - _read = YES; - [self saveWithTransaction:transaction]; - - // Ignore sendReadReceipt - it doesn't apply to calls. -} - -#pragma mark - Methods - -- (void)updateCallType:(RPRecentCallType)callType -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self updateCallType:callType transaction:transaction]; - }]; -} - -- (void)updateCallType:(RPRecentCallType)callType transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - OWSLogInfo(@"updating call type of call: %@ -> %@ with uniqueId: %@ which has timestamp: %llu", - NSStringFromCallType(_callType), - NSStringFromCallType(callType), - self.uniqueId, - self.timestamp); - - _callType = callType; - - [self saveWithTransaction:transaction]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/TSGroupModel.h b/SignalServiceKit/src/Messages/TSGroupModel.h deleted file mode 100644 index 5b312157b..000000000 --- a/SignalServiceKit/src/Messages/TSGroupModel.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "ContactsManagerProtocol.h" -#import "TSYapDatabaseObject.h" -#import "TSAccountManager.h" - - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSInteger, GroupType) { - closedGroup = 0, // a.k.a. private group chat - openGroup = 1, // a.k.a. public group chat - rssFeed = 2 -}; - -extern const int32_t kGroupIdLength; - -@interface TSGroupModel : TSYapDatabaseObject - -@property (nonatomic) NSArray *groupMemberIds; -@property (nonatomic) NSArray *groupAdminIds; -@property (nullable, readonly, nonatomic) NSString *groupName; -@property (readonly, nonatomic) NSData *groupId; -@property (nonatomic) GroupType groupType; -@property (nonatomic) NSMutableSet *removedMembers; - -#if TARGET_OS_IOS -@property (nullable, nonatomic, strong) UIImage *groupImage; - -- (instancetype)initWithTitle:(nullable NSString *)title - memberIds:(NSArray *)memberIds - image:(nullable UIImage *)image - groupId:(NSData *)groupId - groupType:(GroupType)groupType - adminIds:(NSArray *)adminIds; - -- (BOOL)isEqual:(id)other; -- (BOOL)isEqualToGroupModel:(TSGroupModel *)model; -- (NSString *)getInfoStringAboutUpdateTo:(TSGroupModel *)model contactsManager:(id)contactsManager; -- (void)updateGroupId: (NSData *)newGroupId; -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/TSGroupModel.m b/SignalServiceKit/src/Messages/TSGroupModel.m deleted file mode 100644 index f8bbdba35..000000000 --- a/SignalServiceKit/src/Messages/TSGroupModel.m +++ /dev/null @@ -1,194 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSGroupModel.h" -#import "FunctionalUtil.h" -#import "NSString+SSK.h" -#import -#import "OWSIdentityManager.h" - -NS_ASSUME_NONNULL_BEGIN - -const int32_t kGroupIdLength = 16; - -@interface TSGroupModel () - -@property (nullable, nonatomic) NSString *groupName; - -@end - -#pragma mark - - -@implementation TSGroupModel - -#if TARGET_OS_IOS -- (instancetype)initWithTitle:(nullable NSString *)title - memberIds:(NSArray *)memberIds - image:(nullable UIImage *)image - groupId:(NSData *)groupId - groupType:(GroupType)groupType - adminIds:(NSArray *)adminIds -{ - OWSAssertDebug(memberIds); - - _groupName = title; - _groupMemberIds = [memberIds copy]; - _groupImage = image; // image is stored in DB - _groupType = groupType; - _groupId = groupId; - _groupAdminIds = [adminIds copy]; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - // Occasionally seeing this as nil in legacy data, - // which causes crashes. - if (_groupMemberIds == nil) { - _groupMemberIds = [NSArray new]; - } - - if (_groupAdminIds == nil) { - _groupAdminIds = [NSArray new]; - } - - if (_removedMembers == nil) { - _removedMembers = [NSMutableSet new]; - } - - return self; -} - -- (BOOL)isEqual:(id)other { - if (other == self) { - return YES; - } - if (!other || ![other isKindOfClass:[self class]]) { - return NO; - } - return [self isEqualToGroupModel:other]; -} - -- (BOOL)isEqualToGroupModel:(TSGroupModel *)other { - if (self == other) - return YES; - if (![_groupId isEqualToData:other.groupId]) { - return NO; - } - if (![_groupName isEqual:other.groupName]) { - return NO; - } - if (!(_groupImage != nil && other.groupImage != nil && - [UIImagePNGRepresentation(_groupImage) isEqualToData:UIImagePNGRepresentation(other.groupImage)])) { - return NO; - } - if (_groupType != other.groupType) { - return NO; - } - NSMutableArray *compareMyGroupMemberIds = [NSMutableArray arrayWithArray:_groupMemberIds]; - [compareMyGroupMemberIds removeObjectsInArray:other.groupMemberIds]; - if ([compareMyGroupMemberIds count] > 0) { - return NO; - } - return YES; -} - -- (NSString *)getInfoStringAboutUpdateTo:(TSGroupModel *)newModel contactsManager:(id)contactsManager { - NSString *updatedGroupInfoString = @""; - if (self == newModel) { - return NSLocalizedString(@"GROUP_UPDATED", @""); - } - if (![_groupName isEqual:newModel.groupName]) { - if (newModel.groupName.length == 0) { - updatedGroupInfoString = [updatedGroupInfoString stringByAppendingString:@"Closed group created"]; - } else { - updatedGroupInfoString = [updatedGroupInfoString stringByAppendingString:[NSString stringWithFormat:NSLocalizedString(@"GROUP_TITLE_CHANGED", @""), newModel.groupName]]; - } - } - if (_groupImage != nil && newModel.groupImage != nil && - !([UIImagePNGRepresentation(_groupImage) isEqualToData:UIImagePNGRepresentation(newModel.groupImage)])) { - updatedGroupInfoString = - [updatedGroupInfoString stringByAppendingString:NSLocalizedString(@"GROUP_AVATAR_CHANGED", @"")]; - } - NSSet *oldMembers = [NSSet setWithArray:_groupMemberIds]; - NSSet *newMembers = [NSSet setWithArray:newModel.groupMemberIds]; - - NSMutableSet *membersWhoJoined = [NSMutableSet setWithSet:newMembers]; - [membersWhoJoined minusSet:oldMembers]; - - NSMutableSet *membersWhoLeft = [NSMutableSet setWithSet:oldMembers]; - [membersWhoLeft minusSet:newMembers]; - [membersWhoLeft minusSet:newModel.removedMembers]; - - - if ([membersWhoLeft count] > 0) { - NSArray *oldMembersNames = [[membersWhoLeft allObjects] map:^NSString*(NSString* item) { - return [LKUserDisplayNameUtilities getPrivateChatDisplayNameAvoidWriteTransaction:item]; - }]; - updatedGroupInfoString = [updatedGroupInfoString - stringByAppendingString:[NSString - stringWithFormat:NSLocalizedString(@"GROUP_MEMBER_LEFT", @""), - [oldMembersNames componentsJoinedByString:@", "]]]; - } - - if (membersWhoJoined.count > 0) { - updatedGroupInfoString = @"New members joined"; - } - - if (newModel.removedMembers.count > 0) { - NSString *masterDeviceHexEncodedPublicKey = [NSUserDefaults.standardUserDefaults stringForKey:@"masterDeviceHexEncodedPublicKey"]; - NSString *hexEncodedPublicKey = masterDeviceHexEncodedPublicKey != nil ? masterDeviceHexEncodedPublicKey : TSAccountManager.localNumber; - if ([newModel.removedMembers containsObject:hexEncodedPublicKey]) { - updatedGroupInfoString = [updatedGroupInfoString - stringByAppendingString:NSLocalizedString(@"YOU_WERE_REMOVED", @"")]; - } else { - NSArray *removedMemberNames = [newModel.removedMembers.allObjects map:^NSString*(NSString* publicKey) { - return [LKUserDisplayNameUtilities getPrivateChatDisplayNameFor:publicKey]; - }]; - if ([removedMemberNames count] > 1) { - updatedGroupInfoString = [updatedGroupInfoString - stringByAppendingString:[NSString - stringWithFormat:NSLocalizedString(@"GROUP_MEMBERS_REMOVED", @""), - [removedMemberNames componentsJoinedByString:@", "]]]; - } - else { - updatedGroupInfoString = [updatedGroupInfoString - stringByAppendingString:[NSString - stringWithFormat:NSLocalizedString(@"GROUP_MEMBER_REMOVED", @""), - removedMemberNames[0]]]; - } - } - } - if ([updatedGroupInfoString length] == 0) { - updatedGroupInfoString = NSLocalizedString(@"GROUP_UPDATED", @""); - } - return updatedGroupInfoString; -} - -#endif - -- (nullable NSString *)groupName -{ - return _groupName.filterStringForDisplay; -} - -- (void)setRemovedMembers:(NSMutableSet *)removedMembers -{ - _removedMembers = removedMembers; -} - -- (void)updateGroupId: (NSData *)newGroupId -{ - _groupId = newGroupId; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/TypingIndicatorMessage.swift b/SignalServiceKit/src/Messages/TypingIndicatorMessage.swift deleted file mode 100644 index 1623bb628..000000000 --- a/SignalServiceKit/src/Messages/TypingIndicatorMessage.swift +++ /dev/null @@ -1,122 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation - -@objc(OWSTypingIndicatorAction) -public enum TypingIndicatorAction: Int { - case started - case stopped -} - -@objc(OWSTypingIndicatorMessage) -public class TypingIndicatorMessage: TSOutgoingMessage { - private let action: TypingIndicatorAction - - // MARK: Initializers - - @objc - public init(thread: TSThread, - action: TypingIndicatorAction) { - self.action = action - - super.init(outgoingMessageWithTimestamp: NSDate.ows_millisecondTimeStamp(), - in: thread, - messageBody: nil, - attachmentIds: NSMutableArray(), - expiresInSeconds: 0, - expireStartedAt: 0, - isVoiceMessage: false, - groupMetaMessage: .unspecified, - quotedMessage: nil, - contactShare: nil, - linkPreview: nil) - } - - @objc - public required init!(coder: NSCoder) { - self.action = .started - super.init(coder: coder) - } - - @objc - public required init(dictionary dictionaryValue: [String: Any]!) throws { - self.action = .started - try super.init(dictionary: dictionaryValue) - } - - @objc - public override func shouldSyncTranscript() -> Bool { - return false - } - - @objc - public override var isSilent: Bool { - return true - } - - @objc - public override var isOnline: Bool { - return true - } - - private func protoAction(forAction action: TypingIndicatorAction) -> SSKProtoTypingMessage.SSKProtoTypingMessageAction { - switch action { - case .started: - return .started - case .stopped: - return .stopped - } - } - - @objc - public override func buildPlainTextData(_ recipient: SignalRecipient) -> Data? { - - let typingBuilder = SSKProtoTypingMessage.builder(timestamp: self.timestamp, - action: protoAction(forAction: action)) - - if let groupThread = self.thread as? TSGroupThread { - typingBuilder.setGroupID(groupThread.groupModel.groupId) - } - - let contentBuilder = SSKProtoContent.builder() - - do { - contentBuilder.setTypingMessage(try typingBuilder.build()) - - let data = try contentBuilder.buildSerializedData() - return data - } catch let error { - owsFailDebug("failed to build content: \(error)") - return nil - } - } - - // MARK: TSYapDatabaseObject overrides - - @objc - public override func shouldBeSaved() -> Bool { - return false - } - - @objc - public override var ttl: UInt32 { return UInt32(TTLUtilities.getTTL(for: .typingIndicator)) } - - @objc - public override var debugDescription: String { - return "typingIndicatorMessage" - } - - // MARK: - - @objc(stringForTypingIndicatorAction:) - public class func string(forTypingIndicatorAction action: TypingIndicatorAction) -> String { - switch action { - case .started: - return "started" - case .stopped: - return "stopped" - } - } -} diff --git a/SignalServiceKit/src/Messages/UD/OWSRequestMaker.swift b/SignalServiceKit/src/Messages/UD/OWSRequestMaker.swift deleted file mode 100644 index bcb7d9364..000000000 --- a/SignalServiceKit/src/Messages/UD/OWSRequestMaker.swift +++ /dev/null @@ -1,248 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit -import SessionMetadataKit - -@objc -public enum RequestMakerUDAuthError: Int, Error { - case udAuthFailure -} - -public enum RequestMakerError: Error { - case websocketRequestError(statusCode : Int, responseData : Data?, underlyingError : Error) -} - -@objc(OWSRequestMakerResult) -public class RequestMakerResult: NSObject { - @objc - public let responseObject: Any? - - @objc - public let wasSentByUD: Bool - - @objc - public let wasSentByWebsocket: Bool - - @objc - public init(responseObject: Any?, - wasSentByUD: Bool, - wasSentByWebsocket: Bool) { - self.responseObject = responseObject - self.wasSentByUD = wasSentByUD - self.wasSentByWebsocket = wasSentByWebsocket - } -} - -// A utility class that handles: -// -// * UD auth-to-Non-UD auth failover. -// * Websocket-to-REST failover. -@objc(OWSRequestMaker) -public class RequestMaker: NSObject { - - public typealias RequestFactoryBlock = (SMKUDAccessKey?) -> TSRequest - public typealias UDAuthFailureBlock = () -> Void - public typealias WebsocketFailureBlock = () -> Void - - private let label: String - private let requestFactoryBlock: RequestFactoryBlock - private let udAuthFailureBlock: UDAuthFailureBlock - private let websocketFailureBlock: WebsocketFailureBlock - private let recipientId: String - private let udAccess: OWSUDAccess? - private let canFailoverUDAuth: Bool - - @objc - public init(label: String, - requestFactoryBlock : @escaping RequestFactoryBlock, - udAuthFailureBlock : @escaping UDAuthFailureBlock, - websocketFailureBlock : @escaping WebsocketFailureBlock, - recipientId: String, - udAccess: OWSUDAccess?, - canFailoverUDAuth: Bool) { - self.label = label - self.requestFactoryBlock = requestFactoryBlock - self.udAuthFailureBlock = udAuthFailureBlock - self.websocketFailureBlock = websocketFailureBlock - self.recipientId = recipientId - self.udAccess = udAccess - self.canFailoverUDAuth = canFailoverUDAuth - } - - // MARK: - Dependencies - - private var socketManager: TSSocketManager { - return SSKEnvironment.shared.socketManager - } - - private var networkManager: TSNetworkManager { - return SSKEnvironment.shared.networkManager - } - - private var udManager: OWSUDManager { - return SSKEnvironment.shared.udManager - } - - private var profileManager: ProfileManagerProtocol { - return SSKEnvironment.shared.profileManager - } - - // MARK: - - - @objc - public func makeRequestObjc() -> AnyPromise { - let promise = makeRequest() - .recover(on: DispatchQueue.global()) { (error: Error) -> Promise in - switch error { - case NetworkManagerError.taskError(_, let underlyingError): - throw underlyingError - default: - throw error - } - } - let anyPromise = AnyPromise(promise) - anyPromise.retainUntilComplete() - return anyPromise - } - - public func makeRequest() -> Promise { - return makeRequestInternal(skipUD: false, skipWebsocket: false) - } - - private func makeRequestInternal(skipUD: Bool, skipWebsocket: Bool) -> Promise { - var udAccessForRequest: OWSUDAccess? - if !skipUD { - udAccessForRequest = udAccess - } - let isUDRequest: Bool = udAccessForRequest != nil - let request: TSRequest = requestFactoryBlock(udAccessForRequest?.udAccessKey) - let canMakeWebsocketRequests = (socketManager.canMakeRequests() && !skipWebsocket && !isUDRequest) - - if canMakeWebsocketRequests { - return Promise { resolver in - socketManager.make(request, success: { (responseObject: Any?) in - if self.udManager.isUDVerboseLoggingEnabled() { - if isUDRequest { - Logger.debug("UD websocket request '\(self.label)' succeeded.") - } else { - Logger.debug("Non-UD websocket request '\(self.label)' succeeded.") - } - } - - self.requestSucceeded(udAccess: udAccessForRequest) - - resolver.fulfill(RequestMakerResult(responseObject: responseObject, - wasSentByUD: isUDRequest, - wasSentByWebsocket: true)) - }) { (statusCode: Int, responseData: Data?, error: Error) in - resolver.reject(RequestMakerError.websocketRequestError(statusCode: statusCode, responseData: responseData, underlyingError: error)) - } - }.recover { (error: Error) -> Promise in - switch error { - case RequestMakerError.websocketRequestError(let statusCode, _, _): - if isUDRequest && (statusCode == 401 || statusCode == 403) { - // If a UD request fails due to service response (as opposed to network - // failure), mark recipient as _not_ in UD mode, then retry. - self.udManager.setUnidentifiedAccessMode(.disabled, recipientId: self.recipientId) - self.profileManager.fetchProfile(forRecipientId: self.recipientId) - self.udAuthFailureBlock() - - if self.canFailoverUDAuth { - Logger.info("UD websocket request '\(self.label)' auth failed; failing over to non-UD websocket request.") - return self.makeRequestInternal(skipUD: true, skipWebsocket: skipWebsocket) - } else { - Logger.info("UD websocket request '\(self.label)' auth failed; aborting.") - throw RequestMakerUDAuthError.udAuthFailure - } - } - break - default: - break - } - - self.websocketFailureBlock() - if isUDRequest { - Logger.info("UD Web socket request '\(self.label)' failed; failing over to REST request: \(error).") - } else { - Logger.info("Non-UD Web socket request '\(self.label)' failed; failing over to REST request: \(error).") - } - return self.makeRequestInternal(skipUD: skipUD, skipWebsocket: true) - } - } else { - return self.networkManager.makePromise(request: request) - .map(on: DispatchQueue.global()) { (networkManagerResult: TSNetworkManager.NetworkManagerResult) -> RequestMakerResult in - if self.udManager.isUDVerboseLoggingEnabled() { - if isUDRequest { - Logger.debug("UD REST request '\(self.label)' succeeded.") - } else { - Logger.debug("Non-UD REST request '\(self.label)' succeeded.") - } - } - - self.requestSucceeded(udAccess: udAccessForRequest) - - // Unwrap the network manager promise into a request maker promise. - return RequestMakerResult(responseObject: networkManagerResult.responseObject, - wasSentByUD: isUDRequest, - wasSentByWebsocket: false) - }.recover { (error: Error) -> Promise in - switch error { - case NetworkManagerError.taskError(let task, _): - let statusCode = task.statusCode() - if isUDRequest && (statusCode == 401 || statusCode == 403) { - // If a UD request fails due to service response (as opposed to network - // failure), mark recipient as _not_ in UD mode, then retry. - self.udManager.setUnidentifiedAccessMode(.disabled, recipientId: self.recipientId) - self.profileManager.fetchProfile(forRecipientId: self.recipientId) - self.udAuthFailureBlock() - - if self.canFailoverUDAuth { - Logger.info("UD REST request '\(self.label)' auth failed; failing over to non-UD REST request.") - return self.makeRequestInternal(skipUD: true, skipWebsocket: skipWebsocket) - } else { - Logger.info("UD REST request '\(self.label)' auth failed; aborting.") - throw RequestMakerUDAuthError.udAuthFailure - } - } - break - default: - break - } - - if isUDRequest { - Logger.debug("UD REST request '\(self.label)' failed: \(error).") - } else { - Logger.debug("Non-UD REST request '\(self.label)' failed: \(error).") - } - throw error - } - } - } - - private func requestSucceeded(udAccess: OWSUDAccess?) { - // If this was a UD request... - guard let udAccess = udAccess else { - return - } - // ...made for a user in "unknown" UD access mode... - guard udAccess.udAccessMode == .unknown else { - return - } - - if udAccess.isRandomKey { - // If a UD request succeeds for an unknown user with a random key, - // mark recipient as .unrestricted. - udManager.setUnidentifiedAccessMode(.unrestricted, recipientId: recipientId) - } else { - // If a UD request succeeds for an unknown user with a non-random key, - // mark recipient as .enabled. - udManager.setUnidentifiedAccessMode(.enabled, recipientId: recipientId) - } - DispatchQueue.main.async { - self.profileManager.fetchProfile(forRecipientId: self.recipientId) - } - } -} diff --git a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift deleted file mode 100644 index cd74e8707..000000000 --- a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift +++ /dev/null @@ -1,518 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit -import SessionMetadataKit -import SessionCoreKit - -public enum OWSUDError: Error { - case assertionError(description: String) - case invalidData(description: String) -} - -@objc -public enum OWSUDCertificateExpirationPolicy: Int { - // We want to try to rotate the sender certificate - // on a frequent basis, but we don't want to block - // sending on this. - case strict - case permissive -} - -@objc -public enum UnidentifiedAccessMode: Int { - case unknown - case enabled - case disabled - case unrestricted -} - -private func string(forUnidentifiedAccessMode mode: UnidentifiedAccessMode) -> String { - switch mode { - case .unknown: - return "unknown" - case .enabled: - return "enabled" - case .disabled: - return "disabled" - case .unrestricted: - return "unrestricted" - } -} - -@objc -public class OWSUDAccess: NSObject { - @objc - public let udAccessKey: SMKUDAccessKey - - @objc - public let udAccessMode: UnidentifiedAccessMode - - @objc - public let isRandomKey: Bool - - @objc - public required init(udAccessKey: SMKUDAccessKey, - udAccessMode: UnidentifiedAccessMode, - isRandomKey: Bool) { - self.udAccessKey = udAccessKey - self.udAccessMode = udAccessMode - self.isRandomKey = isRandomKey - } -} - -@objc public protocol OWSUDManager: class { - - @objc func setup() - - @objc func trustRoot() -> ECPublicKey - - @objc func isUDVerboseLoggingEnabled() -> Bool - - // MARK: - Recipient State - - @objc - func setUnidentifiedAccessMode(_ mode: UnidentifiedAccessMode, recipientId: String) - - @objc - func unidentifiedAccessMode(forRecipientId recipientId: RecipientIdentifier) -> UnidentifiedAccessMode - - @objc - func udAccessKey(forRecipientId recipientId: RecipientIdentifier) -> SMKUDAccessKey? - - @objc - func udAccess(forRecipientId recipientId: RecipientIdentifier, - requireSyncAccess: Bool) -> OWSUDAccess? - - // MARK: Sender Certificate - - // We use completion handlers instead of a promise so that message sending - // logic can access the strongly typed certificate data. - @objc - func ensureSenderCertificate(success:@escaping (SMKSenderCertificate) -> Void, - failure:@escaping (Error) -> Void) - - // MARK: Unrestricted Access - - @objc - func shouldAllowUnrestrictedAccessLocal() -> Bool - @objc - func setShouldAllowUnrestrictedAccessLocal(_ value: Bool) - - @objc - func getSenderCertificate() -> SMKSenderCertificate? -} - -// MARK: - - -@objc -public class OWSUDManagerImpl: NSObject, OWSUDManager { - - private let dbConnection: YapDatabaseConnection - - // MARK: Local Configuration State - private let kUDCollection = "kUDCollection" - private let kUDCurrentSenderCertificateKey_Production = "kUDCurrentSenderCertificateKey_Production" - private let kUDCurrentSenderCertificateKey_Staging = "kUDCurrentSenderCertificateKey_Staging" - private let kUDCurrentSenderCertificateDateKey_Production = "kUDCurrentSenderCertificateDateKey_Production" - private let kUDCurrentSenderCertificateDateKey_Staging = "kUDCurrentSenderCertificateDateKey_Staging" - private let kUDUnrestrictedAccessKey = "kUDUnrestrictedAccessKey" - - // MARK: Recipient State - private let kUnidentifiedAccessCollection = "kUnidentifiedAccessCollection" - - var certificateValidator: SMKCertificateValidator - - @objc - public required init(primaryStorage: OWSPrimaryStorage) { - self.dbConnection = primaryStorage.newDatabaseConnection() - self.certificateValidator = SMKCertificateDefaultValidator(trustRoot: OWSUDManagerImpl.trustRoot()) - - super.init() - - SwiftSingletons.register(self) - } - - @objc public func setup() { - AppReadiness.runNowOrWhenAppDidBecomeReady { - guard self.tsAccountManager.isRegistered() else { - return - } - - // Any error is silently ignored on startup. - self.ensureSenderCertificate(certificateExpirationPolicy: .strict).retainUntilComplete() - } - NotificationCenter.default.addObserver(self, - selector: #selector(registrationStateDidChange), - name: .RegistrationStateDidChange, - object: nil) - NotificationCenter.default.addObserver(self, - selector: #selector(didBecomeActive), - name: NSNotification.Name.OWSApplicationDidBecomeActive, - object: nil) - } - - @objc - func registrationStateDidChange() { - AssertIsOnMainThread() - - guard tsAccountManager.isRegisteredAndReady() else { - return - } - - // Any error is silently ignored - ensureSenderCertificate(certificateExpirationPolicy: .strict).retainUntilComplete() - } - - @objc func didBecomeActive() { - AssertIsOnMainThread() - - AppReadiness.runNowOrWhenAppDidBecomeReady { - guard self.tsAccountManager.isRegistered() else { - return - } - - // Any error is silently ignored on startup. - self.ensureSenderCertificate(certificateExpirationPolicy: .strict).retainUntilComplete() - } - } - - // MARK: - - - @objc - public func isUDVerboseLoggingEnabled() -> Bool { - return false - } - - // MARK: - Dependencies - - private var profileManager: ProfileManagerProtocol { - return SSKEnvironment.shared.profileManager - } - - private var tsAccountManager: TSAccountManager { - return TSAccountManager.sharedInstance() - } - - // MARK: - Recipient state - - @objc - public func randomUDAccessKey() -> SMKUDAccessKey { - return SMKUDAccessKey(randomKeyData: ()) - } - - private func unidentifiedAccessMode(forRecipientId recipientId: RecipientIdentifier, - isLocalNumber: Bool, - transaction: YapDatabaseReadTransaction) -> UnidentifiedAccessMode { - let defaultValue: UnidentifiedAccessMode = isLocalNumber ? .enabled : .unknown - guard let existingRawValue = transaction.object(forKey: recipientId, inCollection: kUnidentifiedAccessCollection) as? Int else { - return defaultValue - } - guard let existingValue = UnidentifiedAccessMode(rawValue: existingRawValue) else { - owsFailDebug("Couldn't parse mode value.") - return defaultValue - } - return existingValue - } - - @objc - public func unidentifiedAccessMode(forRecipientId recipientId: RecipientIdentifier) -> UnidentifiedAccessMode { - var isLocalNumber = false - if let localNumber = tsAccountManager.localNumber() { - isLocalNumber = recipientId == localNumber - } - - var mode: UnidentifiedAccessMode = .unknown - dbConnection.read { (transaction) in - mode = self.unidentifiedAccessMode(forRecipientId: recipientId, isLocalNumber: isLocalNumber, transaction: transaction) - } - return mode - } - - @objc - public func setUnidentifiedAccessMode(_ mode: UnidentifiedAccessMode, recipientId: String) { - var isLocalNumber = false - if let localNumber = tsAccountManager.localNumber() { - if recipientId == localNumber { - Logger.info("Setting local UD access mode: \(string(forUnidentifiedAccessMode: mode))") - isLocalNumber = true - } - } - - Storage.writeSync { (transaction) in - let oldMode = self.unidentifiedAccessMode(forRecipientId: recipientId, isLocalNumber: isLocalNumber, transaction: transaction) - - transaction.setObject(mode.rawValue as Int, forKey: recipientId, inCollection: self.kUnidentifiedAccessCollection) - - if mode != oldMode { - Logger.info("Setting UD access mode for \(recipientId): \(string(forUnidentifiedAccessMode: oldMode)) -> \(string(forUnidentifiedAccessMode: mode))") - } - } - } - - // Returns the UD access key for a given recipient - // if we have a valid profile key for them. - @objc - public func udAccessKey(forRecipientId recipientId: RecipientIdentifier) -> SMKUDAccessKey? { - guard let profileKey = profileManager.profileKeyData(forRecipientId: recipientId) else { - // Mark as "not a UD recipient". - return nil - } - do { - let udAccessKey = try SMKUDAccessKey(profileKey: profileKey) - return udAccessKey - } catch { - Logger.error("Could not determine udAccessKey: \(error)") - return nil - } - } - - // Returns the UD access key for sending to a given recipient. - @objc - public func udAccess(forRecipientId recipientId: RecipientIdentifier, - requireSyncAccess: Bool) -> OWSUDAccess? { - if requireSyncAccess { - guard let localNumber = tsAccountManager.localNumber() else { - if isUDVerboseLoggingEnabled() { - Logger.info("UD disabled for \(recipientId), no local number.") - } - owsFailDebug("Missing local number.") - return nil - } - if localNumber != recipientId { - let selfAccessMode = unidentifiedAccessMode(forRecipientId: localNumber) - guard selfAccessMode != .disabled else { - if isUDVerboseLoggingEnabled() { - Logger.info("UD disabled for \(recipientId), UD disabled for sync messages.") - } - return nil - } - } - } - - let accessMode = unidentifiedAccessMode(forRecipientId: recipientId) - switch accessMode { - case .unrestricted: - // Unrestricted users should use a random key. - if isUDVerboseLoggingEnabled() { - Logger.info("UD enabled for \(recipientId) with random key.") - } - let udAccessKey = randomUDAccessKey() - return OWSUDAccess(udAccessKey: udAccessKey, udAccessMode: accessMode, isRandomKey: true) - case .unknown: - // Unknown users should use a derived key if possible, - // and otherwise use a random key. - if let udAccessKey = udAccessKey(forRecipientId: recipientId) { - if isUDVerboseLoggingEnabled() { - Logger.info("UD unknown for \(recipientId); trying derived key.") - } - return OWSUDAccess(udAccessKey: udAccessKey, udAccessMode: accessMode, isRandomKey: false) - } else { - if isUDVerboseLoggingEnabled() { - Logger.info("UD unknown for \(recipientId); trying random key.") - } - let udAccessKey = randomUDAccessKey() - return OWSUDAccess(udAccessKey: udAccessKey, udAccessMode: accessMode, isRandomKey: true) - } - case .enabled: - guard let udAccessKey = udAccessKey(forRecipientId: recipientId) else { - if isUDVerboseLoggingEnabled() { - Logger.info("UD disabled for \(recipientId), no profile key for this recipient.") - } - if (!CurrentAppContext().isRunningTests) { - owsFailDebug("Couldn't find profile key for UD-enabled user.") - } - return nil - } - if isUDVerboseLoggingEnabled() { - Logger.info("UD enabled for \(recipientId).") - } - return OWSUDAccess(udAccessKey: udAccessKey, udAccessMode: accessMode, isRandomKey: false) - case .disabled: - if isUDVerboseLoggingEnabled() { - Logger.info("UD disabled for \(recipientId), UD not enabled for this recipient.") - } - return nil - } - } - - // MARK: - Sender Certificate - - #if DEBUG - @objc - public func hasSenderCertificate() -> Bool { - return senderCertificate(certificateExpirationPolicy: .permissive) != nil - } - #endif - - private func senderCertificate(certificateExpirationPolicy: OWSUDCertificateExpirationPolicy) -> SMKSenderCertificate? { - if certificateExpirationPolicy == .strict { - guard let certificateDate = dbConnection.object(forKey: senderCertificateDateKey(), inCollection: kUDCollection) as? Date else { - return nil - } - guard certificateDate.timeIntervalSinceNow < kDayInterval else { - // Discard certificates that we obtained more than 24 hours ago. - return nil - } - } - - guard let certificateData = dbConnection.object(forKey: senderCertificateKey(), inCollection: kUDCollection) as? Data else { - return nil - } - - do { - let certificate = try SMKSenderCertificate.parse(data: certificateData) - - guard isValidCertificate(certificate) else { - Logger.warn("Current sender certificate is not valid.") - return nil - } - - return certificate - } catch { - owsFailDebug("Certificate could not be parsed: \(error)") - return nil - } - } - - func setSenderCertificate(_ certificateData: Data) { - dbConnection.setObject(Date(), forKey: senderCertificateDateKey(), inCollection: kUDCollection) - dbConnection.setObject(certificateData, forKey: senderCertificateKey(), inCollection: kUDCollection) - } - - private func senderCertificateKey() -> String { - return IsUsingProductionService() ? kUDCurrentSenderCertificateKey_Production : kUDCurrentSenderCertificateKey_Staging - } - - private func senderCertificateDateKey() -> String { - return IsUsingProductionService() ? kUDCurrentSenderCertificateDateKey_Production : kUDCurrentSenderCertificateDateKey_Staging - } - - @objc - public func ensureSenderCertificate(success:@escaping (SMKSenderCertificate) -> Void, - failure:@escaping (Error) -> Void) { - return ensureSenderCertificate(certificateExpirationPolicy: .permissive, - success: success, - failure: failure) - } - - private func ensureSenderCertificate(certificateExpirationPolicy: OWSUDCertificateExpirationPolicy, - success:@escaping (SMKSenderCertificate) -> Void, - failure:@escaping (Error) -> Void) { - firstly { - ensureSenderCertificate(certificateExpirationPolicy: certificateExpirationPolicy) - }.map { certificate in - success(certificate) - }.catch { error in - failure(error) - }.retainUntilComplete() - } - - public func ensureSenderCertificate(certificateExpirationPolicy: OWSUDCertificateExpirationPolicy) -> Promise { - // Try to obtain a new sender certificate. - return firstly { - generateSenderCertificate() - }.map { (certificateData: Data, certificate: SMKSenderCertificate) in - - // Cache the current sender certificate. - self.setSenderCertificate(certificateData) - - return certificate - } - } - - private func generateSenderCertificate() -> Promise<(certificateData: Data, certificate: SMKSenderCertificate)> { - return Promise<(certificateData: Data, certificate: SMKSenderCertificate)> { seal in - // Loki: Generate a sender certificate locally - let sender = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey - let certificate = SMKSenderCertificate(senderDeviceId: OWSDevicePrimaryDeviceId, senderRecipientId: sender) - let certificateAsData = try certificate.serialized() - guard isValidCertificate(certificate) else { - throw OWSUDError.invalidData(description: "Invalid sender certificate.") - } - seal.fulfill((certificateData: certificateAsData, certificate: certificate)) - } - } - - @objc - public func getSenderCertificate() -> SMKSenderCertificate? { - do { - let sender = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey - let certificate = SMKSenderCertificate(senderDeviceId: OWSDevicePrimaryDeviceId, senderRecipientId: sender) - guard self.isValidCertificate(certificate) else { - throw OWSUDError.invalidData(description: "Invalid sender certificate returned by server") - } - return certificate - } catch { - print("[Loki] Couldn't get UD sender certificate due to error: \(error).") - return nil - } - } - - private func requestSenderCertificate() -> Promise<(certificateData: Data, certificate: SMKSenderCertificate)> { - return firstly { - SignalServiceRestClient().requestUDSenderCertificate() - }.map { certificateData -> (certificateData: Data, certificate: SMKSenderCertificate) in - let certificate = try SMKSenderCertificate.parse(data: certificateData) - - guard self.isValidCertificate(certificate) else { - throw OWSUDError.invalidData(description: "Invalid sender certificate returned by server") - } - - return (certificateData: certificateData, certificate: certificate) - } - } - - private func isValidCertificate(_ certificate: SMKSenderCertificate) -> Bool { - // Ensure that the certificate will not expire in the next hour. - // We want a threshold long enough to ensure that any outgoing message - // sends will complete before the expiration. - let nowMs = NSDate.ows_millisecondTimeStamp() - let anHourFromNowMs = nowMs + kHourInMs - - do { - try certificateValidator.throwswrapped_validate(senderCertificate: certificate, validationTime: anHourFromNowMs) - return true - } catch { - OWSLogger.error("Invalid certificate") - return false - } - } - - @objc - public func trustRoot() -> ECPublicKey { - return OWSUDManagerImpl.trustRoot() - } - - @objc - public class func trustRoot() -> ECPublicKey { - guard let trustRootData = NSData(fromBase64String: kUDTrustRoot) else { - // This exits. - owsFail("Invalid trust root data.") - } - - do { - return try ECPublicKey(serializedKeyData: trustRootData as Data) - } catch { - // This exits. - owsFail("Invalid trust root.") - } - } - - // MARK: - Unrestricted Access - - @objc - public func shouldAllowUnrestrictedAccessLocal() -> Bool { - return dbConnection.bool(forKey: kUDUnrestrictedAccessKey, inCollection: kUDCollection, defaultValue: false) - } - - @objc - public func setShouldAllowUnrestrictedAccessLocal(_ value: Bool) { - dbConnection.setBool(value, forKey: kUDUnrestrictedAccessKey, inCollection: kUDCollection) - - // Try to update the account attributes to reflect this change. - tsAccountManager.updateAccountAttributes().retainUntilComplete() - } -} diff --git a/SignalServiceKit/src/Network/API/NetworkManager.swift b/SignalServiceKit/src/Network/API/NetworkManager.swift deleted file mode 100644 index c1892b669..000000000 --- a/SignalServiceKit/src/Network/API/NetworkManager.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit - -enum NetworkManagerError: Error { - /// Wraps TSNetworkManager failure callback params in a single throwable error - case taskError(task: URLSessionDataTask, underlyingError: Error) -} - -extension NetworkManagerError { - var isNetworkError: Bool { - switch self { - case .taskError(_, let underlyingError): - return IsNSErrorNetworkFailure(underlyingError) - } - } - - var statusCode: Int { - switch self { - case .taskError(let task, _): - return task.statusCode() - } - } -} - -extension TSNetworkManager { - public typealias NetworkManagerResult = (task: URLSessionDataTask, responseObject: Any?) - - public func perform(_ request: TSRequest, withCompletionQueue queue: DispatchQueue = DispatchQueue.main) -> Promise { - return makePromise(request: request, queue: queue) - } - - public func makePromise(request: TSRequest, queue: DispatchQueue = DispatchQueue.main) -> Promise { - let (promise, resolver) = Promise.pending() - - self.makeRequest(request, - completionQueue: queue, - success: { task, responseObject in - resolver.fulfill((task: task, responseObject: responseObject)) - }, - failure: { task, error in - let nmError = NetworkManagerError.taskError(task: task, underlyingError: error) - let nsError: NSError = nmError as NSError - nsError.isRetryable = (error as NSError).isRetryable - resolver.reject(nsError) - }) - - return promise - } -} diff --git a/SignalServiceKit/src/Network/API/OWSDeviceProvisioningCodeService.h b/SignalServiceKit/src/Network/API/OWSDeviceProvisioningCodeService.h deleted file mode 100644 index 7ea485891..000000000 --- a/SignalServiceKit/src/Network/API/OWSDeviceProvisioningCodeService.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright © 2016 Open Whisper Systems. All rights reserved. - -NS_ASSUME_NONNULL_BEGIN - -@class TSNetworkManager; - -@interface OWSDeviceProvisioningCodeService : NSObject - -- (instancetype)initWithNetworkManager:(TSNetworkManager *)networkManager NS_DESIGNATED_INITIALIZER; - -- (void)requestProvisioningCodeWithSuccess:(void (^)(NSString *))successCallback - failure:(void (^)(NSError *))failureCallback; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/OWSDeviceProvisioningCodeService.m b/SignalServiceKit/src/Network/API/OWSDeviceProvisioningCodeService.m deleted file mode 100644 index 62a19c37e..000000000 --- a/SignalServiceKit/src/Network/API/OWSDeviceProvisioningCodeService.m +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDeviceProvisioningCodeService.h" -#import "OWSRequestFactory.h" -#import "TSNetworkManager.h" - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSDeviceProvisioningCodeServiceProvisioningCodeKey = @"verificationCode"; - -@interface OWSDeviceProvisioningCodeService () - -@property (readonly) TSNetworkManager *networkManager; - -@end - -@implementation OWSDeviceProvisioningCodeService - -- (instancetype)initWithNetworkManager:(TSNetworkManager *)networkManager -{ - - self = [super init]; - if (!self) { - return self; - } - - _networkManager = networkManager; - - return self; -} - -- (instancetype)init -{ - return [self initWithNetworkManager:[TSNetworkManager sharedManager]]; -} - -- (void)requestProvisioningCodeWithSuccess:(void (^)(NSString *))successCallback - failure:(void (^)(NSError *))failureCallback -{ - TSRequest *request = [OWSRequestFactory deviceProvisioningCodeRequest]; - [self.networkManager makeRequest:request - success:^(NSURLSessionDataTask *task, id responseObject) { - OWSLogVerbose(@"ProvisioningCode request succeeded"); - if ([(NSObject *)responseObject isKindOfClass:[NSDictionary class]]) { - NSDictionary *responseDict = (NSDictionary *)responseObject; - NSString *provisioningCode = - [responseDict objectForKey:OWSDeviceProvisioningCodeServiceProvisioningCodeKey]; - successCallback(provisioningCode); - } - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - if (!IsNSErrorNetworkFailure(error)) { - OWSProdError([OWSAnalyticsEvents errorProvisioningCodeRequestFailed]); - } - OWSLogVerbose(@"ProvisioningCode request failed with error: %@", error); - failureCallback(error); - }]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/OWSDeviceProvisioningService.h b/SignalServiceKit/src/Network/API/OWSDeviceProvisioningService.h deleted file mode 100644 index e606d8488..000000000 --- a/SignalServiceKit/src/Network/API/OWSDeviceProvisioningService.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class TSNetworkManager; - -@interface OWSDeviceProvisioningService : NSObject - -- (instancetype)initWithNetworkManager:(TSNetworkManager *)networkManager; - -- (void)provisionWithMessageBody:(NSData *)messageBody - ephemeralDeviceId:(NSString *)deviceId - success:(void (^)(void))successCallback - failure:(void (^)(NSError *))failureCallback; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/OWSDeviceProvisioningService.m b/SignalServiceKit/src/Network/API/OWSDeviceProvisioningService.m deleted file mode 100644 index 098d88fdb..000000000 --- a/SignalServiceKit/src/Network/API/OWSDeviceProvisioningService.m +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDeviceProvisioningService.h" -#import "OWSRequestFactory.h" -#import "TSNetworkManager.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSDeviceProvisioningService () - -@property (nonatomic, readonly) TSNetworkManager *networkManager; - -@end - -@implementation OWSDeviceProvisioningService - -- (instancetype)initWithNetworkManager:(TSNetworkManager *)networkManager -{ - self = [super init]; - if (!self) { - return self; - } - - _networkManager = networkManager; - - return self; -} - -- (instancetype)init -{ - return [self initWithNetworkManager:[TSNetworkManager sharedManager]]; -} - -- (void)provisionWithMessageBody:(NSData *)messageBody - ephemeralDeviceId:(NSString *)deviceId - success:(void (^)(void))successCallback - failure:(void (^)(NSError *))failureCallback -{ - TSRequest *request = - [OWSRequestFactory deviceProvisioningRequestWithMessageBody:messageBody ephemeralDeviceId:deviceId]; - [self.networkManager makeRequest:request - success:^(NSURLSessionDataTask *task, id responseObject) { - OWSLogVerbose(@"Provisioning request succeeded"); - successCallback(); - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - if (!IsNSErrorNetworkFailure(error)) { - OWSProdError([OWSAnalyticsEvents errorProvisioningRequestFailed]); - } - OWSLogVerbose(@"Provisioning request failed with error: %@", error); - failureCallback(error); - }]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/OWSDevicesService.h b/SignalServiceKit/src/Network/API/OWSDevicesService.h deleted file mode 100644 index fcdb36848..000000000 --- a/SignalServiceKit/src/Network/API/OWSDevicesService.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const NSNotificationName_DeviceListUpdateSucceeded; -extern NSString *const NSNotificationName_DeviceListUpdateFailed; -extern NSString *const NSNotificationName_DeviceListUpdateModifiedDeviceList; - -@class OWSDevice; - -@interface OWSDevicesService : NSObject - -+ (void)refreshDevices; - -+ (void)unlinkDevice:(OWSDevice *)device - success:(void (^)(void))successCallback - failure:(void (^)(NSError *))failureCallback; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/OWSDevicesService.m b/SignalServiceKit/src/Network/API/OWSDevicesService.m deleted file mode 100644 index 9ce96728d..000000000 --- a/SignalServiceKit/src/Network/API/OWSDevicesService.m +++ /dev/null @@ -1,131 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDevicesService.h" -#import "NSNotificationCenter+OWS.h" -#import "OWSDevice.h" -#import "OWSError.h" -#import "OWSRequestFactory.h" -#import "TSNetworkManager.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const NSNotificationName_DeviceListUpdateSucceeded = @"NSNotificationName_DeviceListUpdateSucceeded"; -NSString *const NSNotificationName_DeviceListUpdateFailed = @"NSNotificationName_DeviceListUpdateFailed"; -NSString *const NSNotificationName_DeviceListUpdateModifiedDeviceList - = @"NSNotificationName_DeviceListUpdateModifiedDeviceList"; - -@implementation OWSDevicesService - -+ (void)refreshDevices -{ - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self - getDevicesWithSuccess:^(NSArray *devices) { - // If we have more than one device; we may have a linked device. - if (devices.count > 1) { - // Setting this flag here shouldn't be necessary, but we do so - // because the "cost" is low and it will improve robustness. - [OWSDeviceManager.sharedManager setMayHaveLinkedDevices]; - } - - BOOL didAddOrRemove = [OWSDevice replaceAll:devices]; - - [NSNotificationCenter.defaultCenter - postNotificationNameAsync:NSNotificationName_DeviceListUpdateSucceeded - object:nil]; - - if (didAddOrRemove) { - [NSNotificationCenter.defaultCenter - postNotificationNameAsync:NSNotificationName_DeviceListUpdateModifiedDeviceList - object:nil]; - } - } - failure:^(NSError *error) { - OWSLogError(@"Request device list failed with error: %@", error); - - [NSNotificationCenter.defaultCenter postNotificationNameAsync:NSNotificationName_DeviceListUpdateFailed - object:error]; - }]; - }); -} - -+ (void)getDevicesWithSuccess:(void (^)(NSArray *))successCallback - failure:(void (^)(NSError *))failureCallback -{ - TSRequest *request = [OWSRequestFactory getDevicesRequest]; - [[TSNetworkManager sharedManager] makeRequest:request - success:^(NSURLSessionDataTask *task, id responseObject) { - OWSLogVerbose(@"Get devices request succeeded"); - NSArray *devices = [self parseResponse:responseObject]; - - if (devices) { - successCallback(devices); - } else { - OWSLogError(@"unable to parse devices response:%@", responseObject); - NSError *error = OWSErrorMakeUnableToProcessServerResponseError(); - failureCallback(error); - } - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - if (!IsNSErrorNetworkFailure(error)) { - OWSProdError([OWSAnalyticsEvents errorGetDevicesFailed]); - } - OWSLogVerbose(@"Get devices request failed with error: %@", error); - failureCallback(error); - }]; -} - -+ (void)unlinkDevice:(OWSDevice *)device - success:(void (^)(void))successCallback - failure:(void (^)(NSError *))failureCallback -{ - TSRequest *request = [OWSRequestFactory deleteDeviceRequestWithDevice:device]; - - [[TSNetworkManager sharedManager] makeRequest:request - success:^(NSURLSessionDataTask *task, id responseObject) { - OWSLogVerbose(@"Delete device request succeeded"); - successCallback(); - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - if (!IsNSErrorNetworkFailure(error)) { - OWSProdError([OWSAnalyticsEvents errorUnlinkDeviceFailed]); - } - OWSLogVerbose(@"Get devices request failed with error: %@", error); - failureCallback(error); - }]; -} - -+ (NSArray *)parseResponse:(id)responseObject -{ - if (![responseObject isKindOfClass:[NSDictionary class]]) { - OWSLogError(@"Device response was not a dictionary."); - return nil; - } - NSDictionary *response = (NSDictionary *)responseObject; - - NSArray *devicesAttributes = response[@"devices"]; - if (!devicesAttributes) { - OWSLogError(@"Device response had no devices."); - return nil; - } - - NSMutableArray *devices = [NSMutableArray new]; - for (NSDictionary *deviceAttributes in devicesAttributes) { - NSError *error; - OWSDevice *_Nullable device = [OWSDevice deviceFromJSONDictionary:deviceAttributes error:&error]; - if (error || !device) { - OWSLogError(@"Failed to build device from dictionary with error: %@", error); - } else { - [devices addObject:device]; - } - } - - return [devices copy]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/OWSRequestBuilder.h b/SignalServiceKit/src/Network/API/OWSRequestBuilder.h deleted file mode 100644 index 10f155343..000000000 --- a/SignalServiceKit/src/Network/API/OWSRequestBuilder.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class TSRequest; - -@interface OWSRequestBuilder : NSObject - -+ (TSRequest *)profileNameSetRequestWithEncryptedPaddedName:(nullable NSData *)encryptedPaddedName; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/OWSRequestBuilder.m b/SignalServiceKit/src/Network/API/OWSRequestBuilder.m deleted file mode 100644 index 9adf4057c..000000000 --- a/SignalServiceKit/src/Network/API/OWSRequestBuilder.m +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSRequestBuilder.h" -#import "TSConstants.h" -#import "TSRequest.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -const NSUInteger kEncodedNameLength = 72; - -@implementation OWSRequestBuilder - -+ (TSRequest *)profileNameSetRequestWithEncryptedPaddedName:(nullable NSData *)encryptedPaddedName -{ - NSString *urlString; - - NSString *base64EncodedName = [encryptedPaddedName base64EncodedString]; - // name length must match exactly - if (base64EncodedName.length == kEncodedNameLength) { - // Remove any "/" in the base64 (all other base64 chars are URL safe. - // Apples built-in `stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URL*]]` doesn't offer a - // flavor for encoding "/". - NSString *urlEncodedName = [base64EncodedName stringByReplacingOccurrencesOfString:@"/" withString:@"%2F"]; - urlString = [NSString stringWithFormat:textSecureSetProfileNameAPIFormat, urlEncodedName]; - } else { - // if name length doesn't match exactly, assume blank name - OWSAssertDebug(encryptedPaddedName == nil); - urlString = [NSString stringWithFormat:textSecureSetProfileNameAPIFormat, @""]; - } - - NSURL *url = [NSURL URLWithString:urlString]; - TSRequest *request = [[TSRequest alloc] initWithURL:url]; - request.HTTPMethod = @"PUT"; - - return request; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/OWSUploadOperation.h b/SignalServiceKit/src/Network/API/OWSUploadOperation.h deleted file mode 100644 index b8c68d4c3..000000000 --- a/SignalServiceKit/src/Network/API/OWSUploadOperation.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSOperation.h" - -NS_ASSUME_NONNULL_BEGIN - -@class TSOutgoingMessage; -@class YapDatabaseConnection; - -extern NSString *const kAttachmentUploadProgressNotification; -extern NSString *const kAttachmentUploadProgressKey; -extern NSString *const kAttachmentUploadAttachmentIDKey; - -@interface OWSUploadOperation : OWSOperation - -@property (nullable, readonly) NSError *lastError; - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithAttachmentId:(NSString *)attachmentId - threadID:(NSString *)threadID - dbConnection:(YapDatabaseConnection *)dbConnection NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/OWSUploadOperation.m b/SignalServiceKit/src/Network/API/OWSUploadOperation.m deleted file mode 100644 index 6a292d378..000000000 --- a/SignalServiceKit/src/Network/API/OWSUploadOperation.m +++ /dev/null @@ -1,191 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSUploadOperation.h" -#import "MIMETypeUtil.h" -#import "NSError+MessageSending.h" -#import "NSNotificationCenter+OWS.h" -#import "OWSDispatch.h" -#import "OWSError.h" -#import "OWSOperation.h" -#import "OWSRequestFactory.h" -#import "SSKEnvironment.h" -#import "TSAttachmentStream.h" -#import "TSNetworkManager.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const kAttachmentUploadProgressNotification = @"kAttachmentUploadProgressNotification"; -NSString *const kAttachmentUploadProgressKey = @"kAttachmentUploadProgressKey"; -NSString *const kAttachmentUploadAttachmentIDKey = @"kAttachmentUploadAttachmentIDKey"; - -// Use a slightly non-zero value to ensure that the progress -// indicator shows up as quickly as possible. -static const CGFloat kAttachmentUploadProgressTheta = 0.001f; - -@interface OWSUploadOperation () - -@property (readonly, nonatomic) NSString *attachmentId; -@property (readonly, nonatomic) NSString *threadID; -@property (readonly, nonatomic) YapDatabaseConnection *dbConnection; - -@end - -#pragma mark - - -@implementation OWSUploadOperation - -- (instancetype)initWithAttachmentId:(NSString *)attachmentId - threadID:(NSString *)threadID - dbConnection:(YapDatabaseConnection *)dbConnection -{ - self = [super init]; - if (!self) { - return self; - } - - self.remainingRetries = 4; - - _attachmentId = attachmentId; - _threadID = threadID; - _dbConnection = dbConnection; - - return self; -} - -- (TSNetworkManager *)networkManager -{ - return SSKEnvironment.shared.networkManager; -} - -- (void)run -{ - __block TSAttachmentStream *attachmentStream; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - attachmentStream = [TSAttachmentStream fetchObjectWithUniqueID:self.attachmentId transaction:transaction]; - }]; - - if (!attachmentStream) { - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - // Not finding a local attachment is a terminal failure - error.isRetryable = NO; - [self reportError:error]; - return; - } - - if (attachmentStream.isUploaded) { - OWSLogDebug(@"Attachment previously uploaded."); - [self reportSuccess]; - return; - } - - [self fireNotificationWithProgress:0]; - - __block LKPublicChat *publicChat; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - publicChat = [LKDatabaseUtilities getPublicChatForThreadID:self.threadID transaction:transaction]; - }]; - NSString *server = (publicChat != nil) ? publicChat.server : LKFileServerAPI.server; - - [[LKFileServerAPI uploadAttachment:attachmentStream withID:self.attachmentId toServer:server] - .thenOn(dispatch_get_main_queue(), ^() { - [self reportSuccess]; - }) - .catchOn(dispatch_get_main_queue(), ^(NSError *error) { - [self reportError:error]; - }) retainUntilComplete]; -} - -- (void)uploadWithServerId:(UInt64)serverId - location:(NSString *)location - attachmentStream:(TSAttachmentStream *)attachmentStream -{ - OWSLogDebug(@"started uploading data for attachment: %@", self.attachmentId); - NSError *error; - NSData *attachmentData = [attachmentStream readDataFromFileWithError:&error]; - if (error) { - OWSLogError(@"Failed to read attachment data with error: %@", error); - error.isRetryable = YES; - [self reportError:error]; - return; - } - - NSData *encryptionKey; - NSData *digest; - NSData *_Nullable encryptedAttachmentData = - [Cryptography encryptAttachmentData:attachmentData outKey:&encryptionKey outDigest:&digest]; - if (!encryptedAttachmentData) { - OWSFailDebug(@"could not encrypt attachment data."); - error = OWSErrorMakeFailedToSendOutgoingMessageError(); - error.isRetryable = YES; - [self reportError:error]; - return; - } - attachmentStream.encryptionKey = encryptionKey; - attachmentStream.digest = digest; - - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:location]]; - request.HTTPMethod = @"PUT"; - [request setValue:OWSMimeTypeApplicationOctetStream forHTTPHeaderField:@"Content-Type"]; - - AFURLSessionManager *manager = [[AFURLSessionManager alloc] - initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; - - NSURLSessionUploadTask *uploadTask; - uploadTask = [manager uploadTaskWithRequest:request - fromData:encryptedAttachmentData - progress:^(NSProgress *_Nonnull uploadProgress) { - [self fireNotificationWithProgress:uploadProgress.fractionCompleted]; - } - completionHandler:^(NSURLResponse *_Nonnull response, id _Nullable responseObject, NSError *_Nullable error) { - OWSAssertIsOnMainThread(); - if (error) { - error.isRetryable = YES; - [self reportError:error]; - return; - } - - NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; - BOOL isValidResponse = (statusCode >= 200) && (statusCode < 400); - if (!isValidResponse) { - OWSLogError(@"Unexpected server response: %d", (int)statusCode); - NSError *invalidResponseError = OWSErrorMakeUnableToProcessServerResponseError(); - invalidResponseError.isRetryable = YES; - [self reportError:invalidResponseError]; - return; - } - - OWSLogInfo(@"Uploaded attachment: %p serverId: %llu, byteCount: %u", - attachmentStream.uniqueId, - attachmentStream.serverId, - attachmentStream.byteCount); - attachmentStream.serverId = serverId; - attachmentStream.isUploaded = YES; - [attachmentStream saveAsyncWithCompletionBlock:^{ - [self reportSuccess]; - }]; - }]; - - [uploadTask resume]; -} - -- (void)fireNotificationWithProgress:(CGFloat)aProgress -{ - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - - CGFloat progress = MAX(kAttachmentUploadProgressTheta, aProgress); - [notificationCenter postNotificationNameAsync:kAttachmentUploadProgressNotification - object:nil - userInfo:@{ - kAttachmentUploadProgressKey : @(progress), - kAttachmentUploadAttachmentIDKey : self.attachmentId - }]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h deleted file mode 100644 index a83c9f7ef..000000000 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class ECKeyPair; -@class OWSDevice; -@class PreKeyRecord; -@class SMKUDAccessKey; -@class SignedPreKeyRecord; -@class TSRequest; - -typedef NS_ENUM(NSUInteger, TSVerificationTransport) { TSVerificationTransportVoice = 1, TSVerificationTransportSMS }; - -@interface OWSRequestFactory : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -+ (TSRequest *)enable2FARequestWithPin:(NSString *)pin; - -+ (TSRequest *)disable2FARequest; - -+ (TSRequest *)acknowledgeMessageDeliveryRequestWithSource:(NSString *)source timestamp:(UInt64)timestamp; - -+ (TSRequest *)acknowledgeMessageDeliveryRequestWithServerGuid:(NSString *)serverGuid; - -+ (TSRequest *)deleteDeviceRequestWithDevice:(OWSDevice *)device; - -+ (TSRequest *)deviceProvisioningCodeRequest; - -+ (TSRequest *)deviceProvisioningRequestWithMessageBody:(NSData *)messageBody ephemeralDeviceId:(NSString *)deviceId; - -+ (TSRequest *)getDevicesRequest; - -+ (TSRequest *)getMessagesRequest; - -+ (TSRequest *)getProfileRequestWithRecipientId:(NSString *)recipientId - udAccessKey:(nullable SMKUDAccessKey *)udAccessKey - NS_SWIFT_NAME(getProfileRequest(recipientId:udAccessKey:)); - -+ (TSRequest *)turnServerInfoRequest; - -+ (TSRequest *)allocAttachmentRequest; - -+ (TSRequest *)attachmentRequestWithAttachmentId:(UInt64)attachmentId; - -+ (TSRequest *)contactsIntersectionRequestWithHashesArray:(NSArray *)hashes; - -+ (TSRequest *)profileAvatarUploadFormRequest; - -+ (TSRequest *)registerForPushRequestWithPushIdentifier:(NSString *)identifier voipIdentifier:(NSString *)voipId; - -+ (TSRequest *)updateAttributesRequest; - -+ (TSRequest *)unregisterAccountRequest; - -+ (TSRequest *)requestVerificationCodeRequestWithPhoneNumber:(NSString *)phoneNumber - captchaToken:(nullable NSString *)captchaToken - transport:(TSVerificationTransport)transport; - -+ (TSRequest *)submitMessageRequestWithRecipient:(NSString *)recipientId - messages:(NSArray *)messages - timeStamp:(uint64_t)timeStamp - udAccessKey:(nullable SMKUDAccessKey *)udAccessKey; - -+ (TSRequest *)verifyCodeRequestWithVerificationCode:(NSString *)verificationCode - forNumber:(NSString *)phoneNumber - pin:(nullable NSString *)pin - authKey:(NSString *)authKey; - -#pragma mark - Prekeys - -+ (TSRequest *)availablePreKeysCountRequest; - -+ (TSRequest *)currentSignedPreKeyRequest; - -+ (TSRequest *)recipientPrekeyRequestWithRecipient:(NSString *)recipientNumber - deviceId:(NSString *)deviceId - udAccessKey:(nullable SMKUDAccessKey *)udAccessKey; - -+ (TSRequest *)registerSignedPrekeyRequestWithSignedPreKeyRecord:(SignedPreKeyRecord *)signedPreKey; - -+ (TSRequest *)registerPrekeysRequestWithPrekeyArray:(NSArray *)prekeys - identityKey:(NSData *)identityKeyPublic - signedPreKey:(SignedPreKeyRecord *)signedPreKey; - -#pragma mark - CDS - -+ (TSRequest *)remoteAttestationRequest:(ECKeyPair *)keyPair - enclaveId:(NSString *)enclaveId - authUsername:(NSString *)authUsername - authPassword:(NSString *)authPassword; - -+ (TSRequest *)enclaveContactDiscoveryRequestWithId:(NSData *)requestId - addressCount:(NSUInteger)addressCount - encryptedAddressData:(NSData *)encryptedAddressData - cryptIv:(NSData *)cryptIv - cryptMac:(NSData *)cryptMac - enclaveId:(NSString *)enclaveId - authUsername:(NSString *)authUsername - authPassword:(NSString *)authPassword - cookies:(NSArray *)cookies; - -+ (TSRequest *)remoteAttestationAuthRequest; -+ (TSRequest *)cdsFeedbackRequestWithStatus:(NSString *)status - reason:(nullable NSString *)reason NS_SWIFT_NAME(cdsFeedbackRequest(status:reason:)); - -#pragma mark - UD - -+ (TSRequest *)udSenderCertificateRequest; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m deleted file mode 100644 index 7fd62f26a..000000000 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m +++ /dev/null @@ -1,544 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSRequestFactory.h" -#import "OWS2FAManager.h" -#import "OWSDevice.h" -#import "ProfileManagerProtocol.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSConstants.h" -#import "TSRequest.h" -#import -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSRequestFactory - -#pragma mark - Dependencies - -+ (TSAccountManager *)tsAccountManager -{ - return TSAccountManager.sharedInstance; -} - -+ (OWS2FAManager *)ows2FAManager -{ - return OWS2FAManager.sharedManager; -} - -+ (id)profileManager -{ - return SSKEnvironment.shared.profileManager; -} - -+ (id)udManager -{ - return SSKEnvironment.shared.udManager; -} - -#pragma mark - - -+ (TSRequest *)enable2FARequestWithPin:(NSString *)pin -{ - OWSAssertDebug(pin.length > 0); - - return [TSRequest requestWithUrl:[NSURL URLWithString:textSecure2FAAPI] - method:@"PUT" - parameters:@{ - @"pin" : pin, - }]; -} - -+ (TSRequest *)disable2FARequest -{ - return [TSRequest requestWithUrl:[NSURL URLWithString:textSecure2FAAPI] method:@"DELETE" parameters:@{}]; -} - -+ (TSRequest *)acknowledgeMessageDeliveryRequestWithSource:(NSString *)source timestamp:(UInt64)timestamp -{ - OWSAssertDebug(timestamp > 0); - - NSString *path = [NSString stringWithFormat:@"v1/messages/%@/%llu", source, timestamp]; - - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"DELETE" parameters:@{}]; -} - -+ (TSRequest *)acknowledgeMessageDeliveryRequestWithServerGuid:(NSString *)serverGuid -{ - OWSAssertDebug(serverGuid.length > 0); - - NSString *path = [NSString stringWithFormat:@"v1/messages/uuid/%@", serverGuid]; - - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"DELETE" parameters:@{}]; -} - -+ (TSRequest *)deleteDeviceRequestWithDevice:(OWSDevice *)device -{ - OWSAssertDebug(device); - - NSString *path = [NSString stringWithFormat:textSecureDevicesAPIFormat, @(device.deviceId)]; - - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"DELETE" parameters:@{}]; -} - -+ (TSRequest *)deviceProvisioningCodeRequest -{ - return [TSRequest requestWithUrl:[NSURL URLWithString:textSecureDeviceProvisioningCodeAPI] - method:@"GET" - parameters:@{}]; -} - -+ (TSRequest *)deviceProvisioningRequestWithMessageBody:(NSData *)messageBody ephemeralDeviceId:(NSString *)deviceId -{ - OWSAssertDebug(messageBody.length > 0); - OWSAssertDebug(deviceId.length > 0); - - NSString *path = [NSString stringWithFormat:textSecureDeviceProvisioningAPIFormat, deviceId]; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] - method:@"PUT" - parameters:@{ - @"body" : [messageBody base64EncodedString], - }]; -} - -+ (TSRequest *)getDevicesRequest -{ - NSString *path = [NSString stringWithFormat:textSecureDevicesAPIFormat, @""]; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; -} - -+ (TSRequest *)getMessagesRequest -{ - return [TSRequest requestWithUrl:[NSURL URLWithString:@"v1/messages"] method:@"GET" parameters:@{}]; -} - -+ (TSRequest *)getProfileRequestWithRecipientId:(NSString *)recipientId - udAccessKey:(nullable SMKUDAccessKey *)udAccessKey -{ - OWSAssertDebug(recipientId.length > 0); - - NSString *path = [NSString stringWithFormat:textSecureProfileAPIFormat, recipientId]; - TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; - if (udAccessKey != nil) { - [self useUDAuthWithRequest:request accessKey:udAccessKey]; - } - return request; -} - -+ (TSRequest *)turnServerInfoRequest -{ - return [TSRequest requestWithUrl:[NSURL URLWithString:@"v1/accounts/turn"] method:@"GET" parameters:@{}]; -} - -+ (TSRequest *)allocAttachmentRequest -{ - NSString *path = [NSString stringWithFormat:@"%@", textSecureAttachmentsAPI]; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; -} - -+ (TSRequest *)attachmentRequestWithAttachmentId:(UInt64)attachmentId -{ - OWSAssertDebug(attachmentId > 0); - - NSString *path = [NSString stringWithFormat:@"%@/%llu", textSecureAttachmentsAPI, attachmentId]; - - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; -} - -+ (TSRequest *)availablePreKeysCountRequest -{ - NSString *path = [NSString stringWithFormat:@"%@", textSecureKeysAPI]; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; -} - -+ (TSRequest *)contactsIntersectionRequestWithHashesArray:(NSArray *)hashes -{ - OWSAssertDebug(hashes.count > 0); - - NSString *path = [NSString stringWithFormat:@"%@/%@", textSecureDirectoryAPI, @"tokens"]; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] - method:@"PUT" - parameters:@{ - @"contacts" : hashes, - }]; -} - -+ (TSRequest *)currentSignedPreKeyRequest -{ - NSString *path = textSecureSignedKeysAPI; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; -} - -+ (TSRequest *)profileAvatarUploadFormRequest -{ - NSString *path = textSecureProfileAvatarFormAPI; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; -} - -+ (TSRequest *)recipientPrekeyRequestWithRecipient:(NSString *)recipientNumber - deviceId:(NSString *)deviceId - udAccessKey:(nullable SMKUDAccessKey *)udAccessKey -{ - OWSAssertDebug(recipientNumber.length > 0); - OWSAssertDebug(deviceId.length > 0); - - NSString *path = [NSString stringWithFormat:@"%@/%@/%@", textSecureKeysAPI, recipientNumber, deviceId]; - - TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; - if (udAccessKey != nil) { - [self useUDAuthWithRequest:request accessKey:udAccessKey]; - } - return request; -} - -+ (TSRequest *)registerForPushRequestWithPushIdentifier:(NSString *)identifier voipIdentifier:(NSString *)voipId -{ - OWSAssertDebug(identifier.length > 0); - OWSAssertDebug(voipId.length > 0); - - NSString *path = [NSString stringWithFormat:@"%@/%@", textSecureAccountsAPI, @"apn"]; - OWSAssertDebug(voipId); - return [TSRequest requestWithUrl:[NSURL URLWithString:path] - method:@"PUT" - parameters:@{ - @"apnRegistrationId" : identifier, - @"voipRegistrationId" : voipId ?: @"", - }]; -} - -+ (TSRequest *)updateAttributesRequest -{ - NSString *path = [textSecureAccountsAPI stringByAppendingString:textSecureAttributesAPI]; - - NSString *authKey = self.tsAccountManager.serverAuthToken; - NSString *_Nullable pin = [self.ows2FAManager pinCode]; - - NSDictionary *accountAttributes = [self accountAttributesWithPin:pin authKey:authKey]; - - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:accountAttributes]; -} - -+ (TSRequest *)unregisterAccountRequest -{ - NSString *path = [NSString stringWithFormat:@"%@/%@", textSecureAccountsAPI, @"apn"]; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"DELETE" parameters:@{}]; -} - -+ (TSRequest *)requestVerificationCodeRequestWithPhoneNumber:(NSString *)phoneNumber - captchaToken:(nullable NSString *)captchaToken - transport:(TSVerificationTransport)transport -{ - OWSAssertDebug(phoneNumber.length > 0); - - NSString *querystring = @"client=ios"; - if (captchaToken.length > 0) { - querystring = [NSString stringWithFormat:@"%@&captcha=%@", querystring, captchaToken]; - } - - NSString *path = [NSString stringWithFormat:@"%@/%@/code/%@?%@", - textSecureAccountsAPI, - [self stringForTransport:transport], - phoneNumber, - querystring]; - TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; - request.shouldHaveAuthorizationHeaders = NO; - - if (transport == TSVerificationTransportVoice) { - NSString *_Nullable localizationHeader = [self voiceCodeLocalizationHeader]; - if (localizationHeader.length > 0) { - [request setValue:localizationHeader forHTTPHeaderField:@"Accept-Language"]; - } - } - - return request; -} - -+ (nullable NSString *)voiceCodeLocalizationHeader -{ - NSLocale *locale = [NSLocale currentLocale]; - NSString *_Nullable languageCode = [locale objectForKey:NSLocaleLanguageCode]; - NSString *_Nullable countryCode = [locale objectForKey:NSLocaleCountryCode]; - - if (!languageCode) { - return nil; - } - - OWSAssertDebug([languageCode rangeOfString:@"-"].location == NSNotFound); - - if (!countryCode) { - // In the absence of a country code, just send a language code. - return languageCode; - } - - OWSAssertDebug(languageCode.length == 2); - OWSAssertDebug(countryCode.length == 2); - return [NSString stringWithFormat:@"%@-%@", languageCode, countryCode]; -} - -+ (NSString *)stringForTransport:(TSVerificationTransport)transport -{ - switch (transport) { - case TSVerificationTransportSMS: - return @"sms"; - case TSVerificationTransportVoice: - return @"voice"; - } -} - -+ (TSRequest *)verifyCodeRequestWithVerificationCode:(NSString *)verificationCode - forNumber:(NSString *)phoneNumber - pin:(nullable NSString *)pin - authKey:(NSString *)authKey -{ - OWSAssertDebug(verificationCode.length > 0); - OWSAssertDebug(phoneNumber.length > 0); - OWSAssertDebug(authKey.length > 0); - - NSString *path = [NSString stringWithFormat:@"%@/code/%@", textSecureAccountsAPI, verificationCode]; - - NSMutableDictionary *accountAttributes = - [[self accountAttributesWithPin:pin authKey:authKey] mutableCopy]; - [accountAttributes removeObjectForKey:@"AuthKey"]; - - TSRequest *request = - [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:accountAttributes]; - // The "verify code" request handles auth differently. - request.authUsername = phoneNumber; - request.authPassword = authKey; - return request; -} - -+ (NSDictionary *)accountAttributesWithPin:(nullable NSString *)pin - authKey:(NSString *)authKey -{ - uint32_t registrationId = [self.tsAccountManager getOrGenerateRegistrationId]; - - BOOL isManualMessageFetchEnabled = self.tsAccountManager.isManualMessageFetchEnabled; - - OWSAES256Key *profileKey = [self.profileManager localProfileKey]; - NSError *error; - SMKUDAccessKey *_Nullable udAccessKey = [[SMKUDAccessKey alloc] initWithProfileKey:profileKey.keyData error:&error]; - if (error || udAccessKey.keyData.length < 1) { - // Crash app if UD cannot be enabled. - OWSFail(@"Could not determine UD access key: %@.", error); - } - BOOL allowUnrestrictedUD = [self.udManager shouldAllowUnrestrictedAccessLocal] && udAccessKey != nil; - - // We no longer include the signalingKey. - NSMutableDictionary *accountAttributes = [@{ - @"AuthKey" : authKey, - @"voice" : @(YES), // all Signal-iOS clients support voice - @"video" : @(YES), // all Signal-iOS clients support WebRTC-based voice and video calls. - @"fetchesMessages" : @(isManualMessageFetchEnabled), // devices that don't support push must tell the server - // they fetch messages manually - @"registrationId" : [NSString stringWithFormat:@"%i", registrationId], - @"unidentifiedAccessKey" : udAccessKey.keyData.base64EncodedString, - @"unrestrictedUnidentifiedAccess" : @(allowUnrestrictedUD), - } mutableCopy]; - - if (pin.length > 0) { - accountAttributes[@"pin"] = pin; - } - - return [accountAttributes copy]; -} - -+ (TSRequest *)submitMessageRequestWithRecipient:(NSString *)recipientId - messages:(NSArray *)messages - timeStamp:(uint64_t)timeStamp - udAccessKey:(nullable SMKUDAccessKey *)udAccessKey -{ - // NOTE: messages may be empty; See comments in OWSDeviceManager. - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(timeStamp > 0); - - NSString *path = [textSecureMessagesAPI stringByAppendingString:recipientId]; - NSDictionary *parameters = @{ - @"messages" : messages, - @"timestamp" : @(timeStamp), - }; - - TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:parameters]; - if (udAccessKey != nil) { - [self useUDAuthWithRequest:request accessKey:udAccessKey]; - } - return request; -} - -+ (TSRequest *)registerSignedPrekeyRequestWithSignedPreKeyRecord:(SignedPreKeyRecord *)signedPreKey -{ - OWSAssertDebug(signedPreKey); - - NSString *path = textSecureSignedKeysAPI; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] - method:@"PUT" - parameters:[self dictionaryFromSignedPreKey:signedPreKey]]; -} - -+ (TSRequest *)registerPrekeysRequestWithPrekeyArray:(NSArray *)prekeys - identityKey:(NSData *)identityKeyPublic - signedPreKey:(SignedPreKeyRecord *)signedPreKey -{ - OWSAssertDebug(prekeys.count > 0); - OWSAssertDebug(identityKeyPublic.length > 0); - OWSAssertDebug(signedPreKey); - - NSString *path = textSecureKeysAPI; - NSString *publicIdentityKey = [[identityKeyPublic prependKeyType] base64EncodedStringWithOptions:0]; - NSMutableArray *serializedPrekeyList = [NSMutableArray array]; - for (PreKeyRecord *preKey in prekeys) { - [serializedPrekeyList addObject:[self dictionaryFromPreKey:preKey]]; - } - return [TSRequest requestWithUrl:[NSURL URLWithString:path] - method:@"PUT" - parameters:@{ - @"preKeys" : serializedPrekeyList, - @"signedPreKey" : [self dictionaryFromSignedPreKey:signedPreKey], - @"identityKey" : publicIdentityKey - }]; -} - -+ (NSDictionary *)dictionaryFromPreKey:(PreKeyRecord *)preKey -{ - return @{ - @"keyId" : @(preKey.Id), - @"publicKey" : [[preKey.keyPair.publicKey prependKeyType] base64EncodedStringWithOptions:0], - }; -} - -+ (NSDictionary *)dictionaryFromSignedPreKey:(SignedPreKeyRecord *)preKey -{ - return @{ - @"keyId" : @(preKey.Id), - @"publicKey" : [[preKey.keyPair.publicKey prependKeyType] base64EncodedStringWithOptions:0], - @"signature" : [preKey.signature base64EncodedStringWithOptions:0] - }; -} - -+ (TSRequest *)remoteAttestationRequest:(ECKeyPair *)keyPair - enclaveId:(NSString *)enclaveId - authUsername:(NSString *)authUsername - authPassword:(NSString *)authPassword -{ - OWSAssertDebug(keyPair); - OWSAssertDebug(enclaveId.length > 0); - OWSAssertDebug(authUsername.length > 0); - OWSAssertDebug(authPassword.length > 0); - - NSString *path = [NSString stringWithFormat:@"%@/v1/attestation/%@", contactDiscoveryURL, enclaveId]; - TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] - method:@"PUT" - parameters:@{ - // We DO NOT prepend the "key type" byte. - @"clientPublic" : [keyPair.publicKey base64EncodedStringWithOptions:0], - }]; - request.authUsername = authUsername; - request.authPassword = authPassword; - - // Don't bother with the default cookie store; - // these cookies are ephemeral. - // - // NOTE: TSNetworkManager now separately disables default cookie handling for all requests. - [request setHTTPShouldHandleCookies:NO]; - - return request; -} - -+ (TSRequest *)enclaveContactDiscoveryRequestWithId:(NSData *)requestId - addressCount:(NSUInteger)addressCount - encryptedAddressData:(NSData *)encryptedAddressData - cryptIv:(NSData *)cryptIv - cryptMac:(NSData *)cryptMac - enclaveId:(NSString *)enclaveId - authUsername:(NSString *)authUsername - authPassword:(NSString *)authPassword - cookies:(NSArray *)cookies -{ - NSString *path = [NSString stringWithFormat:@"%@/v1/discovery/%@", contactDiscoveryURL, enclaveId]; - - TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] - method:@"PUT" - parameters:@{ - @"requestId" : requestId.base64EncodedString, - @"addressCount" : @(addressCount), - @"data" : encryptedAddressData.base64EncodedString, - @"iv" : cryptIv.base64EncodedString, - @"mac" : cryptMac.base64EncodedString, - }]; - - request.authUsername = authUsername; - request.authPassword = authPassword; - - // Don't bother with the default cookie store; - // these cookies are ephemeral. - // - // NOTE: TSNetworkManager now separately disables default cookie handling for all requests. - [request setHTTPShouldHandleCookies:NO]; - // Set the cookie header. - OWSAssertDebug(request.allHTTPHeaderFields.count == 0); - [request setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:cookies]]; - - return request; -} - -+ (TSRequest *)remoteAttestationAuthRequest -{ - NSString *path = @"/v1/directory/auth"; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; -} - -+ (TSRequest *)cdsFeedbackRequestWithStatus:(NSString *)status - reason:(nullable NSString *)reason -{ - - NSDictionary *parameters; - if (reason == nil) { - parameters = @{}; - } else { - const NSUInteger kServerReasonLimit = 1000; - NSString *limitedReason; - if (reason.length < kServerReasonLimit) { - limitedReason = reason; - } else { - OWSFailDebug(@"failure: reason should be under 1000"); - limitedReason = [reason substringToIndex:kServerReasonLimit - 1]; - } - parameters = @{ @"reason": limitedReason }; - } - NSString *path = [NSString stringWithFormat:@"/v1/directory/feedback-v3/%@", status]; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:parameters]; -} - -#pragma mark - UD - -+ (TSRequest *)udSenderCertificateRequest -{ - NSString *path = @"/v1/certificate/delivery"; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; -} - -+ (void)useUDAuthWithRequest:(TSRequest *)request accessKey:(SMKUDAccessKey *)udAccessKey -{ - OWSAssertDebug(request); - OWSAssertDebug(udAccessKey); - - // Suppress normal auth headers. - request.shouldHaveAuthorizationHeaders = NO; - - // Add UD auth header. - [request setValue:[udAccessKey.keyData base64EncodedString] forHTTPHeaderField:@"Unidentified-Access-Key"]; - - request.isUDRequest = YES; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/Requests/TSRequest.h b/SignalServiceKit/src/Network/API/Requests/TSRequest.h deleted file mode 100644 index 25e9c3933..000000000 --- a/SignalServiceKit/src/Network/API/Requests/TSRequest.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class SMKUDAccessKey; - -@interface TSRequest : NSMutableURLRequest - -@property (nonatomic) BOOL isUDRequest; -@property (nonatomic) BOOL shouldHaveAuthorizationHeaders; -@property (atomic, nullable) NSString *authUsername; -@property (atomic, nullable) NSString *authPassword; - -@property (nonatomic, readonly) NSDictionary *parameters; - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithURL:(NSURL *)URL; - -- (instancetype)initWithURL:(NSURL *)URL - cachePolicy:(NSURLRequestCachePolicy)cachePolicy - timeoutInterval:(NSTimeInterval)timeoutInterval NS_UNAVAILABLE; - -- (instancetype)initWithURL:(NSURL *)URL - method:(NSString *)method - parameters:(nullable NSDictionary *)parameters; - -+ (instancetype)requestWithUrl:(NSURL *)url - method:(NSString *)method - parameters:(nullable NSDictionary *)parameters; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/Requests/TSRequest.m b/SignalServiceKit/src/Network/API/Requests/TSRequest.m deleted file mode 100644 index 76f4dbd0a..000000000 --- a/SignalServiceKit/src/Network/API/Requests/TSRequest.m +++ /dev/null @@ -1,119 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSRequest.h" -#import "TSAccountManager.h" -#import "TSConstants.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation TSRequest - -@synthesize authUsername = _authUsername; -@synthesize authPassword = _authPassword; - -- (id)initWithURL:(NSURL *)URL { - OWSAssertDebug(URL); - self = [super initWithURL:URL - cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData - timeoutInterval:textSecureHTTPTimeOut]; - if (!self) { - return nil; - } - - _parameters = @{}; - self.shouldHaveAuthorizationHeaders = YES; - - return self; -} - -- (instancetype)init -{ - OWSFail(@"You must use the initWithURL: method"); - return nil; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-designated-initializers" - -- (instancetype)initWithURL:(NSURL *)URL - cachePolicy:(NSURLRequestCachePolicy)cachePolicy - timeoutInterval:(NSTimeInterval)timeoutInterval -{ - OWSFail(@"You must use the initWithURL: method"); - return nil; -} - -- (instancetype)initWithURL:(NSURL *)URL - method:(NSString *)method - parameters:(nullable NSDictionary *)parameters -{ - OWSAssertDebug(URL); - OWSAssertDebug(method.length > 0); - OWSAssertDebug(parameters); - - self = [super initWithURL:URL - cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData - timeoutInterval:textSecureHTTPTimeOut]; - if (!self) { - return nil; - } - - _parameters = parameters ?: @{}; - [self setHTTPMethod:method]; - self.shouldHaveAuthorizationHeaders = YES; - - return self; -} - -+ (instancetype)requestWithUrl:(NSURL *)url - method:(NSString *)method - parameters:(nullable NSDictionary *)parameters -{ - return [[TSRequest alloc] initWithURL:url method:method parameters:parameters]; -} - -#pragma mark - Authorization - -- (void)setAuthUsername:(nullable NSString *)authUsername -{ - OWSAssertDebug(self.shouldHaveAuthorizationHeaders); - - @synchronized(self) { - _authUsername = authUsername; - } -} - -- (void)setAuthPassword:(nullable NSString *)authPassword -{ - OWSAssertDebug(self.shouldHaveAuthorizationHeaders); - - @synchronized(self) { - _authPassword = authPassword; - } -} - -- (nullable NSString *)authUsername -{ - OWSAssertDebug(self.shouldHaveAuthorizationHeaders); - - @synchronized(self) { - return (_authUsername ?: [TSAccountManager localNumber]); - } -} - -- (nullable NSString *)authPassword -{ - OWSAssertDebug(self.shouldHaveAuthorizationHeaders); - - @synchronized(self) { - return (_authPassword ?: [TSAccountManager serverAuthToken]); - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/SignalServiceProfile.swift b/SignalServiceKit/src/Network/API/SignalServiceProfile.swift deleted file mode 100644 index e1175884f..000000000 --- a/SignalServiceKit/src/Network/API/SignalServiceProfile.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -@objc -public class SignalServiceProfile: NSObject { - - public enum ValidationError: Error { - case invalid(description: String) - case invalidIdentityKey(description: String) - case invalidProfileName(description: String) - } - - public let recipientId: String - public let identityKey: Data - public let profileNameEncrypted: Data? - public let avatarUrlPath: String? - public let unidentifiedAccessVerifier: Data? - public let hasUnrestrictedUnidentifiedAccess: Bool - - public init(recipientId: String, responseObject: Any?) throws { - self.recipientId = recipientId - - guard let params = ParamParser(responseObject: responseObject) else { - throw ValidationError.invalid(description: "invalid response: \(String(describing: responseObject))") - } - - let identityKeyWithType = try params.requiredBase64EncodedData(key: "identityKey") - let kIdentityKeyLength = 33 - guard identityKeyWithType.count == kIdentityKeyLength else { - throw ValidationError.invalidIdentityKey(description: "malformed identity key \(identityKeyWithType.hexadecimalString) with decoded length: \(identityKeyWithType.count)") - } - do { - // `removeKeyType` is an objc category method only on NSData, so temporarily cast. - self.identityKey = try (identityKeyWithType as NSData).removeKeyType() as Data - } catch { - // `removeKeyType` throws an SCKExceptionWrapperError, which, typically should - // be unwrapped by any objc code calling this method. - owsFailDebug("identify key had unexpected format") - throw ValidationError.invalidIdentityKey(description: "malformed identity key \(identityKeyWithType.hexadecimalString) with data: \(identityKeyWithType)") - } - - self.profileNameEncrypted = try params.optionalBase64EncodedData(key: "name") - - let avatarUrlPath: String? = try params.optional(key: "avatar") - self.avatarUrlPath = avatarUrlPath - - self.unidentifiedAccessVerifier = try params.optionalBase64EncodedData(key: "unidentifiedAccess") - - self.hasUnrestrictedUnidentifiedAccess = try params.optional(key: "unrestrictedUnidentifiedAccess") ?? false - } -} diff --git a/SignalServiceKit/src/Network/API/TSNetworkManager.h b/SignalServiceKit/src/Network/API/TSNetworkManager.h deleted file mode 100644 index 0dda2495a..000000000 --- a/SignalServiceKit/src/Network/API/TSNetworkManager.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -extern NSErrorDomain const TSNetworkManagerErrorDomain; -typedef NS_ERROR_ENUM(TSNetworkManagerErrorDomain, TSNetworkManagerError){ - // It's a shame to use 0 as an enum value for anything other than something like default or unknown, because it's - // indistinguishable from "not set" in Objc. - // However this value was existing behavior for connectivity errors, and since we might be using this in other - // places I didn't want to change it out of hand - TSNetworkManagerErrorFailedConnection = 0, - // Other TSNetworkManagerError's use HTTP status codes (e.g. 404, etc) -}; - -BOOL IsNSErrorNetworkFailure(NSError *_Nullable error); - -typedef void (^TSNetworkManagerSuccess)(NSURLSessionDataTask *task, _Nullable id responseObject); -typedef void (^TSNetworkManagerFailure)(NSURLSessionDataTask *task, NSError *error); - -@class TSRequest; - -@interface TSNetworkManager : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initDefault; - -+ (instancetype)sharedManager; - -- (void)makeRequest:(TSRequest *)request - success:(TSNetworkManagerSuccess)success - failure:(TSNetworkManagerFailure)failure NS_SWIFT_NAME(makeRequest(_:success:failure:)); - -- (void)makeRequest:(TSRequest *)request - completionQueue:(dispatch_queue_t)completionQueue - success:(TSNetworkManagerSuccess)success - failure:(TSNetworkManagerFailure)failure NS_SWIFT_NAME(makeRequest(_:completionQueue:success:failure:)); - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/TSNetworkManager.m b/SignalServiceKit/src/Network/API/TSNetworkManager.m deleted file mode 100644 index f9445906e..000000000 --- a/SignalServiceKit/src/Network/API/TSNetworkManager.m +++ /dev/null @@ -1,583 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSNetworkManager.h" -#import "AppContext.h" -#import "NSError+messageSending.h" -#import "NSURLSessionDataTask+StatusCode.h" -#import "OWSError.h" -#import "OWSQueues.h" -#import "OWSSignalService.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSRequest.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSErrorDomain const TSNetworkManagerErrorDomain = @"SignalServiceKit.TSNetworkManager"; - -BOOL IsNSErrorNetworkFailure(NSError *_Nullable error) -{ - return ([error.domain isEqualToString:TSNetworkManagerErrorDomain] - && error.code == TSNetworkManagerErrorFailedConnection); -} - -dispatch_queue_t NetworkManagerQueue() -{ - static dispatch_queue_t serialQueue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - serialQueue = dispatch_queue_create("org.whispersystems.networkManager", DISPATCH_QUEUE_SERIAL); - }); - return serialQueue; -} - -#pragma mark - - -@interface OWSSessionManager : NSObject - -@property (nonatomic, readonly) AFHTTPSessionManager *sessionManager; -@property (nonatomic, readonly) NSDictionary *defaultHeaders; - -@end - -#pragma mark - - -@implementation OWSSessionManager - -#pragma mark - Dependencies - -- (OWSSignalService *)signalService -{ - return [OWSSignalService sharedInstance]; -} - -#pragma mark - - -- (instancetype)init -{ - AssertOnDispatchQueue(NetworkManagerQueue()); - - self = [super init]; - if (!self) { - return self; - } - - _sessionManager = [self.signalService buildSignalServiceSessionManager]; - self.sessionManager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - // NOTE: We could enable HTTPShouldUsePipelining here. - // Make a copy of the default headers for this session manager. - _defaultHeaders = [self.sessionManager.requestSerializer.HTTPRequestHeaders copy]; - - return self; -} - -// TSNetworkManager.serialQueue -- (void)performRequest:(TSRequest *)request - canUseAuth:(BOOL)canUseAuth - success:(TSNetworkManagerSuccess)success - failure:(TSNetworkManagerFailure)failure -{ - AssertOnDispatchQueue(NetworkManagerQueue()); - OWSAssertDebug(request); - OWSAssertDebug(success); - OWSAssertDebug(failure); - - // Clear all headers so that we don't retain headers from previous requests. - for (NSString *headerField in self.sessionManager.requestSerializer.HTTPRequestHeaders.allKeys.copy) { - [self.sessionManager.requestSerializer setValue:nil forHTTPHeaderField:headerField]; - } - - // Apply the default headers for this session manager. - for (NSString *headerField in self.defaultHeaders) { - NSString *headerValue = self.defaultHeaders[headerField]; - [self.sessionManager.requestSerializer setValue:headerValue forHTTPHeaderField:headerField]; - } - - if (canUseAuth && request.shouldHaveAuthorizationHeaders) { - [self.sessionManager.requestSerializer setAuthorizationHeaderFieldWithUsername:request.authUsername - password:request.authPassword]; - } - - // Honor the request's headers. - for (NSString *headerField in request.allHTTPHeaderFields) { - NSString *headerValue = request.allHTTPHeaderFields[headerField]; - [self.sessionManager.requestSerializer setValue:headerValue forHTTPHeaderField:headerField]; - } - - self.sessionManager.requestSerializer.timeoutInterval = request.timeoutInterval; - - if ([request.HTTPMethod isEqualToString:@"GET"]) { - [self.sessionManager GET:request.URL.absoluteString - parameters:request.parameters - progress:nil - success:success - failure:failure]; - } else if ([request.HTTPMethod isEqualToString:@"POST"]) { - [self.sessionManager POST:request.URL.absoluteString - parameters:request.parameters - progress:nil - success:success - failure:failure]; - } else if ([request.HTTPMethod isEqualToString:@"PUT"]) { - [self.sessionManager PUT:request.URL.absoluteString - parameters:request.parameters - success:success - failure:failure]; - } else if ([request.HTTPMethod isEqualToString:@"DELETE"]) { - [self.sessionManager DELETE:request.URL.absoluteString - parameters:request.parameters - success:success - failure:failure]; - } else if ([request.HTTPMethod isEqualToString:@"PATCH"]) { - [self.sessionManager PATCH:request.URL.absoluteString - parameters:request.parameters - success:success - failure:failure]; - } else { - OWSLogError(@"Trying to perform HTTP operation with unknown verb: %@", request.HTTPMethod); - } -} - -@end - -#pragma mark - - -// You might be asking: "why use a pool at all? We're only using the session manager -// on the serial queue, so can't we just have two session managers (1 UD, 1 non-UD) -// that we use for all requests?" -// -// That assumes that the session managers are not stateful in a way where concurrent -// requests can interfere with each other. I audited the AFNetworking codebase and my -// reading is that sessions managers are safe to use in that way - that the state of -// their properties (e.g. header values) is only used when building the request and -// can be safely changed after performRequest is complete. -// -// But I decided that I didn't want to (silently) bake that assumption into the -// codebase, since the stakes are high. The session managers aren't expensive. IMO -// better to use a pool and not re-use a session manager until its request succeeds -// or fails. -@interface OWSSessionManagerPool : NSObject - -@property (nonatomic) NSMutableArray *pool; - -@end - -#pragma mark - - -@implementation OWSSessionManagerPool - -- (instancetype)init -{ - self = [super init]; - if (!self) { - return self; - } - - self.pool = [NSMutableArray new]; - - return self; -} - -- (OWSSessionManager *)get -{ - AssertOnDispatchQueue(NetworkManagerQueue()); - - OWSSessionManager *_Nullable sessionManager = [self.pool lastObject]; - if (sessionManager) { - [self.pool removeLastObject]; - } else { - sessionManager = [OWSSessionManager new]; - } - OWSAssertDebug(sessionManager); - return sessionManager; -} - -- (void)returnToPool:(OWSSessionManager *)sessionManager -{ - AssertOnDispatchQueue(NetworkManagerQueue()); - - OWSAssertDebug(sessionManager); - const NSUInteger kMaxPoolSize = 3; - if (self.pool.count >= kMaxPoolSize) { - // Discard - return; - } - [self.pool addObject:sessionManager]; -} - -@end - -#pragma mark - - -@interface TSNetworkManager () - -// These properties should only be accessed on serialQueue. -@property (atomic, readonly) OWSSessionManagerPool *udSessionManagerPool; -@property (atomic, readonly) OWSSessionManagerPool *nonUdSessionManagerPool; - -@end - -#pragma mark - - -@implementation TSNetworkManager - -#pragma mark - Dependencies - -+ (TSAccountManager *)tsAccountManager -{ - return TSAccountManager.sharedInstance; -} - -#pragma mark - Singleton - -+ (instancetype)sharedManager -{ - OWSAssertDebug(SSKEnvironment.shared.networkManager); - - return SSKEnvironment.shared.networkManager; -} - -- (instancetype)initDefault -{ - self = [super init]; - if (!self) { - return self; - } - - _udSessionManagerPool = [OWSSessionManagerPool new]; - _nonUdSessionManagerPool = [OWSSessionManagerPool new]; - - OWSSingletonAssert(); - - return self; -} - -#pragma mark Manager Methods - -- (void)makeRequest:(TSRequest *)request - success:(TSNetworkManagerSuccess)success - failure:(TSNetworkManagerFailure)failure -{ - return [self makeRequest:request completionQueue:dispatch_get_main_queue() success:success failure:failure]; -} - -- (void)makeRequest:(TSRequest *)request - completionQueue:(dispatch_queue_t)completionQueue - success:(TSNetworkManagerSuccess)success - failure:(TSNetworkManagerFailure)failure -{ - OWSAssertDebug(request); - OWSAssertDebug(success); - OWSAssertDebug(failure); - - dispatch_async(NetworkManagerQueue(), ^{ - [self makeRequestSync:request completionQueue:completionQueue success:success failure:failure]; - }); -} - -- (void)makeRequestSync:(TSRequest *)request - completionQueue:(dispatch_queue_t)completionQueue - success:(TSNetworkManagerSuccess)successParam - failure:(TSNetworkManagerFailure)failureParam -{ - OWSAssertDebug(request); - OWSAssertDebug(successParam); - OWSAssertDebug(failureParam); - - BOOL isUDRequest = request.isUDRequest; - NSString *label = (isUDRequest ? @"UD request" : @"Non-UD request"); - BOOL canUseAuth = !isUDRequest; - if (isUDRequest) { - OWSAssert(!request.shouldHaveAuthorizationHeaders); - } - OWSLogInfo(@"Making %@: %@", label, request); - - OWSSessionManagerPool *sessionManagerPool - = (isUDRequest ? self.udSessionManagerPool : self.nonUdSessionManagerPool); - OWSSessionManager *sessionManager = [sessionManagerPool get]; - - TSNetworkManagerSuccess success = ^(NSURLSessionDataTask *task, _Nullable id responseObject) { - dispatch_async(NetworkManagerQueue(), ^{ - [sessionManagerPool returnToPool:sessionManager]; - }); - - dispatch_async(completionQueue, ^{ - OWSLogInfo(@"%@ succeeded : %@", label, request); - - if (canUseAuth && request.shouldHaveAuthorizationHeaders) { - [TSNetworkManager.tsAccountManager setIsDeregistered:NO]; - } - - successParam(task, responseObject); - - [OutageDetection.sharedManager reportConnectionSuccess]; - }); - }; - TSNetworkManagerFailure failure = ^(NSURLSessionDataTask *task, NSError *error) { - dispatch_async(NetworkManagerQueue(), ^{ - [sessionManagerPool returnToPool:sessionManager]; - }); - - [TSNetworkManager - handleNetworkFailure:^(NSURLSessionDataTask *task, NSError *error) { - dispatch_async(completionQueue, ^{ - failureParam(task, error); - }); - } - request:request - task:task - error:error]; - }; - - [sessionManager performRequest:request canUseAuth:canUseAuth success:success failure:failure]; -} - -#ifdef DEBUG -+ (void)logCurlForTask:(NSURLSessionDataTask *)task -{ - NSMutableArray *curlComponents = [NSMutableArray new]; - [curlComponents addObject:@"curl"]; - // Verbose - [curlComponents addObject:@"-v"]; - // Insecure - [curlComponents addObject:@"-k"]; - // Method, e.g. GET - [curlComponents addObject:@"-X"]; - [curlComponents addObject:task.originalRequest.HTTPMethod]; - // Headers - for (NSString *header in task.originalRequest.allHTTPHeaderFields) { - NSString *headerValue = task.originalRequest.allHTTPHeaderFields[header]; - // We don't yet support escaping header values. - // If these asserts trip, we'll need to add that. - OWSAssertDebug([header rangeOfString:@"'"].location == NSNotFound); - OWSAssertDebug([headerValue rangeOfString:@"'"].location == NSNotFound); - - [curlComponents addObject:@"-H"]; - [curlComponents addObject:[NSString stringWithFormat:@"'%@: %@'", header, headerValue]]; - } - // Body/parameters (e.g. JSON payload) - if (task.originalRequest.HTTPBody) { - NSString *jsonBody = - [[NSString alloc] initWithData:task.originalRequest.HTTPBody encoding:NSUTF8StringEncoding]; - // We don't yet support escaping JSON. - // If these asserts trip, we'll need to add that. - OWSAssertDebug([jsonBody rangeOfString:@"'"].location == NSNotFound); - [curlComponents addObject:@"--data-ascii"]; - [curlComponents addObject:[NSString stringWithFormat:@"'%@'", jsonBody]]; - } - // TODO: Add support for cookies. - [curlComponents addObject:task.originalRequest.URL.absoluteString]; - NSString *curlCommand = [curlComponents componentsJoinedByString:@" "]; - OWSLogVerbose(@"curl for failed request: %@", curlCommand); -} -#endif - -+ (void)handleNetworkFailure:(TSNetworkManagerFailure)failureBlock - request:(TSRequest *)request - task:(NSURLSessionDataTask *)task - error:(NSError *)networkError -{ - OWSAssertDebug(failureBlock); - OWSAssertDebug(request); - OWSAssertDebug(networkError); - - NSInteger statusCode = [task statusCode]; - -#ifdef DEBUG - [TSNetworkManager logCurlForTask:task]; -#endif - - [OutageDetection.sharedManager reportConnectionFailure]; - - NSError *error = [self errorWithHTTPCode:statusCode - description:nil - failureReason:nil - recoverySuggestion:nil - fallbackError:networkError]; - - switch (statusCode) { - case 0: { - NSError *connectivityError = - [self errorWithHTTPCode:TSNetworkManagerErrorFailedConnection - description:NSLocalizedString(@"ERROR_DESCRIPTION_NO_INTERNET", - @"Generic error used whenever Signal can't contact the server") - failureReason:networkError.localizedFailureReason - recoverySuggestion:NSLocalizedString(@"NETWORK_ERROR_RECOVERY", nil) - fallbackError:networkError]; - connectivityError.isRetryable = YES; - - OWSLogWarn(@"The network request failed because of a connectivity error: %@", request); - failureBlock(task, connectivityError); - break; - } - case 400: { - OWSLogError(@"The request contains an invalid parameter : %@, %@", networkError.debugDescription, request); - - error.isRetryable = NO; - - failureBlock(task, error); - break; - } - case 401: { - OWSLogError(@"The server returned an error about the authorization header: %@, %@", - networkError.debugDescription, - request); - error.isRetryable = NO; - [self deregisterAfterAuthErrorIfNecessary:task request:request statusCode:statusCode]; - failureBlock(task, error); - break; - } - case 403: { - OWSLogError( - @"The server returned an authentication failure: %@, %@", networkError.debugDescription, request); - error.isRetryable = NO; - [self deregisterAfterAuthErrorIfNecessary:task request:request statusCode:statusCode]; - failureBlock(task, error); - break; - } - case 404: { - OWSLogError(@"The requested resource could not be found: %@, %@", networkError.debugDescription, request); - error.isRetryable = NO; - failureBlock(task, error); - break; - } - case 411: { - OWSLogInfo(@"Multi-device pairing: %ld, %@, %@", (long)statusCode, networkError.debugDescription, request); - NSError *customError = [self errorWithHTTPCode:statusCode - description:NSLocalizedString(@"MULTIDEVICE_PAIRING_MAX_DESC", - @"alert title: cannot link - reached max linked devices") - failureReason:networkError.localizedFailureReason - recoverySuggestion:NSLocalizedString(@"MULTIDEVICE_PAIRING_MAX_RECOVERY", - @"alert body: cannot link - reached max linked devices") - fallbackError:networkError]; - customError.isRetryable = NO; - failureBlock(task, customError); - break; - } - case 413: { - OWSLogWarn(@"Rate limit exceeded: %@", request); - NSError *customError = [self errorWithHTTPCode:statusCode - description:NSLocalizedString(@"REGISTER_RATE_LIMITING_ERROR", nil) - failureReason:networkError.localizedFailureReason - recoverySuggestion:NSLocalizedString(@"REGISTER_RATE_LIMITING_BODY", nil) - fallbackError:networkError]; - customError.isRetryable = NO; - failureBlock(task, customError); - break; - } - case 417: { - // TODO: Is this response code obsolete? - OWSLogWarn(@"The number is already registered on a relay. Please unregister there first: %@", request); - NSError *customError = [self errorWithHTTPCode:statusCode - description:NSLocalizedString(@"REGISTRATION_ERROR", nil) - failureReason:networkError.localizedFailureReason - recoverySuggestion:NSLocalizedString(@"RELAY_REGISTERED_ERROR_RECOVERY", nil) - fallbackError:networkError]; - customError.isRetryable = NO; - failureBlock(task, customError); - break; - } - case 422: { - OWSLogError(@"The registration was requested over an unknown transport: %@, %@", - networkError.debugDescription, - request); - error.isRetryable = NO; - failureBlock(task, error); - break; - } - default: { - OWSLogWarn(@"Unknown error: %ld, %@, %@", (long)statusCode, networkError.debugDescription, request); - error.isRetryable = NO; - failureBlock(task, error); - break; - } - } -} - -+ (void)deregisterAfterAuthErrorIfNecessary:(NSURLSessionDataTask *)task - request:(TSRequest *)request - statusCode:(NSInteger)statusCode { - /* Loki: Original code - * We don't care about invalid auth - * ======== - - OWSLogVerbose(@"Invalid auth: %@", task.originalRequest.allHTTPHeaderFields); - - // We only want to de-register for: - // - // * Auth errors... - // * ...received from Signal service... - // * ...that used standard authorization. - // - // * We don't want want to deregister for: - // - // * CDS requests. - // * Requests using UD auth. - // * etc. - if ([task.originalRequest.URL.absoluteString hasPrefix:textSecureServerURL] - && request.shouldHaveAuthorizationHeaders) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (self.tsAccountManager.isRegisteredAndReady) { - [self.tsAccountManager setIsDeregistered:YES]; - } else { - OWSLogWarn( - @"Ignoring auth failure; not registered and ready: %@.", task.originalRequest.URL.absoluteString); - } - }); - } else { - OWSLogWarn(@"Ignoring %d for URL: %@", (int)statusCode, task.originalRequest.URL.absoluteString); - } - - * ======== - */ -} - -+ (NSError *)errorWithHTTPCode:(NSInteger)code - description:(nullable NSString *)description - failureReason:(nullable NSString *)failureReason - recoverySuggestion:(nullable NSString *)recoverySuggestion - fallbackError:(NSError *)fallbackError -{ - OWSAssertDebug(fallbackError); - - if (!description) { - description = fallbackError.localizedDescription; - } - if (!failureReason) { - failureReason = fallbackError.localizedFailureReason; - } - if (!recoverySuggestion) { - recoverySuggestion = fallbackError.localizedRecoverySuggestion; - } - - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - - if (description) { - [dict setObject:description forKey:NSLocalizedDescriptionKey]; - } - if (failureReason) { - [dict setObject:failureReason forKey:NSLocalizedFailureReasonErrorKey]; - } - if (recoverySuggestion) { - [dict setObject:recoverySuggestion forKey:NSLocalizedRecoverySuggestionErrorKey]; - } - - NSData *failureData = fallbackError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]; - - if (failureData) { - [dict setObject:failureData forKey:AFNetworkingOperationFailingURLResponseDataErrorKey]; - } - - dict[NSUnderlyingErrorKey] = fallbackError; - - return [NSError errorWithDomain:TSNetworkManagerErrorDomain code:code userInfo:dict]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/ContentProxy.swift b/SignalServiceKit/src/Network/ContentProxy.swift deleted file mode 100644 index 956e0f349..000000000 --- a/SignalServiceKit/src/Network/ContentProxy.swift +++ /dev/null @@ -1,127 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import SessionCoreKit - -@objc -public class ContentProxy: NSObject { - - @available(*, unavailable, message:"do not instantiate this class.") - private override init() { - } - - @objc - public class func sessionConfiguration() -> URLSessionConfiguration { - let configuration = URLSessionConfiguration.ephemeral - let proxyHost = "contentproxy.signal.org" - let proxyPort = 443 - configuration.connectionProxyDictionary = [ - "HTTPEnable": 1, - "HTTPProxy": proxyHost, - "HTTPPort": proxyPort, - "HTTPSEnable": 1, - "HTTPSProxy": proxyHost, - "HTTPSPort": proxyPort - ] - return configuration - } - - @objc - public class func sessionManager(baseUrl baseUrlString: String?) -> AFHTTPSessionManager? { - guard let baseUrlString = baseUrlString else { - return AFHTTPSessionManager(baseURL: nil, sessionConfiguration: sessionConfiguration()) - } - guard let baseUrl = URL(string: baseUrlString) else { - owsFailDebug("Invalid base URL.") - return nil - } - let sessionManager = AFHTTPSessionManager(baseURL: baseUrl, - sessionConfiguration: sessionConfiguration()) - return sessionManager - } - - @objc - public class func jsonSessionManager(baseUrl: String) -> AFHTTPSessionManager? { - guard let sessionManager = self.sessionManager(baseUrl: baseUrl) else { - owsFailDebug("Could not create session manager") - return nil - } - sessionManager.requestSerializer = AFJSONRequestSerializer() - sessionManager.responseSerializer = AFJSONResponseSerializer() - return sessionManager - } - - static let userAgent = "Signal iOS (+https://signal.org/download)" - - public class func configureProxiedRequest(request: inout URLRequest) -> Bool { - request.addValue(userAgent, forHTTPHeaderField: "User-Agent") - - padRequestSize(request: &request) - - guard let url = request.url, - let scheme = url.scheme, - scheme.lowercased() == "https" else { - return false - } - return true - } - - // This mutates the session manager state, so its the caller's obligation to avoid conflicts by: - // - // * Using a new session manager for each request. - // * Pooling session managers. - // * Using a single session manager on a single queue. - @objc - public class func configureSessionManager(sessionManager: AFHTTPSessionManager, - forUrl urlString: String) -> Bool { - - guard let url = URL(string: urlString, relativeTo: sessionManager.baseURL) else { - owsFailDebug("Invalid URL query: \(urlString).") - return false - } - - var request = URLRequest(url: url) - - guard configureProxiedRequest(request: &request) else { - owsFailDebug("Invalid URL query: \(urlString).") - return false - } - - // Remove all headers from the request. - for headerField in sessionManager.requestSerializer.httpRequestHeaders.keys { - sessionManager.requestSerializer.setValue(nil, forHTTPHeaderField: headerField) - } - // Honor the request's headers. - if let allHTTPHeaderFields = request.allHTTPHeaderFields { - for (headerField, headerValue) in allHTTPHeaderFields { - sessionManager.requestSerializer.setValue(headerValue, forHTTPHeaderField: headerField) - } - } - return true - } - - public class func padRequestSize(request: inout URLRequest) { - // Generate 1-64 chars of padding. - let paddingLength: Int = 1 + Int(arc4random_uniform(64)) - let padding = self.padding(withLength: paddingLength) - assert(padding.count == paddingLength) - request.addValue(padding, forHTTPHeaderField: "X-SignalPadding") - } - - private class func padding(withLength length: Int) -> String { - // Pick a random ASCII char in the range 48-122 - var result = "" - // Min and max values, inclusive. - let minValue: UInt32 = 48 - let maxValue: UInt32 = 122 - for _ in 1...length { - let value = minValue + arc4random_uniform(maxValue - minValue + 1) - assert(value >= minValue) - assert(value <= maxValue) - result += String(UnicodeScalar(UInt8(value))) - } - return result - } -} diff --git a/SignalServiceKit/src/Network/MessageSenderJobQueue.swift b/SignalServiceKit/src/Network/MessageSenderJobQueue.swift deleted file mode 100644 index c6136f0bd..000000000 --- a/SignalServiceKit/src/Network/MessageSenderJobQueue.swift +++ /dev/null @@ -1,256 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation - -/// Durably enqueues a message for sending. -/// -/// The queue's operations (`MessageSenderOperation`) uses `MessageSender` to send a message. -/// -/// ## Retry behavior -/// -/// Like all JobQueue's, MessageSenderJobQueue implements retry handling for operation errors. -/// -/// `MessageSender` also includes it's own retry logic necessary to encapsulate business logic around -/// a user changing their Registration ID, or adding/removing devices. That is, it is sometimes *normal* -/// for MessageSender to have to resend to a recipient multiple times before it is accepted, and doesn't -/// represent a "failure" from the application standpoint. -/// -/// So we have an inner non-durable retry (MessageSender) and an outer durable retry (MessageSenderJobQueue). -/// -/// Both respect the `error.isRetryable` convention to be sure we don't keep retrying in some situations -/// (e.g. rate limiting) - -@objc(SSKMessageSenderJobQueue) -public class MessageSenderJobQueue: NSObject, JobQueue { - - @objc - public override init() { - super.init() - - AppReadiness.runNowOrWhenAppWillBecomeReady { - self.setup() - } - } - - @objc(addMessage:transaction:) - public func add(message: TSOutgoingMessage, transaction: YapDatabaseReadWriteTransaction) { - self.add(message: message, removeMessageAfterSending: false, transaction: transaction) - } - - @objc(addMediaMessage:dataSource:contentType:sourceFilename:caption:albumMessageId:isTemporaryAttachment:) - public func add(mediaMessage: TSOutgoingMessage, dataSource: DataSource, contentType: String, sourceFilename: String?, caption: String?, albumMessageId: String?, isTemporaryAttachment: Bool) { - let attachmentInfo = OutgoingAttachmentInfo(dataSource: dataSource, contentType: contentType, sourceFilename: sourceFilename, caption: caption, albumMessageId: albumMessageId) - add(mediaMessage: mediaMessage, attachmentInfos: [attachmentInfo], isTemporaryAttachment: isTemporaryAttachment) - } - - @objc(addMediaMessage:attachmentInfos:isTemporaryAttachment:) - public func add(mediaMessage: TSOutgoingMessage, attachmentInfos: [OutgoingAttachmentInfo], isTemporaryAttachment: Bool) { - OutgoingMessagePreparer.prepareAttachments(attachmentInfos, - inMessage: mediaMessage, - completionHandler: { error in - if let error = error { - Storage.writeSync { transaction in - mediaMessage.update(sendingError: error, transaction: transaction) - } - } else { - Storage.writeSync { transaction in - self.add(message: mediaMessage, removeMessageAfterSending: isTemporaryAttachment, transaction: transaction) - } - } - }) - } - - private func add(message: TSOutgoingMessage, removeMessageAfterSending: Bool, transaction: YapDatabaseReadWriteTransaction) { - assert(AppReadiness.isAppReady() || CurrentAppContext().isRunningTests) - - let jobRecord: SSKMessageSenderJobRecord - do { - jobRecord = try SSKMessageSenderJobRecord(message: message, removeMessageAfterSending: false, label: self.jobRecordLabel) - } catch { - owsFailDebug("Failed to build job due to error: \(error).") - return - } - self.add(jobRecord: jobRecord, transaction: transaction) - } - - // MARK: JobQueue - - public typealias DurableOperationType = MessageSenderOperation - public static let jobRecordLabel: String = "MessageSender" - public static let maxRetries: UInt = 1 // Loki: We have our own retrying - public let requiresInternet: Bool = true - public var runningOperations: [MessageSenderOperation] = [] - - public var jobRecordLabel: String { - return type(of: self).jobRecordLabel - } - - @objc - public func setup() { - defaultSetup() - } - - public var isSetup: Bool = false - - /// Used when the user clears their database to cancel any outstanding jobs. - @objc public func clearAllJobs() { - Storage.writeSync { transaction in - let statuses: [SSKJobRecordStatus] = [ .unknown, .ready, .running, .permanentlyFailed ] - var records: [SSKJobRecord] = [] - statuses.forEach { - records += self.finder.allRecords(label: self.jobRecordLabel, status: $0, transaction: transaction) - } - records.forEach { $0.remove(with: transaction) } - } - } - - public func didMarkAsReady(oldJobRecord: SSKMessageSenderJobRecord, transaction: YapDatabaseReadWriteTransaction) { - if let messageId = oldJobRecord.messageId, let message = TSOutgoingMessage.fetch(uniqueId: messageId, transaction: transaction) { - message.updateWithMarkingAllUnsentRecipientsAsSending(with: transaction) - } - } - - public func buildOperation(jobRecord: SSKMessageSenderJobRecord, transaction: YapDatabaseReadTransaction) throws -> MessageSenderOperation { - let message: TSOutgoingMessage - if let invisibleMessage = jobRecord.invisibleMessage { - message = invisibleMessage - } else if let messageId = jobRecord.messageId, let fetchedMessage = TSOutgoingMessage.fetch(uniqueId: messageId, transaction: transaction) { - message = fetchedMessage - } else { - assert(jobRecord.messageId != nil) - throw JobError.obsolete(description: "Message no longer exists.") - } - - return MessageSenderOperation(message: message, jobRecord: jobRecord) - } - - var senderQueues: [String: OperationQueue] = [:] - let defaultQueue: OperationQueue = { - let operationQueue = OperationQueue() - operationQueue.name = "DefaultSendingQueue" - operationQueue.maxConcurrentOperationCount = 1 - operationQueue.qualityOfService = .userInitiated - - return operationQueue - }() - - // We use a per-thread serial OperationQueue to ensure messages are delivered to the - // service in the order the user sent them. - public func operationQueue(jobRecord: SSKMessageSenderJobRecord) -> OperationQueue { - guard let threadId = jobRecord.threadId else { - return defaultQueue - } - - guard let existingQueue = senderQueues[threadId] else { - let operationQueue = OperationQueue() - operationQueue.name = "SendingQueue:\(threadId)" - operationQueue.maxConcurrentOperationCount = 1 - operationQueue.qualityOfService = .userInitiated - - senderQueues[threadId] = operationQueue - - return operationQueue - } - - return existingQueue - } -} - -public class MessageSenderOperation: OWSOperation, DurableOperation { - - // MARK: DurableOperation - - public let jobRecord: SSKMessageSenderJobRecord - - weak public var durableOperationDelegate: MessageSenderJobQueue? - - public var operation: OWSOperation { - return self - } - - // MARK: Init - - let message: TSOutgoingMessage - - init(message: TSOutgoingMessage, jobRecord: SSKMessageSenderJobRecord) { - self.message = message - self.jobRecord = jobRecord - super.init() - } - - // MARK: Dependencies - - var messageSender: MessageSender { - return SSKEnvironment.shared.messageSender - } - - // MARK: OWSOperation - - override public func run() { - self.messageSender.send(message, success: reportSuccess, failure: reportError) - } - - override public func didSucceed() { - Storage.writeSync { transaction in - self.durableOperationDelegate?.durableOperationDidSucceed(self, transaction: transaction) - - if self.jobRecord.removeMessageAfterSending { - self.message.remove(with: transaction) - } - } - } - - override public func didReportError(_ error: Error) { - let message = self.message - var isFailedSessionRequest = false - if message is SessionRequestMessage, let publicKey = message.thread.contactIdentifier() { - isFailedSessionRequest = (Storage.getSessionRequestSentTimestamp(for: publicKey) == message.timestamp) - } - Storage.writeSync { transaction in - if isFailedSessionRequest, let publicKey = message.thread.contactIdentifier() { - Storage.setSessionRequestSentTimestamp(for: publicKey, to: 0, using: transaction) - } - - self.durableOperationDelegate?.durableOperation(self, didReportError: error, transaction: transaction) - } - } - - override public func retryInterval() -> TimeInterval { - // Arbitrary backoff factor... - // With backOffFactor of 1.9 - // try 1 delay: 0.00s - // try 2 delay: 0.19s - // ... - // try 5 delay: 1.30s - // ... - // try 11 delay: 61.31s - let backoffFactor = 1.9 - let maxBackoff = 15 * kMinuteInterval - - let seconds = 0.1 * min(maxBackoff, pow(backoffFactor, Double(self.jobRecord.failureCount))) - return seconds - } - - override public func didFail(error: Error) { - let message = self.message - var isFailedSessionRequest = false - if message is SessionRequestMessage, let publicKey = message.thread.contactIdentifier() { - isFailedSessionRequest = (Storage.getSessionRequestSentTimestamp(for: publicKey) == message.timestamp) - } - Storage.writeSync { transaction in - if isFailedSessionRequest, let publicKey = message.thread.contactIdentifier() { - Storage.setSessionRequestSentTimestamp(for: publicKey, to: 0, using: transaction) - } - - self.durableOperationDelegate?.durableOperation(self, didFailWithError: error, transaction: transaction) - - self.message.update(sendingError: error, transaction: transaction) - - if self.jobRecord.removeMessageAfterSending { - self.message.remove(with: transaction) - } - } - } -} diff --git a/SignalServiceKit/src/Network/OWSCensorshipConfiguration.h b/SignalServiceKit/src/Network/OWSCensorshipConfiguration.h deleted file mode 100644 index 5b0a16193..000000000 --- a/SignalServiceKit/src/Network/OWSCensorshipConfiguration.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class AFSecurityPolicy; - -extern NSString *const OWSFrontingHost_GoogleEgypt; -extern NSString *const OWSFrontingHost_GoogleUAE; -extern NSString *const OWSFrontingHost_GoogleOman; -extern NSString *const OWSFrontingHost_GoogleQatar; - -@interface OWSCensorshipConfiguration : NSObject - -// returns nil if phone number is not known to be censored -+ (nullable instancetype)censorshipConfigurationWithPhoneNumber:(NSString *)e164PhoneNumber; - -// returns best censorship configuration for country code. Will return a default if one hasn't -// been specifically configured. -+ (instancetype)censorshipConfigurationWithCountryCode:(NSString *)countryCode; -+ (instancetype)defaultConfiguration; - -+ (BOOL)isCensoredPhoneNumber:(NSString *)e164PhoneNumber; - -@property (nonatomic, readonly) NSString *signalServiceReflectorHost; -@property (nonatomic, readonly) NSString *CDNReflectorHost; -@property (nonatomic, readonly) NSURL *domainFrontBaseURL; -@property (nonatomic, readonly) AFSecurityPolicy *domainFrontSecurityPolicy; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/OWSCensorshipConfiguration.m b/SignalServiceKit/src/Network/OWSCensorshipConfiguration.m deleted file mode 100644 index 95bc3d279..000000000 --- a/SignalServiceKit/src/Network/OWSCensorshipConfiguration.m +++ /dev/null @@ -1,246 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSCensorshipConfiguration.h" -#import "OWSCountryMetadata.h" -#import "OWSError.h" -#import "OWSPrimaryStorage.h" -#import "TSConstants.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSFrontingHost_GoogleEgypt = @"www.google.com.eg"; -NSString *const OWSFrontingHost_GoogleUAE = @"www.google.ae"; -NSString *const OWSFrontingHost_GoogleOman = @"www.google.com.om"; -NSString *const OWSFrontingHost_GoogleQatar = @"www.google.com.qa"; -NSString *const OWSFrontingHost_Default = @"www.google.com"; - -@implementation OWSCensorshipConfiguration - -// returns nil if phone number is not known to be censored -+ (nullable instancetype)censorshipConfigurationWithPhoneNumber:(NSString *)e164PhoneNumber -{ - NSString *countryCode = [self censoredCountryCodeWithPhoneNumber:e164PhoneNumber]; - if (countryCode.length == 0) { - return nil; - } - - return [self censorshipConfigurationWithCountryCode:countryCode]; -} - -// returns best censorship configuration for country code. Will return a default if one hasn't -// been specifically configured. -+ (instancetype)censorshipConfigurationWithCountryCode:(NSString *)countryCode -{ - OWSCountryMetadata *countryMetadadata = [OWSCountryMetadata countryMetadataForCountryCode:countryCode]; - OWSAssertDebug(countryMetadadata); - - NSString *_Nullable specifiedDomain = countryMetadadata.frontingDomain; - if (specifiedDomain.length == 0) { - return self.defaultConfiguration; - } - - NSString *frontingURLString = [NSString stringWithFormat:@"https://%@", specifiedDomain]; - NSURL *_Nullable baseURL = [NSURL URLWithString:frontingURLString]; - if (baseURL == nil) { - OWSFailDebug(@"baseURL was unexpectedly nil with specifiedDomain: %@", specifiedDomain); - return self.defaultConfiguration; - } - AFSecurityPolicy *securityPolicy = [self securityPolicyForDomain:specifiedDomain]; - OWSAssertDebug(securityPolicy); - - return [[OWSCensorshipConfiguration alloc] initWithDomainFrontBaseURL:baseURL securityPolicy:securityPolicy]; -} - -+ (instancetype)defaultConfiguration -{ - NSString *frontingURLString = [NSString stringWithFormat:@"https://%@", OWSFrontingHost_Default]; - NSURL *baseURL = [NSURL URLWithString:frontingURLString]; - AFSecurityPolicy *securityPolicy = [self securityPolicyForDomain:OWSFrontingHost_Default]; - - return [[OWSCensorshipConfiguration alloc] initWithDomainFrontBaseURL:baseURL securityPolicy:securityPolicy]; -} - -- (instancetype)initWithDomainFrontBaseURL:(NSURL *)domainFrontBaseURL securityPolicy:(AFSecurityPolicy *)securityPolicy -{ - OWSAssertDebug(domainFrontBaseURL); - OWSAssertDebug(securityPolicy); - - self = [super init]; - if (!self) { - return self; - } - - _domainFrontBaseURL = domainFrontBaseURL; - _domainFrontSecurityPolicy = securityPolicy; - - return self; -} - -// MARK: Public Getters - -- (NSString *)signalServiceReflectorHost -{ - return textSecureServiceReflectorHost; -} - -- (NSString *)CDNReflectorHost -{ - return textSecureCDNReflectorHost; -} - -// MARK: Util - -+ (NSDictionary *)censoredCountryCodes -{ - // The set of countries for which domain fronting should be automatically enabled. - // - // If you want to use a domain front other than the default, specify the domain front - // in OWSCountryMetadata, and ensure we have a Security Policy for that domain in - // `securityPolicyForDomain:` - return @{ - // Egypt - @"+20" : @"EG", - // Oman - @"+968" : @"OM", - // Qatar - @"+974" : @"QA", - // UAE - @"+971" : @"AE", - }; -} - -+ (BOOL)isCensoredPhoneNumber:(NSString *)e164PhoneNumber; -{ - return [self censoredCountryCodeWithPhoneNumber:e164PhoneNumber].length > 0; -} - -// Returns nil if the phone number is not known to be censored -+ (nullable NSString *)censoredCountryCodeWithPhoneNumber:(NSString *)e164PhoneNumber -{ - NSDictionary *censoredCountryCodes = self.censoredCountryCodes; - - for (NSString *callingCode in censoredCountryCodes) { - if ([e164PhoneNumber hasPrefix:callingCode]) { - return censoredCountryCodes[callingCode]; - } - } - - return nil; -} - -#pragma mark - Reflector Pinning Policy - -// When using censorship circumvention, we pin to the fronted domain host. -// Adding a new domain front entails adding a corresponding AFSecurityPolicy -// and pinning to it's CA. -// If the security policy requires new certificates, include them in the SSK bundle -+ (AFSecurityPolicy *)securityPolicyForDomain:(NSString *)domain -{ - if ([domain isEqualToString:OWSFrontingHost_GoogleEgypt]) { - return self.googlePinningPolicy; - } else if ([domain isEqualToString:OWSFrontingHost_GoogleQatar]) { - return self.googlePinningPolicy; - } else if ([domain isEqualToString:OWSFrontingHost_GoogleOman]) { - return self.googlePinningPolicy; - } else if ([domain isEqualToString:OWSFrontingHost_GoogleUAE]) { - return self.googlePinningPolicy; - } else { - OWSFailDebug(@"unknown pinning domain."); - return self.googlePinningPolicy; - } -} - -+ (AFSecurityPolicy *)pinningPolicyWithCertNames:(NSArray *)certNames -{ - NSMutableSet *certificates = [NSMutableSet new]; - for (NSString *certName in certNames) { - NSError *error; - NSData *certData = [self certificateDataWithName:certName error:&error]; - if (error) { - OWSFail(@"reading data for certificate: %@ failed with error: %@", certName, error); - } - - if (!certData) { - OWSFail(@"reading data for certificate: %@ failed with error: %@", certName, error); - } - [certificates addObject:certData]; - } - - return [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:certificates]; -} - -+ (nullable NSData *)certificateDataWithName:(NSString *)name error:(NSError **)error -{ - if (!name.length) { - NSString *failureDescription = [NSString stringWithFormat:@"%@ expected name with length > 0", self.logTag]; - *error = OWSErrorMakeAssertionError(failureDescription); - return nil; - } - - NSBundle *bundle = [NSBundle bundleForClass:self.class]; - NSString *path = [bundle pathForResource:name ofType:@"crt"]; - if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { - NSString *failureDescription = - [NSString stringWithFormat:@"%@ Missing certificate for name: %@", self.logTag, name]; - *error = OWSErrorMakeAssertionError(failureDescription); - return nil; - } - - NSData *_Nullable certData = [NSData dataWithContentsOfFile:path options:0 error:error]; - - if (*error != nil) { - OWSFailDebug(@"Failed to read cert file with path: %@", path); - return nil; - } - - if (certData.length == 0) { - OWSFailDebug(@"empty certData for name: %@", name); - return nil; - } - - OWSLogVerbose(@"read cert data with name: %@ length: %lu", name, (unsigned long)certData.length); - return certData; -} - -+ (AFSecurityPolicy *)yahooViewPinningPolicy_deprecated -{ - static AFSecurityPolicy *securityPolicy = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // DigiCertGlobalRootG2 - view.yahoo.com - NSArray *certNames = @[ @"DigiCertSHA2HighAssuranceServerCA" ]; - securityPolicy = [self pinningPolicyWithCertNames:certNames]; - }); - return securityPolicy; -} - -+ (AFSecurityPolicy *)souqPinningPolicy_deprecated -{ - static AFSecurityPolicy *securityPolicy = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // SFSRootCAG2 - cms.souqcdn.com - NSArray *certNames = @[ @"SFSRootCAG2" ]; - securityPolicy = [self pinningPolicyWithCertNames:certNames]; - }); - return securityPolicy; -} - -+ (AFSecurityPolicy *)googlePinningPolicy -{ - static AFSecurityPolicy *securityPolicy = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // GIAG2 cert plus root certs from pki.goog - NSArray *certNames = @[ @"GIAG2", @"GSR2", @"GSR4", @"GTSR1", @"GTSR2", @"GTSR3", @"GTSR4" ]; - securityPolicy = [self pinningPolicyWithCertNames:certNames]; - }); - return securityPolicy; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/OWSCountryMetadata.h b/SignalServiceKit/src/Network/OWSCountryMetadata.h deleted file mode 100644 index 9e383db2e..000000000 --- a/SignalServiceKit/src/Network/OWSCountryMetadata.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSCountryMetadata : NSObject - -@property (nonatomic) NSString *name; -@property (nonatomic) NSString *tld; -@property (nonatomic, nullable) NSString *frontingDomain; -@property (nonatomic) NSString *countryCode; -@property (nonatomic) NSString *localizedCountryName; - -+ (OWSCountryMetadata *)countryMetadataForCountryCode:(NSString *)countryCode; - -+ (NSArray *)allCountryMetadatas; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/OWSCountryMetadata.m b/SignalServiceKit/src/Network/OWSCountryMetadata.m deleted file mode 100644 index 6d3811353..000000000 --- a/SignalServiceKit/src/Network/OWSCountryMetadata.m +++ /dev/null @@ -1,378 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSCountryMetadata.h" -#import "OWSCensorshipConfiguration.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSCountryMetadata - -+ (OWSCountryMetadata *)countryMetadataWithName:(NSString *)name - tld:(NSString *)tld - frontingDomain:(nullable NSString *)frontingDomain - countryCode:(NSString *)countryCode -{ - OWSAssertDebug(name.length > 0); - OWSAssertDebug(tld.length > 0); - OWSAssertDebug(countryCode.length > 0); - - OWSCountryMetadata *instance = [OWSCountryMetadata new]; - instance.name = name; - instance.tld = tld; - instance.frontingDomain = frontingDomain; - instance.countryCode = countryCode; - - NSString *localizedCountryName = [[NSLocale currentLocale] displayNameForKey:NSLocaleCountryCode value:countryCode]; - if (localizedCountryName.length < 1) { - localizedCountryName = name; - } - instance.localizedCountryName = localizedCountryName; - - return instance; -} - -+ (OWSCountryMetadata *)countryMetadataForCountryCode:(NSString *)countryCode -{ - OWSAssertDebug(countryCode.length > 0); - - return [self countryCodeToCountryMetadataMap][countryCode]; -} - -+ (NSDictionary *)countryCodeToCountryMetadataMap -{ - static NSDictionary *cachedValue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSMutableDictionary *map = [NSMutableDictionary new]; - for (OWSCountryMetadata *metadata in [self allCountryMetadatas]) { - map[metadata.countryCode] = metadata; - } - cachedValue = map; - }); - return cachedValue; -} - -+ (NSArray *)allCountryMetadatas -{ - static NSArray *cachedValue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - cachedValue = @[ - [OWSCountryMetadata countryMetadataWithName:@"Andorra" tld:@".ad" frontingDomain:nil countryCode:@"AD"], - [OWSCountryMetadata countryMetadataWithName:@"United Arab Emirates" - tld:@".ae" - frontingDomain:OWSFrontingHost_GoogleUAE - countryCode:@"AE"], - [OWSCountryMetadata countryMetadataWithName:@"Afghanistan" tld:@".af" frontingDomain:nil countryCode:@"AF"], - [OWSCountryMetadata countryMetadataWithName:@"Antigua and Barbuda" - tld:@".ag" - frontingDomain:nil - countryCode:@"AG"], - [OWSCountryMetadata countryMetadataWithName:@"Anguilla" tld:@".ai" frontingDomain:nil countryCode:@"AI"], - [OWSCountryMetadata countryMetadataWithName:@"Albania" tld:@".al" frontingDomain:nil countryCode:@"AL"], - [OWSCountryMetadata countryMetadataWithName:@"Armenia" tld:@".am" frontingDomain:nil countryCode:@"AM"], - [OWSCountryMetadata countryMetadataWithName:@"Angola" tld:@".ao" frontingDomain:nil countryCode:@"AO"], - [OWSCountryMetadata countryMetadataWithName:@"Argentina" tld:@".ar" frontingDomain:nil countryCode:@"AR"], - [OWSCountryMetadata countryMetadataWithName:@"American Samoa" - tld:@".as" - frontingDomain:nil - countryCode:@"AS"], - [OWSCountryMetadata countryMetadataWithName:@"Austria" tld:@".at" frontingDomain:nil countryCode:@"AT"], - [OWSCountryMetadata countryMetadataWithName:@"Australia" tld:@".au" frontingDomain:nil countryCode:@"AU"], - [OWSCountryMetadata countryMetadataWithName:@"Azerbaijan" tld:@".az" frontingDomain:nil countryCode:@"AZ"], - [OWSCountryMetadata countryMetadataWithName:@"Bosnia and Herzegovina" - tld:@".ba" - frontingDomain:nil - countryCode:@"BA"], - [OWSCountryMetadata countryMetadataWithName:@"Bangladesh" tld:@".bd" frontingDomain:nil countryCode:@"BD"], - [OWSCountryMetadata countryMetadataWithName:@"Belgium" tld:@".be" frontingDomain:nil countryCode:@"BE"], - [OWSCountryMetadata countryMetadataWithName:@"Burkina Faso" - tld:@".bf" - frontingDomain:nil - countryCode:@"BF"], - [OWSCountryMetadata countryMetadataWithName:@"Bulgaria" tld:@".bg" frontingDomain:nil countryCode:@"BG"], - [OWSCountryMetadata countryMetadataWithName:@"Bahrain" tld:@".bh" frontingDomain:nil countryCode:@"BH"], - [OWSCountryMetadata countryMetadataWithName:@"Burundi" tld:@".bi" frontingDomain:nil countryCode:@"BI"], - [OWSCountryMetadata countryMetadataWithName:@"Benin" tld:@".bj" frontingDomain:nil countryCode:@"BJ"], - [OWSCountryMetadata countryMetadataWithName:@"Brunei" tld:@".bn" frontingDomain:nil countryCode:@"BN"], - [OWSCountryMetadata countryMetadataWithName:@"Bolivia" tld:@".bo" frontingDomain:nil countryCode:@"BO"], - [OWSCountryMetadata countryMetadataWithName:@"Brazil" tld:@".br" frontingDomain:nil countryCode:@"BR"], - [OWSCountryMetadata countryMetadataWithName:@"Bahamas" tld:@".bs" frontingDomain:nil countryCode:@"BS"], - [OWSCountryMetadata countryMetadataWithName:@"Bhutan" tld:@".bt" frontingDomain:nil countryCode:@"BT"], - [OWSCountryMetadata countryMetadataWithName:@"Botswana" tld:@".bw" frontingDomain:nil countryCode:@"BW"], - [OWSCountryMetadata countryMetadataWithName:@"Belarus" tld:@".by" frontingDomain:nil countryCode:@"BY"], - [OWSCountryMetadata countryMetadataWithName:@"Belize" tld:@".bz" frontingDomain:nil countryCode:@"BZ"], - [OWSCountryMetadata countryMetadataWithName:@"Canada" tld:@".ca" frontingDomain:nil countryCode:@"CA"], - [OWSCountryMetadata countryMetadataWithName:@"Cambodia" tld:@".kh" frontingDomain:nil countryCode:@"KH"], - [OWSCountryMetadata countryMetadataWithName:@"Cocos (Keeling) Islands" - tld:@".cc" - frontingDomain:nil - countryCode:@"CC"], - [OWSCountryMetadata countryMetadataWithName:@"Democratic Republic of the Congo" - tld:@".cd" - frontingDomain:nil - countryCode:@"CD"], - [OWSCountryMetadata countryMetadataWithName:@"Central African Republic" - tld:@".cf" - frontingDomain:nil - countryCode:@"CF"], - [OWSCountryMetadata countryMetadataWithName:@"Republic of the Congo" - tld:@".cg" - frontingDomain:nil - countryCode:@"CG"], - [OWSCountryMetadata countryMetadataWithName:@"Switzerland" tld:@".ch" frontingDomain:nil countryCode:@"CH"], - [OWSCountryMetadata countryMetadataWithName:@"Ivory Coast" tld:@".ci" frontingDomain:nil countryCode:@"CI"], - [OWSCountryMetadata countryMetadataWithName:@"Cook Islands" - tld:@".ck" - frontingDomain:nil - countryCode:@"CK"], - [OWSCountryMetadata countryMetadataWithName:@"Chile" tld:@".cl" frontingDomain:nil countryCode:@"CL"], - [OWSCountryMetadata countryMetadataWithName:@"Cameroon" tld:@".cm" frontingDomain:nil countryCode:@"CM"], - [OWSCountryMetadata countryMetadataWithName:@"China" tld:@".cn" frontingDomain:nil countryCode:@"CN"], - [OWSCountryMetadata countryMetadataWithName:@"Colombia" tld:@".co" frontingDomain:nil countryCode:@"CO"], - [OWSCountryMetadata countryMetadataWithName:@"Costa Rica" tld:@".cr" frontingDomain:nil countryCode:@"CR"], - [OWSCountryMetadata countryMetadataWithName:@"Cuba" tld:@".cu" frontingDomain:nil countryCode:@"CU"], - [OWSCountryMetadata countryMetadataWithName:@"Cape Verde" tld:@".cv" frontingDomain:nil countryCode:@"CV"], - [OWSCountryMetadata countryMetadataWithName:@"Christmas Island" - tld:@".cx" - frontingDomain:nil - countryCode:@"CX"], - [OWSCountryMetadata countryMetadataWithName:@"Cyprus" tld:@".cy" frontingDomain:nil countryCode:@"CY"], - [OWSCountryMetadata countryMetadataWithName:@"Czech Republic" - tld:@".cz" - frontingDomain:nil - countryCode:@"CZ"], - [OWSCountryMetadata countryMetadataWithName:@"Germany" tld:@".de" frontingDomain:nil countryCode:@"DE"], - [OWSCountryMetadata countryMetadataWithName:@"Djibouti" tld:@".dj" frontingDomain:nil countryCode:@"DJ"], - [OWSCountryMetadata countryMetadataWithName:@"Denmark" tld:@".dk" frontingDomain:nil countryCode:@"DK"], - [OWSCountryMetadata countryMetadataWithName:@"Dominica" tld:@".dm" frontingDomain:nil countryCode:@"DM"], - [OWSCountryMetadata countryMetadataWithName:@"Dominican Republic" - tld:@".do" - frontingDomain:nil - countryCode:@"DO"], - [OWSCountryMetadata countryMetadataWithName:@"Algeria" tld:@".dz" frontingDomain:nil countryCode:@"DZ"], - [OWSCountryMetadata countryMetadataWithName:@"Ecuador" tld:@".ec" frontingDomain:nil countryCode:@"EC"], - [OWSCountryMetadata countryMetadataWithName:@"Estonia" tld:@".ee" frontingDomain:nil countryCode:@"EE"], - [OWSCountryMetadata countryMetadataWithName:@"Egypt" - tld:@".eg" - frontingDomain:OWSFrontingHost_GoogleEgypt - countryCode:@"EG"], - [OWSCountryMetadata countryMetadataWithName:@"Spain" tld:@".es" frontingDomain:nil countryCode:@"ES"], - [OWSCountryMetadata countryMetadataWithName:@"Ethiopia" tld:@".et" frontingDomain:nil countryCode:@"ET"], - [OWSCountryMetadata countryMetadataWithName:@"Finland" tld:@".fi" frontingDomain:nil countryCode:@"FI"], - [OWSCountryMetadata countryMetadataWithName:@"Fiji" tld:@".fj" frontingDomain:nil countryCode:@"FJ"], - [OWSCountryMetadata countryMetadataWithName:@"Federated States of Micronesia" - tld:@".fm" - frontingDomain:nil - countryCode:@"FM"], - [OWSCountryMetadata countryMetadataWithName:@"France" tld:@".fr" frontingDomain:nil countryCode:@"FR"], - [OWSCountryMetadata countryMetadataWithName:@"Gabon" tld:@".ga" frontingDomain:nil countryCode:@"GA"], - [OWSCountryMetadata countryMetadataWithName:@"Georgia" tld:@".ge" frontingDomain:nil countryCode:@"GE"], - [OWSCountryMetadata countryMetadataWithName:@"French Guiana" - tld:@".gf" - frontingDomain:nil - countryCode:@"GF"], - [OWSCountryMetadata countryMetadataWithName:@"Guernsey" tld:@".gg" frontingDomain:nil countryCode:@"GG"], - [OWSCountryMetadata countryMetadataWithName:@"Ghana" tld:@".gh" frontingDomain:nil countryCode:@"GH"], - [OWSCountryMetadata countryMetadataWithName:@"Gibraltar" tld:@".gi" frontingDomain:nil countryCode:@"GI"], - [OWSCountryMetadata countryMetadataWithName:@"Greenland" tld:@".gl" frontingDomain:nil countryCode:@"GL"], - [OWSCountryMetadata countryMetadataWithName:@"Gambia" tld:@".gm" frontingDomain:nil countryCode:@"GM"], - [OWSCountryMetadata countryMetadataWithName:@"Guadeloupe" tld:@".gp" frontingDomain:nil countryCode:@"GP"], - [OWSCountryMetadata countryMetadataWithName:@"Greece" tld:@".gr" frontingDomain:nil countryCode:@"GR"], - [OWSCountryMetadata countryMetadataWithName:@"Guatemala" tld:@".gt" frontingDomain:nil countryCode:@"GT"], - [OWSCountryMetadata countryMetadataWithName:@"Guyana" tld:@".gy" frontingDomain:nil countryCode:@"GY"], - [OWSCountryMetadata countryMetadataWithName:@"Hong Kong" tld:@".hk" frontingDomain:nil countryCode:@"HK"], - [OWSCountryMetadata countryMetadataWithName:@"Honduras" tld:@".hn" frontingDomain:nil countryCode:@"HN"], - [OWSCountryMetadata countryMetadataWithName:@"Croatia" tld:@".hr" frontingDomain:nil countryCode:@"HR"], - [OWSCountryMetadata countryMetadataWithName:@"Haiti" tld:@".ht" frontingDomain:nil countryCode:@"HT"], - [OWSCountryMetadata countryMetadataWithName:@"Hungary" tld:@".hu" frontingDomain:nil countryCode:@"HU"], - [OWSCountryMetadata countryMetadataWithName:@"Indonesia" tld:@".id" frontingDomain:nil countryCode:@"ID"], - [OWSCountryMetadata countryMetadataWithName:@"Iraq" tld:@".iq" frontingDomain:nil countryCode:@"IQ"], - [OWSCountryMetadata countryMetadataWithName:@"Ireland" tld:@".ie" frontingDomain:nil countryCode:@"IE"], - [OWSCountryMetadata countryMetadataWithName:@"Israel" tld:@".il" frontingDomain:nil countryCode:@"IL"], - [OWSCountryMetadata countryMetadataWithName:@"Isle of Man" tld:@".im" frontingDomain:nil countryCode:@"IM"], - [OWSCountryMetadata countryMetadataWithName:@"India" tld:@".in" frontingDomain:nil countryCode:@"IN"], - [OWSCountryMetadata countryMetadataWithName:@"British Indian Ocean Territory" - tld:@".io" - frontingDomain:nil - countryCode:@"IO"], - [OWSCountryMetadata countryMetadataWithName:@"Iceland" tld:@".is" frontingDomain:nil countryCode:@"IS"], - [OWSCountryMetadata countryMetadataWithName:@"Italy" tld:@".it" frontingDomain:nil countryCode:@"IT"], - [OWSCountryMetadata countryMetadataWithName:@"Jersey" tld:@".je" frontingDomain:nil countryCode:@"JE"], - [OWSCountryMetadata countryMetadataWithName:@"Jamaica" tld:@".jm" frontingDomain:nil countryCode:@"JM"], - [OWSCountryMetadata countryMetadataWithName:@"Jordan" tld:@".jo" frontingDomain:nil countryCode:@"JO"], - [OWSCountryMetadata countryMetadataWithName:@"Japan" tld:@".jp" frontingDomain:nil countryCode:@"JP"], - [OWSCountryMetadata countryMetadataWithName:@"Kenya" tld:@".ke" frontingDomain:nil countryCode:@"KE"], - [OWSCountryMetadata countryMetadataWithName:@"Kiribati" tld:@".ki" frontingDomain:nil countryCode:@"KI"], - [OWSCountryMetadata countryMetadataWithName:@"Kyrgyzstan" tld:@".kg" frontingDomain:nil countryCode:@"KG"], - [OWSCountryMetadata countryMetadataWithName:@"South Korea" tld:@".kr" frontingDomain:nil countryCode:@"KR"], - [OWSCountryMetadata countryMetadataWithName:@"Kuwait" tld:@".kw" frontingDomain:nil countryCode:@"KW"], - [OWSCountryMetadata countryMetadataWithName:@"Kazakhstan" tld:@".kz" frontingDomain:nil countryCode:@"KZ"], - [OWSCountryMetadata countryMetadataWithName:@"Laos" tld:@".la" frontingDomain:nil countryCode:@"LA"], - [OWSCountryMetadata countryMetadataWithName:@"Lebanon" tld:@".lb" frontingDomain:nil countryCode:@"LB"], - [OWSCountryMetadata countryMetadataWithName:@"Saint Lucia" tld:@".lc" frontingDomain:nil countryCode:@"LC"], - [OWSCountryMetadata countryMetadataWithName:@"Liechtenstein" - tld:@".li" - frontingDomain:nil - countryCode:@"LI"], - [OWSCountryMetadata countryMetadataWithName:@"Sri Lanka" tld:@".lk" frontingDomain:nil countryCode:@"LK"], - [OWSCountryMetadata countryMetadataWithName:@"Lesotho" tld:@".ls" frontingDomain:nil countryCode:@"LS"], - [OWSCountryMetadata countryMetadataWithName:@"Lithuania" tld:@".lt" frontingDomain:nil countryCode:@"LT"], - [OWSCountryMetadata countryMetadataWithName:@"Luxembourg" tld:@".lu" frontingDomain:nil countryCode:@"LU"], - [OWSCountryMetadata countryMetadataWithName:@"Latvia" tld:@".lv" frontingDomain:nil countryCode:@"LV"], - [OWSCountryMetadata countryMetadataWithName:@"Libya" tld:@".ly" frontingDomain:nil countryCode:@"LY"], - [OWSCountryMetadata countryMetadataWithName:@"Morocco" tld:@".ma" frontingDomain:nil countryCode:@"MA"], - [OWSCountryMetadata countryMetadataWithName:@"Moldova" tld:@".md" frontingDomain:nil countryCode:@"MD"], - [OWSCountryMetadata countryMetadataWithName:@"Montenegro" tld:@".me" frontingDomain:nil countryCode:@"ME"], - [OWSCountryMetadata countryMetadataWithName:@"Madagascar" tld:@".mg" frontingDomain:nil countryCode:@"MG"], - [OWSCountryMetadata countryMetadataWithName:@"Macedonia" tld:@".mk" frontingDomain:nil countryCode:@"MK"], - [OWSCountryMetadata countryMetadataWithName:@"Mali" tld:@".ml" frontingDomain:nil countryCode:@"ML"], - [OWSCountryMetadata countryMetadataWithName:@"Myanmar" tld:@".mm" frontingDomain:nil countryCode:@"MM"], - [OWSCountryMetadata countryMetadataWithName:@"Mongolia" tld:@".mn" frontingDomain:nil countryCode:@"MN"], - [OWSCountryMetadata countryMetadataWithName:@"Montserrat" tld:@".ms" frontingDomain:nil countryCode:@"MS"], - [OWSCountryMetadata countryMetadataWithName:@"Malta" tld:@".mt" frontingDomain:nil countryCode:@"MT"], - [OWSCountryMetadata countryMetadataWithName:@"Mauritius" tld:@".mu" frontingDomain:nil countryCode:@"MU"], - [OWSCountryMetadata countryMetadataWithName:@"Maldives" tld:@".mv" frontingDomain:nil countryCode:@"MV"], - [OWSCountryMetadata countryMetadataWithName:@"Malawi" tld:@".mw" frontingDomain:nil countryCode:@"MW"], - [OWSCountryMetadata countryMetadataWithName:@"Mexico" tld:@".mx" frontingDomain:nil countryCode:@"MX"], - [OWSCountryMetadata countryMetadataWithName:@"Malaysia" tld:@".my" frontingDomain:nil countryCode:@"MY"], - [OWSCountryMetadata countryMetadataWithName:@"Mozambique" tld:@".mz" frontingDomain:nil countryCode:@"MZ"], - [OWSCountryMetadata countryMetadataWithName:@"Namibia" tld:@".na" frontingDomain:nil countryCode:@"NA"], - [OWSCountryMetadata countryMetadataWithName:@"Niger" tld:@".ne" frontingDomain:nil countryCode:@"NE"], - [OWSCountryMetadata countryMetadataWithName:@"Norfolk Island" - tld:@".nf" - frontingDomain:nil - countryCode:@"NF"], - [OWSCountryMetadata countryMetadataWithName:@"Nigeria" tld:@".ng" frontingDomain:nil countryCode:@"NG"], - [OWSCountryMetadata countryMetadataWithName:@"Nicaragua" tld:@".ni" frontingDomain:nil countryCode:@"NI"], - [OWSCountryMetadata countryMetadataWithName:@"Netherlands" tld:@".nl" frontingDomain:nil countryCode:@"NL"], - [OWSCountryMetadata countryMetadataWithName:@"Norway" tld:@".no" frontingDomain:nil countryCode:@"NO"], - [OWSCountryMetadata countryMetadataWithName:@"Nepal" tld:@".np" frontingDomain:nil countryCode:@"NP"], - [OWSCountryMetadata countryMetadataWithName:@"Nauru" tld:@".nr" frontingDomain:nil countryCode:@"NR"], - [OWSCountryMetadata countryMetadataWithName:@"Niue" tld:@".nu" frontingDomain:nil countryCode:@"NU"], - [OWSCountryMetadata countryMetadataWithName:@"New Zealand" tld:@".nz" frontingDomain:nil countryCode:@"NZ"], - [OWSCountryMetadata countryMetadataWithName:@"Oman" - tld:@".om" - frontingDomain:OWSFrontingHost_GoogleOman - countryCode:@"OM"], - [OWSCountryMetadata countryMetadataWithName:@"Pakistan" tld:@".pk" frontingDomain:nil countryCode:@"PK"], - [OWSCountryMetadata countryMetadataWithName:@"Panama" tld:@".pa" frontingDomain:nil countryCode:@"PA"], - [OWSCountryMetadata countryMetadataWithName:@"Peru" tld:@".pe" frontingDomain:nil countryCode:@"PE"], - [OWSCountryMetadata countryMetadataWithName:@"Philippines" tld:@".ph" frontingDomain:nil countryCode:@"PH"], - [OWSCountryMetadata countryMetadataWithName:@"Poland" tld:@".pl" frontingDomain:nil countryCode:@"PL"], - [OWSCountryMetadata countryMetadataWithName:@"Papua New Guinea" - tld:@".pg" - frontingDomain:nil - countryCode:@"PG"], - [OWSCountryMetadata countryMetadataWithName:@"Pitcairn Islands" - tld:@".pn" - frontingDomain:nil - countryCode:@"PN"], - [OWSCountryMetadata countryMetadataWithName:@"Puerto Rico" tld:@".pr" frontingDomain:nil countryCode:@"PR"], - [OWSCountryMetadata countryMetadataWithName:@"Palestine[4]" - tld:@".ps" - frontingDomain:nil - countryCode:@"PS"], - [OWSCountryMetadata countryMetadataWithName:@"Portugal" tld:@".pt" frontingDomain:nil countryCode:@"PT"], - [OWSCountryMetadata countryMetadataWithName:@"Paraguay" tld:@".py" frontingDomain:nil countryCode:@"PY"], - [OWSCountryMetadata countryMetadataWithName:@"Qatar" - tld:@".qa" - frontingDomain:OWSFrontingHost_GoogleQatar - countryCode:@"QA"], - [OWSCountryMetadata countryMetadataWithName:@"Romania" tld:@".ro" frontingDomain:nil countryCode:@"RO"], - [OWSCountryMetadata countryMetadataWithName:@"Serbia" tld:@".rs" frontingDomain:nil countryCode:@"RS"], - [OWSCountryMetadata countryMetadataWithName:@"Russia" tld:@".ru" frontingDomain:nil countryCode:@"RU"], - [OWSCountryMetadata countryMetadataWithName:@"Rwanda" tld:@".rw" frontingDomain:nil countryCode:@"RW"], - [OWSCountryMetadata countryMetadataWithName:@"Saudi Arabia" - tld:@".sa" - frontingDomain:nil - countryCode:@"SA"], - [OWSCountryMetadata countryMetadataWithName:@"Solomon Islands" - tld:@".sb" - frontingDomain:nil - countryCode:@"SB"], - [OWSCountryMetadata countryMetadataWithName:@"Seychelles" tld:@".sc" frontingDomain:nil countryCode:@"SC"], - [OWSCountryMetadata countryMetadataWithName:@"Sweden" tld:@".se" frontingDomain:nil countryCode:@"SE"], - [OWSCountryMetadata countryMetadataWithName:@"Singapore" tld:@".sg" frontingDomain:nil countryCode:@"SG"], - [OWSCountryMetadata countryMetadataWithName:@"Saint Helena, Ascension and Tristan da Cunha" - tld:@".sh" - frontingDomain:nil - countryCode:@"SH"], - [OWSCountryMetadata countryMetadataWithName:@"Slovenia" tld:@".si" frontingDomain:nil countryCode:@"SI"], - [OWSCountryMetadata countryMetadataWithName:@"Slovakia" tld:@".sk" frontingDomain:nil countryCode:@"SK"], - [OWSCountryMetadata countryMetadataWithName:@"Sierra Leone" - tld:@".sl" - frontingDomain:nil - countryCode:@"SL"], - [OWSCountryMetadata countryMetadataWithName:@"Senegal" tld:@".sn" frontingDomain:nil countryCode:@"SN"], - [OWSCountryMetadata countryMetadataWithName:@"San Marino" tld:@".sm" frontingDomain:nil countryCode:@"SM"], - [OWSCountryMetadata countryMetadataWithName:@"Somalia" tld:@".so" frontingDomain:nil countryCode:@"SO"], - [OWSCountryMetadata countryMetadataWithName:@"São Tomé and Príncipe" - tld:@".st" - frontingDomain:nil - countryCode:@"ST"], - [OWSCountryMetadata countryMetadataWithName:@"Suriname" tld:@".sr" frontingDomain:nil countryCode:@"SR"], - [OWSCountryMetadata countryMetadataWithName:@"El Salvador" tld:@".sv" frontingDomain:nil countryCode:@"SV"], - [OWSCountryMetadata countryMetadataWithName:@"Chad" tld:@".td" frontingDomain:nil countryCode:@"TD"], - [OWSCountryMetadata countryMetadataWithName:@"Togo" tld:@".tg" frontingDomain:nil countryCode:@"TG"], - [OWSCountryMetadata countryMetadataWithName:@"Thailand" tld:@".th" frontingDomain:nil countryCode:@"TH"], - [OWSCountryMetadata countryMetadataWithName:@"Tajikistan" tld:@".tj" frontingDomain:nil countryCode:@"TJ"], - [OWSCountryMetadata countryMetadataWithName:@"Tokelau" tld:@".tk" frontingDomain:nil countryCode:@"TK"], - [OWSCountryMetadata countryMetadataWithName:@"Timor-Leste" tld:@".tl" frontingDomain:nil countryCode:@"TL"], - [OWSCountryMetadata countryMetadataWithName:@"Turkmenistan" - tld:@".tm" - frontingDomain:nil - countryCode:@"TM"], - [OWSCountryMetadata countryMetadataWithName:@"Tonga" tld:@".to" frontingDomain:nil countryCode:@"TO"], - [OWSCountryMetadata countryMetadataWithName:@"Tunisia" tld:@".tn" frontingDomain:nil countryCode:@"TN"], - [OWSCountryMetadata countryMetadataWithName:@"Turkey" tld:@".tr" frontingDomain:nil countryCode:@"TR"], - [OWSCountryMetadata countryMetadataWithName:@"Trinidad and Tobago" - tld:@".tt" - frontingDomain:nil - countryCode:@"TT"], - [OWSCountryMetadata countryMetadataWithName:@"Taiwan" tld:@".tw" frontingDomain:nil countryCode:@"TW"], - [OWSCountryMetadata countryMetadataWithName:@"Tanzania" tld:@".tz" frontingDomain:nil countryCode:@"TZ"], - [OWSCountryMetadata countryMetadataWithName:@"Ukraine" tld:@".ua" frontingDomain:nil countryCode:@"UA"], - [OWSCountryMetadata countryMetadataWithName:@"Uganda" tld:@".ug" frontingDomain:nil countryCode:@"UG"], - [OWSCountryMetadata countryMetadataWithName:@"United States" - tld:@".com" - frontingDomain:nil - countryCode:@"US"], - [OWSCountryMetadata countryMetadataWithName:@"Uruguay" tld:@".uy" frontingDomain:nil countryCode:@"UY"], - [OWSCountryMetadata countryMetadataWithName:@"Uzbekistan" tld:@".uz" frontingDomain:nil countryCode:@"UZ"], - [OWSCountryMetadata countryMetadataWithName:@"Saint Vincent and the Grenadines" - tld:@".vc" - frontingDomain:nil - countryCode:@"VC"], - [OWSCountryMetadata countryMetadataWithName:@"Venezuela" tld:@".ve" frontingDomain:nil countryCode:@"VE"], - [OWSCountryMetadata countryMetadataWithName:@"British Virgin Islands" - tld:@".vg" - frontingDomain:nil - countryCode:@"VG"], - [OWSCountryMetadata countryMetadataWithName:@"United States Virgin Islands" - tld:@".vi" - frontingDomain:nil - countryCode:@"VI"], - [OWSCountryMetadata countryMetadataWithName:@"Vietnam" tld:@".vn" frontingDomain:nil countryCode:@"VN"], - [OWSCountryMetadata countryMetadataWithName:@"Vanuatu" tld:@".vu" frontingDomain:nil countryCode:@"VU"], - [OWSCountryMetadata countryMetadataWithName:@"Samoa" tld:@".ws" frontingDomain:nil countryCode:@"WS"], - [OWSCountryMetadata countryMetadataWithName:@"South Africa" - tld:@".za" - frontingDomain:nil - countryCode:@"ZA"], - [OWSCountryMetadata countryMetadataWithName:@"Zambia" tld:@".zm" frontingDomain:nil countryCode:@"ZM"], - [OWSCountryMetadata countryMetadataWithName:@"Zimbabwe" tld:@".zw" frontingDomain:nil countryCode:@"ZW"], - ]; - cachedValue = [cachedValue sortedArrayUsingComparator:^NSComparisonResult( - OWSCountryMetadata *_Nonnull left, OWSCountryMetadata *_Nonnull right) { - return [left.localizedCountryName compare:right.localizedCountryName]; - }]; - }); - return cachedValue; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/OWSSignalService.h b/SignalServiceKit/src/Network/OWSSignalService.h deleted file mode 100644 index 0c1621b25..000000000 --- a/SignalServiceKit/src/Network/OWSSignalService.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const kNSNotificationName_IsCensorshipCircumventionActiveDidChange; - -@class AFHTTPSessionManager; -@class OWSPrimaryStorage; -@class TSAccountManager; - -@interface OWSSignalService : NSObject - -/// For uploading avatar assets. -@property (nonatomic, readonly) AFHTTPSessionManager *CDNSessionManager; - -+ (instancetype)sharedInstance; - -- (instancetype)init NS_UNAVAILABLE; - -#pragma mark - Censorship Circumvention - -@property (atomic, readonly) BOOL isCensorshipCircumventionActive; -@property (atomic, readonly) BOOL hasCensoredPhoneNumber; -@property (atomic) BOOL isCensorshipCircumventionManuallyActivated; -@property (atomic) BOOL isCensorshipCircumventionManuallyDisabled; -@property (atomic, nullable) NSString *manualCensorshipCircumventionCountryCode; - -/// For interacting with the Signal Service -- (AFHTTPSessionManager *)buildSignalServiceSessionManager; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/OWSSignalService.m b/SignalServiceKit/src/Network/OWSSignalService.m deleted file mode 100644 index a3b5ae31b..000000000 --- a/SignalServiceKit/src/Network/OWSSignalService.m +++ /dev/null @@ -1,326 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSSignalService.h" -#import "NSNotificationCenter+OWS.h" -#import "OWSCensorshipConfiguration.h" -#import "OWSError.h" -#import "OWSHTTPSecurityPolicy.h" -#import "OWSPrimaryStorage.h" -#import "TSAccountManager.h" -#import "TSConstants.h" -#import "YapDatabaseConnection+OWS.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const kOWSPrimaryStorage_OWSSignalService = @"kTSStorageManager_OWSSignalService"; -NSString *const kOWSPrimaryStorage_isCensorshipCircumventionManuallyActivated - = @"kTSStorageManager_isCensorshipCircumventionManuallyActivated"; -NSString *const kOWSPrimaryStorage_isCensorshipCircumventionManuallyDisabled - = @"kTSStorageManager_isCensorshipCircumventionManuallyDisabled"; -NSString *const kOWSPrimaryStorage_ManualCensorshipCircumventionDomain - = @"kTSStorageManager_ManualCensorshipCircumventionDomain"; -NSString *const kOWSPrimaryStorage_ManualCensorshipCircumventionCountryCode - = @"kTSStorageManager_ManualCensorshipCircumventionCountryCode"; - -NSString *const kNSNotificationName_IsCensorshipCircumventionActiveDidChange = - @"kNSNotificationName_IsCensorshipCircumventionActiveDidChange"; - -@interface OWSSignalService () - -@property (atomic) BOOL hasCensoredPhoneNumber; - -@property (atomic) BOOL isCensorshipCircumventionActive; - -@end - -#pragma mark - - -@implementation OWSSignalService - -@synthesize isCensorshipCircumventionActive = _isCensorshipCircumventionActive; - -+ (instancetype)sharedInstance -{ - static OWSSignalService *sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [[self alloc] initDefault]; - }); - return sharedInstance; -} - -- (instancetype)initDefault -{ - self = [super init]; - if (!self) { - return self; - } - - [self observeNotifications]; - - [self updateHasCensoredPhoneNumber]; - [self updateIsCensorshipCircumventionActive]; - - OWSSingletonAssert(); - - return self; -} - -- (void)observeNotifications -{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(registrationStateDidChange:) - name:RegistrationStateDidChangeNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(localNumberDidChange:) - name:kNSNotificationName_LocalNumberDidChange - object:nil]; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)updateHasCensoredPhoneNumber -{ - NSString *localNumber = [TSAccountManager localNumber]; - - if (localNumber) { - self.hasCensoredPhoneNumber = [OWSCensorshipConfiguration isCensoredPhoneNumber:localNumber]; - } else { - OWSLogError(@"no known phone number to check for censorship."); - self.hasCensoredPhoneNumber = NO; - } - - [self updateIsCensorshipCircumventionActive]; -} - -- (BOOL)isCensorshipCircumventionManuallyActivated -{ - return - [[OWSPrimaryStorage dbReadConnection] boolForKey:kOWSPrimaryStorage_isCensorshipCircumventionManuallyActivated - inCollection:kOWSPrimaryStorage_OWSSignalService - defaultValue:NO]; -} - -- (void)setIsCensorshipCircumventionManuallyActivated:(BOOL)value -{ - [[OWSPrimaryStorage dbReadWriteConnection] setObject:@(value) - forKey:kOWSPrimaryStorage_isCensorshipCircumventionManuallyActivated - inCollection:kOWSPrimaryStorage_OWSSignalService]; - - [self updateIsCensorshipCircumventionActive]; -} - -- (BOOL)isCensorshipCircumventionManuallyDisabled -{ - return [[OWSPrimaryStorage dbReadConnection] boolForKey:kOWSPrimaryStorage_isCensorshipCircumventionManuallyDisabled - inCollection:kOWSPrimaryStorage_OWSSignalService - defaultValue:NO]; -} - -- (void)setIsCensorshipCircumventionManuallyDisabled:(BOOL)value -{ - [[OWSPrimaryStorage dbReadWriteConnection] setObject:@(value) - forKey:kOWSPrimaryStorage_isCensorshipCircumventionManuallyDisabled - inCollection:kOWSPrimaryStorage_OWSSignalService]; - - [self updateIsCensorshipCircumventionActive]; -} - - -- (void)updateIsCensorshipCircumventionActive -{ - if (self.isCensorshipCircumventionManuallyDisabled) { - self.isCensorshipCircumventionActive = NO; - } else if (self.isCensorshipCircumventionManuallyActivated) { - self.isCensorshipCircumventionActive = YES; - } else if (self.hasCensoredPhoneNumber) { - self.isCensorshipCircumventionActive = YES; - } else { - self.isCensorshipCircumventionActive = NO; - } -} - -- (void)setIsCensorshipCircumventionActive:(BOOL)isCensorshipCircumventionActive -{ - @synchronized(self) - { - if (_isCensorshipCircumventionActive == isCensorshipCircumventionActive) { - return; - } - - _isCensorshipCircumventionActive = isCensorshipCircumventionActive; - } - - [[NSNotificationCenter defaultCenter] - postNotificationNameAsync:kNSNotificationName_IsCensorshipCircumventionActiveDidChange - object:nil - userInfo:nil]; -} - -- (BOOL)isCensorshipCircumventionActive -{ - @synchronized(self) - { - return _isCensorshipCircumventionActive; - } -} - -- (AFHTTPSessionManager *)buildSignalServiceSessionManager -{ - if (self.isCensorshipCircumventionActive) { - OWSCensorshipConfiguration *censorshipConfiguration = [self buildCensorshipConfiguration]; - OWSLogInfo(@"using reflector HTTPSessionManager via: %@", censorshipConfiguration.domainFrontBaseURL); - return [self reflectorSignalServiceSessionManagerWithCensorshipConfiguration:censorshipConfiguration]; - } else { - return self.defaultSignalServiceSessionManager; - } -} - -- (AFHTTPSessionManager *)defaultSignalServiceSessionManager -{ - NSURLSessionConfiguration *configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration; - AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration]; - AFSecurityPolicy *securityPolicy = AFSecurityPolicy.defaultPolicy; - // Snode to snode communication uses self-signed certificates but clients can safely ignore this - securityPolicy.allowInvalidCertificates = YES; - securityPolicy.validatesDomainName = NO; - sessionManager.securityPolicy = securityPolicy; - sessionManager.requestSerializer = [AFJSONRequestSerializer serializer]; - sessionManager.requestSerializer.HTTPShouldHandleCookies = NO; - sessionManager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments]; - NSMutableSet *acceptableContentTypes = sessionManager.responseSerializer.acceptableContentTypes.mutableCopy; - [acceptableContentTypes addObject:@"text/plain"]; - sessionManager.responseSerializer.acceptableContentTypes = acceptableContentTypes; - return sessionManager; -} - -- (AFHTTPSessionManager *)reflectorSignalServiceSessionManagerWithCensorshipConfiguration: - (OWSCensorshipConfiguration *)censorshipConfiguration -{ - NSURLSessionConfiguration *sessionConf = NSURLSessionConfiguration.ephemeralSessionConfiguration; - AFHTTPSessionManager *sessionManager = - [[AFHTTPSessionManager alloc] initWithBaseURL:censorshipConfiguration.domainFrontBaseURL - sessionConfiguration:sessionConf]; - - sessionManager.securityPolicy = censorshipConfiguration.domainFrontSecurityPolicy; - - sessionManager.requestSerializer = [AFJSONRequestSerializer serializer]; - [sessionManager.requestSerializer setValue:censorshipConfiguration.signalServiceReflectorHost - forHTTPHeaderField:@"Host"]; - sessionManager.responseSerializer = [AFJSONResponseSerializer serializer]; - // Disable default cookie handling for all requests. - sessionManager.requestSerializer.HTTPShouldHandleCookies = NO; - - return sessionManager; -} - -#pragma mark - Profile Uploading - -- (AFHTTPSessionManager *)CDNSessionManager -{ - if (self.isCensorshipCircumventionActive) { - OWSCensorshipConfiguration *censorshipConfiguration = [self buildCensorshipConfiguration]; - OWSLogInfo(@"using reflector CDNSessionManager via: %@", censorshipConfiguration.domainFrontBaseURL); - return [self reflectorCDNSessionManagerWithCensorshipConfiguration:censorshipConfiguration]; - } else { - return self.defaultCDNSessionManager; - } -} - -- (AFHTTPSessionManager *)defaultCDNSessionManager -{ - NSURLSessionConfiguration *sessionConf = NSURLSessionConfiguration.ephemeralSessionConfiguration; - AFHTTPSessionManager *sessionManager = - [[AFHTTPSessionManager alloc] initWithBaseURL:nil sessionConfiguration:sessionConf]; - - sessionManager.securityPolicy = [OWSHTTPSecurityPolicy sharedPolicy]; - - // Default acceptable content headers are rejected by AWS - sessionManager.responseSerializer.acceptableContentTypes = nil; - - return sessionManager; -} - -- (AFHTTPSessionManager *)reflectorCDNSessionManagerWithCensorshipConfiguration: - (OWSCensorshipConfiguration *)censorshipConfiguration -{ - NSURLSessionConfiguration *sessionConf = NSURLSessionConfiguration.ephemeralSessionConfiguration; - - AFHTTPSessionManager *sessionManager = - [[AFHTTPSessionManager alloc] initWithBaseURL:censorshipConfiguration.domainFrontBaseURL - sessionConfiguration:sessionConf]; - - sessionManager.securityPolicy = censorshipConfiguration.domainFrontSecurityPolicy; - - sessionManager.requestSerializer = [AFJSONRequestSerializer serializer]; - [sessionManager.requestSerializer setValue:censorshipConfiguration.CDNReflectorHost forHTTPHeaderField:@"Host"]; - - sessionManager.responseSerializer = [AFJSONResponseSerializer serializer]; - - return sessionManager; -} - -#pragma mark - Events - -- (void)registrationStateDidChange:(NSNotification *)notification -{ - [self updateHasCensoredPhoneNumber]; -} - -- (void)localNumberDidChange:(NSNotification *)notification -{ - [self updateHasCensoredPhoneNumber]; -} - -#pragma mark - Manual Censorship Circumvention - -- (OWSCensorshipConfiguration *)buildCensorshipConfiguration -{ - OWSAssertDebug(self.isCensorshipCircumventionActive); - - if (self.isCensorshipCircumventionManuallyActivated) { - NSString *countryCode = self.manualCensorshipCircumventionCountryCode; - if (countryCode.length == 0) { - OWSFailDebug(@"manualCensorshipCircumventionCountryCode was unexpectedly 0"); - } - - OWSCensorshipConfiguration *configuration = - [OWSCensorshipConfiguration censorshipConfigurationWithCountryCode:countryCode]; - OWSAssertDebug(configuration); - - return configuration; - } - - OWSCensorshipConfiguration *_Nullable configuration = - [OWSCensorshipConfiguration censorshipConfigurationWithPhoneNumber:TSAccountManager.localNumber]; - if (configuration != nil) { - return configuration; - } - - return OWSCensorshipConfiguration.defaultConfiguration; -} - -- (nullable NSString *)manualCensorshipCircumventionCountryCode -{ - return - [[OWSPrimaryStorage dbReadConnection] objectForKey:kOWSPrimaryStorage_ManualCensorshipCircumventionCountryCode - inCollection:kOWSPrimaryStorage_OWSSignalService]; -} - -- (void)setManualCensorshipCircumventionCountryCode:(nullable NSString *)value -{ - [[OWSPrimaryStorage dbReadWriteConnection] setObject:value - forKey:kOWSPrimaryStorage_ManualCensorshipCircumventionCountryCode - inCollection:kOWSPrimaryStorage_OWSSignalService]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/OutageDetection.swift b/SignalServiceKit/src/Network/OutageDetection.swift deleted file mode 100644 index 2717dc570..000000000 --- a/SignalServiceKit/src/Network/OutageDetection.swift +++ /dev/null @@ -1,128 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import os - -@objc -public class OutageDetection: NSObject { - @objc(sharedManager) - public static let shared = OutageDetection() - - @objc public static let outageStateDidChange = Notification.Name("OutageStateDidChange") - - // These properties should only be accessed on the main thread. - @objc - public var hasOutage = false { - didSet { - AssertIsOnMainThread() - - if hasOutage != oldValue { - Logger.info("hasOutage: \(hasOutage).") - - NotificationCenter.default.postNotificationNameAsync(OutageDetection.outageStateDidChange, object: nil) - } - } - } - private var shouldCheckForOutage = false { - didSet { - // Loki: Don't check for outages -// AssertIsOnMainThread() -// ensureCheckTimer() - } - } - - // We only show the outage warning when we're certain there's an outage. - // DNS lookup failures, etc. are not considered an outage. - private func checkForOutageSync() -> Bool { - let host = CFHostCreateWithName(nil, "uptime.signal.org" as CFString).takeRetainedValue() - CFHostStartInfoResolution(host, .addresses, nil) - var success: DarwinBoolean = false - guard let addresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as NSArray? else { - Logger.error("CFHostGetAddressing failed: no addresses.") - return false - } - guard success.boolValue else { - Logger.error("CFHostGetAddressing failed.") - return false - } - var isOutageDetected = false - for case let address as NSData in addresses { - var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - if getnameinfo(address.bytes.assumingMemoryBound(to: sockaddr.self), socklen_t(address.length), - &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 { - let addressString = String(cString: hostname) - let kHealthyAddress = "127.0.0.1" - let kOutageAddress = "127.0.0.2" - if addressString == kHealthyAddress { - // Do nothing. - } else if addressString == kOutageAddress { - isOutageDetected = true - } else { - owsFailDebug("unexpected address: \(addressString)") - } - } - } - return isOutageDetected - } - - private func checkForOutageAsync() { - Logger.info("") - - DispatchQueue.global().async { - let isOutageDetected = self.checkForOutageSync() - DispatchQueue.main.async { - self.hasOutage = isOutageDetected - } - } - } - - private var checkTimer: Timer? - private func ensureCheckTimer() { - // Only monitor for outages in the main app. - guard CurrentAppContext().isMainApp else { - return - } - - if shouldCheckForOutage { - if checkTimer != nil { - // Already has timer. - return - } - - // The TTL of the DNS record is 60 seconds. - checkTimer = WeakTimer.scheduledTimer(timeInterval: 60, target: self, userInfo: nil, repeats: true) { [weak self] _ in - AssertIsOnMainThread() - - guard CurrentAppContext().isMainAppAndActive else { - return - } - - guard let strongSelf = self else { - return - } - - strongSelf.checkForOutageAsync() - } - } else { - checkTimer?.invalidate() - checkTimer = nil - } - } - - @objc - public func reportConnectionSuccess() { - DispatchMainThreadSafe { - self.shouldCheckForOutage = false - self.hasOutage = false - } - } - - @objc - public func reportConnectionFailure() { - DispatchMainThreadSafe { - self.shouldCheckForOutage = true - } - } -} diff --git a/SignalServiceKit/src/Network/ProxiedContentDownloader.swift b/SignalServiceKit/src/Network/ProxiedContentDownloader.swift deleted file mode 100644 index 5b6d2f23e..000000000 --- a/SignalServiceKit/src/Network/ProxiedContentDownloader.swift +++ /dev/null @@ -1,932 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import ObjectiveC - -// Stills should be loaded before full GIFs. -public enum ProxiedContentRequestPriority { - case low, high -} - -// MARK: - - -@objc -open class ProxiedContentAssetDescription: NSObject { - @objc - public let url: NSURL - - @objc - public let fileExtension: String - - public init?(url: NSURL, - fileExtension: String? = nil) { - self.url = url - - if let fileExtension = fileExtension { - self.fileExtension = fileExtension - } else { - guard let pathExtension = url.pathExtension else { - owsFailDebug("URL has not path extension.") - return nil - } - self.fileExtension = pathExtension - } - } -} - -// MARK: - - -public enum ProxiedContentAssetSegmentState: UInt { - case waiting - case downloading - case complete - case failed -} - -// MARK: - - -public class ProxiedContentAssetSegment: NSObject { - - public let index: UInt - public let segmentStart: UInt - public let segmentLength: UInt - // The amount of the segment that is overlap. - // The overlap lies in the _first_ n bytes of the segment data. - public let redundantLength: UInt - - // This state should only be accessed on the main thread. - public var state: ProxiedContentAssetSegmentState = .waiting { - didSet { - AssertIsOnMainThread() - } - } - - // This state is accessed off the main thread. - // - // * During downloads it will be accessed on the task delegate queue. - // * After downloads it will be accessed on a worker queue. - private var segmentData = Data() - - // This state should only be accessed on the main thread. - public weak var task: URLSessionDataTask? - - init(index: UInt, - segmentStart: UInt, - segmentLength: UInt, - redundantLength: UInt) { - self.index = index - self.segmentStart = segmentStart - self.segmentLength = segmentLength - self.redundantLength = redundantLength - } - - public func totalDataSize() -> UInt { - return UInt(segmentData.count) - } - - public func append(data: Data) { - guard state == .downloading else { - owsFailDebug("appending data in invalid state: \(state)") - return - } - - segmentData.append(data) - } - - public func mergeData(assetData: inout Data) -> Bool { - guard state == .complete else { - owsFailDebug("merging data in invalid state: \(state)") - return false - } - guard UInt(segmentData.count) == segmentLength else { - owsFailDebug("segment data length: \(segmentData.count) doesn't match expected length: \(segmentLength)") - return false - } - - // In some cases the last two segments will overlap. - // In that case, we only want to append the non-overlapping - // tail of the segment data. - let bytesToIgnore = Int(redundantLength) - if bytesToIgnore > 0 { - let subdata = segmentData.subdata(in: bytesToIgnore.. Void)? - private var failure: ((ProxiedContentAssetRequest) -> Void)? - - var wasCancelled = false - // This property is an internal implementation detail of the download process. - var assetFilePath: String? - - // This state should only be accessed on the main thread. - private var segments = [ProxiedContentAssetSegment]() - public var state: ProxiedContentAssetRequestState = .waiting - public var contentLength: Int = 0 { - didSet { - AssertIsOnMainThread() - assert(oldValue == 0) - assert(contentLength > 0) - } - } - public weak var contentLengthTask: URLSessionDataTask? - - init(assetDescription: ProxiedContentAssetDescription, - priority: ProxiedContentRequestPriority, - success:@escaping ((ProxiedContentAssetRequest?, ProxiedContentAsset) -> Void), - failure:@escaping ((ProxiedContentAssetRequest) -> Void)) { - self.assetDescription = assetDescription - self.priority = priority - self.success = success - self.failure = failure - - super.init() - } - - private func segmentSize() -> UInt { - AssertIsOnMainThread() - - let contentLength = UInt(self.contentLength) - guard contentLength > 0 else { - owsFailDebug("asset missing contentLength") - requestDidFail() - return 0 - } - - let k1MB: UInt = 1024 * 1024 - let k500KB: UInt = 500 * 1024 - let k100KB: UInt = 100 * 1024 - let k50KB: UInt = 50 * 1024 - let k10KB: UInt = 10 * 1024 - let k1KB: UInt = 1 * 1024 - for segmentSize in [k1MB, k500KB, k100KB, k50KB, k10KB, k1KB ] { - if contentLength >= segmentSize { - return segmentSize - } - } - return contentLength - } - - fileprivate func createSegments(withInitialData initialData: Data) { - AssertIsOnMainThread() - - let segmentLength = segmentSize() - guard segmentLength > 0 else { - return - } - let contentLength = UInt(self.contentLength) - - // Make the initial segment. - let assetSegment = ProxiedContentAssetSegment(index: 0, - segmentStart: 0, - segmentLength: UInt(initialData.count), - redundantLength: 0) - // "Download" the initial segment using the initialData. - assetSegment.state = .downloading - assetSegment.append(data: initialData) - // Mark the initial segment as complete. - assetSegment.state = .complete - segments.append(assetSegment) - - var nextSegmentStart = UInt(initialData.count) - var index: UInt = 1 - while nextSegmentStart < contentLength { - var segmentStart: UInt = nextSegmentStart - var redundantLength: UInt = 0 - // The last segment may overlap the penultimate segment - // in order to keep the segment sizes uniform. - if segmentStart + segmentLength > contentLength { - redundantLength = segmentStart + segmentLength - contentLength - segmentStart = contentLength - segmentLength - } - let assetSegment = ProxiedContentAssetSegment(index: index, - segmentStart: segmentStart, - segmentLength: segmentLength, - redundantLength: redundantLength) - segments.append(assetSegment) - nextSegmentStart = segmentStart + segmentLength - index += 1 - } - } - - private func firstSegmentWithState(state: ProxiedContentAssetSegmentState) -> ProxiedContentAssetSegment? { - AssertIsOnMainThread() - - for segment in segments { - guard segment.state != .failed else { - owsFailDebug("unexpected failed segment.") - continue - } - if segment.state == state { - return segment - } - } - return nil - } - - public func firstWaitingSegment() -> ProxiedContentAssetSegment? { - AssertIsOnMainThread() - - return firstSegmentWithState(state: .waiting) - } - - public func downloadingSegmentsCount() -> UInt { - AssertIsOnMainThread() - - var result: UInt = 0 - for segment in segments { - guard segment.state != .failed else { - owsFailDebug("unexpected failed segment.") - continue - } - if segment.state == .downloading { - result += 1 - } - } - return result - } - - public func areAllSegmentsComplete() -> Bool { - AssertIsOnMainThread() - - for segment in segments { - guard segment.state == .complete else { - return false - } - } - return true - } - - public func writeAssetToFile(downloadFolderPath: String) -> ProxiedContentAsset? { - - var assetData = Data() - for segment in segments { - guard segment.state == .complete else { - owsFailDebug("unexpected incomplete segment.") - return nil - } - guard segment.totalDataSize() > 0 else { - owsFailDebug("could not merge empty segment.") - return nil - } - guard segment.mergeData(assetData: &assetData) else { - owsFailDebug("failed to merge segment data.") - return nil - } - } - - guard assetData.count == contentLength else { - owsFailDebug("asset data has unexpected length.") - return nil - } - - guard assetData.count > 0 else { - owsFailDebug("could not write empty asset to disk.") - return nil - } - - let fileExtension = assetDescription.fileExtension - let fileName = (NSUUID().uuidString as NSString).appendingPathExtension(fileExtension)! - let filePath = (downloadFolderPath as NSString).appendingPathComponent(fileName) - - Logger.verbose("filePath: \(filePath).") - - do { - try assetData.write(to: NSURL.fileURL(withPath: filePath), options: .atomicWrite) - let asset = ProxiedContentAsset(assetDescription: assetDescription, filePath: filePath) - return asset - } catch let error as NSError { - owsFailDebug("file write failed: \(filePath), \(error)") - return nil - } - } - - public func cancel() { - AssertIsOnMainThread() - - wasCancelled = true - contentLengthTask?.cancel() - contentLengthTask = nil - for segment in segments { - segment.task?.cancel() - segment.task = nil - } - - // Don't call the callbacks if the request is cancelled. - clearCallbacks() - } - - private func clearCallbacks() { - AssertIsOnMainThread() - - success = nil - failure = nil - } - - public func requestDidSucceed(asset: ProxiedContentAsset) { - AssertIsOnMainThread() - - success?(self, asset) - - // Only one of the callbacks should be called, and only once. - clearCallbacks() - } - - public func requestDidFail() { - AssertIsOnMainThread() - - failure?(self) - - // Only one of the callbacks should be called, and only once. - clearCallbacks() - } -} - -// MARK: - - -// Represents a downloaded asset. -// -// The blob on disk is cleaned up when this instance is deallocated, -// so consumers of this resource should retain a strong reference to -// this instance as long as they are using the asset. -@objc -public class ProxiedContentAsset: NSObject { - - @objc - public let assetDescription: ProxiedContentAssetDescription - - @objc - public let filePath: String - - init(assetDescription: ProxiedContentAssetDescription, - filePath: String) { - self.assetDescription = assetDescription - self.filePath = filePath - } - - deinit { - // Clean up on the asset on disk. - let filePathCopy = filePath - DispatchQueue.global().async { - do { - let fileManager = FileManager.default - try fileManager.removeItem(atPath: filePathCopy) - } catch let error as NSError { - owsFailDebug("file cleanup failed: \(filePathCopy), \(error)") - } - } - } -} - -// MARK: - - -private var URLSessionTaskProxiedContentAssetRequest: UInt8 = 0 -private var URLSessionTaskProxiedContentAssetSegment: UInt8 = 0 - -// This extension is used to punch an asset request onto a download task. -extension URLSessionTask { - var assetRequest: ProxiedContentAssetRequest { - get { - return objc_getAssociatedObject(self, &URLSessionTaskProxiedContentAssetRequest) as! ProxiedContentAssetRequest - } - set { - objc_setAssociatedObject(self, &URLSessionTaskProxiedContentAssetRequest, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } - var assetSegment: ProxiedContentAssetSegment { - get { - return objc_getAssociatedObject(self, &URLSessionTaskProxiedContentAssetSegment) as! ProxiedContentAssetSegment - } - set { - objc_setAssociatedObject(self, &URLSessionTaskProxiedContentAssetSegment, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } -} - -// MARK: - - -@objc -open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessionDataDelegate { - - // MARK: - Properties - - @objc - public static let defaultDownloader = ProxiedContentDownloader(downloadFolderName: "proxiedContent") - - private let downloadFolderName: String - - private var downloadFolderPath: String? - - // Force usage as a singleton - public required init(downloadFolderName: String) { - AssertIsOnMainThread() - - self.downloadFolderName = downloadFolderName - - super.init() - - SwiftSingletons.register(self) - - ensureDownloadFolder() - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - private lazy var downloadSession: URLSession = { - AssertIsOnMainThread() - - let configuration = ContentProxy.sessionConfiguration() - - // Don't use any caching to protect privacy of these requests. - configuration.urlCache = nil - configuration.requestCachePolicy = .reloadIgnoringCacheData - - configuration.httpMaximumConnectionsPerHost = 10 - let session = URLSession(configuration: configuration, - delegate: self, - delegateQueue: nil) - return session - }() - - // 100 entries of which at least half will probably be stills. - // Actual animated GIFs will usually be less than 3 MB so the - // max size of the cache on disk should be ~150 MB. Bear in mind - // that assets are not always deleted on disk as soon as they are - // evacuated from the cache; if a cache consumer (e.g. view) is - // still using the asset, the asset won't be deleted on disk until - // it is no longer in use. - private var assetMap = LRUCache(maxSize: 100) - // TODO: We could use a proper queue, e.g. implemented with a linked - // list. - private var assetRequestQueue = [ProxiedContentAssetRequest]() - - // The success and failure callbacks are always called on main queue. - // - // The success callbacks may be called synchronously on cache hit, in - // which case the ProxiedContentAssetRequest parameter will be nil. - public func requestAsset(assetDescription: ProxiedContentAssetDescription, - priority: ProxiedContentRequestPriority, - success:@escaping ((ProxiedContentAssetRequest?, ProxiedContentAsset) -> Void), - failure:@escaping ((ProxiedContentAssetRequest) -> Void)) -> ProxiedContentAssetRequest? { - AssertIsOnMainThread() - - if let asset = assetMap.get(key: assetDescription.url) { - // Synchronous cache hit. - Logger.verbose("asset cache hit: \(assetDescription.url)") - success(nil, asset) - return nil - } - - // Cache miss. - // - // Asset requests are done queued and performed asynchronously. - Logger.verbose("asset cache miss: \(assetDescription.url)") - let assetRequest = ProxiedContentAssetRequest(assetDescription: assetDescription, - priority: priority, - success: success, - failure: failure) - assetRequestQueue.append(assetRequest) - // Process the queue (which may start this request) - // asynchronously so that the caller has time to store - // a reference to the asset request returned by this - // method before its success/failure handler is called. - processRequestQueueAsync() - return assetRequest - } - - public func cancelAllRequests() { - AssertIsOnMainThread() - - Logger.verbose("cancelAllRequests") - - self.assetRequestQueue.forEach { $0.cancel() } - self.assetRequestQueue = [] - } - - private func segmentRequestDidSucceed(assetRequest: ProxiedContentAssetRequest, assetSegment: ProxiedContentAssetSegment) { - DispatchQueue.main.async { - assetSegment.state = .complete - - if !self.tryToCompleteRequest(assetRequest: assetRequest) { - self.processRequestQueueSync() - } - } - } - - // Returns true if the request is completed. - private func tryToCompleteRequest(assetRequest: ProxiedContentAssetRequest) -> Bool { - AssertIsOnMainThread() - - guard assetRequest.areAllSegmentsComplete() else { - return false - } - - // If the asset request has completed all of its segments, - // try to write the asset to file. - assetRequest.state = .complete - - // Move write off main thread. - DispatchQueue.global().async { - guard let downloadFolderPath = self.downloadFolderPath else { - owsFailDebug("Missing downloadFolderPath") - return - } - guard let asset = assetRequest.writeAssetToFile(downloadFolderPath: downloadFolderPath) else { - self.segmentRequestDidFail(assetRequest: assetRequest) - return - } - self.assetRequestDidSucceed(assetRequest: assetRequest, asset: asset) - } - return true - } - - private func assetRequestDidSucceed(assetRequest: ProxiedContentAssetRequest, asset: ProxiedContentAsset) { - DispatchQueue.main.async { - self.assetMap.set(key: assetRequest.assetDescription.url, value: asset) - self.removeAssetRequestFromQueue(assetRequest: assetRequest) - assetRequest.requestDidSucceed(asset: asset) - } - } - - private func segmentRequestDidFail(assetRequest: ProxiedContentAssetRequest, assetSegment: ProxiedContentAssetSegment? = nil) { - DispatchQueue.main.async { - if let assetSegment = assetSegment { - assetSegment.state = .failed - - // TODO: If we wanted to implement segment retry, we'd do so here. - // For now, we just fail the entire asset request. - } - assetRequest.state = .failed - self.assetRequestDidFail(assetRequest: assetRequest) - } - } - - private func assetRequestDidFail(assetRequest: ProxiedContentAssetRequest) { - - DispatchQueue.main.async { - self.removeAssetRequestFromQueue(assetRequest: assetRequest) - assetRequest.requestDidFail() - } - } - - private func removeAssetRequestFromQueue(assetRequest: ProxiedContentAssetRequest) { - AssertIsOnMainThread() - - guard assetRequestQueue.contains(assetRequest) else { - Logger.warn("could not remove asset request from queue: \(assetRequest.assetDescription.url)") - return - } - - assetRequestQueue = assetRequestQueue.filter { $0 != assetRequest } - // Process the queue async to ensure that state in the downloader - // classes is consistent before we try to start a new request. - processRequestQueueAsync() - } - - private func processRequestQueueAsync() { - DispatchQueue.main.async { - self.processRequestQueueSync() - } - } - - // * Start a segment request or content length request if possible. - // * Complete/cancel asset requests if possible. - // - private func processRequestQueueSync() { - AssertIsOnMainThread() - - guard let assetRequest = popNextAssetRequest() else { - return - } - guard !assetRequest.wasCancelled else { - // Discard the cancelled asset request and try again. - removeAssetRequestFromQueue(assetRequest: assetRequest) - return - } - guard CurrentAppContext().isMainAppAndActive else { - // If app is not active, fail the asset request. - assetRequest.state = .failed - assetRequestDidFail(assetRequest: assetRequest) - processRequestQueueSync() - return - } - - if let asset = assetMap.get(key: assetRequest.assetDescription.url) { - // Deferred cache hit, avoids re-downloading assets that were - // downloaded while this request was queued. - - assetRequest.state = .complete - assetRequestDidSucceed(assetRequest: assetRequest, asset: asset) - return - } - - if assetRequest.state == .waiting { - // If asset request hasn't yet determined the resource size, - // try to do so now, by requesting a small initial segment. - assetRequest.state = .requestingSize - - let segmentStart: UInt = 0 - // Vary the initial segment size to obscure the length of the response headers. - let segmentLength: UInt = 1024 + UInt(arc4random_uniform(1024)) - var request = URLRequest(url: assetRequest.assetDescription.url as URL) - request.httpShouldUsePipelining = true - let rangeHeaderValue = "bytes=\(segmentStart)-\(segmentStart + segmentLength - 1)" - request.addValue(rangeHeaderValue, forHTTPHeaderField: "Range") - - guard ContentProxy.configureProxiedRequest(request: &request) else { - assetRequest.state = .failed - assetRequestDidFail(assetRequest: assetRequest) - processRequestQueueSync() - return - } - - let task = downloadSession.dataTask(with: request, completionHandler: { data, response, error -> Void in - self.handleAssetSizeResponse(assetRequest: assetRequest, data: data, response: response, error: error) - }) - - assetRequest.contentLengthTask = task - task.resume() - } else { - // Start a download task. - - guard let assetSegment = assetRequest.firstWaitingSegment() else { - owsFailDebug("queued asset request does not have a waiting segment.") - return - } - assetSegment.state = .downloading - - var request = URLRequest(url: assetRequest.assetDescription.url as URL) - request.httpShouldUsePipelining = true - let rangeHeaderValue = "bytes=\(assetSegment.segmentStart)-\(assetSegment.segmentStart + assetSegment.segmentLength - 1)" - request.addValue(rangeHeaderValue, forHTTPHeaderField: "Range") - - guard ContentProxy.configureProxiedRequest(request: &request) else { - assetRequest.state = .failed - assetRequestDidFail(assetRequest: assetRequest) - processRequestQueueSync() - return - } - - let task: URLSessionDataTask = downloadSession.dataTask(with: request) - task.assetRequest = assetRequest - task.assetSegment = assetSegment - assetSegment.task = task - task.resume() - } - - // Recurse; we may be able to start multiple downloads. - processRequestQueueSync() - } - - private func handleAssetSizeResponse(assetRequest: ProxiedContentAssetRequest, data: Data?, response: URLResponse?, error: Error?) { - guard error == nil else { - assetRequest.state = .failed - self.assetRequestDidFail(assetRequest: assetRequest) - return - } - guard let data = data, - data.count > 0 else { - owsFailDebug("Asset size response missing data.") - assetRequest.state = .failed - self.assetRequestDidFail(assetRequest: assetRequest) - return - } - guard let httpResponse = response as? HTTPURLResponse else { - owsFailDebug("Asset size response is invalid.") - assetRequest.state = .failed - self.assetRequestDidFail(assetRequest: assetRequest) - return - } - var firstContentRangeString: String? - for header in httpResponse.allHeaderFields.keys { - guard let headerString = header as? String else { - owsFailDebug("Invalid header: \(header)") - continue - } - if headerString.lowercased() == "content-range" { - firstContentRangeString = httpResponse.allHeaderFields[header] as? String - } - } - guard let contentRangeString = firstContentRangeString else { - owsFailDebug("Asset size response is missing content range.") - assetRequest.state = .failed - self.assetRequestDidFail(assetRequest: assetRequest) - return - } - - // Example: content-range: bytes 0-1023/7630 - guard let contentLengthString = NSRegularExpression.parseFirstMatch(pattern: "^bytes \\d+\\-\\d+/(\\d+)$", - text: contentRangeString) else { - owsFailDebug("Asset size response has invalid content range.") - assetRequest.state = .failed - self.assetRequestDidFail(assetRequest: assetRequest) - return - } - guard contentLengthString.count > 0, - let contentLength = Int(contentLengthString) else { - owsFailDebug("Asset size response has unparsable content length.") - assetRequest.state = .failed - self.assetRequestDidFail(assetRequest: assetRequest) - return - } - guard contentLength > 0 else { - owsFailDebug("Asset size response has invalid content length.") - assetRequest.state = .failed - self.assetRequestDidFail(assetRequest: assetRequest) - return - } - - DispatchQueue.main.async { - assetRequest.contentLength = contentLength - assetRequest.createSegments(withInitialData: data) - assetRequest.state = .active - - if !self.tryToCompleteRequest(assetRequest: assetRequest) { - self.processRequestQueueSync() - } - } - } - - // Return the first asset request for which we either: - // - // * Need to download the content length. - // * Need to download at least one of its segments. - private func popNextAssetRequest() -> ProxiedContentAssetRequest? { - AssertIsOnMainThread() - - let kMaxAssetRequestCount: UInt = 3 - let kMaxAssetRequestsPerAssetCount: UInt = kMaxAssetRequestCount - 1 - - // Prefer the first "high" priority request; - // fall back to the first "low" priority request. - var activeAssetRequestsCount: UInt = 0 - for priority in [ProxiedContentRequestPriority.high, ProxiedContentRequestPriority.low] { - for assetRequest in assetRequestQueue where assetRequest.priority == priority { - switch assetRequest.state { - case .waiting: - // This asset request needs its content length. - return assetRequest - case .requestingSize: - activeAssetRequestsCount += 1 - // Ensure that only N requests are active at a time. - guard activeAssetRequestsCount < kMaxAssetRequestCount else { - return nil - } - continue - case .active: - break - case .complete: - continue - case .failed: - continue - } - - let downloadingSegmentsCount = assetRequest.downloadingSegmentsCount() - activeAssetRequestsCount += downloadingSegmentsCount - // Ensure that only N segment requests are active per asset at a time. - guard downloadingSegmentsCount < kMaxAssetRequestsPerAssetCount else { - continue - } - // Ensure that only N requests are active at a time. - guard activeAssetRequestsCount < kMaxAssetRequestCount else { - return nil - } - guard assetRequest.firstWaitingSegment() != nil else { - /// Asset request does not have a waiting segment. - continue - } - return assetRequest - } - } - - return nil - } - - // MARK: URLSessionDataDelegate - - @nonobjc - public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { - - completionHandler(.allow) - } - - public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { - let assetRequest = dataTask.assetRequest - let assetSegment = dataTask.assetSegment - guard !assetRequest.wasCancelled else { - dataTask.cancel() - segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment) - return - } - assetSegment.append(data: data) - } - - public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) { - completionHandler(nil) - } - - // MARK: URLSessionTaskDelegate - - public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - - let assetRequest = task.assetRequest - let assetSegment = task.assetSegment - guard !assetRequest.wasCancelled else { - task.cancel() - segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment) - return - } - if let error = error { - Logger.error("download failed with error: \(error)") - segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment) - return - } - guard let httpResponse = task.response as? HTTPURLResponse else { - Logger.error("missing or unexpected response: \(String(describing: task.response))") - segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment) - return - } - let statusCode = httpResponse.statusCode - guard statusCode >= 200 && statusCode < 400 else { - Logger.error("response has invalid status code: \(statusCode)") - segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment) - return - } - guard assetSegment.totalDataSize() == assetSegment.segmentLength else { - Logger.error("segment is missing data: \(statusCode)") - segmentRequestDidFail(assetRequest: assetRequest, assetSegment: assetSegment) - return - } - - segmentRequestDidSucceed(assetRequest: assetRequest, assetSegment: assetSegment) - } - - // MARK: Temp Directory - - public func ensureDownloadFolder() { - // We write assets to the temporary directory so that iOS can clean them up. - // We try to eagerly clean up these assets when they are no longer in use. - - let tempDirPath = OWSTemporaryDirectory() - let dirPath = (tempDirPath as NSString).appendingPathComponent(downloadFolderName) - do { - let fileManager = FileManager.default - - // Try to delete existing folder if necessary. - if fileManager.fileExists(atPath: dirPath) { - try fileManager.removeItem(atPath: dirPath) - downloadFolderPath = dirPath - } - // Try to create folder if necessary. - if !fileManager.fileExists(atPath: dirPath) { - try fileManager.createDirectory(atPath: dirPath, - withIntermediateDirectories: true, - attributes: nil) - downloadFolderPath = dirPath - } - - // Don't back up ProxiedContent downloads. - OWSFileSystem.protectFileOrFolder(atPath: dirPath) - } catch let error as NSError { - owsFailDebug("ensureTempFolder failed: \(dirPath), \(error)") - downloadFolderPath = tempDirPath - } - } -} diff --git a/SignalServiceKit/src/Network/ReachabilityManager.swift b/SignalServiceKit/src/Network/ReachabilityManager.swift deleted file mode 100644 index 2d02f4570..000000000 --- a/SignalServiceKit/src/Network/ReachabilityManager.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -@objc(SSKReachabilityType) -public enum ReachabilityType: Int { - case any, wifi, cellular -} - -@objc -public protocol SSKReachabilityManager { - var observationContext: AnyObject { get } - func setup() - - var isReachable: Bool { get } - func isReachable(via reachabilityType: ReachabilityType) -> Bool -} - -@objc -public class SSKReachabilityManagerImpl: NSObject, SSKReachabilityManager { - - public let reachability: Reachability - public var observationContext: AnyObject { - return self.reachability - } - - public var isReachable: Bool { - return isReachable(via: .any) - } - - public func isReachable(via reachabilityType: ReachabilityType) -> Bool { - switch reachabilityType { - case .any: - return reachability.isReachable() - case .wifi: - return reachability.isReachableViaWiFi() - case .cellular: - return reachability.isReachableViaWWAN() - } - } - - @objc - override public init() { - self.reachability = Reachability.forInternetConnection() - } - - @objc - public func setup() { - guard reachability.startNotifier() else { - owsFailDebug("failed to start notifier") - return - } - Logger.debug("started notifier") - } -} diff --git a/SignalServiceKit/src/Network/SSKWebSocket.swift b/SignalServiceKit/src/Network/SSKWebSocket.swift deleted file mode 100644 index b72a680ad..000000000 --- a/SignalServiceKit/src/Network/SSKWebSocket.swift +++ /dev/null @@ -1,185 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import Starscream - -@objc -public enum SSKWebSocketState: UInt { - case open, connecting, disconnected -} - -@objc -public class SSKWebSocketError: NSObject, CustomNSError { - - init(underlyingError: Starscream.WSError) { - self.underlyingError = underlyingError - } - - // MARK: - CustomNSError - - @objc - public static let errorDomain = "SignalServiceKit.SSKWebSocketError" - - public var errorUserInfo: [String: Any] { - return [ - type(of: self).kStatusCodeKey: underlyingError.code, - NSUnderlyingErrorKey: (underlyingError as NSError) - ] - } - - // MARK: - - - @objc - public static let kStatusCodeKey = "SSKWebSocketErrorStatusCode" - - let underlyingError: Starscream.WSError -} - -@objc -public protocol SSKWebSocket { - - @objc - var delegate: SSKWebSocketDelegate? { get set } - - @objc - var state: SSKWebSocketState { get } - - @objc - func connect() - - @objc - func disconnect() - - @objc(writeData:error:) - func write(data: Data) throws - - @objc - func writePing() throws -} - -@objc -public protocol SSKWebSocketDelegate: class { - func websocketDidConnect(socket: SSKWebSocket) - - func websocketDidDisconnect(socket: SSKWebSocket, error: Error?) - - func websocketDidReceiveData(socket: SSKWebSocket, data: Data) - - @objc optional func websocketDidReceiveMessage(socket: SSKWebSocket, text: String) -} - -@objc -public class SSKWebSocketManager: NSObject { - - @objc - public class func buildSocket(request: URLRequest) -> SSKWebSocket { - return SSKWebSocketImpl(request: request) - } -} - -class SSKWebSocketImpl: SSKWebSocket { - - private let socket: Starscream.WebSocket - - init(request: URLRequest) { - let socket = WebSocket(request: request) - - socket.disableSSLCertValidation = true - socket.socketSecurityLevel = StreamSocketSecurityLevel.tlSv1_2 - let security = SSLSecurity(certs: [TextSecureCertificate()], usePublicKeys: false) - security.validateEntireChain = false - socket.security = security - - // TODO cipher suite selection - // socket.enabledSSLCipherSuites = [TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256] - - self.socket = socket - - socket.delegate = self - } - - // MARK: - SSKWebSocket - - weak var delegate: SSKWebSocketDelegate? - - var hasEverConnected = false - var state: SSKWebSocketState { - if socket.isConnected { - return .open - } - - if hasEverConnected { - return .disconnected - } - - return .connecting - } - - func connect() { - socket.connect() - } - - func disconnect() { - socket.disconnect() - } - - func write(data: Data) throws { - socket.write(data: data) - } - - func writePing() throws { - socket.write(ping: Data()) - } -} - -extension SSKWebSocketImpl: WebSocketDelegate { - func websocketDidConnect(socket: WebSocketClient) { - hasEverConnected = true - delegate?.websocketDidConnect(socket: self) - } - - func websocketDidDisconnect(socket: WebSocketClient, error: Error?) { - let websocketError: Error? - switch error { - case let wsError as WSError: - websocketError = SSKWebSocketError(underlyingError: wsError) - case let nsError as NSError: - // Assert that error is either a Starscream.WSError or an OS level networking error - if #available(iOS 10, *) { - let networkDownCode = 50 - assert(nsError.domain == "NSPOSIXErrorDomain" && nsError.code == networkDownCode) - } else { - assert(nsError.domain == kCFErrorDomainCFNetwork as String) - } - websocketError = error - default: - assert(error == nil, "unexpected error type: \(String(describing: error))") - websocketError = error - } - - delegate?.websocketDidDisconnect(socket: self, error: websocketError) - } - - func websocketDidReceiveMessage(socket: WebSocketClient, text: String) { - if let websocketDidReceiveMessage = self.delegate?.websocketDidReceiveMessage { - websocketDidReceiveMessage(self, text) - } - } - - func websocketDidReceiveData(socket: WebSocketClient, data: Data) { - delegate?.websocketDidReceiveData(socket: self, data: data) - } -} - -private func TextSecureCertificate() -> SSLCert { - let data = SSKTextSecureServiceCertificateData() - return SSLCert(data: data) -} - -private extension StreamSocketSecurityLevel { - static var tlSv1_2: StreamSocketSecurityLevel { - return StreamSocketSecurityLevel(rawValue: "kCFStreamSocketSecurityLevelTLSv1_2") - } -} diff --git a/SignalServiceKit/src/Network/SignalServiceClient.swift b/SignalServiceKit/src/Network/SignalServiceClient.swift deleted file mode 100644 index 39bc8ba82..000000000 --- a/SignalServiceKit/src/Network/SignalServiceClient.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit -import SessionMetadataKit - -public typealias RecipientIdentifier = String - -@objc -public protocol SignalServiceClientObjC { - @objc func updateAccountAttributesObjC() -> AnyPromise -} - -public protocol SignalServiceClient: SignalServiceClientObjC { - func getAvailablePreKeys() -> Promise - func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise - func setCurrentSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise - func requestUDSenderCertificate() -> Promise - func updateAccountAttributes() -> Promise -} - -/// Based on libsignal-service-java's PushServiceSocket class -@objc -public class SignalServiceRestClient: NSObject, SignalServiceClient { - - var networkManager: TSNetworkManager { - return TSNetworkManager.shared() - } - - private var udManager: OWSUDManager { - return SSKEnvironment.shared.udManager - } - - func unexpectedServerResponseError() -> Error { - return OWSErrorMakeUnableToProcessServerResponseError() - } - - public func getAvailablePreKeys() -> Promise { - Logger.debug("") - - let request = OWSRequestFactory.availablePreKeysCountRequest() - return firstly { - networkManager.makePromise(request: request) - }.map { _, responseObject in - Logger.debug("got response") - guard let params = ParamParser(responseObject: responseObject) else { - throw self.unexpectedServerResponseError() - } - - let count: Int = try params.required(key: "count") - - return count - } - } - - public func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise { - Logger.debug("") - - let request = OWSRequestFactory.registerPrekeysRequest(withPrekeyArray: preKeyRecords, identityKey: identityKey, signedPreKey: signedPreKeyRecord) - return networkManager.makePromise(request: request).asVoid() - } - - public func setCurrentSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise { - Logger.debug("") - - let request = OWSRequestFactory.registerSignedPrekeyRequest(with: signedPreKey) - return networkManager.makePromise(request: request).asVoid() - } - - public func requestUDSenderCertificate() -> Promise { - let request = OWSRequestFactory.udSenderCertificateRequest() - return firstly { - self.networkManager.makePromise(request: request) - }.map { _, responseObject in - guard let parser = ParamParser(responseObject: responseObject) else { - throw OWSUDError.invalidData(description: "Invalid sender certificate response") - } - - return try parser.requiredBase64EncodedData(key: "certificate") - } - } - - @objc - public func updateAccountAttributesObjC() -> AnyPromise { - return AnyPromise(updateAccountAttributes()) - } - - public func updateAccountAttributes() -> Promise { - let request = OWSRequestFactory.updateAttributesRequest() - return networkManager.makePromise(request: request).asVoid() - } -} diff --git a/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.h b/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.h deleted file mode 100644 index 8582a5791..000000000 --- a/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -static void *OWSWebSocketStateObservationContext = &OWSWebSocketStateObservationContext; - -extern NSString *const kNSNotification_OWSWebSocketStateDidChange; - -typedef NS_ENUM(NSUInteger, OWSWebSocketState) { - OWSWebSocketStateClosed, - OWSWebSocketStateConnecting, - OWSWebSocketStateOpen, -}; - -typedef void (^TSSocketMessageSuccess)(id _Nullable responseObject); -// statusCode is zero by default, if request never made or failed. -typedef void (^TSSocketMessageFailure)(NSInteger statusCode, NSData *_Nullable responseData, NSError *error); - -@class TSRequest; - -@interface OWSWebSocket : NSObject - -@property (nonatomic, readonly) OWSWebSocketState state; - -- (instancetype)init NS_DESIGNATED_INITIALIZER; - -// If the app is in the foreground, we'll try to open the socket unless it's already -// open or connecting. -// -// If the app is in the background, we'll try to open the socket unless it's already -// open or connecting _and_ keep it open for at least N seconds. -// If the app is in the background and the socket is already open or connecting this -// might prolong how long we keep the socket open. -// -// This method can be called from any thread. -- (void)requestSocketOpen; - -// This can be used to force the socket to close and re-open, if it is open. -- (void)cycleSocket; - -#pragma mark - Message Sending - -@property (atomic, readonly) BOOL canMakeRequests; - -- (void)makeRequest:(TSRequest *)request - success:(TSSocketMessageSuccess)success - failure:(TSSocketMessageFailure)failure; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.m b/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.m deleted file mode 100644 index 5a6968bc3..000000000 --- a/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.m +++ /dev/null @@ -1,1141 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSWebSocket.h" -#import "AppContext.h" -#import "AppReadiness.h" -#import "NSNotificationCenter+OWS.h" -#import "NSTimer+OWS.h" -#import "NotificationsProtocol.h" -#import "OWSBackgroundTask.h" -#import "OWSDevicesService.h" -#import "OWSError.h" -#import "OWSMessageManager.h" -#import "OWSMessageReceiver.h" -#import "OWSPrimaryStorage.h" -#import "OWSSignalService.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSConstants.h" -#import "TSErrorMessage.h" -#import "TSRequest.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -static const CGFloat kSocketHeartbeatPeriodSeconds = 30.f; -static const CGFloat kSocketReconnectDelaySeconds = 5.f; - -// If the app is in the background, it should keep the -// websocket open if: -// -// a) It has received a notification in the last 25 seconds. -static const CGFloat kBackgroundOpenSocketDurationSeconds = 25.f; -// b) It has received a message over the socket in the last 15 seconds. -static const CGFloat kBackgroundKeepSocketAliveDurationSeconds = 15.f; -// c) It is in the process of making a request. -static const CGFloat kMakeRequestKeepSocketAliveDurationSeconds = 30.f; - -NSString *const kNSNotification_OWSWebSocketStateDidChange = @"kNSNotification_OWSWebSocketStateDidChange"; - -@interface TSSocketMessage : NSObject - -@property (nonatomic, readonly) UInt64 requestId; -@property (nonatomic, nullable) TSSocketMessageSuccess success; -@property (nonatomic, nullable) TSSocketMessageFailure failure; -@property (nonatomic) BOOL hasCompleted; -@property (nonatomic, readonly) OWSBackgroundTask *backgroundTask; - -- (instancetype)init NS_UNAVAILABLE; - -@end - -#pragma mark - - -@implementation TSSocketMessage - -- (instancetype)initWithRequestId:(UInt64)requestId - success:(TSSocketMessageSuccess)success - failure:(TSSocketMessageFailure)failure -{ - if (self = [super init]) { - OWSAssertDebug(success); - OWSAssertDebug(failure); - - _requestId = requestId; - _success = success; - _failure = failure; - _backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - } - - return self; -} - -- (void)didSucceedWithResponseObject:(id _Nullable)responseObject -{ - @synchronized(self) { - if (self.hasCompleted) { - return; - } - self.hasCompleted = YES; - } - - OWSAssertDebug(self.success); - OWSAssertDebug(self.failure); - - TSSocketMessageSuccess success = self.success; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - success(responseObject); - }); - - self.success = nil; - self.failure = nil; -} - -- (void)timeoutIfNecessary -{ - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageRequestFailed, - NSLocalizedString( - @"ERROR_DESCRIPTION_REQUEST_TIMED_OUT", @"Error indicating that a socket request timed out.")); - - [self didFailWithStatusCode:0 responseData:nil error:error]; -} - -- (void)didFailBeforeSending -{ - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageRequestFailed, - NSLocalizedString(@"ERROR_DESCRIPTION_REQUEST_FAILED", @"Error indicating that a socket request failed.")); - - [self didFailWithStatusCode:0 responseData:nil error:error]; -} - -- (void)didFailWithStatusCode:(NSInteger)statusCode responseData:(nullable NSData *)responseData error:(NSError *)error -{ - OWSAssertDebug(error); - - @synchronized(self) { - if (self.hasCompleted) { - return; - } - self.hasCompleted = YES; - } - - OWSLogError(@"didFailWithStatusCode: %zd, %@", statusCode, error); - - OWSAssertDebug(self.success); - OWSAssertDebug(self.failure); - - TSSocketMessageFailure failure = self.failure; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - failure(statusCode, responseData, error); - }); - - self.success = nil; - self.failure = nil; -} - -@end - -#pragma mark - - -// OWSWebSocket's properties should only be accessed from the main thread. -@interface OWSWebSocket () - -// This class has a few "tiers" of state. -// -// The first tier is the actual websocket and the timers used -// to keep it alive and connected. -@property (nonatomic, nullable) id websocket; -@property (nonatomic, nullable) NSTimer *heartbeatTimer; -@property (nonatomic, nullable) NSTimer *reconnectTimer; - -#pragma mark - - -// The second tier is the state property. We initiate changes -// to the websocket by changing this property's value, and delegate -// events from the websocket also update this value as the websocket's -// state changes. -// -// Due to concurrency, this property can fall out of sync with the -// websocket's actual state, so we're defensive and distrustful of -// this property. -// -// We only ever access this state on the main thread. -@property (nonatomic) OWSWebSocketState state; - -#pragma mark - - -// The third tier is the state that is used to determine what the -// "desired" state of the websocket is. -// -// If we're keeping the socket open in the background, all three of these -// properties will be set. Otherwise (if the app is active or if we're not -// trying to keep the socket open), all three should be clear. -// -// This represents how long we're trying to keep the socket open. -@property (nonatomic, nullable) NSDate *backgroundKeepAliveUntilDate; -// This timer is used to check periodically whether we should -// close the socket. -@property (nonatomic, nullable) NSTimer *backgroundKeepAliveTimer; -// This is used to manage the iOS "background task" used to -// keep the app alive in the background. -@property (nonatomic, nullable) OWSBackgroundTask *backgroundTask; - -// We cache this value instead of consulting [UIApplication sharedApplication].applicationState, -// because UIKit only provides a "will resign active" notification, not a "did resign active" -// notification. -@property (nonatomic) BOOL appIsActive; - -@property (nonatomic) BOOL hasObservedNotifications; - -// This property should only be accessed while synchronized on the socket manager. -@property (nonatomic, readonly) NSMutableDictionary *socketMessageMap; - -@property (atomic) BOOL canMakeRequests; - -@end - -#pragma mark - - -@implementation OWSWebSocket - -- (instancetype)init -{ - self = [super init]; - - if (!self) { - return self; - } - - OWSAssertIsOnMainThread(); - - _state = OWSWebSocketStateClosed; - _socketMessageMap = [NSMutableDictionary new]; - - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -#pragma mark - Dependencies - -- (OWSSignalService *)signalService -{ - return [OWSSignalService sharedInstance]; -} - -- (OWSMessageReceiver *)messageReceiver -{ - return SSKEnvironment.shared.messageReceiver; -} - -- (TSAccountManager *)tsAccountManager -{ - return TSAccountManager.sharedInstance; -} - -- (OutageDetection *)outageDetection -{ - return OutageDetection.sharedManager; -} - -- (OWSPrimaryStorage *)primaryStorage -{ - return SSKEnvironment.shared.primaryStorage; -} - -- (id)notificationsManager -{ - return SSKEnvironment.shared.notificationsManager; -} - -- (id)udManager { - return SSKEnvironment.shared.udManager; -} - -#pragma mark - - -// We want to observe these notifications lazily to avoid accessing -// the data store in [application: didFinishLaunchingWithOptions:]. -- (void)observeNotificationsIfNecessary -{ - if (self.hasObservedNotifications) { - return; - } - self.hasObservedNotifications = YES; - - self.appIsActive = CurrentAppContext().isMainAppAndActive; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidBecomeActive:) - name:OWSApplicationDidBecomeActiveNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillResignActive:) - name:OWSApplicationWillResignActiveNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(registrationStateDidChange:) - name:RegistrationStateDidChangeNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(isCensorshipCircumventionActiveDidChange:) - name:kNSNotificationName_IsCensorshipCircumventionActiveDidChange - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(deviceListUpdateModifiedDeviceList:) - name:NSNotificationName_DeviceListUpdateModifiedDeviceList - object:nil]; -} - -#pragma mark - Manage Socket - -- (void)ensureWebsocketIsOpen -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(!self.signalService.isCensorshipCircumventionActive); - - // Try to reuse the existing socket (if any) if it is in a valid state. - if (self.websocket) { - switch (self.websocket.state) { - case SSKWebSocketStateOpen: - self.state = OWSWebSocketStateOpen; - return; - case SSKWebSocketStateConnecting: - OWSLogVerbose(@"WebSocket is already connecting"); - self.state = OWSWebSocketStateConnecting; - return; - default: - break; - } - } - - OWSLogWarn(@"Creating new websocket"); - - // If socket is not already open or connecting, connect now. - // - // First we need to close the existing websocket, if any. - // The websocket delegate methods are invoked _after_ the websocket - // state changes, so we may be just learning about a socket failure - // or close event now. - self.state = OWSWebSocketStateClosed; - // Now open a new socket. - self.state = OWSWebSocketStateConnecting; -} - -- (NSString *)stringFromOWSWebSocketState:(OWSWebSocketState)state -{ - switch (state) { - case OWSWebSocketStateClosed: - return @"Closed"; - case OWSWebSocketStateOpen: - return @"Open"; - case OWSWebSocketStateConnecting: - return @"Connecting"; - } -} - -// We need to keep websocket state and class state tightly aligned. -// -// Sometimes we'll need to update class state to reflect changes -// in socket state; sometimes we'll need to update socket state -// and class state to reflect changes in app state. -// -// We learn about changes to socket state through websocket -// delegate methods. These delegate methods are sometimes -// invoked _after_ web socket state changes, so we sometimes learn -// about changes to socket state in [ensureWebsocket]. Put another way, -// it's not safe to assume we'll learn of changes to websocket state -// in the websocket delegate methods. -// -// Therefore, we use the [setState:] setter to ensure alignment between -// websocket state and class state. -- (void)setState:(OWSWebSocketState)state -{ - OWSAssertIsOnMainThread(); - - // If this state update is redundant, verify that - // class state and socket state are aligned. - // - // Note: it's not safe to check the socket's readyState here as - // it may have been just updated on another thread. If so, - // we'll learn of that state change soon. - if (_state == state) { - switch (state) { - case OWSWebSocketStateClosed: - OWSAssertDebug(!self.websocket); - break; - case OWSWebSocketStateOpen: - OWSAssertDebug(self.websocket); - break; - case OWSWebSocketStateConnecting: - OWSAssertDebug(self.websocket); - break; - } - return; - } - - OWSLogWarn( - @"Socket state: %@ -> %@", [self stringFromOWSWebSocketState:_state], [self stringFromOWSWebSocketState:state]); - - // If this state update is _not_ redundant, - // update class state to reflect the new state. - switch (state) { - case OWSWebSocketStateClosed: { - [self resetSocket]; - break; - } - case OWSWebSocketStateOpen: { - OWSAssertDebug(self.state == OWSWebSocketStateConnecting); - - self.heartbeatTimer = [NSTimer timerWithTimeInterval:kSocketHeartbeatPeriodSeconds - target:self - selector:@selector(webSocketHeartBeat) - userInfo:nil - repeats:YES]; - - // Additionally, we want the ping timer to work in the background too. - [[NSRunLoop mainRunLoop] addTimer:self.heartbeatTimer forMode:NSDefaultRunLoopMode]; - - // If the socket is open, we don't need to worry about reconnecting. - [self clearReconnect]; - break; - } - case OWSWebSocketStateConnecting: { - // Discard the old socket which is already closed or is closing. - [self resetSocket]; - - // Create a new web socket. - NSString *webSocketConnect = - [textSecureWebSocketAPI stringByAppendingString:[self webSocketAuthenticationString]]; - NSURL *webSocketConnectURL = [NSURL URLWithString:webSocketConnect]; - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:webSocketConnectURL]; - - id socket = [SSKWebSocketManager buildSocketWithRequest:request]; - socket.delegate = self; - - [self setWebsocket:socket]; - - // `connect` could hypothetically call a delegate method (e.g. if - // the socket failed immediately for some reason), so we update the state - // _before_ calling it, not after. - _state = state; - self.canMakeRequests = state == OWSWebSocketStateOpen; - [socket connect]; - [self failAllPendingSocketMessagesIfNecessary]; - return; - } - } - - _state = state; - self.canMakeRequests = state == OWSWebSocketStateOpen; - - [self failAllPendingSocketMessagesIfNecessary]; - [self notifyStatusChange]; -} - -- (void)notifyStatusChange -{ - [[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotification_OWSWebSocketStateDidChange - object:nil - userInfo:nil]; -} - -#pragma mark - - -- (void)resetSocket -{ - OWSAssertIsOnMainThread(); - - self.websocket.delegate = nil; - [self.websocket disconnect]; - self.websocket = nil; - [self.heartbeatTimer invalidate]; - self.heartbeatTimer = nil; -} - -- (void)closeWebSocket -{ - OWSAssertIsOnMainThread(); - - if (self.websocket) { - OWSLogWarn(@"closeWebSocket."); - } - - self.state = OWSWebSocketStateClosed; -} - -#pragma mark - Message Sending - -- (void)makeRequest:(TSRequest *)request success:(TSSocketMessageSuccess)success failure:(TSSocketMessageFailure)failure -{ - OWSAssertDebug(request); - OWSAssertDebug(request.HTTPMethod.length > 0); - OWSAssertDebug(success); - OWSAssertDebug(failure); - - TSSocketMessage *socketMessage = [[TSSocketMessage alloc] initWithRequestId:[Cryptography randomUInt64] - success:success - failure:failure]; - - @synchronized(self) { - self.socketMessageMap[@(socketMessage.requestId)] = socketMessage; - } - - NSURL *requestUrl = request.URL; - NSString *requestPath = [@"/" stringByAppendingString:requestUrl.path]; - - NSData *_Nullable jsonData = nil; - if (request.parameters) { - NSError *error; - jsonData = - [NSJSONSerialization dataWithJSONObject:request.parameters options:(NSJSONWritingOptions)0 error:&error]; - if (!jsonData || error) { - OWSFailDebug(@"could not serialize request JSON: %@", error); - [socketMessage didFailBeforeSending]; - return; - } - } - - WebSocketProtoWebSocketRequestMessageBuilder *requestBuilder = - [WebSocketProtoWebSocketRequestMessage builderWithVerb:request.HTTPMethod - path:requestPath - requestID:socketMessage.requestId]; - if (jsonData) { - // TODO: Do we need body & headers for requests with no parameters? - [requestBuilder setBody:jsonData]; - [requestBuilder addHeaders:@"content-type:application/json"]; - } - - for (NSString *headerField in request.allHTTPHeaderFields) { - NSString *headerValue = request.allHTTPHeaderFields[headerField]; - - OWSAssertDebug([headerField isKindOfClass:[NSString class]]); - OWSAssertDebug([headerValue isKindOfClass:[NSString class]]); - [requestBuilder addHeaders:[NSString stringWithFormat:@"%@:%@", headerField, headerValue]]; - } - - NSError *error; - WebSocketProtoWebSocketRequestMessage *_Nullable requestProto = [requestBuilder buildAndReturnError:&error]; - if (!requestProto || error) { - OWSFailDebug(@"could not build proto: %@", error); - return; - } - - WebSocketProtoWebSocketMessageBuilder *messageBuilder = - [WebSocketProtoWebSocketMessage builderWithType:WebSocketProtoWebSocketMessageTypeRequest]; - [messageBuilder setRequest:requestProto]; - - NSData *_Nullable messageData = [messageBuilder buildSerializedDataAndReturnError:&error]; - if (!messageData || error) { - OWSFailDebug(@"could not serialize proto: %@.", error); - [socketMessage didFailBeforeSending]; - return; - } - - if (!self.canMakeRequests) { - OWSLogError(@"makeRequest: socket not open."); - [socketMessage didFailBeforeSending]; - return; - } - - BOOL wasScheduled = [self.websocket writeData:messageData error:&error]; - if (!wasScheduled || error) { - OWSFailDebug(@"could not send socket request: %@", error); - [socketMessage didFailBeforeSending]; - return; - } - OWSLogInfo(@"making request: %llu, %@: %@, jsonData.length: %zd", - socketMessage.requestId, - request.HTTPMethod, - requestPath, - jsonData.length); - - const int64_t kSocketTimeoutSeconds = 10; - __weak TSSocketMessage *weakSocketMessage = socketMessage; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kSocketTimeoutSeconds * NSEC_PER_SEC), - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - ^{ - [weakSocketMessage timeoutIfNecessary]; - }); -} - -- (void)processWebSocketResponseMessage:(WebSocketProtoWebSocketResponseMessage *)message -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(message); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self processWebSocketResponseMessageAsync:message]; - }); -} - -- (void)processWebSocketResponseMessageAsync:(WebSocketProtoWebSocketResponseMessage *)message -{ - OWSAssertDebug(message); - - OWSLogInfo(@"received WebSocket response requestId: %llu, status: %u", message.requestID, message.status); - - DispatchMainThreadSafe(^{ - [self requestSocketAliveForAtLeastSeconds:kMakeRequestKeepSocketAliveDurationSeconds]; - }); - - UInt64 requestId = message.requestID; - UInt32 responseStatus = message.status; - NSString *_Nullable responseMessage; - if (message.hasMessage) { - responseMessage = message.message; - } - NSData *_Nullable responseData; - if (message.hasBody) { - responseData = message.body; - } - - BOOL hasValidResponse = YES; - id responseObject = responseData; - if (responseData) { - NSError *error; - id _Nullable responseJson = - [NSJSONSerialization JSONObjectWithData:responseData options:(NSJSONReadingOptions)0 error:&error]; - if (!responseJson || error) { - OWSFailDebug(@"could not parse WebSocket response JSON: %@.", error); - hasValidResponse = NO; - } else { - responseObject = responseJson; - } - } - - TSSocketMessage *_Nullable socketMessage; - @synchronized(self) { - socketMessage = self.socketMessageMap[@(requestId)]; - [self.socketMessageMap removeObjectForKey:@(requestId)]; - } - - if (!socketMessage) { - OWSLogError(@"received response to unknown request."); - } else { - BOOL hasSuccessStatus = 200 <= responseStatus && responseStatus <= 299; - BOOL didSucceed = hasSuccessStatus && hasValidResponse; - if (didSucceed) { - [self.tsAccountManager setIsDeregistered:NO]; - - [socketMessage didSucceedWithResponseObject:responseObject]; - } else { - if (responseStatus == 403) { - // This should be redundant with our check for the socket - // failing due to 403, but let's be thorough. - if (self.tsAccountManager.isRegisteredAndReady) { - [self.tsAccountManager setIsDeregistered:YES]; - } else { - OWSFailDebug(@"Ignoring auth failure; not registered and ready."); - } - } - - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageResponseFailed, - NSLocalizedString( - @"ERROR_DESCRIPTION_RESPONSE_FAILED", @"Error indicating that a socket response failed.")); - [socketMessage didFailWithStatusCode:(NSInteger)responseStatus responseData:responseData error:error]; - } - } -} - -- (void)failAllPendingSocketMessagesIfNecessary -{ - if (!self.canMakeRequests) { - [self failAllPendingSocketMessages]; - } -} - -- (void)failAllPendingSocketMessages -{ - NSArray *socketMessages; - @synchronized(self) { - socketMessages = self.socketMessageMap.allValues; - [self.socketMessageMap removeAllObjects]; - } - - OWSLogInfo(@"failAllPendingSocketMessages: %zd.", socketMessages.count); - - for (TSSocketMessage *socketMessage in socketMessages) { - [socketMessage didFailBeforeSending]; - } -} - -#pragma mark - SSKWebSocketDelegate - -- (void)websocketDidConnectWithSocket:(id)websocket -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(websocket); - if (websocket != self.websocket) { - // Ignore events from obsolete web sockets. - return; - } - - self.state = OWSWebSocketStateOpen; - - // If socket opens, we know we're not de-registered. - [self.tsAccountManager setIsDeregistered:NO]; - - [self.outageDetection reportConnectionSuccess]; -} - -- (void)websocketDidDisconnectWithSocket:(id)websocket error:(nullable NSError *)error -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(websocket); - if (websocket != self.websocket) { - // Ignore events from obsolete web sockets. - return; - } - - OWSLogError(@"Websocket did fail with error: %@", error); - if ([error.domain isEqualToString:SSKWebSocketError.errorDomain]) { - NSNumber *_Nullable statusCode = error.userInfo[SSKWebSocketError.kStatusCodeKey]; - if (statusCode.unsignedIntegerValue == 403) { - if (self.tsAccountManager.isRegisteredAndReady) { - [self.tsAccountManager setIsDeregistered:YES]; - } else { - OWSLogWarn(@"Ignoring auth failure; not registered and ready."); - } - } - } - - [self handleSocketFailure]; -} - -- (void)websocketDidReceiveDataWithSocket:(id)websocket data:(NSData *)data -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(websocket); - - if (websocket != self.websocket) { - // Ignore events from obsolete web sockets. - return; - } - - // If we receive a response, we know we're not de-registered. - [self.tsAccountManager setIsDeregistered:NO]; - - NSError *error; - WebSocketProtoWebSocketMessage *_Nullable wsMessage = [WebSocketProtoWebSocketMessage parseData:data error:&error]; - if (!wsMessage || error) { - OWSFailDebug(@"could not parse proto: %@", error); - return; - } - - if (wsMessage.type == WebSocketProtoWebSocketMessageTypeRequest) { - [self processWebSocketRequestMessage:wsMessage.request]; - } else if (wsMessage.type == WebSocketProtoWebSocketMessageTypeResponse) { - [self processWebSocketResponseMessage:wsMessage.response]; - } else { - OWSLogWarn(@"webSocket:didReceiveMessage: unknown."); - } -} - -#pragma mark - - -- (dispatch_queue_t)serialQueue -{ - static dispatch_queue_t _serialQueue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _serialQueue = dispatch_queue_create("org.signal.websocket", DISPATCH_QUEUE_SERIAL); - }); - - return _serialQueue; -} - -- (void)processWebSocketRequestMessage:(WebSocketProtoWebSocketRequestMessage *)message -{ - OWSAssertIsOnMainThread(); - - OWSLogInfo(@"Got message with verb: %@ and path: %@", message.verb, message.path); - - // If we receive a message over the socket while the app is in the background, - // prolong how long the socket stays open. - [self requestSocketAliveForAtLeastSeconds:kBackgroundKeepSocketAliveDurationSeconds]; - - if ([message.path isEqualToString:@"/api/v1/message"] && [message.verb isEqualToString:@"PUT"]) { - - __block OWSBackgroundTask *_Nullable backgroundTask = - [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - - dispatch_async(self.serialQueue, ^{ - BOOL success = NO; - @try { - BOOL useSignalingKey = [message.headers containsObject:@"X-Signal-Key: true"]; - NSData *_Nullable decryptedPayload; - if (useSignalingKey) { - NSString *_Nullable signalingKey = TSAccountManager.signalingKey; - OWSAssertDebug(signalingKey); - decryptedPayload = - [Cryptography decryptAppleMessagePayload:message.body withSignalingKey:signalingKey]; - } else { - OWSAssertDebug([message.headers containsObject:@"X-Signal-Key: false"]); - - decryptedPayload = message.body; - } - - if (!decryptedPayload) { - OWSLogWarn(@"Failed to decrypt incoming payload or bad HMAC"); - } else { - [self.messageReceiver handleReceivedEnvelopeData:decryptedPayload]; - success = YES; - } - } @catch (NSException *exception) { - OWSFailDebug(@"Received an invalid envelope: %@", exception.debugDescription); - // TODO: Add analytics. - } - - if (!success) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread]; - [self.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage - transaction:transaction]; - }]; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - [self sendWebSocketMessageAcknowledgement:message]; - OWSAssertDebug(backgroundTask); - backgroundTask = nil; - }); - }); - } else if ([message.path isEqualToString:@"/api/v1/queue/empty"]) { - // Queue is drained. - - [self sendWebSocketMessageAcknowledgement:message]; - } else { - OWSLogWarn(@"Unsupported WebSocket Request"); - - [self sendWebSocketMessageAcknowledgement:message]; - } -} - -- (void)sendWebSocketMessageAcknowledgement:(WebSocketProtoWebSocketRequestMessage *)request -{ - OWSAssertIsOnMainThread(); - - NSError *error; - - WebSocketProtoWebSocketResponseMessageBuilder *responseBuilder = - [WebSocketProtoWebSocketResponseMessage builderWithRequestID:request.requestID status:200]; - [responseBuilder setMessage:@"OK"]; - WebSocketProtoWebSocketResponseMessage *_Nullable response = [responseBuilder buildAndReturnError:&error]; - if (!response || error) { - OWSFailDebug(@"could not build proto: %@", error); - return; - } - - WebSocketProtoWebSocketMessageBuilder *messageBuilder = - [WebSocketProtoWebSocketMessage builderWithType:WebSocketProtoWebSocketMessageTypeResponse]; - [messageBuilder setResponse:response]; - - NSData *_Nullable messageData = [messageBuilder buildSerializedDataAndReturnError:&error]; - if (!messageData || error) { - OWSFailDebug(@"could not serialize proto: %@", error); - return; - } - - [self.websocket writeData:messageData error:&error]; - if (error) { - OWSLogWarn(@"Error while trying to write on websocket %@", error); - [self handleSocketFailure]; - } -} - -- (void)cycleSocket -{ - OWSAssertIsOnMainThread(); - - [self closeWebSocket]; - - [self applyDesiredSocketState]; -} - -- (void)handleSocketFailure -{ - OWSAssertIsOnMainThread(); - - [self closeWebSocket]; - - if ([self shouldSocketBeOpen]) { - // If we should retry, use `ensureReconnect` to - // reconnect after a delay. - [self ensureReconnect]; - } else { - // Otherwise clean up and align state. - [self applyDesiredSocketState]; - } - - [self.outageDetection reportConnectionFailure]; -} - -- (void)webSocketHeartBeat -{ - OWSAssertIsOnMainThread(); - - if ([self shouldSocketBeOpen]) { - NSError *error; - [self.websocket writePingAndReturnError:&error]; - if (error) { - OWSLogWarn(@"Error in websocket heartbeat: %@", error.localizedDescription); - [self handleSocketFailure]; - } - } else { - OWSLogWarn(@"webSocketHeartBeat closing web socket"); - [self closeWebSocket]; - [self applyDesiredSocketState]; - } -} - -- (NSString *)webSocketAuthenticationString -{ - return [NSString stringWithFormat:@"?login=%@&password=%@", - [[TSAccountManager localNumber] stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"], - [TSAccountManager serverAuthToken]]; -} - -#pragma mark - Socket LifeCycle - -- (BOOL)shouldSocketBeOpen -{ - OWSAssertIsOnMainThread(); - - // Loki: Since we don't use web sockets, disable them - return NO; - - // Don't open socket in app extensions. - if (!CurrentAppContext().isMainApp) { - return NO; - } - - if (![self.tsAccountManager isRegisteredAndReady]) { - return NO; - } - - if (self.signalService.isCensorshipCircumventionActive) { - OWSLogWarn(@"Skipping opening of websocket due to censorship circumvention."); - return NO; - } - - if (self.appIsActive) { - // If app is active, keep web socket alive. - return YES; - } else if (self.backgroundKeepAliveUntilDate && [self.backgroundKeepAliveUntilDate timeIntervalSinceNow] > 0.f) { - OWSAssertDebug(self.backgroundKeepAliveTimer); - // If app is doing any work in the background, keep web socket alive. - return YES; - } else { - return NO; - } -} - -- (void)requestSocketAliveForAtLeastSeconds:(CGFloat)durationSeconds -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(durationSeconds > 0.f); - - if (self.appIsActive) { - // If app is active, clean up state used to keep socket alive in background. - [self clearBackgroundState]; - } else if (!self.backgroundKeepAliveUntilDate) { - OWSAssertDebug(!self.backgroundKeepAliveUntilDate); - OWSAssertDebug(!self.backgroundKeepAliveTimer); - - OWSLogInfo(@"activating socket in the background"); - - // Set up state used to keep socket alive in background. - self.backgroundKeepAliveUntilDate = [NSDate dateWithTimeIntervalSinceNow:durationSeconds]; - - // To be defensive, clean up any existing backgroundKeepAliveTimer. - [self.backgroundKeepAliveTimer invalidate]; - // Start a new timer that will fire every second while the socket is open in the background. - // This timer will ensure we close the websocket when the time comes. - self.backgroundKeepAliveTimer = [NSTimer weakScheduledTimerWithTimeInterval:1.f - target:self - selector:@selector(backgroundKeepAliveFired) - userInfo:nil - repeats:YES]; - // Additionally, we want the reconnect timer to work in the background too. - [[NSRunLoop mainRunLoop] addTimer:self.backgroundKeepAliveTimer forMode:NSDefaultRunLoopMode]; - - __weak typeof(self) weakSelf = self; - self.backgroundTask = - [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__ - completionBlock:^(BackgroundTaskState backgroundTaskState) { - OWSAssertIsOnMainThread(); - __strong typeof(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - if (backgroundTaskState == BackgroundTaskState_Expired) { - [strongSelf clearBackgroundState]; - } - [strongSelf applyDesiredSocketState]; - }]; - } else { - OWSAssertDebug(self.backgroundKeepAliveUntilDate); - OWSAssertDebug(self.backgroundKeepAliveTimer); - OWSAssertDebug([self.backgroundKeepAliveTimer isValid]); - - if ([self.backgroundKeepAliveUntilDate timeIntervalSinceNow] < durationSeconds) { - // Update state used to keep socket alive in background. - self.backgroundKeepAliveUntilDate = [NSDate dateWithTimeIntervalSinceNow:durationSeconds]; - } - } - - [self applyDesiredSocketState]; -} - -- (void)backgroundKeepAliveFired -{ - OWSAssertIsOnMainThread(); - - [self applyDesiredSocketState]; -} - -- (void)requestSocketOpen -{ - DispatchMainThreadSafe(^{ - [self observeNotificationsIfNecessary]; - - // If the app is active and the user is registered, this will - // simply open the websocket. - // - // If the app is inactive, it will open the websocket for a - // period of time. - [self requestSocketAliveForAtLeastSeconds:kBackgroundOpenSocketDurationSeconds]; - }); -} - -// This method aligns the socket state with the "desired" socket state. -- (void)applyDesiredSocketState -{ - OWSAssertIsOnMainThread(); - -#ifdef DEBUG - if (CurrentAppContext().isRunningTests) { - OWSLogWarn(@"Suppressing socket in tests."); - return; - } -#endif - - if (!AppReadiness.isAppReady) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - [self applyDesiredSocketState]; - }]; - }); - return; - } - - if ([self shouldSocketBeOpen]) { - if (self.state != OWSWebSocketStateOpen) { - // If we want the socket to be open and it's not open, - // start up the reconnect timer immediately (don't wait for an error). - // There's little harm in it and this will make us more robust to edge - // cases. - [self ensureReconnect]; - } - [self ensureWebsocketIsOpen]; - } else { - [self clearBackgroundState]; - [self clearReconnect]; - [self closeWebSocket]; - } -} - -- (void)clearBackgroundState -{ - OWSAssertIsOnMainThread(); - - self.backgroundKeepAliveUntilDate = nil; - [self.backgroundKeepAliveTimer invalidate]; - self.backgroundKeepAliveTimer = nil; - self.backgroundTask = nil; -} - -#pragma mark - Reconnect - -- (void)ensureReconnect -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug([self shouldSocketBeOpen]); - - if (self.reconnectTimer) { - OWSAssertDebug([self.reconnectTimer isValid]); - } else { - // TODO: It'd be nice to do exponential backoff. - self.reconnectTimer = [NSTimer timerWithTimeInterval:kSocketReconnectDelaySeconds - target:self - selector:@selector(applyDesiredSocketState) - userInfo:nil - repeats:YES]; - // Additionally, we want the reconnect timer to work in the background too. - [[NSRunLoop mainRunLoop] addTimer:self.reconnectTimer forMode:NSDefaultRunLoopMode]; - } -} - -- (void)clearReconnect -{ - OWSAssertIsOnMainThread(); - - [self.reconnectTimer invalidate]; - self.reconnectTimer = nil; -} - -#pragma mark - Notifications - -- (void)applicationDidBecomeActive:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - self.appIsActive = YES; - [self applyDesiredSocketState]; -} - -- (void)applicationWillResignActive:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - self.appIsActive = NO; - // TODO: It might be nice to use `requestSocketAliveForAtLeastSeconds:` to - // keep the socket open for a few seconds after the app is - // inactivated. - [self applyDesiredSocketState]; -} - -- (void)registrationStateDidChange:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [self applyDesiredSocketState]; -} - -- (void)isCensorshipCircumventionActiveDidChange:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [self applyDesiredSocketState]; -} - -- (void)deviceListUpdateModifiedDeviceList:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [self cycleSocket]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.h b/SignalServiceKit/src/Network/WebSockets/TSSocketManager.h deleted file mode 100644 index 28e17d17f..000000000 --- a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSWebSocket.h" - -NS_ASSUME_NONNULL_BEGIN - -@class TSRequest; - -@interface TSSocketManager : NSObject - -@property (class, readonly, nonatomic) TSSocketManager *shared; - -- (instancetype)init NS_DESIGNATED_INITIALIZER; - -// Returns the "best" state of any of the sockets. -// -// We surface the socket state in various places in the UI. -// We generally are trying to indicate/help resolve network -// connectivity issues. We want to show the "best" or "highest" -// socket state of the sockets. e.g. the UI should reflect -// "open" if any of the sockets is open. -- (OWSWebSocketState)highestSocketState; - -// If the app is in the foreground, we'll try to open the socket unless it's already -// open or connecting. -// -// If the app is in the background, we'll try to open the socket unless it's already -// open or connecting _and_ keep it open for at least N seconds. -// If the app is in the background and the socket is already open or connecting this -// might prolong how long we keep the socket open. -// -// This method can be called from any thread. -- (void)requestSocketOpen; - -// This can be used to force the socket to close and re-open, if it is open. -- (void)cycleSocket; - -#pragma mark - Message Sending - -- (BOOL)canMakeRequests; - -- (void)makeRequest:(TSRequest *)request - success:(TSSocketMessageSuccess)success - failure:(TSSocketMessageFailure)failure; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m b/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m deleted file mode 100644 index cd255a3cd..000000000 --- a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSSocketManager.h" -#import "SSKEnvironment.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface TSSocketManager () - -@property (nonatomic) OWSWebSocket *websocket; - -@end - -#pragma mark - - -@implementation TSSocketManager - -- (instancetype)init -{ - self = [super init]; - - if (!self) { - return self; - } - - OWSAssertIsOnMainThread(); - - _websocket = [[OWSWebSocket alloc] init]; - - OWSSingletonAssert(); - - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -+ (instancetype)shared -{ - OWSAssert(SSKEnvironment.shared.socketManager); - - return SSKEnvironment.shared.socketManager; -} - -- (BOOL)canMakeRequests -{ - return self.websocket.canMakeRequests; -} - -- (void)makeRequest:(TSRequest *)request - success:(TSSocketMessageSuccess)success - failure:(TSSocketMessageFailure)failure -{ - [self.websocket makeRequest:request success:success failure:failure]; -} - -- (void)requestSocketOpen -{ - [self.websocket requestSocketOpen]; -} - -- (void)cycleSocket -{ - [self.websocket cycleSocket]; -} - -- (OWSWebSocketState)highestSocketState -{ - return self.websocket.state; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h b/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h deleted file mode 100644 index 1ac159d43..000000000 --- a/SignalServiceKit/src/Protocols/ContactsManagerProtocol.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class CNContact; -@class Contact; -@class PhoneNumber; -@class SignalAccount; -@class UIImage; -@class YapDatabaseReadTransaction; - -@protocol ContactsManagerProtocol - -- (NSString *)displayNameForPhoneIdentifier:(nullable NSString *)recipientId; -- (NSString *)displayNameForPhoneIdentifier:(NSString *_Nullable)recipientId - transaction:(YapDatabaseReadTransaction *)transaction; -- (NSArray *)signalAccounts; - -- (BOOL)isSystemContact:(NSString *)recipientId; -- (BOOL)isSystemContactWithSignalAccount:(NSString *)recipientId; - -- (NSComparisonResult)compareSignalAccount:(SignalAccount *)left - withSignalAccount:(SignalAccount *)right NS_SWIFT_NAME(compare(signalAccount:with:)); - -#pragma mark - CNContacts - -- (nullable CNContact *)cnContactWithId:(nullable NSString *)contactId; -- (nullable NSData *)avatarDataForCNContactId:(nullable NSString *)contactId; -- (nullable UIImage *)avatarImageForCNContactId:(nullable NSString *)contactId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Protocols/NotificationsProtocol.h b/SignalServiceKit/src/Protocols/NotificationsProtocol.h deleted file mode 100644 index 423d607e7..000000000 --- a/SignalServiceKit/src/Protocols/NotificationsProtocol.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class TSErrorMessage; -@class TSIncomingMessage; -@class TSThread; -@class YapDatabaseReadTransaction; -@class YapDatabaseReadWriteTransaction; - -@protocol ContactsManagerProtocol; - -@protocol NotificationsProtocol - -- (void)notifyUserForIncomingMessage:(TSIncomingMessage *)incomingMessage - inThread:(TSThread *)thread - transaction:(YapDatabaseReadTransaction *)transaction; - -- (void)notifyUserForErrorMessage:(TSErrorMessage *)error - thread:(TSThread *)thread - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)notifyUserForThreadlessErrorMessage:(TSErrorMessage *)error - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)clearAllNotifications; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Protocols/OWSCallMessageHandler.h b/SignalServiceKit/src/Protocols/OWSCallMessageHandler.h deleted file mode 100644 index 2ab05e474..000000000 --- a/SignalServiceKit/src/Protocols/OWSCallMessageHandler.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class SSKProtoCallMessageAnswer; -@class SSKProtoCallMessageBusy; -@class SSKProtoCallMessageHangup; -@class SSKProtoCallMessageIceUpdate; -@class SSKProtoCallMessageOffer; - -@protocol OWSCallMessageHandler - -- (void)receivedOffer:(SSKProtoCallMessageOffer *)offer - fromCallerId:(NSString *)callerId NS_SWIFT_NAME(receivedOffer(_:from:)); -- (void)receivedAnswer:(SSKProtoCallMessageAnswer *)answer - fromCallerId:(NSString *)callerId NS_SWIFT_NAME(receivedAnswer(_:from:)); -- (void)receivedIceUpdate:(SSKProtoCallMessageIceUpdate *)iceUpdate - fromCallerId:(NSString *)callerId NS_SWIFT_NAME(receivedIceUpdate(_:from:)); -- (void)receivedHangup:(SSKProtoCallMessageHangup *)hangup - fromCallerId:(NSString *)callerId NS_SWIFT_NAME(receivedHangup(_:from:)); -- (void)receivedBusy:(SSKProtoCallMessageBusy *)busy - fromCallerId:(NSString *)callerId NS_SWIFT_NAME(receivedBusy(_:from:)); - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h deleted file mode 100644 index 01694420a..000000000 --- a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -@class OWSAES256Key; -@class TSThread; -@class YapDatabaseReadWriteTransaction; -@class OWSUserProfile; - -NS_ASSUME_NONNULL_BEGIN - -@protocol ProfileManagerProtocol - -- (OWSAES256Key *)localProfileKey; - -- (nullable NSString *)localProfileName; -- (nullable NSString *)profileNameForRecipientWithID:(NSString *)recipientID avoidingWriteTransaction:(BOOL)avoidWriteTransaction; -- (nullable NSString *)profileNameForRecipientWithID:(NSString *)recipientID; -- (nullable NSString *)profileNameForRecipientWithID:(NSString *)recipientID transaction:(YapDatabaseReadWriteTransaction *)transaction; -- (nullable NSString *)profilePictureURL; - -- (nullable NSData *)profileKeyDataForRecipientId:(NSString *)recipientId; -- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId; -- (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId avatarURL:(nullable NSString *)avatarURL; - -- (BOOL)isUserInProfileWhitelist:(NSString *)recipientId; - -- (BOOL)isThreadInProfileWhitelist:(TSThread *)thread; - -- (void)addUserToProfileWhitelist:(NSString *)recipientId; -- (void)addGroupIdToProfileWhitelist:(NSData *)groupId; -- (void)addThreadToProfileWhitelist:(TSThread *)thread; - -- (void)fetchLocalUsersProfile; - -- (void)fetchProfileForRecipientId:(NSString *)recipientId; - -- (void)updateProfileForContactWithID:(NSString *)contactID displayName:(NSString *)displayName with:(YapDatabaseReadWriteTransaction *)transaction; -- (void)updateServiceWithProfileName:(nullable NSString *)localProfileName avatarURL:(nullable NSString *)avatarURL; - -- (void)ensureLocalProfileCached; -- (void)ensureProfileCachedForContactWithID:(NSString *)contactID with:(YapDatabaseReadWriteTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Protocols/ProtoUtils.h b/SignalServiceKit/src/Protocols/ProtoUtils.h deleted file mode 100644 index 82c257338..000000000 --- a/SignalServiceKit/src/Protocols/ProtoUtils.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class SSKProtoCallMessageBuilder; -@class SSKProtoDataMessageBuilder; -@class TSThread; - -@interface ProtoUtils : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -+ (void)addLocalProfileKeyIfNecessary:(TSThread *)thread - recipientId:(NSString *_Nullable)recipientId - dataMessageBuilder:(SSKProtoDataMessageBuilder *)dataMessageBuilder; - -+ (void)addLocalProfileKeyToDataMessageBuilder:(SSKProtoDataMessageBuilder *)dataMessageBuilder; - -+ (void)addLocalProfileKeyIfNecessary:(TSThread *)thread - recipientId:(NSString *)recipientId - callMessageBuilder:(SSKProtoCallMessageBuilder *)callMessageBuilder; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Protocols/ProtoUtils.m b/SignalServiceKit/src/Protocols/ProtoUtils.m deleted file mode 100644 index 41279cb1e..000000000 --- a/SignalServiceKit/src/Protocols/ProtoUtils.m +++ /dev/null @@ -1,97 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "ProtoUtils.h" -#import "ProfileManagerProtocol.h" -#import "SSKEnvironment.h" -#import "TSThread.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation ProtoUtils - -#pragma mark - Dependencies - -+ (id)profileManager { - return SSKEnvironment.shared.profileManager; -} - -+ (OWSAES256Key *)localProfileKey -{ - return self.profileManager.localProfileKey; -} - -#pragma mark - - -+ (BOOL)shouldMessageHaveLocalProfileKey:(TSThread *)thread recipientId:(NSString *_Nullable)recipientId -{ - OWSAssertDebug(thread); - - // For 1:1 threads, we want to include the profile key IFF the - // contact is in the whitelist. - // - // For Group threads, we want to include the profile key IFF the - // recipient OR the group is in the whitelist. - if (recipientId.length > 0 && [self.profileManager isUserInProfileWhitelist:recipientId]) { - return YES; - } else if ([self.profileManager isThreadInProfileWhitelist:thread]) { - return YES; - } - - return NO; -} - -+ (void)addLocalProfileKeyIfNecessary:(TSThread *)thread - recipientId:(NSString *_Nullable)recipientId - dataMessageBuilder:(SSKProtoDataMessageBuilder *)dataMessageBuilder -{ - OWSAssertDebug(thread); - OWSAssertDebug(dataMessageBuilder); - - if ([self shouldMessageHaveLocalProfileKey:thread recipientId:recipientId]) { - [dataMessageBuilder setProfileKey:self.localProfileKey.keyData]; - - if (recipientId.length > 0) { - // Once we've shared our profile key with a user (perhaps due to being - // a member of a whitelisted group), make sure they're whitelisted. - // FIXME PERF avoid this dispatch. It's going to happen for *each* recipient in a group message. - dispatch_async(dispatch_get_main_queue(), ^{ - [self.profileManager addUserToProfileWhitelist:recipientId]; - }); - } - } -} - -+ (void)addLocalProfileKeyToDataMessageBuilder:(SSKProtoDataMessageBuilder *)dataMessageBuilder -{ - OWSAssertDebug(dataMessageBuilder); - - [dataMessageBuilder setProfileKey:self.localProfileKey.keyData]; -} - -+ (void)addLocalProfileKeyIfNecessary:(TSThread *)thread - recipientId:(NSString *)recipientId - callMessageBuilder:(SSKProtoCallMessageBuilder *)callMessageBuilder -{ - OWSAssertDebug(thread); - OWSAssertDebug(recipientId.length > 0); - OWSAssertDebug(callMessageBuilder); - - if ([self shouldMessageHaveLocalProfileKey:thread recipientId:recipientId]) { - [callMessageBuilder setProfileKey:self.localProfileKey.keyData]; - - // Once we've shared our profile key with a user (perhaps due to being - // a member of a whitelisted group), make sure they're whitelisted. - // FIXME PERF avoid this dispatch. It's going to happen for *each* recipient in a group message. - dispatch_async(dispatch_get_main_queue(), ^{ - [self.profileManager addUserToProfileWhitelist:recipientId]; - }); - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Protos/Generated/Fingerprint.pb.swift b/SignalServiceKit/src/Protos/Generated/Fingerprint.pb.swift deleted file mode 100644 index 7ebca2ef2..000000000 --- a/SignalServiceKit/src/Protos/Generated/Fingerprint.pb.swift +++ /dev/null @@ -1,164 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: Fingerprint.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -/// iOS - since we use a modern proto-compiler, we must specify -/// the legacy proto format. - -import Foundation -import SwiftProtobuf - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -struct FingerprintProtos_LogicalFingerprint { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var identityData: Data { - get {return _identityData ?? SwiftProtobuf.Internal.emptyData} - set {_identityData = newValue} - } - /// Returns true if `identityData` has been explicitly set. - var hasIdentityData: Bool {return self._identityData != nil} - /// Clears the value of `identityData`. Subsequent reads from it will return its default value. - mutating func clearIdentityData() {self._identityData = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _identityData: Data? = nil -} - -struct FingerprintProtos_LogicalFingerprints { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var version: UInt32 { - get {return _version ?? 0} - set {_version = newValue} - } - /// Returns true if `version` has been explicitly set. - var hasVersion: Bool {return self._version != nil} - /// Clears the value of `version`. Subsequent reads from it will return its default value. - mutating func clearVersion() {self._version = nil} - - /// @required - var localFingerprint: FingerprintProtos_LogicalFingerprint { - get {return _localFingerprint ?? FingerprintProtos_LogicalFingerprint()} - set {_localFingerprint = newValue} - } - /// Returns true if `localFingerprint` has been explicitly set. - var hasLocalFingerprint: Bool {return self._localFingerprint != nil} - /// Clears the value of `localFingerprint`. Subsequent reads from it will return its default value. - mutating func clearLocalFingerprint() {self._localFingerprint = nil} - - /// @required - var remoteFingerprint: FingerprintProtos_LogicalFingerprint { - get {return _remoteFingerprint ?? FingerprintProtos_LogicalFingerprint()} - set {_remoteFingerprint = newValue} - } - /// Returns true if `remoteFingerprint` has been explicitly set. - var hasRemoteFingerprint: Bool {return self._remoteFingerprint != nil} - /// Clears the value of `remoteFingerprint`. Subsequent reads from it will return its default value. - mutating func clearRemoteFingerprint() {self._remoteFingerprint = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _version: UInt32? = nil - fileprivate var _localFingerprint: FingerprintProtos_LogicalFingerprint? = nil - fileprivate var _remoteFingerprint: FingerprintProtos_LogicalFingerprint? = nil -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "FingerprintProtos" - -extension FingerprintProtos_LogicalFingerprint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".LogicalFingerprint" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "identityData"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self._identityData) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._identityData { - try visitor.visitSingularBytesField(value: v, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: FingerprintProtos_LogicalFingerprint, rhs: FingerprintProtos_LogicalFingerprint) -> Bool { - if lhs._identityData != rhs._identityData {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension FingerprintProtos_LogicalFingerprints: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".LogicalFingerprints" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "version"), - 2: .same(proto: "localFingerprint"), - 3: .same(proto: "remoteFingerprint"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularUInt32Field(value: &self._version) - case 2: try decoder.decodeSingularMessageField(value: &self._localFingerprint) - case 3: try decoder.decodeSingularMessageField(value: &self._remoteFingerprint) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._version { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) - } - if let v = self._localFingerprint { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } - if let v = self._remoteFingerprint { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: FingerprintProtos_LogicalFingerprints, rhs: FingerprintProtos_LogicalFingerprints) -> Bool { - if lhs._version != rhs._version {return false} - if lhs._localFingerprint != rhs._localFingerprint {return false} - if lhs._remoteFingerprint != rhs._remoteFingerprint {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/SignalServiceKit/src/Protos/Generated/FingerprintProto.swift b/SignalServiceKit/src/Protos/Generated/FingerprintProto.swift deleted file mode 100644 index b841ce062..000000000 --- a/SignalServiceKit/src/Protos/Generated/FingerprintProto.swift +++ /dev/null @@ -1,235 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -// WARNING: This code is generated. Only edit within the markers. - -public enum FingerprintProtoError: Error { - case invalidProtobuf(description: String) -} - -// MARK: - FingerprintProtoLogicalFingerprint - -@objc public class FingerprintProtoLogicalFingerprint: NSObject { - - // MARK: - FingerprintProtoLogicalFingerprintBuilder - - @objc public class func builder(identityData: Data) -> FingerprintProtoLogicalFingerprintBuilder { - return FingerprintProtoLogicalFingerprintBuilder(identityData: identityData) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> FingerprintProtoLogicalFingerprintBuilder { - let builder = FingerprintProtoLogicalFingerprintBuilder(identityData: identityData) - return builder - } - - @objc public class FingerprintProtoLogicalFingerprintBuilder: NSObject { - - private var proto = FingerprintProtos_LogicalFingerprint() - - @objc fileprivate override init() {} - - @objc fileprivate init(identityData: Data) { - super.init() - - setIdentityData(identityData) - } - - @objc public func setIdentityData(_ valueParam: Data) { - proto.identityData = valueParam - } - - @objc public func build() throws -> FingerprintProtoLogicalFingerprint { - return try FingerprintProtoLogicalFingerprint.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try FingerprintProtoLogicalFingerprint.parseProto(proto).serializedData() - } - } - - fileprivate let proto: FingerprintProtos_LogicalFingerprint - - @objc public let identityData: Data - - private init(proto: FingerprintProtos_LogicalFingerprint, - identityData: Data) { - self.proto = proto - self.identityData = identityData - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> FingerprintProtoLogicalFingerprint { - let proto = try FingerprintProtos_LogicalFingerprint(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: FingerprintProtos_LogicalFingerprint) throws -> FingerprintProtoLogicalFingerprint { - guard proto.hasIdentityData else { - throw FingerprintProtoError.invalidProtobuf(description: "\(logTag) missing required field: identityData") - } - let identityData = proto.identityData - - // MARK: - Begin Validation Logic for FingerprintProtoLogicalFingerprint - - - // MARK: - End Validation Logic for FingerprintProtoLogicalFingerprint - - - let result = FingerprintProtoLogicalFingerprint(proto: proto, - identityData: identityData) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension FingerprintProtoLogicalFingerprint { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension FingerprintProtoLogicalFingerprint.FingerprintProtoLogicalFingerprintBuilder { - @objc public func buildIgnoringErrors() -> FingerprintProtoLogicalFingerprint? { - return try! self.build() - } -} - -#endif - -// MARK: - FingerprintProtoLogicalFingerprints - -@objc public class FingerprintProtoLogicalFingerprints: NSObject { - - // MARK: - FingerprintProtoLogicalFingerprintsBuilder - - @objc public class func builder(version: UInt32, localFingerprint: FingerprintProtoLogicalFingerprint, remoteFingerprint: FingerprintProtoLogicalFingerprint) -> FingerprintProtoLogicalFingerprintsBuilder { - return FingerprintProtoLogicalFingerprintsBuilder(version: version, localFingerprint: localFingerprint, remoteFingerprint: remoteFingerprint) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> FingerprintProtoLogicalFingerprintsBuilder { - let builder = FingerprintProtoLogicalFingerprintsBuilder(version: version, localFingerprint: localFingerprint, remoteFingerprint: remoteFingerprint) - return builder - } - - @objc public class FingerprintProtoLogicalFingerprintsBuilder: NSObject { - - private var proto = FingerprintProtos_LogicalFingerprints() - - @objc fileprivate override init() {} - - @objc fileprivate init(version: UInt32, localFingerprint: FingerprintProtoLogicalFingerprint, remoteFingerprint: FingerprintProtoLogicalFingerprint) { - super.init() - - setVersion(version) - setLocalFingerprint(localFingerprint) - setRemoteFingerprint(remoteFingerprint) - } - - @objc public func setVersion(_ valueParam: UInt32) { - proto.version = valueParam - } - - @objc public func setLocalFingerprint(_ valueParam: FingerprintProtoLogicalFingerprint) { - proto.localFingerprint = valueParam.proto - } - - @objc public func setRemoteFingerprint(_ valueParam: FingerprintProtoLogicalFingerprint) { - proto.remoteFingerprint = valueParam.proto - } - - @objc public func build() throws -> FingerprintProtoLogicalFingerprints { - return try FingerprintProtoLogicalFingerprints.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try FingerprintProtoLogicalFingerprints.parseProto(proto).serializedData() - } - } - - fileprivate let proto: FingerprintProtos_LogicalFingerprints - - @objc public let version: UInt32 - - @objc public let localFingerprint: FingerprintProtoLogicalFingerprint - - @objc public let remoteFingerprint: FingerprintProtoLogicalFingerprint - - private init(proto: FingerprintProtos_LogicalFingerprints, - version: UInt32, - localFingerprint: FingerprintProtoLogicalFingerprint, - remoteFingerprint: FingerprintProtoLogicalFingerprint) { - self.proto = proto - self.version = version - self.localFingerprint = localFingerprint - self.remoteFingerprint = remoteFingerprint - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> FingerprintProtoLogicalFingerprints { - let proto = try FingerprintProtos_LogicalFingerprints(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: FingerprintProtos_LogicalFingerprints) throws -> FingerprintProtoLogicalFingerprints { - guard proto.hasVersion else { - throw FingerprintProtoError.invalidProtobuf(description: "\(logTag) missing required field: version") - } - let version = proto.version - - guard proto.hasLocalFingerprint else { - throw FingerprintProtoError.invalidProtobuf(description: "\(logTag) missing required field: localFingerprint") - } - let localFingerprint = try FingerprintProtoLogicalFingerprint.parseProto(proto.localFingerprint) - - guard proto.hasRemoteFingerprint else { - throw FingerprintProtoError.invalidProtobuf(description: "\(logTag) missing required field: remoteFingerprint") - } - let remoteFingerprint = try FingerprintProtoLogicalFingerprint.parseProto(proto.remoteFingerprint) - - // MARK: - Begin Validation Logic for FingerprintProtoLogicalFingerprints - - - // MARK: - End Validation Logic for FingerprintProtoLogicalFingerprints - - - let result = FingerprintProtoLogicalFingerprints(proto: proto, - version: version, - localFingerprint: localFingerprint, - remoteFingerprint: remoteFingerprint) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension FingerprintProtoLogicalFingerprints { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension FingerprintProtoLogicalFingerprints.FingerprintProtoLogicalFingerprintsBuilder { - @objc public func buildIgnoringErrors() -> FingerprintProtoLogicalFingerprints? { - return try! self.build() - } -} - -#endif diff --git a/SignalServiceKit/src/Protos/Generated/Provisioning.pb.swift b/SignalServiceKit/src/Protos/Generated/Provisioning.pb.swift deleted file mode 100644 index 713fb00c9..000000000 --- a/SignalServiceKit/src/Protos/Generated/Provisioning.pb.swift +++ /dev/null @@ -1,254 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: Provisioning.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -//* -// Copyright (C) 2014-2016 Open Whisper Systems -// -// Licensed according to the LICENSE file in this repository. - -/// iOS - since we use a modern proto-compiler, we must specify -/// the legacy proto format. - -import Foundation -import SwiftProtobuf - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -struct ProvisioningProtos_ProvisionEnvelope { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var publicKey: Data { - get {return _publicKey ?? SwiftProtobuf.Internal.emptyData} - set {_publicKey = newValue} - } - /// Returns true if `publicKey` has been explicitly set. - var hasPublicKey: Bool {return self._publicKey != nil} - /// Clears the value of `publicKey`. Subsequent reads from it will return its default value. - mutating func clearPublicKey() {self._publicKey = nil} - - /// @required - var body: Data { - get {return _body ?? SwiftProtobuf.Internal.emptyData} - set {_body = newValue} - } - /// Returns true if `body` has been explicitly set. - var hasBody: Bool {return self._body != nil} - /// Clears the value of `body`. Subsequent reads from it will return its default value. - mutating func clearBody() {self._body = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _publicKey: Data? = nil - fileprivate var _body: Data? = nil -} - -struct ProvisioningProtos_ProvisionMessage { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var identityKeyPublic: Data { - get {return _identityKeyPublic ?? SwiftProtobuf.Internal.emptyData} - set {_identityKeyPublic = newValue} - } - /// Returns true if `identityKeyPublic` has been explicitly set. - var hasIdentityKeyPublic: Bool {return self._identityKeyPublic != nil} - /// Clears the value of `identityKeyPublic`. Subsequent reads from it will return its default value. - mutating func clearIdentityKeyPublic() {self._identityKeyPublic = nil} - - /// @required - var identityKeyPrivate: Data { - get {return _identityKeyPrivate ?? SwiftProtobuf.Internal.emptyData} - set {_identityKeyPrivate = newValue} - } - /// Returns true if `identityKeyPrivate` has been explicitly set. - var hasIdentityKeyPrivate: Bool {return self._identityKeyPrivate != nil} - /// Clears the value of `identityKeyPrivate`. Subsequent reads from it will return its default value. - mutating func clearIdentityKeyPrivate() {self._identityKeyPrivate = nil} - - /// @required - var number: String { - get {return _number ?? String()} - set {_number = newValue} - } - /// Returns true if `number` has been explicitly set. - var hasNumber: Bool {return self._number != nil} - /// Clears the value of `number`. Subsequent reads from it will return its default value. - mutating func clearNumber() {self._number = nil} - - /// @required - var provisioningCode: String { - get {return _provisioningCode ?? String()} - set {_provisioningCode = newValue} - } - /// Returns true if `provisioningCode` has been explicitly set. - var hasProvisioningCode: Bool {return self._provisioningCode != nil} - /// Clears the value of `provisioningCode`. Subsequent reads from it will return its default value. - mutating func clearProvisioningCode() {self._provisioningCode = nil} - - /// @required - var userAgent: String { - get {return _userAgent ?? String()} - set {_userAgent = newValue} - } - /// Returns true if `userAgent` has been explicitly set. - var hasUserAgent: Bool {return self._userAgent != nil} - /// Clears the value of `userAgent`. Subsequent reads from it will return its default value. - mutating func clearUserAgent() {self._userAgent = nil} - - /// @required - var profileKey: Data { - get {return _profileKey ?? SwiftProtobuf.Internal.emptyData} - set {_profileKey = newValue} - } - /// Returns true if `profileKey` has been explicitly set. - var hasProfileKey: Bool {return self._profileKey != nil} - /// Clears the value of `profileKey`. Subsequent reads from it will return its default value. - mutating func clearProfileKey() {self._profileKey = nil} - - /// @required - var readReceipts: Bool { - get {return _readReceipts ?? false} - set {_readReceipts = newValue} - } - /// Returns true if `readReceipts` has been explicitly set. - var hasReadReceipts: Bool {return self._readReceipts != nil} - /// Clears the value of `readReceipts`. Subsequent reads from it will return its default value. - mutating func clearReadReceipts() {self._readReceipts = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _identityKeyPublic: Data? = nil - fileprivate var _identityKeyPrivate: Data? = nil - fileprivate var _number: String? = nil - fileprivate var _provisioningCode: String? = nil - fileprivate var _userAgent: String? = nil - fileprivate var _profileKey: Data? = nil - fileprivate var _readReceipts: Bool? = nil -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "ProvisioningProtos" - -extension ProvisioningProtos_ProvisionEnvelope: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ProvisionEnvelope" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "publicKey"), - 2: .same(proto: "body"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self._publicKey) - case 2: try decoder.decodeSingularBytesField(value: &self._body) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._publicKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 1) - } - if let v = self._body { - try visitor.visitSingularBytesField(value: v, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: ProvisioningProtos_ProvisionEnvelope, rhs: ProvisioningProtos_ProvisionEnvelope) -> Bool { - if lhs._publicKey != rhs._publicKey {return false} - if lhs._body != rhs._body {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension ProvisioningProtos_ProvisionMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ProvisionMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "identityKeyPublic"), - 2: .same(proto: "identityKeyPrivate"), - 3: .same(proto: "number"), - 4: .same(proto: "provisioningCode"), - 5: .same(proto: "userAgent"), - 6: .same(proto: "profileKey"), - 7: .same(proto: "readReceipts"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self._identityKeyPublic) - case 2: try decoder.decodeSingularBytesField(value: &self._identityKeyPrivate) - case 3: try decoder.decodeSingularStringField(value: &self._number) - case 4: try decoder.decodeSingularStringField(value: &self._provisioningCode) - case 5: try decoder.decodeSingularStringField(value: &self._userAgent) - case 6: try decoder.decodeSingularBytesField(value: &self._profileKey) - case 7: try decoder.decodeSingularBoolField(value: &self._readReceipts) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._identityKeyPublic { - try visitor.visitSingularBytesField(value: v, fieldNumber: 1) - } - if let v = self._identityKeyPrivate { - try visitor.visitSingularBytesField(value: v, fieldNumber: 2) - } - if let v = self._number { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) - } - if let v = self._provisioningCode { - try visitor.visitSingularStringField(value: v, fieldNumber: 4) - } - if let v = self._userAgent { - try visitor.visitSingularStringField(value: v, fieldNumber: 5) - } - if let v = self._profileKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 6) - } - if let v = self._readReceipts { - try visitor.visitSingularBoolField(value: v, fieldNumber: 7) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: ProvisioningProtos_ProvisionMessage, rhs: ProvisioningProtos_ProvisionMessage) -> Bool { - if lhs._identityKeyPublic != rhs._identityKeyPublic {return false} - if lhs._identityKeyPrivate != rhs._identityKeyPrivate {return false} - if lhs._number != rhs._number {return false} - if lhs._provisioningCode != rhs._provisioningCode {return false} - if lhs._userAgent != rhs._userAgent {return false} - if lhs._profileKey != rhs._profileKey {return false} - if lhs._readReceipts != rhs._readReceipts {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/SignalServiceKit/src/Protos/Generated/ProvisioningProto.swift b/SignalServiceKit/src/Protos/Generated/ProvisioningProto.swift deleted file mode 100644 index 4b1f7c948..000000000 --- a/SignalServiceKit/src/Protos/Generated/ProvisioningProto.swift +++ /dev/null @@ -1,310 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -// WARNING: This code is generated. Only edit within the markers. - -public enum ProvisioningProtoError: Error { - case invalidProtobuf(description: String) -} - -// MARK: - ProvisioningProtoProvisionEnvelope - -@objc public class ProvisioningProtoProvisionEnvelope: NSObject { - - // MARK: - ProvisioningProtoProvisionEnvelopeBuilder - - @objc public class func builder(publicKey: Data, body: Data) -> ProvisioningProtoProvisionEnvelopeBuilder { - return ProvisioningProtoProvisionEnvelopeBuilder(publicKey: publicKey, body: body) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> ProvisioningProtoProvisionEnvelopeBuilder { - let builder = ProvisioningProtoProvisionEnvelopeBuilder(publicKey: publicKey, body: body) - return builder - } - - @objc public class ProvisioningProtoProvisionEnvelopeBuilder: NSObject { - - private var proto = ProvisioningProtos_ProvisionEnvelope() - - @objc fileprivate override init() {} - - @objc fileprivate init(publicKey: Data, body: Data) { - super.init() - - setPublicKey(publicKey) - setBody(body) - } - - @objc public func setPublicKey(_ valueParam: Data) { - proto.publicKey = valueParam - } - - @objc public func setBody(_ valueParam: Data) { - proto.body = valueParam - } - - @objc public func build() throws -> ProvisioningProtoProvisionEnvelope { - return try ProvisioningProtoProvisionEnvelope.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try ProvisioningProtoProvisionEnvelope.parseProto(proto).serializedData() - } - } - - fileprivate let proto: ProvisioningProtos_ProvisionEnvelope - - @objc public let publicKey: Data - - @objc public let body: Data - - private init(proto: ProvisioningProtos_ProvisionEnvelope, - publicKey: Data, - body: Data) { - self.proto = proto - self.publicKey = publicKey - self.body = body - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> ProvisioningProtoProvisionEnvelope { - let proto = try ProvisioningProtos_ProvisionEnvelope(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: ProvisioningProtos_ProvisionEnvelope) throws -> ProvisioningProtoProvisionEnvelope { - guard proto.hasPublicKey else { - throw ProvisioningProtoError.invalidProtobuf(description: "\(logTag) missing required field: publicKey") - } - let publicKey = proto.publicKey - - guard proto.hasBody else { - throw ProvisioningProtoError.invalidProtobuf(description: "\(logTag) missing required field: body") - } - let body = proto.body - - // MARK: - Begin Validation Logic for ProvisioningProtoProvisionEnvelope - - - // MARK: - End Validation Logic for ProvisioningProtoProvisionEnvelope - - - let result = ProvisioningProtoProvisionEnvelope(proto: proto, - publicKey: publicKey, - body: body) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension ProvisioningProtoProvisionEnvelope { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension ProvisioningProtoProvisionEnvelope.ProvisioningProtoProvisionEnvelopeBuilder { - @objc public func buildIgnoringErrors() -> ProvisioningProtoProvisionEnvelope? { - return try! self.build() - } -} - -#endif - -// MARK: - ProvisioningProtoProvisionMessage - -@objc public class ProvisioningProtoProvisionMessage: NSObject { - - // MARK: - ProvisioningProtoProvisionMessageBuilder - - @objc public class func builder(identityKeyPublic: Data, identityKeyPrivate: Data, number: String, provisioningCode: String, userAgent: String, profileKey: Data, readReceipts: Bool) -> ProvisioningProtoProvisionMessageBuilder { - return ProvisioningProtoProvisionMessageBuilder(identityKeyPublic: identityKeyPublic, identityKeyPrivate: identityKeyPrivate, number: number, provisioningCode: provisioningCode, userAgent: userAgent, profileKey: profileKey, readReceipts: readReceipts) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> ProvisioningProtoProvisionMessageBuilder { - let builder = ProvisioningProtoProvisionMessageBuilder(identityKeyPublic: identityKeyPublic, identityKeyPrivate: identityKeyPrivate, number: number, provisioningCode: provisioningCode, userAgent: userAgent, profileKey: profileKey, readReceipts: readReceipts) - return builder - } - - @objc public class ProvisioningProtoProvisionMessageBuilder: NSObject { - - private var proto = ProvisioningProtos_ProvisionMessage() - - @objc fileprivate override init() {} - - @objc fileprivate init(identityKeyPublic: Data, identityKeyPrivate: Data, number: String, provisioningCode: String, userAgent: String, profileKey: Data, readReceipts: Bool) { - super.init() - - setIdentityKeyPublic(identityKeyPublic) - setIdentityKeyPrivate(identityKeyPrivate) - setNumber(number) - setProvisioningCode(provisioningCode) - setUserAgent(userAgent) - setProfileKey(profileKey) - setReadReceipts(readReceipts) - } - - @objc public func setIdentityKeyPublic(_ valueParam: Data) { - proto.identityKeyPublic = valueParam - } - - @objc public func setIdentityKeyPrivate(_ valueParam: Data) { - proto.identityKeyPrivate = valueParam - } - - @objc public func setNumber(_ valueParam: String) { - proto.number = valueParam - } - - @objc public func setProvisioningCode(_ valueParam: String) { - proto.provisioningCode = valueParam - } - - @objc public func setUserAgent(_ valueParam: String) { - proto.userAgent = valueParam - } - - @objc public func setProfileKey(_ valueParam: Data) { - proto.profileKey = valueParam - } - - @objc public func setReadReceipts(_ valueParam: Bool) { - proto.readReceipts = valueParam - } - - @objc public func build() throws -> ProvisioningProtoProvisionMessage { - return try ProvisioningProtoProvisionMessage.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try ProvisioningProtoProvisionMessage.parseProto(proto).serializedData() - } - } - - fileprivate let proto: ProvisioningProtos_ProvisionMessage - - @objc public let identityKeyPublic: Data - - @objc public let identityKeyPrivate: Data - - @objc public let number: String - - @objc public let provisioningCode: String - - @objc public let userAgent: String - - @objc public let profileKey: Data - - @objc public let readReceipts: Bool - - private init(proto: ProvisioningProtos_ProvisionMessage, - identityKeyPublic: Data, - identityKeyPrivate: Data, - number: String, - provisioningCode: String, - userAgent: String, - profileKey: Data, - readReceipts: Bool) { - self.proto = proto - self.identityKeyPublic = identityKeyPublic - self.identityKeyPrivate = identityKeyPrivate - self.number = number - self.provisioningCode = provisioningCode - self.userAgent = userAgent - self.profileKey = profileKey - self.readReceipts = readReceipts - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> ProvisioningProtoProvisionMessage { - let proto = try ProvisioningProtos_ProvisionMessage(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: ProvisioningProtos_ProvisionMessage) throws -> ProvisioningProtoProvisionMessage { - guard proto.hasIdentityKeyPublic else { - throw ProvisioningProtoError.invalidProtobuf(description: "\(logTag) missing required field: identityKeyPublic") - } - let identityKeyPublic = proto.identityKeyPublic - - guard proto.hasIdentityKeyPrivate else { - throw ProvisioningProtoError.invalidProtobuf(description: "\(logTag) missing required field: identityKeyPrivate") - } - let identityKeyPrivate = proto.identityKeyPrivate - - guard proto.hasNumber else { - throw ProvisioningProtoError.invalidProtobuf(description: "\(logTag) missing required field: number") - } - let number = proto.number - - guard proto.hasProvisioningCode else { - throw ProvisioningProtoError.invalidProtobuf(description: "\(logTag) missing required field: provisioningCode") - } - let provisioningCode = proto.provisioningCode - - guard proto.hasUserAgent else { - throw ProvisioningProtoError.invalidProtobuf(description: "\(logTag) missing required field: userAgent") - } - let userAgent = proto.userAgent - - guard proto.hasProfileKey else { - throw ProvisioningProtoError.invalidProtobuf(description: "\(logTag) missing required field: profileKey") - } - let profileKey = proto.profileKey - - guard proto.hasReadReceipts else { - throw ProvisioningProtoError.invalidProtobuf(description: "\(logTag) missing required field: readReceipts") - } - let readReceipts = proto.readReceipts - - // MARK: - Begin Validation Logic for ProvisioningProtoProvisionMessage - - - // MARK: - End Validation Logic for ProvisioningProtoProvisionMessage - - - let result = ProvisioningProtoProvisionMessage(proto: proto, - identityKeyPublic: identityKeyPublic, - identityKeyPrivate: identityKeyPrivate, - number: number, - provisioningCode: provisioningCode, - userAgent: userAgent, - profileKey: profileKey, - readReceipts: readReceipts) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension ProvisioningProtoProvisionMessage { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension ProvisioningProtoProvisionMessage.ProvisioningProtoProvisionMessageBuilder { - @objc public func buildIgnoringErrors() -> ProvisioningProtoProvisionMessage? { - return try! self.build() - } -} - -#endif diff --git a/SignalServiceKit/src/Protos/Generated/SSKProto.swift b/SignalServiceKit/src/Protos/Generated/SSKProto.swift deleted file mode 100644 index 6b7e07e7f..000000000 --- a/SignalServiceKit/src/Protos/Generated/SSKProto.swift +++ /dev/null @@ -1,7075 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -// WARNING: This code is generated. Only edit within the markers. - -public enum SSKProtoError: Error { - case invalidProtobuf(description: String) -} - -// MARK: - SSKProtoEnvelope - -@objc public class SSKProtoEnvelope: NSObject { - - // MARK: - SSKProtoEnvelopeType - - @objc public enum SSKProtoEnvelopeType: Int32 { - case unknown = 0 - case ciphertext = 1 - case keyExchange = 2 - case prekeyBundle = 3 - case receipt = 5 - case unidentifiedSender = 6 - case closedGroupCiphertext = 7 - case fallbackMessage = 101 - } - - private class func SSKProtoEnvelopeTypeWrap(_ value: SignalServiceProtos_Envelope.TypeEnum) -> SSKProtoEnvelopeType { - switch value { - case .unknown: return .unknown - case .ciphertext: return .ciphertext - case .keyExchange: return .keyExchange - case .prekeyBundle: return .prekeyBundle - case .receipt: return .receipt - case .unidentifiedSender: return .unidentifiedSender - case .closedGroupCiphertext: return .closedGroupCiphertext - case .fallbackMessage: return .fallbackMessage - } - } - - private class func SSKProtoEnvelopeTypeUnwrap(_ value: SSKProtoEnvelopeType) -> SignalServiceProtos_Envelope.TypeEnum { - switch value { - case .unknown: return .unknown - case .ciphertext: return .ciphertext - case .keyExchange: return .keyExchange - case .prekeyBundle: return .prekeyBundle - case .receipt: return .receipt - case .unidentifiedSender: return .unidentifiedSender - case .closedGroupCiphertext: return .closedGroupCiphertext - case .fallbackMessage: return .fallbackMessage - } - } - - // MARK: - SSKProtoEnvelopeBuilder - - @objc public class func builder(type: SSKProtoEnvelopeType, timestamp: UInt64) -> SSKProtoEnvelopeBuilder { - return SSKProtoEnvelopeBuilder(type: type, timestamp: timestamp) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoEnvelopeBuilder { - let builder = SSKProtoEnvelopeBuilder(type: type, timestamp: timestamp) - if let _value = source { - builder.setSource(_value) - } - if hasSourceDevice { - builder.setSourceDevice(sourceDevice) - } - if let _value = relay { - builder.setRelay(_value) - } - if let _value = legacyMessage { - builder.setLegacyMessage(_value) - } - if let _value = content { - builder.setContent(_value) - } - if let _value = serverGuid { - builder.setServerGuid(_value) - } - if hasServerTimestamp { - builder.setServerTimestamp(serverTimestamp) - } - return builder - } - - @objc public class SSKProtoEnvelopeBuilder: NSObject { - - private var proto = SignalServiceProtos_Envelope() - - @objc fileprivate override init() {} - - @objc fileprivate init(type: SSKProtoEnvelopeType, timestamp: UInt64) { - super.init() - - setType(type) - setTimestamp(timestamp) - } - - @objc public func setType(_ valueParam: SSKProtoEnvelopeType) { - proto.type = SSKProtoEnvelopeTypeUnwrap(valueParam) - } - - @objc public func setSource(_ valueParam: String) { - proto.source = valueParam - } - - @objc public func setSourceDevice(_ valueParam: UInt32) { - proto.sourceDevice = valueParam - } - - @objc public func setRelay(_ valueParam: String) { - proto.relay = valueParam - } - - @objc public func setTimestamp(_ valueParam: UInt64) { - proto.timestamp = valueParam - } - - @objc public func setLegacyMessage(_ valueParam: Data) { - proto.legacyMessage = valueParam - } - - @objc public func setContent(_ valueParam: Data) { - proto.content = valueParam - } - - @objc public func setServerGuid(_ valueParam: String) { - proto.serverGuid = valueParam - } - - @objc public func setServerTimestamp(_ valueParam: UInt64) { - proto.serverTimestamp = valueParam - } - - @objc public func build() throws -> SSKProtoEnvelope { - return try SSKProtoEnvelope.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoEnvelope.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_Envelope - - @objc public let type: SSKProtoEnvelopeType - - @objc public let timestamp: UInt64 - - @objc public var source: String? { - guard proto.hasSource else { - return nil - } - return proto.source - } - @objc public var hasSource: Bool { - return proto.hasSource - } - - @objc public var sourceDevice: UInt32 { - return proto.sourceDevice - } - @objc public var hasSourceDevice: Bool { - return proto.hasSourceDevice - } - - @objc public var relay: String? { - guard proto.hasRelay else { - return nil - } - return proto.relay - } - @objc public var hasRelay: Bool { - return proto.hasRelay - } - - @objc public var legacyMessage: Data? { - guard proto.hasLegacyMessage else { - return nil - } - return proto.legacyMessage - } - @objc public var hasLegacyMessage: Bool { - return proto.hasLegacyMessage - } - - @objc public var content: Data? { - guard proto.hasContent else { - return nil - } - return proto.content - } - @objc public var hasContent: Bool { - return proto.hasContent - } - - @objc public var serverGuid: String? { - guard proto.hasServerGuid else { - return nil - } - return proto.serverGuid - } - @objc public var hasServerGuid: Bool { - return proto.hasServerGuid - } - - @objc public var serverTimestamp: UInt64 { - return proto.serverTimestamp - } - @objc public var hasServerTimestamp: Bool { - return proto.hasServerTimestamp - } - - private init(proto: SignalServiceProtos_Envelope, - type: SSKProtoEnvelopeType, - timestamp: UInt64) { - self.proto = proto - self.type = type - self.timestamp = timestamp - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoEnvelope { - let proto = try SignalServiceProtos_Envelope(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_Envelope) throws -> SSKProtoEnvelope { - guard proto.hasType else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") - } - let type = SSKProtoEnvelopeTypeWrap(proto.type) - - guard proto.hasTimestamp else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: timestamp") - } - let timestamp = proto.timestamp - - // MARK: - Begin Validation Logic for SSKProtoEnvelope - - - // MARK: - End Validation Logic for SSKProtoEnvelope - - - let result = SSKProtoEnvelope(proto: proto, - type: type, - timestamp: timestamp) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoEnvelope { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoEnvelope.SSKProtoEnvelopeBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoEnvelope? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoTypingMessage - -@objc public class SSKProtoTypingMessage: NSObject { - - // MARK: - SSKProtoTypingMessageAction - - @objc public enum SSKProtoTypingMessageAction: Int32 { - case started = 0 - case stopped = 1 - } - - private class func SSKProtoTypingMessageActionWrap(_ value: SignalServiceProtos_TypingMessage.Action) -> SSKProtoTypingMessageAction { - switch value { - case .started: return .started - case .stopped: return .stopped - } - } - - private class func SSKProtoTypingMessageActionUnwrap(_ value: SSKProtoTypingMessageAction) -> SignalServiceProtos_TypingMessage.Action { - switch value { - case .started: return .started - case .stopped: return .stopped - } - } - - // MARK: - SSKProtoTypingMessageBuilder - - @objc public class func builder(timestamp: UInt64, action: SSKProtoTypingMessageAction) -> SSKProtoTypingMessageBuilder { - return SSKProtoTypingMessageBuilder(timestamp: timestamp, action: action) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoTypingMessageBuilder { - let builder = SSKProtoTypingMessageBuilder(timestamp: timestamp, action: action) - if let _value = groupID { - builder.setGroupID(_value) - } - return builder - } - - @objc public class SSKProtoTypingMessageBuilder: NSObject { - - private var proto = SignalServiceProtos_TypingMessage() - - @objc fileprivate override init() {} - - @objc fileprivate init(timestamp: UInt64, action: SSKProtoTypingMessageAction) { - super.init() - - setTimestamp(timestamp) - setAction(action) - } - - @objc public func setTimestamp(_ valueParam: UInt64) { - proto.timestamp = valueParam - } - - @objc public func setAction(_ valueParam: SSKProtoTypingMessageAction) { - proto.action = SSKProtoTypingMessageActionUnwrap(valueParam) - } - - @objc public func setGroupID(_ valueParam: Data) { - proto.groupID = valueParam - } - - @objc public func build() throws -> SSKProtoTypingMessage { - return try SSKProtoTypingMessage.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoTypingMessage.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_TypingMessage - - @objc public let timestamp: UInt64 - - @objc public let action: SSKProtoTypingMessageAction - - @objc public var groupID: Data? { - guard proto.hasGroupID else { - return nil - } - return proto.groupID - } - @objc public var hasGroupID: Bool { - return proto.hasGroupID - } - - private init(proto: SignalServiceProtos_TypingMessage, - timestamp: UInt64, - action: SSKProtoTypingMessageAction) { - self.proto = proto - self.timestamp = timestamp - self.action = action - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoTypingMessage { - let proto = try SignalServiceProtos_TypingMessage(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_TypingMessage) throws -> SSKProtoTypingMessage { - guard proto.hasTimestamp else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: timestamp") - } - let timestamp = proto.timestamp - - guard proto.hasAction else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: action") - } - let action = SSKProtoTypingMessageActionWrap(proto.action) - - // MARK: - Begin Validation Logic for SSKProtoTypingMessage - - - // MARK: - End Validation Logic for SSKProtoTypingMessage - - - let result = SSKProtoTypingMessage(proto: proto, - timestamp: timestamp, - action: action) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoTypingMessage { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoTypingMessage.SSKProtoTypingMessageBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoTypingMessage? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoContent - -@objc public class SSKProtoContent: NSObject { - - // MARK: - SSKProtoContentBuilder - - @objc public class func builder() -> SSKProtoContentBuilder { - return SSKProtoContentBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoContentBuilder { - let builder = SSKProtoContentBuilder() - if let _value = dataMessage { - builder.setDataMessage(_value) - } - if let _value = syncMessage { - builder.setSyncMessage(_value) - } - if let _value = callMessage { - builder.setCallMessage(_value) - } - if let _value = nullMessage { - builder.setNullMessage(_value) - } - if let _value = receiptMessage { - builder.setReceiptMessage(_value) - } - if let _value = typingMessage { - builder.setTypingMessage(_value) - } - if let _value = prekeyBundleMessage { - builder.setPrekeyBundleMessage(_value) - } - if let _value = lokiDeviceLinkMessage { - builder.setLokiDeviceLinkMessage(_value) - } - return builder - } - - @objc public class SSKProtoContentBuilder: NSObject { - - private var proto = SignalServiceProtos_Content() - - @objc fileprivate override init() {} - - @objc public func setDataMessage(_ valueParam: SSKProtoDataMessage) { - proto.dataMessage = valueParam.proto - } - - @objc public func setSyncMessage(_ valueParam: SSKProtoSyncMessage) { - proto.syncMessage = valueParam.proto - } - - @objc public func setCallMessage(_ valueParam: SSKProtoCallMessage) { - proto.callMessage = valueParam.proto - } - - @objc public func setNullMessage(_ valueParam: SSKProtoNullMessage) { - proto.nullMessage = valueParam.proto - } - - @objc public func setReceiptMessage(_ valueParam: SSKProtoReceiptMessage) { - proto.receiptMessage = valueParam.proto - } - - @objc public func setTypingMessage(_ valueParam: SSKProtoTypingMessage) { - proto.typingMessage = valueParam.proto - } - - @objc public func setPrekeyBundleMessage(_ valueParam: SSKProtoPrekeyBundleMessage) { - proto.prekeyBundleMessage = valueParam.proto - } - - @objc public func setLokiDeviceLinkMessage(_ valueParam: SSKProtoLokiDeviceLinkMessage) { - proto.lokiDeviceLinkMessage = valueParam.proto - } - - @objc public func build() throws -> SSKProtoContent { - return try SSKProtoContent.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoContent.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_Content - - @objc public let dataMessage: SSKProtoDataMessage? - - @objc public let syncMessage: SSKProtoSyncMessage? - - @objc public let callMessage: SSKProtoCallMessage? - - @objc public let nullMessage: SSKProtoNullMessage? - - @objc public let receiptMessage: SSKProtoReceiptMessage? - - @objc public let typingMessage: SSKProtoTypingMessage? - - @objc public let prekeyBundleMessage: SSKProtoPrekeyBundleMessage? - - @objc public let lokiDeviceLinkMessage: SSKProtoLokiDeviceLinkMessage? - - private init(proto: SignalServiceProtos_Content, - dataMessage: SSKProtoDataMessage?, - syncMessage: SSKProtoSyncMessage?, - callMessage: SSKProtoCallMessage?, - nullMessage: SSKProtoNullMessage?, - receiptMessage: SSKProtoReceiptMessage?, - typingMessage: SSKProtoTypingMessage?, - prekeyBundleMessage: SSKProtoPrekeyBundleMessage?, - lokiDeviceLinkMessage: SSKProtoLokiDeviceLinkMessage?) { - self.proto = proto - self.dataMessage = dataMessage - self.syncMessage = syncMessage - self.callMessage = callMessage - self.nullMessage = nullMessage - self.receiptMessage = receiptMessage - self.typingMessage = typingMessage - self.prekeyBundleMessage = prekeyBundleMessage - self.lokiDeviceLinkMessage = lokiDeviceLinkMessage - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoContent { - let proto = try SignalServiceProtos_Content(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_Content) throws -> SSKProtoContent { - var dataMessage: SSKProtoDataMessage? = nil - if proto.hasDataMessage { - dataMessage = try SSKProtoDataMessage.parseProto(proto.dataMessage) - } - - var syncMessage: SSKProtoSyncMessage? = nil - if proto.hasSyncMessage { - syncMessage = try SSKProtoSyncMessage.parseProto(proto.syncMessage) - } - - var callMessage: SSKProtoCallMessage? = nil - if proto.hasCallMessage { - callMessage = try SSKProtoCallMessage.parseProto(proto.callMessage) - } - - var nullMessage: SSKProtoNullMessage? = nil - if proto.hasNullMessage { - nullMessage = try SSKProtoNullMessage.parseProto(proto.nullMessage) - } - - var receiptMessage: SSKProtoReceiptMessage? = nil - if proto.hasReceiptMessage { - receiptMessage = try SSKProtoReceiptMessage.parseProto(proto.receiptMessage) - } - - var typingMessage: SSKProtoTypingMessage? = nil - if proto.hasTypingMessage { - typingMessage = try SSKProtoTypingMessage.parseProto(proto.typingMessage) - } - - var prekeyBundleMessage: SSKProtoPrekeyBundleMessage? = nil - if proto.hasPrekeyBundleMessage { - prekeyBundleMessage = try SSKProtoPrekeyBundleMessage.parseProto(proto.prekeyBundleMessage) - } - - var lokiDeviceLinkMessage: SSKProtoLokiDeviceLinkMessage? = nil - if proto.hasLokiDeviceLinkMessage { - lokiDeviceLinkMessage = try SSKProtoLokiDeviceLinkMessage.parseProto(proto.lokiDeviceLinkMessage) - } - - // MARK: - Begin Validation Logic for SSKProtoContent - - - // MARK: - End Validation Logic for SSKProtoContent - - - let result = SSKProtoContent(proto: proto, - dataMessage: dataMessage, - syncMessage: syncMessage, - callMessage: callMessage, - nullMessage: nullMessage, - receiptMessage: receiptMessage, - typingMessage: typingMessage, - prekeyBundleMessage: prekeyBundleMessage, - lokiDeviceLinkMessage: lokiDeviceLinkMessage) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoContent { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoContent.SSKProtoContentBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoContent? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoPrekeyBundleMessage - -@objc public class SSKProtoPrekeyBundleMessage: NSObject { - - // MARK: - SSKProtoPrekeyBundleMessageBuilder - - @objc public class func builder() -> SSKProtoPrekeyBundleMessageBuilder { - return SSKProtoPrekeyBundleMessageBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoPrekeyBundleMessageBuilder { - let builder = SSKProtoPrekeyBundleMessageBuilder() - if let _value = identityKey { - builder.setIdentityKey(_value) - } - if hasDeviceID { - builder.setDeviceID(deviceID) - } - if hasPrekeyID { - builder.setPrekeyID(prekeyID) - } - if hasSignedKeyID { - builder.setSignedKeyID(signedKeyID) - } - if let _value = prekey { - builder.setPrekey(_value) - } - if let _value = signedKey { - builder.setSignedKey(_value) - } - if let _value = signature { - builder.setSignature(_value) - } - return builder - } - - @objc public class SSKProtoPrekeyBundleMessageBuilder: NSObject { - - private var proto = SignalServiceProtos_PrekeyBundleMessage() - - @objc fileprivate override init() {} - - @objc public func setIdentityKey(_ valueParam: Data) { - proto.identityKey = valueParam - } - - @objc public func setDeviceID(_ valueParam: UInt32) { - proto.deviceID = valueParam - } - - @objc public func setPrekeyID(_ valueParam: UInt32) { - proto.prekeyID = valueParam - } - - @objc public func setSignedKeyID(_ valueParam: UInt32) { - proto.signedKeyID = valueParam - } - - @objc public func setPrekey(_ valueParam: Data) { - proto.prekey = valueParam - } - - @objc public func setSignedKey(_ valueParam: Data) { - proto.signedKey = valueParam - } - - @objc public func setSignature(_ valueParam: Data) { - proto.signature = valueParam - } - - @objc public func build() throws -> SSKProtoPrekeyBundleMessage { - return try SSKProtoPrekeyBundleMessage.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoPrekeyBundleMessage.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_PrekeyBundleMessage - - @objc public var identityKey: Data? { - guard proto.hasIdentityKey else { - return nil - } - return proto.identityKey - } - @objc public var hasIdentityKey: Bool { - return proto.hasIdentityKey - } - - @objc public var deviceID: UInt32 { - return proto.deviceID - } - @objc public var hasDeviceID: Bool { - return proto.hasDeviceID - } - - @objc public var prekeyID: UInt32 { - return proto.prekeyID - } - @objc public var hasPrekeyID: Bool { - return proto.hasPrekeyID - } - - @objc public var signedKeyID: UInt32 { - return proto.signedKeyID - } - @objc public var hasSignedKeyID: Bool { - return proto.hasSignedKeyID - } - - @objc public var prekey: Data? { - guard proto.hasPrekey else { - return nil - } - return proto.prekey - } - @objc public var hasPrekey: Bool { - return proto.hasPrekey - } - - @objc public var signedKey: Data? { - guard proto.hasSignedKey else { - return nil - } - return proto.signedKey - } - @objc public var hasSignedKey: Bool { - return proto.hasSignedKey - } - - @objc public var signature: Data? { - guard proto.hasSignature else { - return nil - } - return proto.signature - } - @objc public var hasSignature: Bool { - return proto.hasSignature - } - - private init(proto: SignalServiceProtos_PrekeyBundleMessage) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoPrekeyBundleMessage { - let proto = try SignalServiceProtos_PrekeyBundleMessage(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_PrekeyBundleMessage) throws -> SSKProtoPrekeyBundleMessage { - // MARK: - Begin Validation Logic for SSKProtoPrekeyBundleMessage - - - // MARK: - End Validation Logic for SSKProtoPrekeyBundleMessage - - - let result = SSKProtoPrekeyBundleMessage(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoPrekeyBundleMessage { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoPrekeyBundleMessage.SSKProtoPrekeyBundleMessageBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoPrekeyBundleMessage? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoLokiDeviceLinkMessage - -@objc public class SSKProtoLokiDeviceLinkMessage: NSObject { - - // MARK: - SSKProtoLokiDeviceLinkMessageBuilder - - @objc public class func builder() -> SSKProtoLokiDeviceLinkMessageBuilder { - return SSKProtoLokiDeviceLinkMessageBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoLokiDeviceLinkMessageBuilder { - let builder = SSKProtoLokiDeviceLinkMessageBuilder() - if let _value = masterPublicKey { - builder.setMasterPublicKey(_value) - } - if let _value = slavePublicKey { - builder.setSlavePublicKey(_value) - } - if let _value = slaveSignature { - builder.setSlaveSignature(_value) - } - if let _value = masterSignature { - builder.setMasterSignature(_value) - } - return builder - } - - @objc public class SSKProtoLokiDeviceLinkMessageBuilder: NSObject { - - private var proto = SignalServiceProtos_LokiDeviceLinkMessage() - - @objc fileprivate override init() {} - - @objc public func setMasterPublicKey(_ valueParam: String) { - proto.masterPublicKey = valueParam - } - - @objc public func setSlavePublicKey(_ valueParam: String) { - proto.slavePublicKey = valueParam - } - - @objc public func setSlaveSignature(_ valueParam: Data) { - proto.slaveSignature = valueParam - } - - @objc public func setMasterSignature(_ valueParam: Data) { - proto.masterSignature = valueParam - } - - @objc public func build() throws -> SSKProtoLokiDeviceLinkMessage { - return try SSKProtoLokiDeviceLinkMessage.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoLokiDeviceLinkMessage.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_LokiDeviceLinkMessage - - @objc public var masterPublicKey: String? { - guard proto.hasMasterPublicKey else { - return nil - } - return proto.masterPublicKey - } - @objc public var hasMasterPublicKey: Bool { - return proto.hasMasterPublicKey - } - - @objc public var slavePublicKey: String? { - guard proto.hasSlavePublicKey else { - return nil - } - return proto.slavePublicKey - } - @objc public var hasSlavePublicKey: Bool { - return proto.hasSlavePublicKey - } - - @objc public var slaveSignature: Data? { - guard proto.hasSlaveSignature else { - return nil - } - return proto.slaveSignature - } - @objc public var hasSlaveSignature: Bool { - return proto.hasSlaveSignature - } - - @objc public var masterSignature: Data? { - guard proto.hasMasterSignature else { - return nil - } - return proto.masterSignature - } - @objc public var hasMasterSignature: Bool { - return proto.hasMasterSignature - } - - private init(proto: SignalServiceProtos_LokiDeviceLinkMessage) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoLokiDeviceLinkMessage { - let proto = try SignalServiceProtos_LokiDeviceLinkMessage(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_LokiDeviceLinkMessage) throws -> SSKProtoLokiDeviceLinkMessage { - // MARK: - Begin Validation Logic for SSKProtoLokiDeviceLinkMessage - - - // MARK: - End Validation Logic for SSKProtoLokiDeviceLinkMessage - - - let result = SSKProtoLokiDeviceLinkMessage(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoLokiDeviceLinkMessage { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoLokiDeviceLinkMessage.SSKProtoLokiDeviceLinkMessageBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoLokiDeviceLinkMessage? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoCallMessageOffer - -@objc public class SSKProtoCallMessageOffer: NSObject { - - // MARK: - SSKProtoCallMessageOfferBuilder - - @objc public class func builder(id: UInt64, sessionDescription: String) -> SSKProtoCallMessageOfferBuilder { - return SSKProtoCallMessageOfferBuilder(id: id, sessionDescription: sessionDescription) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoCallMessageOfferBuilder { - let builder = SSKProtoCallMessageOfferBuilder(id: id, sessionDescription: sessionDescription) - return builder - } - - @objc public class SSKProtoCallMessageOfferBuilder: NSObject { - - private var proto = SignalServiceProtos_CallMessage.Offer() - - @objc fileprivate override init() {} - - @objc fileprivate init(id: UInt64, sessionDescription: String) { - super.init() - - setId(id) - setSessionDescription(sessionDescription) - } - - @objc public func setId(_ valueParam: UInt64) { - proto.id = valueParam - } - - @objc public func setSessionDescription(_ valueParam: String) { - proto.sessionDescription = valueParam - } - - @objc public func build() throws -> SSKProtoCallMessageOffer { - return try SSKProtoCallMessageOffer.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoCallMessageOffer.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_CallMessage.Offer - - @objc public let id: UInt64 - - @objc public let sessionDescription: String - - private init(proto: SignalServiceProtos_CallMessage.Offer, - id: UInt64, - sessionDescription: String) { - self.proto = proto - self.id = id - self.sessionDescription = sessionDescription - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoCallMessageOffer { - let proto = try SignalServiceProtos_CallMessage.Offer(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_CallMessage.Offer) throws -> SSKProtoCallMessageOffer { - guard proto.hasID else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") - } - let id = proto.id - - guard proto.hasSessionDescription else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: sessionDescription") - } - let sessionDescription = proto.sessionDescription - - // MARK: - Begin Validation Logic for SSKProtoCallMessageOffer - - - // MARK: - End Validation Logic for SSKProtoCallMessageOffer - - - let result = SSKProtoCallMessageOffer(proto: proto, - id: id, - sessionDescription: sessionDescription) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoCallMessageOffer { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoCallMessageOffer.SSKProtoCallMessageOfferBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoCallMessageOffer? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoCallMessageAnswer - -@objc public class SSKProtoCallMessageAnswer: NSObject { - - // MARK: - SSKProtoCallMessageAnswerBuilder - - @objc public class func builder(id: UInt64, sessionDescription: String) -> SSKProtoCallMessageAnswerBuilder { - return SSKProtoCallMessageAnswerBuilder(id: id, sessionDescription: sessionDescription) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoCallMessageAnswerBuilder { - let builder = SSKProtoCallMessageAnswerBuilder(id: id, sessionDescription: sessionDescription) - return builder - } - - @objc public class SSKProtoCallMessageAnswerBuilder: NSObject { - - private var proto = SignalServiceProtos_CallMessage.Answer() - - @objc fileprivate override init() {} - - @objc fileprivate init(id: UInt64, sessionDescription: String) { - super.init() - - setId(id) - setSessionDescription(sessionDescription) - } - - @objc public func setId(_ valueParam: UInt64) { - proto.id = valueParam - } - - @objc public func setSessionDescription(_ valueParam: String) { - proto.sessionDescription = valueParam - } - - @objc public func build() throws -> SSKProtoCallMessageAnswer { - return try SSKProtoCallMessageAnswer.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoCallMessageAnswer.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_CallMessage.Answer - - @objc public let id: UInt64 - - @objc public let sessionDescription: String - - private init(proto: SignalServiceProtos_CallMessage.Answer, - id: UInt64, - sessionDescription: String) { - self.proto = proto - self.id = id - self.sessionDescription = sessionDescription - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoCallMessageAnswer { - let proto = try SignalServiceProtos_CallMessage.Answer(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_CallMessage.Answer) throws -> SSKProtoCallMessageAnswer { - guard proto.hasID else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") - } - let id = proto.id - - guard proto.hasSessionDescription else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: sessionDescription") - } - let sessionDescription = proto.sessionDescription - - // MARK: - Begin Validation Logic for SSKProtoCallMessageAnswer - - - // MARK: - End Validation Logic for SSKProtoCallMessageAnswer - - - let result = SSKProtoCallMessageAnswer(proto: proto, - id: id, - sessionDescription: sessionDescription) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoCallMessageAnswer { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoCallMessageAnswer.SSKProtoCallMessageAnswerBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoCallMessageAnswer? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoCallMessageIceUpdate - -@objc public class SSKProtoCallMessageIceUpdate: NSObject { - - // MARK: - SSKProtoCallMessageIceUpdateBuilder - - @objc public class func builder(id: UInt64, sdpMid: String, sdpMlineIndex: UInt32, sdp: String) -> SSKProtoCallMessageIceUpdateBuilder { - return SSKProtoCallMessageIceUpdateBuilder(id: id, sdpMid: sdpMid, sdpMlineIndex: sdpMlineIndex, sdp: sdp) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoCallMessageIceUpdateBuilder { - let builder = SSKProtoCallMessageIceUpdateBuilder(id: id, sdpMid: sdpMid, sdpMlineIndex: sdpMlineIndex, sdp: sdp) - return builder - } - - @objc public class SSKProtoCallMessageIceUpdateBuilder: NSObject { - - private var proto = SignalServiceProtos_CallMessage.IceUpdate() - - @objc fileprivate override init() {} - - @objc fileprivate init(id: UInt64, sdpMid: String, sdpMlineIndex: UInt32, sdp: String) { - super.init() - - setId(id) - setSdpMid(sdpMid) - setSdpMlineIndex(sdpMlineIndex) - setSdp(sdp) - } - - @objc public func setId(_ valueParam: UInt64) { - proto.id = valueParam - } - - @objc public func setSdpMid(_ valueParam: String) { - proto.sdpMid = valueParam - } - - @objc public func setSdpMlineIndex(_ valueParam: UInt32) { - proto.sdpMlineIndex = valueParam - } - - @objc public func setSdp(_ valueParam: String) { - proto.sdp = valueParam - } - - @objc public func build() throws -> SSKProtoCallMessageIceUpdate { - return try SSKProtoCallMessageIceUpdate.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoCallMessageIceUpdate.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_CallMessage.IceUpdate - - @objc public let id: UInt64 - - @objc public let sdpMid: String - - @objc public let sdpMlineIndex: UInt32 - - @objc public let sdp: String - - private init(proto: SignalServiceProtos_CallMessage.IceUpdate, - id: UInt64, - sdpMid: String, - sdpMlineIndex: UInt32, - sdp: String) { - self.proto = proto - self.id = id - self.sdpMid = sdpMid - self.sdpMlineIndex = sdpMlineIndex - self.sdp = sdp - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoCallMessageIceUpdate { - let proto = try SignalServiceProtos_CallMessage.IceUpdate(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_CallMessage.IceUpdate) throws -> SSKProtoCallMessageIceUpdate { - guard proto.hasID else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") - } - let id = proto.id - - guard proto.hasSdpMid else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: sdpMid") - } - let sdpMid = proto.sdpMid - - guard proto.hasSdpMlineIndex else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: sdpMlineIndex") - } - let sdpMlineIndex = proto.sdpMlineIndex - - guard proto.hasSdp else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: sdp") - } - let sdp = proto.sdp - - // MARK: - Begin Validation Logic for SSKProtoCallMessageIceUpdate - - - // MARK: - End Validation Logic for SSKProtoCallMessageIceUpdate - - - let result = SSKProtoCallMessageIceUpdate(proto: proto, - id: id, - sdpMid: sdpMid, - sdpMlineIndex: sdpMlineIndex, - sdp: sdp) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoCallMessageIceUpdate { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoCallMessageIceUpdate.SSKProtoCallMessageIceUpdateBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoCallMessageIceUpdate? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoCallMessageBusy - -@objc public class SSKProtoCallMessageBusy: NSObject { - - // MARK: - SSKProtoCallMessageBusyBuilder - - @objc public class func builder(id: UInt64) -> SSKProtoCallMessageBusyBuilder { - return SSKProtoCallMessageBusyBuilder(id: id) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoCallMessageBusyBuilder { - let builder = SSKProtoCallMessageBusyBuilder(id: id) - return builder - } - - @objc public class SSKProtoCallMessageBusyBuilder: NSObject { - - private var proto = SignalServiceProtos_CallMessage.Busy() - - @objc fileprivate override init() {} - - @objc fileprivate init(id: UInt64) { - super.init() - - setId(id) - } - - @objc public func setId(_ valueParam: UInt64) { - proto.id = valueParam - } - - @objc public func build() throws -> SSKProtoCallMessageBusy { - return try SSKProtoCallMessageBusy.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoCallMessageBusy.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_CallMessage.Busy - - @objc public let id: UInt64 - - private init(proto: SignalServiceProtos_CallMessage.Busy, - id: UInt64) { - self.proto = proto - self.id = id - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoCallMessageBusy { - let proto = try SignalServiceProtos_CallMessage.Busy(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_CallMessage.Busy) throws -> SSKProtoCallMessageBusy { - guard proto.hasID else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") - } - let id = proto.id - - // MARK: - Begin Validation Logic for SSKProtoCallMessageBusy - - - // MARK: - End Validation Logic for SSKProtoCallMessageBusy - - - let result = SSKProtoCallMessageBusy(proto: proto, - id: id) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoCallMessageBusy { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoCallMessageBusy.SSKProtoCallMessageBusyBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoCallMessageBusy? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoCallMessageHangup - -@objc public class SSKProtoCallMessageHangup: NSObject { - - // MARK: - SSKProtoCallMessageHangupBuilder - - @objc public class func builder(id: UInt64) -> SSKProtoCallMessageHangupBuilder { - return SSKProtoCallMessageHangupBuilder(id: id) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoCallMessageHangupBuilder { - let builder = SSKProtoCallMessageHangupBuilder(id: id) - return builder - } - - @objc public class SSKProtoCallMessageHangupBuilder: NSObject { - - private var proto = SignalServiceProtos_CallMessage.Hangup() - - @objc fileprivate override init() {} - - @objc fileprivate init(id: UInt64) { - super.init() - - setId(id) - } - - @objc public func setId(_ valueParam: UInt64) { - proto.id = valueParam - } - - @objc public func build() throws -> SSKProtoCallMessageHangup { - return try SSKProtoCallMessageHangup.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoCallMessageHangup.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_CallMessage.Hangup - - @objc public let id: UInt64 - - private init(proto: SignalServiceProtos_CallMessage.Hangup, - id: UInt64) { - self.proto = proto - self.id = id - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoCallMessageHangup { - let proto = try SignalServiceProtos_CallMessage.Hangup(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_CallMessage.Hangup) throws -> SSKProtoCallMessageHangup { - guard proto.hasID else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") - } - let id = proto.id - - // MARK: - Begin Validation Logic for SSKProtoCallMessageHangup - - - // MARK: - End Validation Logic for SSKProtoCallMessageHangup - - - let result = SSKProtoCallMessageHangup(proto: proto, - id: id) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoCallMessageHangup { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoCallMessageHangup.SSKProtoCallMessageHangupBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoCallMessageHangup? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoCallMessage - -@objc public class SSKProtoCallMessage: NSObject { - - // MARK: - SSKProtoCallMessageBuilder - - @objc public class func builder() -> SSKProtoCallMessageBuilder { - return SSKProtoCallMessageBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoCallMessageBuilder { - let builder = SSKProtoCallMessageBuilder() - if let _value = offer { - builder.setOffer(_value) - } - if let _value = answer { - builder.setAnswer(_value) - } - builder.setIceUpdate(iceUpdate) - if let _value = hangup { - builder.setHangup(_value) - } - if let _value = busy { - builder.setBusy(_value) - } - if let _value = profileKey { - builder.setProfileKey(_value) - } - return builder - } - - @objc public class SSKProtoCallMessageBuilder: NSObject { - - private var proto = SignalServiceProtos_CallMessage() - - @objc fileprivate override init() {} - - @objc public func setOffer(_ valueParam: SSKProtoCallMessageOffer) { - proto.offer = valueParam.proto - } - - @objc public func setAnswer(_ valueParam: SSKProtoCallMessageAnswer) { - proto.answer = valueParam.proto - } - - @objc public func addIceUpdate(_ valueParam: SSKProtoCallMessageIceUpdate) { - var items = proto.iceUpdate - items.append(valueParam.proto) - proto.iceUpdate = items - } - - @objc public func setIceUpdate(_ wrappedItems: [SSKProtoCallMessageIceUpdate]) { - proto.iceUpdate = wrappedItems.map { $0.proto } - } - - @objc public func setHangup(_ valueParam: SSKProtoCallMessageHangup) { - proto.hangup = valueParam.proto - } - - @objc public func setBusy(_ valueParam: SSKProtoCallMessageBusy) { - proto.busy = valueParam.proto - } - - @objc public func setProfileKey(_ valueParam: Data) { - proto.profileKey = valueParam - } - - @objc public func build() throws -> SSKProtoCallMessage { - return try SSKProtoCallMessage.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoCallMessage.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_CallMessage - - @objc public let offer: SSKProtoCallMessageOffer? - - @objc public let answer: SSKProtoCallMessageAnswer? - - @objc public let iceUpdate: [SSKProtoCallMessageIceUpdate] - - @objc public let hangup: SSKProtoCallMessageHangup? - - @objc public let busy: SSKProtoCallMessageBusy? - - @objc public var profileKey: Data? { - guard proto.hasProfileKey else { - return nil - } - return proto.profileKey - } - @objc public var hasProfileKey: Bool { - return proto.hasProfileKey - } - - private init(proto: SignalServiceProtos_CallMessage, - offer: SSKProtoCallMessageOffer?, - answer: SSKProtoCallMessageAnswer?, - iceUpdate: [SSKProtoCallMessageIceUpdate], - hangup: SSKProtoCallMessageHangup?, - busy: SSKProtoCallMessageBusy?) { - self.proto = proto - self.offer = offer - self.answer = answer - self.iceUpdate = iceUpdate - self.hangup = hangup - self.busy = busy - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoCallMessage { - let proto = try SignalServiceProtos_CallMessage(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_CallMessage) throws -> SSKProtoCallMessage { - var offer: SSKProtoCallMessageOffer? = nil - if proto.hasOffer { - offer = try SSKProtoCallMessageOffer.parseProto(proto.offer) - } - - var answer: SSKProtoCallMessageAnswer? = nil - if proto.hasAnswer { - answer = try SSKProtoCallMessageAnswer.parseProto(proto.answer) - } - - var iceUpdate: [SSKProtoCallMessageIceUpdate] = [] - iceUpdate = try proto.iceUpdate.map { try SSKProtoCallMessageIceUpdate.parseProto($0) } - - var hangup: SSKProtoCallMessageHangup? = nil - if proto.hasHangup { - hangup = try SSKProtoCallMessageHangup.parseProto(proto.hangup) - } - - var busy: SSKProtoCallMessageBusy? = nil - if proto.hasBusy { - busy = try SSKProtoCallMessageBusy.parseProto(proto.busy) - } - - // MARK: - Begin Validation Logic for SSKProtoCallMessage - - - // MARK: - End Validation Logic for SSKProtoCallMessage - - - let result = SSKProtoCallMessage(proto: proto, - offer: offer, - answer: answer, - iceUpdate: iceUpdate, - hangup: hangup, - busy: busy) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoCallMessage { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoCallMessage.SSKProtoCallMessageBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoCallMessage? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoClosedGroupCiphertextMessageWrapper - -@objc public class SSKProtoClosedGroupCiphertextMessageWrapper: NSObject { - - // MARK: - SSKProtoClosedGroupCiphertextMessageWrapperBuilder - - @objc public class func builder(ciphertext: Data, ephemeralPublicKey: Data) -> SSKProtoClosedGroupCiphertextMessageWrapperBuilder { - return SSKProtoClosedGroupCiphertextMessageWrapperBuilder(ciphertext: ciphertext, ephemeralPublicKey: ephemeralPublicKey) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoClosedGroupCiphertextMessageWrapperBuilder { - let builder = SSKProtoClosedGroupCiphertextMessageWrapperBuilder(ciphertext: ciphertext, ephemeralPublicKey: ephemeralPublicKey) - return builder - } - - @objc public class SSKProtoClosedGroupCiphertextMessageWrapperBuilder: NSObject { - - private var proto = SignalServiceProtos_ClosedGroupCiphertextMessageWrapper() - - @objc fileprivate override init() {} - - @objc fileprivate init(ciphertext: Data, ephemeralPublicKey: Data) { - super.init() - - setCiphertext(ciphertext) - setEphemeralPublicKey(ephemeralPublicKey) - } - - @objc public func setCiphertext(_ valueParam: Data) { - proto.ciphertext = valueParam - } - - @objc public func setEphemeralPublicKey(_ valueParam: Data) { - proto.ephemeralPublicKey = valueParam - } - - @objc public func build() throws -> SSKProtoClosedGroupCiphertextMessageWrapper { - return try SSKProtoClosedGroupCiphertextMessageWrapper.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoClosedGroupCiphertextMessageWrapper.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_ClosedGroupCiphertextMessageWrapper - - @objc public let ciphertext: Data - - @objc public let ephemeralPublicKey: Data - - private init(proto: SignalServiceProtos_ClosedGroupCiphertextMessageWrapper, - ciphertext: Data, - ephemeralPublicKey: Data) { - self.proto = proto - self.ciphertext = ciphertext - self.ephemeralPublicKey = ephemeralPublicKey - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoClosedGroupCiphertextMessageWrapper { - let proto = try SignalServiceProtos_ClosedGroupCiphertextMessageWrapper(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_ClosedGroupCiphertextMessageWrapper) throws -> SSKProtoClosedGroupCiphertextMessageWrapper { - guard proto.hasCiphertext else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: ciphertext") - } - let ciphertext = proto.ciphertext - - guard proto.hasEphemeralPublicKey else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: ephemeralPublicKey") - } - let ephemeralPublicKey = proto.ephemeralPublicKey - - // MARK: - Begin Validation Logic for SSKProtoClosedGroupCiphertextMessageWrapper - - - // MARK: - End Validation Logic for SSKProtoClosedGroupCiphertextMessageWrapper - - - let result = SSKProtoClosedGroupCiphertextMessageWrapper(proto: proto, - ciphertext: ciphertext, - ephemeralPublicKey: ephemeralPublicKey) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoClosedGroupCiphertextMessageWrapper { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoClosedGroupCiphertextMessageWrapper.SSKProtoClosedGroupCiphertextMessageWrapperBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoClosedGroupCiphertextMessageWrapper? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessageQuoteQuotedAttachment - -@objc public class SSKProtoDataMessageQuoteQuotedAttachment: NSObject { - - // MARK: - SSKProtoDataMessageQuoteQuotedAttachmentFlags - - @objc public enum SSKProtoDataMessageQuoteQuotedAttachmentFlags: Int32 { - case voiceMessage = 1 - } - - private class func SSKProtoDataMessageQuoteQuotedAttachmentFlagsWrap(_ value: SignalServiceProtos_DataMessage.Quote.QuotedAttachment.Flags) -> SSKProtoDataMessageQuoteQuotedAttachmentFlags { - switch value { - case .voiceMessage: return .voiceMessage - } - } - - private class func SSKProtoDataMessageQuoteQuotedAttachmentFlagsUnwrap(_ value: SSKProtoDataMessageQuoteQuotedAttachmentFlags) -> SignalServiceProtos_DataMessage.Quote.QuotedAttachment.Flags { - switch value { - case .voiceMessage: return .voiceMessage - } - } - - // MARK: - SSKProtoDataMessageQuoteQuotedAttachmentBuilder - - @objc public class func builder() -> SSKProtoDataMessageQuoteQuotedAttachmentBuilder { - return SSKProtoDataMessageQuoteQuotedAttachmentBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessageQuoteQuotedAttachmentBuilder { - let builder = SSKProtoDataMessageQuoteQuotedAttachmentBuilder() - if let _value = contentType { - builder.setContentType(_value) - } - if let _value = fileName { - builder.setFileName(_value) - } - if let _value = thumbnail { - builder.setThumbnail(_value) - } - if hasFlags { - builder.setFlags(flags) - } - return builder - } - - @objc public class SSKProtoDataMessageQuoteQuotedAttachmentBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage.Quote.QuotedAttachment() - - @objc fileprivate override init() {} - - @objc public func setContentType(_ valueParam: String) { - proto.contentType = valueParam - } - - @objc public func setFileName(_ valueParam: String) { - proto.fileName = valueParam - } - - @objc public func setThumbnail(_ valueParam: SSKProtoAttachmentPointer) { - proto.thumbnail = valueParam.proto - } - - @objc public func setFlags(_ valueParam: UInt32) { - proto.flags = valueParam - } - - @objc public func build() throws -> SSKProtoDataMessageQuoteQuotedAttachment { - return try SSKProtoDataMessageQuoteQuotedAttachment.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessageQuoteQuotedAttachment.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage.Quote.QuotedAttachment - - @objc public let thumbnail: SSKProtoAttachmentPointer? - - @objc public var contentType: String? { - guard proto.hasContentType else { - return nil - } - return proto.contentType - } - @objc public var hasContentType: Bool { - return proto.hasContentType - } - - @objc public var fileName: String? { - guard proto.hasFileName else { - return nil - } - return proto.fileName - } - @objc public var hasFileName: Bool { - return proto.hasFileName - } - - @objc public var flags: UInt32 { - return proto.flags - } - @objc public var hasFlags: Bool { - return proto.hasFlags - } - - private init(proto: SignalServiceProtos_DataMessage.Quote.QuotedAttachment, - thumbnail: SSKProtoAttachmentPointer?) { - self.proto = proto - self.thumbnail = thumbnail - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessageQuoteQuotedAttachment { - let proto = try SignalServiceProtos_DataMessage.Quote.QuotedAttachment(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage.Quote.QuotedAttachment) throws -> SSKProtoDataMessageQuoteQuotedAttachment { - var thumbnail: SSKProtoAttachmentPointer? = nil - if proto.hasThumbnail { - thumbnail = try SSKProtoAttachmentPointer.parseProto(proto.thumbnail) - } - - // MARK: - Begin Validation Logic for SSKProtoDataMessageQuoteQuotedAttachment - - - // MARK: - End Validation Logic for SSKProtoDataMessageQuoteQuotedAttachment - - - let result = SSKProtoDataMessageQuoteQuotedAttachment(proto: proto, - thumbnail: thumbnail) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessageQuoteQuotedAttachment { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessageQuoteQuotedAttachment.SSKProtoDataMessageQuoteQuotedAttachmentBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessageQuoteQuotedAttachment? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessageQuote - -@objc public class SSKProtoDataMessageQuote: NSObject { - - // MARK: - SSKProtoDataMessageQuoteBuilder - - @objc public class func builder(id: UInt64, author: String) -> SSKProtoDataMessageQuoteBuilder { - return SSKProtoDataMessageQuoteBuilder(id: id, author: author) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessageQuoteBuilder { - let builder = SSKProtoDataMessageQuoteBuilder(id: id, author: author) - if let _value = text { - builder.setText(_value) - } - builder.setAttachments(attachments) - return builder - } - - @objc public class SSKProtoDataMessageQuoteBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage.Quote() - - @objc fileprivate override init() {} - - @objc fileprivate init(id: UInt64, author: String) { - super.init() - - setId(id) - setAuthor(author) - } - - @objc public func setId(_ valueParam: UInt64) { - proto.id = valueParam - } - - @objc public func setAuthor(_ valueParam: String) { - proto.author = valueParam - } - - @objc public func setText(_ valueParam: String) { - proto.text = valueParam - } - - @objc public func addAttachments(_ valueParam: SSKProtoDataMessageQuoteQuotedAttachment) { - var items = proto.attachments - items.append(valueParam.proto) - proto.attachments = items - } - - @objc public func setAttachments(_ wrappedItems: [SSKProtoDataMessageQuoteQuotedAttachment]) { - proto.attachments = wrappedItems.map { $0.proto } - } - - @objc public func build() throws -> SSKProtoDataMessageQuote { - return try SSKProtoDataMessageQuote.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessageQuote.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage.Quote - - @objc public let id: UInt64 - - @objc public let author: String - - @objc public let attachments: [SSKProtoDataMessageQuoteQuotedAttachment] - - @objc public var text: String? { - guard proto.hasText else { - return nil - } - return proto.text - } - @objc public var hasText: Bool { - return proto.hasText - } - - private init(proto: SignalServiceProtos_DataMessage.Quote, - id: UInt64, - author: String, - attachments: [SSKProtoDataMessageQuoteQuotedAttachment]) { - self.proto = proto - self.id = id - self.author = author - self.attachments = attachments - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessageQuote { - let proto = try SignalServiceProtos_DataMessage.Quote(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage.Quote) throws -> SSKProtoDataMessageQuote { - guard proto.hasID else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") - } - let id = proto.id - - guard proto.hasAuthor else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: author") - } - let author = proto.author - - var attachments: [SSKProtoDataMessageQuoteQuotedAttachment] = [] - attachments = try proto.attachments.map { try SSKProtoDataMessageQuoteQuotedAttachment.parseProto($0) } - - // MARK: - Begin Validation Logic for SSKProtoDataMessageQuote - - - // MARK: - End Validation Logic for SSKProtoDataMessageQuote - - - let result = SSKProtoDataMessageQuote(proto: proto, - id: id, - author: author, - attachments: attachments) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessageQuote { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessageQuote.SSKProtoDataMessageQuoteBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessageQuote? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessageContactName - -@objc public class SSKProtoDataMessageContactName: NSObject { - - // MARK: - SSKProtoDataMessageContactNameBuilder - - @objc public class func builder() -> SSKProtoDataMessageContactNameBuilder { - return SSKProtoDataMessageContactNameBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessageContactNameBuilder { - let builder = SSKProtoDataMessageContactNameBuilder() - if let _value = givenName { - builder.setGivenName(_value) - } - if let _value = familyName { - builder.setFamilyName(_value) - } - if let _value = prefix { - builder.setPrefix(_value) - } - if let _value = suffix { - builder.setSuffix(_value) - } - if let _value = middleName { - builder.setMiddleName(_value) - } - if let _value = displayName { - builder.setDisplayName(_value) - } - return builder - } - - @objc public class SSKProtoDataMessageContactNameBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage.Contact.Name() - - @objc fileprivate override init() {} - - @objc public func setGivenName(_ valueParam: String) { - proto.givenName = valueParam - } - - @objc public func setFamilyName(_ valueParam: String) { - proto.familyName = valueParam - } - - @objc public func setPrefix(_ valueParam: String) { - proto.prefix = valueParam - } - - @objc public func setSuffix(_ valueParam: String) { - proto.suffix = valueParam - } - - @objc public func setMiddleName(_ valueParam: String) { - proto.middleName = valueParam - } - - @objc public func setDisplayName(_ valueParam: String) { - proto.displayName = valueParam - } - - @objc public func build() throws -> SSKProtoDataMessageContactName { - return try SSKProtoDataMessageContactName.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessageContactName.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage.Contact.Name - - @objc public var givenName: String? { - guard proto.hasGivenName else { - return nil - } - return proto.givenName - } - @objc public var hasGivenName: Bool { - return proto.hasGivenName - } - - @objc public var familyName: String? { - guard proto.hasFamilyName else { - return nil - } - return proto.familyName - } - @objc public var hasFamilyName: Bool { - return proto.hasFamilyName - } - - @objc public var prefix: String? { - guard proto.hasPrefix else { - return nil - } - return proto.prefix - } - @objc public var hasPrefix: Bool { - return proto.hasPrefix - } - - @objc public var suffix: String? { - guard proto.hasSuffix else { - return nil - } - return proto.suffix - } - @objc public var hasSuffix: Bool { - return proto.hasSuffix - } - - @objc public var middleName: String? { - guard proto.hasMiddleName else { - return nil - } - return proto.middleName - } - @objc public var hasMiddleName: Bool { - return proto.hasMiddleName - } - - @objc public var displayName: String? { - guard proto.hasDisplayName else { - return nil - } - return proto.displayName - } - @objc public var hasDisplayName: Bool { - return proto.hasDisplayName - } - - private init(proto: SignalServiceProtos_DataMessage.Contact.Name) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessageContactName { - let proto = try SignalServiceProtos_DataMessage.Contact.Name(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage.Contact.Name) throws -> SSKProtoDataMessageContactName { - // MARK: - Begin Validation Logic for SSKProtoDataMessageContactName - - - // MARK: - End Validation Logic for SSKProtoDataMessageContactName - - - let result = SSKProtoDataMessageContactName(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessageContactName { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessageContactName.SSKProtoDataMessageContactNameBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessageContactName? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessageContactPhone - -@objc public class SSKProtoDataMessageContactPhone: NSObject { - - // MARK: - SSKProtoDataMessageContactPhoneType - - @objc public enum SSKProtoDataMessageContactPhoneType: Int32 { - case home = 1 - case mobile = 2 - case work = 3 - case custom = 4 - } - - private class func SSKProtoDataMessageContactPhoneTypeWrap(_ value: SignalServiceProtos_DataMessage.Contact.Phone.TypeEnum) -> SSKProtoDataMessageContactPhoneType { - switch value { - case .home: return .home - case .mobile: return .mobile - case .work: return .work - case .custom: return .custom - } - } - - private class func SSKProtoDataMessageContactPhoneTypeUnwrap(_ value: SSKProtoDataMessageContactPhoneType) -> SignalServiceProtos_DataMessage.Contact.Phone.TypeEnum { - switch value { - case .home: return .home - case .mobile: return .mobile - case .work: return .work - case .custom: return .custom - } - } - - // MARK: - SSKProtoDataMessageContactPhoneBuilder - - @objc public class func builder() -> SSKProtoDataMessageContactPhoneBuilder { - return SSKProtoDataMessageContactPhoneBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessageContactPhoneBuilder { - let builder = SSKProtoDataMessageContactPhoneBuilder() - if let _value = value { - builder.setValue(_value) - } - if hasType { - builder.setType(type) - } - if let _value = label { - builder.setLabel(_value) - } - return builder - } - - @objc public class SSKProtoDataMessageContactPhoneBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage.Contact.Phone() - - @objc fileprivate override init() {} - - @objc public func setValue(_ valueParam: String) { - proto.value = valueParam - } - - @objc public func setType(_ valueParam: SSKProtoDataMessageContactPhoneType) { - proto.type = SSKProtoDataMessageContactPhoneTypeUnwrap(valueParam) - } - - @objc public func setLabel(_ valueParam: String) { - proto.label = valueParam - } - - @objc public func build() throws -> SSKProtoDataMessageContactPhone { - return try SSKProtoDataMessageContactPhone.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessageContactPhone.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage.Contact.Phone - - @objc public var value: String? { - guard proto.hasValue else { - return nil - } - return proto.value - } - @objc public var hasValue: Bool { - return proto.hasValue - } - - @objc public var type: SSKProtoDataMessageContactPhoneType { - return SSKProtoDataMessageContactPhone.SSKProtoDataMessageContactPhoneTypeWrap(proto.type) - } - @objc public var hasType: Bool { - return proto.hasType - } - - @objc public var label: String? { - guard proto.hasLabel else { - return nil - } - return proto.label - } - @objc public var hasLabel: Bool { - return proto.hasLabel - } - - private init(proto: SignalServiceProtos_DataMessage.Contact.Phone) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessageContactPhone { - let proto = try SignalServiceProtos_DataMessage.Contact.Phone(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage.Contact.Phone) throws -> SSKProtoDataMessageContactPhone { - // MARK: - Begin Validation Logic for SSKProtoDataMessageContactPhone - - - // MARK: - End Validation Logic for SSKProtoDataMessageContactPhone - - - let result = SSKProtoDataMessageContactPhone(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessageContactPhone { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessageContactPhone.SSKProtoDataMessageContactPhoneBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessageContactPhone? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessageContactEmail - -@objc public class SSKProtoDataMessageContactEmail: NSObject { - - // MARK: - SSKProtoDataMessageContactEmailType - - @objc public enum SSKProtoDataMessageContactEmailType: Int32 { - case home = 1 - case mobile = 2 - case work = 3 - case custom = 4 - } - - private class func SSKProtoDataMessageContactEmailTypeWrap(_ value: SignalServiceProtos_DataMessage.Contact.Email.TypeEnum) -> SSKProtoDataMessageContactEmailType { - switch value { - case .home: return .home - case .mobile: return .mobile - case .work: return .work - case .custom: return .custom - } - } - - private class func SSKProtoDataMessageContactEmailTypeUnwrap(_ value: SSKProtoDataMessageContactEmailType) -> SignalServiceProtos_DataMessage.Contact.Email.TypeEnum { - switch value { - case .home: return .home - case .mobile: return .mobile - case .work: return .work - case .custom: return .custom - } - } - - // MARK: - SSKProtoDataMessageContactEmailBuilder - - @objc public class func builder() -> SSKProtoDataMessageContactEmailBuilder { - return SSKProtoDataMessageContactEmailBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessageContactEmailBuilder { - let builder = SSKProtoDataMessageContactEmailBuilder() - if let _value = value { - builder.setValue(_value) - } - if hasType { - builder.setType(type) - } - if let _value = label { - builder.setLabel(_value) - } - return builder - } - - @objc public class SSKProtoDataMessageContactEmailBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage.Contact.Email() - - @objc fileprivate override init() {} - - @objc public func setValue(_ valueParam: String) { - proto.value = valueParam - } - - @objc public func setType(_ valueParam: SSKProtoDataMessageContactEmailType) { - proto.type = SSKProtoDataMessageContactEmailTypeUnwrap(valueParam) - } - - @objc public func setLabel(_ valueParam: String) { - proto.label = valueParam - } - - @objc public func build() throws -> SSKProtoDataMessageContactEmail { - return try SSKProtoDataMessageContactEmail.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessageContactEmail.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage.Contact.Email - - @objc public var value: String? { - guard proto.hasValue else { - return nil - } - return proto.value - } - @objc public var hasValue: Bool { - return proto.hasValue - } - - @objc public var type: SSKProtoDataMessageContactEmailType { - return SSKProtoDataMessageContactEmail.SSKProtoDataMessageContactEmailTypeWrap(proto.type) - } - @objc public var hasType: Bool { - return proto.hasType - } - - @objc public var label: String? { - guard proto.hasLabel else { - return nil - } - return proto.label - } - @objc public var hasLabel: Bool { - return proto.hasLabel - } - - private init(proto: SignalServiceProtos_DataMessage.Contact.Email) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessageContactEmail { - let proto = try SignalServiceProtos_DataMessage.Contact.Email(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage.Contact.Email) throws -> SSKProtoDataMessageContactEmail { - // MARK: - Begin Validation Logic for SSKProtoDataMessageContactEmail - - - // MARK: - End Validation Logic for SSKProtoDataMessageContactEmail - - - let result = SSKProtoDataMessageContactEmail(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessageContactEmail { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessageContactEmail.SSKProtoDataMessageContactEmailBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessageContactEmail? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessageContactPostalAddress - -@objc public class SSKProtoDataMessageContactPostalAddress: NSObject { - - // MARK: - SSKProtoDataMessageContactPostalAddressType - - @objc public enum SSKProtoDataMessageContactPostalAddressType: Int32 { - case home = 1 - case work = 2 - case custom = 3 - } - - private class func SSKProtoDataMessageContactPostalAddressTypeWrap(_ value: SignalServiceProtos_DataMessage.Contact.PostalAddress.TypeEnum) -> SSKProtoDataMessageContactPostalAddressType { - switch value { - case .home: return .home - case .work: return .work - case .custom: return .custom - } - } - - private class func SSKProtoDataMessageContactPostalAddressTypeUnwrap(_ value: SSKProtoDataMessageContactPostalAddressType) -> SignalServiceProtos_DataMessage.Contact.PostalAddress.TypeEnum { - switch value { - case .home: return .home - case .work: return .work - case .custom: return .custom - } - } - - // MARK: - SSKProtoDataMessageContactPostalAddressBuilder - - @objc public class func builder() -> SSKProtoDataMessageContactPostalAddressBuilder { - return SSKProtoDataMessageContactPostalAddressBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessageContactPostalAddressBuilder { - let builder = SSKProtoDataMessageContactPostalAddressBuilder() - if hasType { - builder.setType(type) - } - if let _value = label { - builder.setLabel(_value) - } - if let _value = street { - builder.setStreet(_value) - } - if let _value = pobox { - builder.setPobox(_value) - } - if let _value = neighborhood { - builder.setNeighborhood(_value) - } - if let _value = city { - builder.setCity(_value) - } - if let _value = region { - builder.setRegion(_value) - } - if let _value = postcode { - builder.setPostcode(_value) - } - if let _value = country { - builder.setCountry(_value) - } - return builder - } - - @objc public class SSKProtoDataMessageContactPostalAddressBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage.Contact.PostalAddress() - - @objc fileprivate override init() {} - - @objc public func setType(_ valueParam: SSKProtoDataMessageContactPostalAddressType) { - proto.type = SSKProtoDataMessageContactPostalAddressTypeUnwrap(valueParam) - } - - @objc public func setLabel(_ valueParam: String) { - proto.label = valueParam - } - - @objc public func setStreet(_ valueParam: String) { - proto.street = valueParam - } - - @objc public func setPobox(_ valueParam: String) { - proto.pobox = valueParam - } - - @objc public func setNeighborhood(_ valueParam: String) { - proto.neighborhood = valueParam - } - - @objc public func setCity(_ valueParam: String) { - proto.city = valueParam - } - - @objc public func setRegion(_ valueParam: String) { - proto.region = valueParam - } - - @objc public func setPostcode(_ valueParam: String) { - proto.postcode = valueParam - } - - @objc public func setCountry(_ valueParam: String) { - proto.country = valueParam - } - - @objc public func build() throws -> SSKProtoDataMessageContactPostalAddress { - return try SSKProtoDataMessageContactPostalAddress.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessageContactPostalAddress.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage.Contact.PostalAddress - - @objc public var type: SSKProtoDataMessageContactPostalAddressType { - return SSKProtoDataMessageContactPostalAddress.SSKProtoDataMessageContactPostalAddressTypeWrap(proto.type) - } - @objc public var hasType: Bool { - return proto.hasType - } - - @objc public var label: String? { - guard proto.hasLabel else { - return nil - } - return proto.label - } - @objc public var hasLabel: Bool { - return proto.hasLabel - } - - @objc public var street: String? { - guard proto.hasStreet else { - return nil - } - return proto.street - } - @objc public var hasStreet: Bool { - return proto.hasStreet - } - - @objc public var pobox: String? { - guard proto.hasPobox else { - return nil - } - return proto.pobox - } - @objc public var hasPobox: Bool { - return proto.hasPobox - } - - @objc public var neighborhood: String? { - guard proto.hasNeighborhood else { - return nil - } - return proto.neighborhood - } - @objc public var hasNeighborhood: Bool { - return proto.hasNeighborhood - } - - @objc public var city: String? { - guard proto.hasCity else { - return nil - } - return proto.city - } - @objc public var hasCity: Bool { - return proto.hasCity - } - - @objc public var region: String? { - guard proto.hasRegion else { - return nil - } - return proto.region - } - @objc public var hasRegion: Bool { - return proto.hasRegion - } - - @objc public var postcode: String? { - guard proto.hasPostcode else { - return nil - } - return proto.postcode - } - @objc public var hasPostcode: Bool { - return proto.hasPostcode - } - - @objc public var country: String? { - guard proto.hasCountry else { - return nil - } - return proto.country - } - @objc public var hasCountry: Bool { - return proto.hasCountry - } - - private init(proto: SignalServiceProtos_DataMessage.Contact.PostalAddress) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessageContactPostalAddress { - let proto = try SignalServiceProtos_DataMessage.Contact.PostalAddress(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage.Contact.PostalAddress) throws -> SSKProtoDataMessageContactPostalAddress { - // MARK: - Begin Validation Logic for SSKProtoDataMessageContactPostalAddress - - - // MARK: - End Validation Logic for SSKProtoDataMessageContactPostalAddress - - - let result = SSKProtoDataMessageContactPostalAddress(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessageContactPostalAddress { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessageContactPostalAddress.SSKProtoDataMessageContactPostalAddressBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessageContactPostalAddress? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessageContactAvatar - -@objc public class SSKProtoDataMessageContactAvatar: NSObject { - - // MARK: - SSKProtoDataMessageContactAvatarBuilder - - @objc public class func builder() -> SSKProtoDataMessageContactAvatarBuilder { - return SSKProtoDataMessageContactAvatarBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessageContactAvatarBuilder { - let builder = SSKProtoDataMessageContactAvatarBuilder() - if let _value = avatar { - builder.setAvatar(_value) - } - if hasIsProfile { - builder.setIsProfile(isProfile) - } - return builder - } - - @objc public class SSKProtoDataMessageContactAvatarBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage.Contact.Avatar() - - @objc fileprivate override init() {} - - @objc public func setAvatar(_ valueParam: SSKProtoAttachmentPointer) { - proto.avatar = valueParam.proto - } - - @objc public func setIsProfile(_ valueParam: Bool) { - proto.isProfile = valueParam - } - - @objc public func build() throws -> SSKProtoDataMessageContactAvatar { - return try SSKProtoDataMessageContactAvatar.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessageContactAvatar.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage.Contact.Avatar - - @objc public let avatar: SSKProtoAttachmentPointer? - - @objc public var isProfile: Bool { - return proto.isProfile - } - @objc public var hasIsProfile: Bool { - return proto.hasIsProfile - } - - private init(proto: SignalServiceProtos_DataMessage.Contact.Avatar, - avatar: SSKProtoAttachmentPointer?) { - self.proto = proto - self.avatar = avatar - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessageContactAvatar { - let proto = try SignalServiceProtos_DataMessage.Contact.Avatar(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage.Contact.Avatar) throws -> SSKProtoDataMessageContactAvatar { - var avatar: SSKProtoAttachmentPointer? = nil - if proto.hasAvatar { - avatar = try SSKProtoAttachmentPointer.parseProto(proto.avatar) - } - - // MARK: - Begin Validation Logic for SSKProtoDataMessageContactAvatar - - - // MARK: - End Validation Logic for SSKProtoDataMessageContactAvatar - - - let result = SSKProtoDataMessageContactAvatar(proto: proto, - avatar: avatar) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessageContactAvatar { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessageContactAvatar.SSKProtoDataMessageContactAvatarBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessageContactAvatar? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessageContact - -@objc public class SSKProtoDataMessageContact: NSObject { - - // MARK: - SSKProtoDataMessageContactBuilder - - @objc public class func builder() -> SSKProtoDataMessageContactBuilder { - return SSKProtoDataMessageContactBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessageContactBuilder { - let builder = SSKProtoDataMessageContactBuilder() - if let _value = name { - builder.setName(_value) - } - builder.setNumber(number) - builder.setEmail(email) - builder.setAddress(address) - if let _value = avatar { - builder.setAvatar(_value) - } - if let _value = organization { - builder.setOrganization(_value) - } - return builder - } - - @objc public class SSKProtoDataMessageContactBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage.Contact() - - @objc fileprivate override init() {} - - @objc public func setName(_ valueParam: SSKProtoDataMessageContactName) { - proto.name = valueParam.proto - } - - @objc public func addNumber(_ valueParam: SSKProtoDataMessageContactPhone) { - var items = proto.number - items.append(valueParam.proto) - proto.number = items - } - - @objc public func setNumber(_ wrappedItems: [SSKProtoDataMessageContactPhone]) { - proto.number = wrappedItems.map { $0.proto } - } - - @objc public func addEmail(_ valueParam: SSKProtoDataMessageContactEmail) { - var items = proto.email - items.append(valueParam.proto) - proto.email = items - } - - @objc public func setEmail(_ wrappedItems: [SSKProtoDataMessageContactEmail]) { - proto.email = wrappedItems.map { $0.proto } - } - - @objc public func addAddress(_ valueParam: SSKProtoDataMessageContactPostalAddress) { - var items = proto.address - items.append(valueParam.proto) - proto.address = items - } - - @objc public func setAddress(_ wrappedItems: [SSKProtoDataMessageContactPostalAddress]) { - proto.address = wrappedItems.map { $0.proto } - } - - @objc public func setAvatar(_ valueParam: SSKProtoDataMessageContactAvatar) { - proto.avatar = valueParam.proto - } - - @objc public func setOrganization(_ valueParam: String) { - proto.organization = valueParam - } - - @objc public func build() throws -> SSKProtoDataMessageContact { - return try SSKProtoDataMessageContact.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessageContact.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage.Contact - - @objc public let name: SSKProtoDataMessageContactName? - - @objc public let number: [SSKProtoDataMessageContactPhone] - - @objc public let email: [SSKProtoDataMessageContactEmail] - - @objc public let address: [SSKProtoDataMessageContactPostalAddress] - - @objc public let avatar: SSKProtoDataMessageContactAvatar? - - @objc public var organization: String? { - guard proto.hasOrganization else { - return nil - } - return proto.organization - } - @objc public var hasOrganization: Bool { - return proto.hasOrganization - } - - private init(proto: SignalServiceProtos_DataMessage.Contact, - name: SSKProtoDataMessageContactName?, - number: [SSKProtoDataMessageContactPhone], - email: [SSKProtoDataMessageContactEmail], - address: [SSKProtoDataMessageContactPostalAddress], - avatar: SSKProtoDataMessageContactAvatar?) { - self.proto = proto - self.name = name - self.number = number - self.email = email - self.address = address - self.avatar = avatar - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessageContact { - let proto = try SignalServiceProtos_DataMessage.Contact(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage.Contact) throws -> SSKProtoDataMessageContact { - var name: SSKProtoDataMessageContactName? = nil - if proto.hasName { - name = try SSKProtoDataMessageContactName.parseProto(proto.name) - } - - var number: [SSKProtoDataMessageContactPhone] = [] - number = try proto.number.map { try SSKProtoDataMessageContactPhone.parseProto($0) } - - var email: [SSKProtoDataMessageContactEmail] = [] - email = try proto.email.map { try SSKProtoDataMessageContactEmail.parseProto($0) } - - var address: [SSKProtoDataMessageContactPostalAddress] = [] - address = try proto.address.map { try SSKProtoDataMessageContactPostalAddress.parseProto($0) } - - var avatar: SSKProtoDataMessageContactAvatar? = nil - if proto.hasAvatar { - avatar = try SSKProtoDataMessageContactAvatar.parseProto(proto.avatar) - } - - // MARK: - Begin Validation Logic for SSKProtoDataMessageContact - - - // MARK: - End Validation Logic for SSKProtoDataMessageContact - - - let result = SSKProtoDataMessageContact(proto: proto, - name: name, - number: number, - email: email, - address: address, - avatar: avatar) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessageContact { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessageContact.SSKProtoDataMessageContactBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessageContact? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessagePreview - -@objc public class SSKProtoDataMessagePreview: NSObject { - - // MARK: - SSKProtoDataMessagePreviewBuilder - - @objc public class func builder(url: String) -> SSKProtoDataMessagePreviewBuilder { - return SSKProtoDataMessagePreviewBuilder(url: url) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessagePreviewBuilder { - let builder = SSKProtoDataMessagePreviewBuilder(url: url) - if let _value = title { - builder.setTitle(_value) - } - if let _value = image { - builder.setImage(_value) - } - return builder - } - - @objc public class SSKProtoDataMessagePreviewBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage.Preview() - - @objc fileprivate override init() {} - - @objc fileprivate init(url: String) { - super.init() - - setUrl(url) - } - - @objc public func setUrl(_ valueParam: String) { - proto.url = valueParam - } - - @objc public func setTitle(_ valueParam: String) { - proto.title = valueParam - } - - @objc public func setImage(_ valueParam: SSKProtoAttachmentPointer) { - proto.image = valueParam.proto - } - - @objc public func build() throws -> SSKProtoDataMessagePreview { - return try SSKProtoDataMessagePreview.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessagePreview.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage.Preview - - @objc public let url: String - - @objc public let image: SSKProtoAttachmentPointer? - - @objc public var title: String? { - guard proto.hasTitle else { - return nil - } - return proto.title - } - @objc public var hasTitle: Bool { - return proto.hasTitle - } - - private init(proto: SignalServiceProtos_DataMessage.Preview, - url: String, - image: SSKProtoAttachmentPointer?) { - self.proto = proto - self.url = url - self.image = image - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessagePreview { - let proto = try SignalServiceProtos_DataMessage.Preview(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage.Preview) throws -> SSKProtoDataMessagePreview { - guard proto.hasURL else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: url") - } - let url = proto.url - - var image: SSKProtoAttachmentPointer? = nil - if proto.hasImage { - image = try SSKProtoAttachmentPointer.parseProto(proto.image) - } - - // MARK: - Begin Validation Logic for SSKProtoDataMessagePreview - - - // MARK: - End Validation Logic for SSKProtoDataMessagePreview - - - let result = SSKProtoDataMessagePreview(proto: proto, - url: url, - image: image) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessagePreview { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessagePreview.SSKProtoDataMessagePreviewBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessagePreview? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessageLokiProfile - -@objc public class SSKProtoDataMessageLokiProfile: NSObject { - - // MARK: - SSKProtoDataMessageLokiProfileBuilder - - @objc public class func builder() -> SSKProtoDataMessageLokiProfileBuilder { - return SSKProtoDataMessageLokiProfileBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessageLokiProfileBuilder { - let builder = SSKProtoDataMessageLokiProfileBuilder() - if let _value = displayName { - builder.setDisplayName(_value) - } - if let _value = profilePicture { - builder.setProfilePicture(_value) - } - return builder - } - - @objc public class SSKProtoDataMessageLokiProfileBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage.LokiProfile() - - @objc fileprivate override init() {} - - @objc public func setDisplayName(_ valueParam: String) { - proto.displayName = valueParam - } - - @objc public func setProfilePicture(_ valueParam: String) { - proto.profilePicture = valueParam - } - - @objc public func build() throws -> SSKProtoDataMessageLokiProfile { - return try SSKProtoDataMessageLokiProfile.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessageLokiProfile.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage.LokiProfile - - @objc public var displayName: String? { - guard proto.hasDisplayName else { - return nil - } - return proto.displayName - } - @objc public var hasDisplayName: Bool { - return proto.hasDisplayName - } - - @objc public var profilePicture: String? { - guard proto.hasProfilePicture else { - return nil - } - return proto.profilePicture - } - @objc public var hasProfilePicture: Bool { - return proto.hasProfilePicture - } - - private init(proto: SignalServiceProtos_DataMessage.LokiProfile) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessageLokiProfile { - let proto = try SignalServiceProtos_DataMessage.LokiProfile(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage.LokiProfile) throws -> SSKProtoDataMessageLokiProfile { - // MARK: - Begin Validation Logic for SSKProtoDataMessageLokiProfile - - - // MARK: - End Validation Logic for SSKProtoDataMessageLokiProfile - - - let result = SSKProtoDataMessageLokiProfile(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessageLokiProfile { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessageLokiProfile? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessageClosedGroupUpdateSenderKey - -@objc public class SSKProtoDataMessageClosedGroupUpdateSenderKey: NSObject { - - // MARK: - SSKProtoDataMessageClosedGroupUpdateSenderKeyBuilder - - @objc public class func builder(chainKey: Data, keyIndex: UInt32, publicKey: Data) -> SSKProtoDataMessageClosedGroupUpdateSenderKeyBuilder { - return SSKProtoDataMessageClosedGroupUpdateSenderKeyBuilder(chainKey: chainKey, keyIndex: keyIndex, publicKey: publicKey) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessageClosedGroupUpdateSenderKeyBuilder { - let builder = SSKProtoDataMessageClosedGroupUpdateSenderKeyBuilder(chainKey: chainKey, keyIndex: keyIndex, publicKey: publicKey) - return builder - } - - @objc public class SSKProtoDataMessageClosedGroupUpdateSenderKeyBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey() - - @objc fileprivate override init() {} - - @objc fileprivate init(chainKey: Data, keyIndex: UInt32, publicKey: Data) { - super.init() - - setChainKey(chainKey) - setKeyIndex(keyIndex) - setPublicKey(publicKey) - } - - @objc public func setChainKey(_ valueParam: Data) { - proto.chainKey = valueParam - } - - @objc public func setKeyIndex(_ valueParam: UInt32) { - proto.keyIndex = valueParam - } - - @objc public func setPublicKey(_ valueParam: Data) { - proto.publicKey = valueParam - } - - @objc public func build() throws -> SSKProtoDataMessageClosedGroupUpdateSenderKey { - return try SSKProtoDataMessageClosedGroupUpdateSenderKey.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessageClosedGroupUpdateSenderKey.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey - - @objc public let chainKey: Data - - @objc public let keyIndex: UInt32 - - @objc public let publicKey: Data - - private init(proto: SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey, - chainKey: Data, - keyIndex: UInt32, - publicKey: Data) { - self.proto = proto - self.chainKey = chainKey - self.keyIndex = keyIndex - self.publicKey = publicKey - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessageClosedGroupUpdateSenderKey { - let proto = try SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey) throws -> SSKProtoDataMessageClosedGroupUpdateSenderKey { - guard proto.hasChainKey else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: chainKey") - } - let chainKey = proto.chainKey - - guard proto.hasKeyIndex else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: keyIndex") - } - let keyIndex = proto.keyIndex - - guard proto.hasPublicKey else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: publicKey") - } - let publicKey = proto.publicKey - - // MARK: - Begin Validation Logic for SSKProtoDataMessageClosedGroupUpdateSenderKey - - - // MARK: - End Validation Logic for SSKProtoDataMessageClosedGroupUpdateSenderKey - - - let result = SSKProtoDataMessageClosedGroupUpdateSenderKey(proto: proto, - chainKey: chainKey, - keyIndex: keyIndex, - publicKey: publicKey) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessageClosedGroupUpdateSenderKey { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessageClosedGroupUpdateSenderKey.SSKProtoDataMessageClosedGroupUpdateSenderKeyBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessageClosedGroupUpdateSenderKey? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessageClosedGroupUpdate - -@objc public class SSKProtoDataMessageClosedGroupUpdate: NSObject { - - // MARK: - SSKProtoDataMessageClosedGroupUpdateType - - @objc public enum SSKProtoDataMessageClosedGroupUpdateType: Int32 { - case new = 0 - case info = 1 - case senderKeyRequest = 2 - case senderKey = 3 - } - - private class func SSKProtoDataMessageClosedGroupUpdateTypeWrap(_ value: SignalServiceProtos_DataMessage.ClosedGroupUpdate.TypeEnum) -> SSKProtoDataMessageClosedGroupUpdateType { - switch value { - case .new: return .new - case .info: return .info - case .senderKeyRequest: return .senderKeyRequest - case .senderKey: return .senderKey - } - } - - private class func SSKProtoDataMessageClosedGroupUpdateTypeUnwrap(_ value: SSKProtoDataMessageClosedGroupUpdateType) -> SignalServiceProtos_DataMessage.ClosedGroupUpdate.TypeEnum { - switch value { - case .new: return .new - case .info: return .info - case .senderKeyRequest: return .senderKeyRequest - case .senderKey: return .senderKey - } - } - - // MARK: - SSKProtoDataMessageClosedGroupUpdateBuilder - - @objc public class func builder(groupPublicKey: Data, type: SSKProtoDataMessageClosedGroupUpdateType) -> SSKProtoDataMessageClosedGroupUpdateBuilder { - return SSKProtoDataMessageClosedGroupUpdateBuilder(groupPublicKey: groupPublicKey, type: type) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessageClosedGroupUpdateBuilder { - let builder = SSKProtoDataMessageClosedGroupUpdateBuilder(groupPublicKey: groupPublicKey, type: type) - if let _value = name { - builder.setName(_value) - } - if let _value = groupPrivateKey { - builder.setGroupPrivateKey(_value) - } - builder.setSenderKeys(senderKeys) - builder.setMembers(members) - builder.setAdmins(admins) - return builder - } - - @objc public class SSKProtoDataMessageClosedGroupUpdateBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage.ClosedGroupUpdate() - - @objc fileprivate override init() {} - - @objc fileprivate init(groupPublicKey: Data, type: SSKProtoDataMessageClosedGroupUpdateType) { - super.init() - - setGroupPublicKey(groupPublicKey) - setType(type) - } - - @objc public func setName(_ valueParam: String) { - proto.name = valueParam - } - - @objc public func setGroupPublicKey(_ valueParam: Data) { - proto.groupPublicKey = valueParam - } - - @objc public func setGroupPrivateKey(_ valueParam: Data) { - proto.groupPrivateKey = valueParam - } - - @objc public func addSenderKeys(_ valueParam: SSKProtoDataMessageClosedGroupUpdateSenderKey) { - var items = proto.senderKeys - items.append(valueParam.proto) - proto.senderKeys = items - } - - @objc public func setSenderKeys(_ wrappedItems: [SSKProtoDataMessageClosedGroupUpdateSenderKey]) { - proto.senderKeys = wrappedItems.map { $0.proto } - } - - @objc public func addMembers(_ valueParam: Data) { - var items = proto.members - items.append(valueParam) - proto.members = items - } - - @objc public func setMembers(_ wrappedItems: [Data]) { - proto.members = wrappedItems - } - - @objc public func addAdmins(_ valueParam: Data) { - var items = proto.admins - items.append(valueParam) - proto.admins = items - } - - @objc public func setAdmins(_ wrappedItems: [Data]) { - proto.admins = wrappedItems - } - - @objc public func setType(_ valueParam: SSKProtoDataMessageClosedGroupUpdateType) { - proto.type = SSKProtoDataMessageClosedGroupUpdateTypeUnwrap(valueParam) - } - - @objc public func build() throws -> SSKProtoDataMessageClosedGroupUpdate { - return try SSKProtoDataMessageClosedGroupUpdate.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessageClosedGroupUpdate.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage.ClosedGroupUpdate - - @objc public let groupPublicKey: Data - - @objc public let senderKeys: [SSKProtoDataMessageClosedGroupUpdateSenderKey] - - @objc public let type: SSKProtoDataMessageClosedGroupUpdateType - - @objc public var name: String? { - guard proto.hasName else { - return nil - } - return proto.name - } - @objc public var hasName: Bool { - return proto.hasName - } - - @objc public var groupPrivateKey: Data? { - guard proto.hasGroupPrivateKey else { - return nil - } - return proto.groupPrivateKey - } - @objc public var hasGroupPrivateKey: Bool { - return proto.hasGroupPrivateKey - } - - @objc public var members: [Data] { - return proto.members - } - - @objc public var admins: [Data] { - return proto.admins - } - - private init(proto: SignalServiceProtos_DataMessage.ClosedGroupUpdate, - groupPublicKey: Data, - senderKeys: [SSKProtoDataMessageClosedGroupUpdateSenderKey], - type: SSKProtoDataMessageClosedGroupUpdateType) { - self.proto = proto - self.groupPublicKey = groupPublicKey - self.senderKeys = senderKeys - self.type = type - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessageClosedGroupUpdate { - let proto = try SignalServiceProtos_DataMessage.ClosedGroupUpdate(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage.ClosedGroupUpdate) throws -> SSKProtoDataMessageClosedGroupUpdate { - guard proto.hasGroupPublicKey else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: groupPublicKey") - } - let groupPublicKey = proto.groupPublicKey - - var senderKeys: [SSKProtoDataMessageClosedGroupUpdateSenderKey] = [] - senderKeys = try proto.senderKeys.map { try SSKProtoDataMessageClosedGroupUpdateSenderKey.parseProto($0) } - - guard proto.hasType else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") - } - let type = SSKProtoDataMessageClosedGroupUpdateTypeWrap(proto.type) - - // MARK: - Begin Validation Logic for SSKProtoDataMessageClosedGroupUpdate - - - // MARK: - End Validation Logic for SSKProtoDataMessageClosedGroupUpdate - - - let result = SSKProtoDataMessageClosedGroupUpdate(proto: proto, - groupPublicKey: groupPublicKey, - senderKeys: senderKeys, - type: type) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessageClosedGroupUpdate { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessageClosedGroupUpdate.SSKProtoDataMessageClosedGroupUpdateBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessageClosedGroupUpdate? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoDataMessage - -@objc public class SSKProtoDataMessage: NSObject { - - // MARK: - SSKProtoDataMessageFlags - - @objc public enum SSKProtoDataMessageFlags: Int32 { - case endSession = 1 - case expirationTimerUpdate = 2 - case profileKeyUpdate = 4 - case unlinkDevice = 128 - } - - private class func SSKProtoDataMessageFlagsWrap(_ value: SignalServiceProtos_DataMessage.Flags) -> SSKProtoDataMessageFlags { - switch value { - case .endSession: return .endSession - case .expirationTimerUpdate: return .expirationTimerUpdate - case .profileKeyUpdate: return .profileKeyUpdate - case .unlinkDevice: return .unlinkDevice - } - } - - private class func SSKProtoDataMessageFlagsUnwrap(_ value: SSKProtoDataMessageFlags) -> SignalServiceProtos_DataMessage.Flags { - switch value { - case .endSession: return .endSession - case .expirationTimerUpdate: return .expirationTimerUpdate - case .profileKeyUpdate: return .profileKeyUpdate - case .unlinkDevice: return .unlinkDevice - } - } - - // MARK: - SSKProtoDataMessageBuilder - - @objc public class func builder() -> SSKProtoDataMessageBuilder { - return SSKProtoDataMessageBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoDataMessageBuilder { - let builder = SSKProtoDataMessageBuilder() - if let _value = body { - builder.setBody(_value) - } - builder.setAttachments(attachments) - if let _value = group { - builder.setGroup(_value) - } - if hasFlags { - builder.setFlags(flags) - } - if hasExpireTimer { - builder.setExpireTimer(expireTimer) - } - if let _value = profileKey { - builder.setProfileKey(_value) - } - if hasTimestamp { - builder.setTimestamp(timestamp) - } - if let _value = quote { - builder.setQuote(_value) - } - builder.setContact(contact) - builder.setPreview(preview) - if let _value = profile { - builder.setProfile(_value) - } - if let _value = closedGroupUpdate { - builder.setClosedGroupUpdate(_value) - } - if let _value = publicChatInfo { - builder.setPublicChatInfo(_value) - } - return builder - } - - @objc public class SSKProtoDataMessageBuilder: NSObject { - - private var proto = SignalServiceProtos_DataMessage() - - @objc fileprivate override init() {} - - @objc public func setBody(_ valueParam: String) { - proto.body = valueParam - } - - @objc public func addAttachments(_ valueParam: SSKProtoAttachmentPointer) { - var items = proto.attachments - items.append(valueParam.proto) - proto.attachments = items - } - - @objc public func setAttachments(_ wrappedItems: [SSKProtoAttachmentPointer]) { - proto.attachments = wrappedItems.map { $0.proto } - } - - @objc public func setGroup(_ valueParam: SSKProtoGroupContext) { - proto.group = valueParam.proto - } - - @objc public func setFlags(_ valueParam: UInt32) { - proto.flags = valueParam - } - - @objc public func setExpireTimer(_ valueParam: UInt32) { - proto.expireTimer = valueParam - } - - @objc public func setProfileKey(_ valueParam: Data) { - proto.profileKey = valueParam - } - - @objc public func setTimestamp(_ valueParam: UInt64) { - proto.timestamp = valueParam - } - - @objc public func setQuote(_ valueParam: SSKProtoDataMessageQuote) { - proto.quote = valueParam.proto - } - - @objc public func addContact(_ valueParam: SSKProtoDataMessageContact) { - var items = proto.contact - items.append(valueParam.proto) - proto.contact = items - } - - @objc public func setContact(_ wrappedItems: [SSKProtoDataMessageContact]) { - proto.contact = wrappedItems.map { $0.proto } - } - - @objc public func addPreview(_ valueParam: SSKProtoDataMessagePreview) { - var items = proto.preview - items.append(valueParam.proto) - proto.preview = items - } - - @objc public func setPreview(_ wrappedItems: [SSKProtoDataMessagePreview]) { - proto.preview = wrappedItems.map { $0.proto } - } - - @objc public func setProfile(_ valueParam: SSKProtoDataMessageLokiProfile) { - proto.profile = valueParam.proto - } - - @objc public func setClosedGroupUpdate(_ valueParam: SSKProtoDataMessageClosedGroupUpdate) { - proto.closedGroupUpdate = valueParam.proto - } - - @objc public func setPublicChatInfo(_ valueParam: SSKProtoPublicChatInfo) { - proto.publicChatInfo = valueParam.proto - } - - @objc public func build() throws -> SSKProtoDataMessage { - return try SSKProtoDataMessage.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoDataMessage.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_DataMessage - - @objc public let attachments: [SSKProtoAttachmentPointer] - - @objc public let group: SSKProtoGroupContext? - - @objc public let quote: SSKProtoDataMessageQuote? - - @objc public let contact: [SSKProtoDataMessageContact] - - @objc public let preview: [SSKProtoDataMessagePreview] - - @objc public let profile: SSKProtoDataMessageLokiProfile? - - @objc public let closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate? - - @objc public let publicChatInfo: SSKProtoPublicChatInfo? - - @objc public var body: String? { - guard proto.hasBody else { - return nil - } - return proto.body - } - @objc public var hasBody: Bool { - return proto.hasBody - } - - @objc public var flags: UInt32 { - return proto.flags - } - @objc public var hasFlags: Bool { - return proto.hasFlags - } - - @objc public var expireTimer: UInt32 { - return proto.expireTimer - } - @objc public var hasExpireTimer: Bool { - return proto.hasExpireTimer - } - - @objc public var profileKey: Data? { - guard proto.hasProfileKey else { - return nil - } - return proto.profileKey - } - @objc public var hasProfileKey: Bool { - return proto.hasProfileKey - } - - @objc public var timestamp: UInt64 { - return proto.timestamp - } - @objc public var hasTimestamp: Bool { - return proto.hasTimestamp - } - - private init(proto: SignalServiceProtos_DataMessage, - attachments: [SSKProtoAttachmentPointer], - group: SSKProtoGroupContext?, - quote: SSKProtoDataMessageQuote?, - contact: [SSKProtoDataMessageContact], - preview: [SSKProtoDataMessagePreview], - profile: SSKProtoDataMessageLokiProfile?, - closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate?, - publicChatInfo: SSKProtoPublicChatInfo?) { - self.proto = proto - self.attachments = attachments - self.group = group - self.quote = quote - self.contact = contact - self.preview = preview - self.profile = profile - self.closedGroupUpdate = closedGroupUpdate - self.publicChatInfo = publicChatInfo - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoDataMessage { - let proto = try SignalServiceProtos_DataMessage(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_DataMessage) throws -> SSKProtoDataMessage { - var attachments: [SSKProtoAttachmentPointer] = [] - attachments = try proto.attachments.map { try SSKProtoAttachmentPointer.parseProto($0) } - - var group: SSKProtoGroupContext? = nil - if proto.hasGroup { - group = try SSKProtoGroupContext.parseProto(proto.group) - } - - var quote: SSKProtoDataMessageQuote? = nil - if proto.hasQuote { - quote = try SSKProtoDataMessageQuote.parseProto(proto.quote) - } - - var contact: [SSKProtoDataMessageContact] = [] - contact = try proto.contact.map { try SSKProtoDataMessageContact.parseProto($0) } - - var preview: [SSKProtoDataMessagePreview] = [] - preview = try proto.preview.map { try SSKProtoDataMessagePreview.parseProto($0) } - - var profile: SSKProtoDataMessageLokiProfile? = nil - if proto.hasProfile { - profile = try SSKProtoDataMessageLokiProfile.parseProto(proto.profile) - } - - var closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate? = nil - if proto.hasClosedGroupUpdate { - closedGroupUpdate = try SSKProtoDataMessageClosedGroupUpdate.parseProto(proto.closedGroupUpdate) - } - - var publicChatInfo: SSKProtoPublicChatInfo? = nil - if proto.hasPublicChatInfo { - publicChatInfo = try SSKProtoPublicChatInfo.parseProto(proto.publicChatInfo) - } - - // MARK: - Begin Validation Logic for SSKProtoDataMessage - - - // MARK: - End Validation Logic for SSKProtoDataMessage - - - let result = SSKProtoDataMessage(proto: proto, - attachments: attachments, - group: group, - quote: quote, - contact: contact, - preview: preview, - profile: profile, - closedGroupUpdate: closedGroupUpdate, - publicChatInfo: publicChatInfo) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoDataMessage { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoDataMessage.SSKProtoDataMessageBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoDataMessage? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoNullMessage - -@objc public class SSKProtoNullMessage: NSObject { - - // MARK: - SSKProtoNullMessageBuilder - - @objc public class func builder() -> SSKProtoNullMessageBuilder { - return SSKProtoNullMessageBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoNullMessageBuilder { - let builder = SSKProtoNullMessageBuilder() - if let _value = padding { - builder.setPadding(_value) - } - return builder - } - - @objc public class SSKProtoNullMessageBuilder: NSObject { - - private var proto = SignalServiceProtos_NullMessage() - - @objc fileprivate override init() {} - - @objc public func setPadding(_ valueParam: Data) { - proto.padding = valueParam - } - - @objc public func build() throws -> SSKProtoNullMessage { - return try SSKProtoNullMessage.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoNullMessage.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_NullMessage - - @objc public var padding: Data? { - guard proto.hasPadding else { - return nil - } - return proto.padding - } - @objc public var hasPadding: Bool { - return proto.hasPadding - } - - private init(proto: SignalServiceProtos_NullMessage) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoNullMessage { - let proto = try SignalServiceProtos_NullMessage(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_NullMessage) throws -> SSKProtoNullMessage { - // MARK: - Begin Validation Logic for SSKProtoNullMessage - - - // MARK: - End Validation Logic for SSKProtoNullMessage - - - let result = SSKProtoNullMessage(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoNullMessage { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoNullMessage.SSKProtoNullMessageBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoNullMessage? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoReceiptMessage - -@objc public class SSKProtoReceiptMessage: NSObject { - - // MARK: - SSKProtoReceiptMessageType - - @objc public enum SSKProtoReceiptMessageType: Int32 { - case delivery = 0 - case read = 1 - } - - private class func SSKProtoReceiptMessageTypeWrap(_ value: SignalServiceProtos_ReceiptMessage.TypeEnum) -> SSKProtoReceiptMessageType { - switch value { - case .delivery: return .delivery - case .read: return .read - } - } - - private class func SSKProtoReceiptMessageTypeUnwrap(_ value: SSKProtoReceiptMessageType) -> SignalServiceProtos_ReceiptMessage.TypeEnum { - switch value { - case .delivery: return .delivery - case .read: return .read - } - } - - // MARK: - SSKProtoReceiptMessageBuilder - - @objc public class func builder(type: SSKProtoReceiptMessageType) -> SSKProtoReceiptMessageBuilder { - return SSKProtoReceiptMessageBuilder(type: type) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoReceiptMessageBuilder { - let builder = SSKProtoReceiptMessageBuilder(type: type) - builder.setTimestamp(timestamp) - return builder - } - - @objc public class SSKProtoReceiptMessageBuilder: NSObject { - - private var proto = SignalServiceProtos_ReceiptMessage() - - @objc fileprivate override init() {} - - @objc fileprivate init(type: SSKProtoReceiptMessageType) { - super.init() - - setType(type) - } - - @objc public func setType(_ valueParam: SSKProtoReceiptMessageType) { - proto.type = SSKProtoReceiptMessageTypeUnwrap(valueParam) - } - - @objc public func addTimestamp(_ valueParam: UInt64) { - var items = proto.timestamp - items.append(valueParam) - proto.timestamp = items - } - - @objc public func setTimestamp(_ wrappedItems: [UInt64]) { - proto.timestamp = wrappedItems - } - - @objc public func build() throws -> SSKProtoReceiptMessage { - return try SSKProtoReceiptMessage.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoReceiptMessage.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_ReceiptMessage - - @objc public let type: SSKProtoReceiptMessageType - - @objc public var timestamp: [UInt64] { - return proto.timestamp - } - - private init(proto: SignalServiceProtos_ReceiptMessage, - type: SSKProtoReceiptMessageType) { - self.proto = proto - self.type = type - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoReceiptMessage { - let proto = try SignalServiceProtos_ReceiptMessage(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_ReceiptMessage) throws -> SSKProtoReceiptMessage { - guard proto.hasType else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") - } - let type = SSKProtoReceiptMessageTypeWrap(proto.type) - - // MARK: - Begin Validation Logic for SSKProtoReceiptMessage - - - // MARK: - End Validation Logic for SSKProtoReceiptMessage - - - let result = SSKProtoReceiptMessage(proto: proto, - type: type) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoReceiptMessage { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoReceiptMessage.SSKProtoReceiptMessageBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoReceiptMessage? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoVerified - -@objc public class SSKProtoVerified: NSObject { - - // MARK: - SSKProtoVerifiedState - - @objc public enum SSKProtoVerifiedState: Int32 { - case `default` = 0 - case verified = 1 - case unverified = 2 - } - - private class func SSKProtoVerifiedStateWrap(_ value: SignalServiceProtos_Verified.State) -> SSKProtoVerifiedState { - switch value { - case .default: return .default - case .verified: return .verified - case .unverified: return .unverified - } - } - - private class func SSKProtoVerifiedStateUnwrap(_ value: SSKProtoVerifiedState) -> SignalServiceProtos_Verified.State { - switch value { - case .default: return .default - case .verified: return .verified - case .unverified: return .unverified - } - } - - // MARK: - SSKProtoVerifiedBuilder - - @objc public class func builder(destination: String) -> SSKProtoVerifiedBuilder { - return SSKProtoVerifiedBuilder(destination: destination) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoVerifiedBuilder { - let builder = SSKProtoVerifiedBuilder(destination: destination) - if let _value = identityKey { - builder.setIdentityKey(_value) - } - if hasState { - builder.setState(state) - } - if let _value = nullMessage { - builder.setNullMessage(_value) - } - return builder - } - - @objc public class SSKProtoVerifiedBuilder: NSObject { - - private var proto = SignalServiceProtos_Verified() - - @objc fileprivate override init() {} - - @objc fileprivate init(destination: String) { - super.init() - - setDestination(destination) - } - - @objc public func setDestination(_ valueParam: String) { - proto.destination = valueParam - } - - @objc public func setIdentityKey(_ valueParam: Data) { - proto.identityKey = valueParam - } - - @objc public func setState(_ valueParam: SSKProtoVerifiedState) { - proto.state = SSKProtoVerifiedStateUnwrap(valueParam) - } - - @objc public func setNullMessage(_ valueParam: Data) { - proto.nullMessage = valueParam - } - - @objc public func build() throws -> SSKProtoVerified { - return try SSKProtoVerified.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoVerified.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_Verified - - @objc public let destination: String - - @objc public var identityKey: Data? { - guard proto.hasIdentityKey else { - return nil - } - return proto.identityKey - } - @objc public var hasIdentityKey: Bool { - return proto.hasIdentityKey - } - - @objc public var state: SSKProtoVerifiedState { - return SSKProtoVerified.SSKProtoVerifiedStateWrap(proto.state) - } - @objc public var hasState: Bool { - return proto.hasState - } - - @objc public var nullMessage: Data? { - guard proto.hasNullMessage else { - return nil - } - return proto.nullMessage - } - @objc public var hasNullMessage: Bool { - return proto.hasNullMessage - } - - private init(proto: SignalServiceProtos_Verified, - destination: String) { - self.proto = proto - self.destination = destination - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoVerified { - let proto = try SignalServiceProtos_Verified(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_Verified) throws -> SSKProtoVerified { - guard proto.hasDestination else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: destination") - } - let destination = proto.destination - - // MARK: - Begin Validation Logic for SSKProtoVerified - - - // MARK: - End Validation Logic for SSKProtoVerified - - - let result = SSKProtoVerified(proto: proto, - destination: destination) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoVerified { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoVerified.SSKProtoVerifiedBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoVerified? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoSyncMessageSentUnidentifiedDeliveryStatus - -@objc public class SSKProtoSyncMessageSentUnidentifiedDeliveryStatus: NSObject { - - // MARK: - SSKProtoSyncMessageSentUnidentifiedDeliveryStatusBuilder - - @objc public class func builder() -> SSKProtoSyncMessageSentUnidentifiedDeliveryStatusBuilder { - return SSKProtoSyncMessageSentUnidentifiedDeliveryStatusBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoSyncMessageSentUnidentifiedDeliveryStatusBuilder { - let builder = SSKProtoSyncMessageSentUnidentifiedDeliveryStatusBuilder() - if let _value = destination { - builder.setDestination(_value) - } - if hasUnidentified { - builder.setUnidentified(unidentified) - } - return builder - } - - @objc public class SSKProtoSyncMessageSentUnidentifiedDeliveryStatusBuilder: NSObject { - - private var proto = SignalServiceProtos_SyncMessage.Sent.UnidentifiedDeliveryStatus() - - @objc fileprivate override init() {} - - @objc public func setDestination(_ valueParam: String) { - proto.destination = valueParam - } - - @objc public func setUnidentified(_ valueParam: Bool) { - proto.unidentified = valueParam - } - - @objc public func build() throws -> SSKProtoSyncMessageSentUnidentifiedDeliveryStatus { - return try SSKProtoSyncMessageSentUnidentifiedDeliveryStatus.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoSyncMessageSentUnidentifiedDeliveryStatus.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_SyncMessage.Sent.UnidentifiedDeliveryStatus - - @objc public var destination: String? { - guard proto.hasDestination else { - return nil - } - return proto.destination - } - @objc public var hasDestination: Bool { - return proto.hasDestination - } - - @objc public var unidentified: Bool { - return proto.unidentified - } - @objc public var hasUnidentified: Bool { - return proto.hasUnidentified - } - - private init(proto: SignalServiceProtos_SyncMessage.Sent.UnidentifiedDeliveryStatus) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoSyncMessageSentUnidentifiedDeliveryStatus { - let proto = try SignalServiceProtos_SyncMessage.Sent.UnidentifiedDeliveryStatus(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_SyncMessage.Sent.UnidentifiedDeliveryStatus) throws -> SSKProtoSyncMessageSentUnidentifiedDeliveryStatus { - // MARK: - Begin Validation Logic for SSKProtoSyncMessageSentUnidentifiedDeliveryStatus - - - // MARK: - End Validation Logic for SSKProtoSyncMessageSentUnidentifiedDeliveryStatus - - - let result = SSKProtoSyncMessageSentUnidentifiedDeliveryStatus(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoSyncMessageSentUnidentifiedDeliveryStatus { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoSyncMessageSentUnidentifiedDeliveryStatus.SSKProtoSyncMessageSentUnidentifiedDeliveryStatusBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoSyncMessageSentUnidentifiedDeliveryStatus? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoSyncMessageSent - -@objc public class SSKProtoSyncMessageSent: NSObject { - - // MARK: - SSKProtoSyncMessageSentBuilder - - @objc public class func builder() -> SSKProtoSyncMessageSentBuilder { - return SSKProtoSyncMessageSentBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoSyncMessageSentBuilder { - let builder = SSKProtoSyncMessageSentBuilder() - if let _value = destination { - builder.setDestination(_value) - } - if hasTimestamp { - builder.setTimestamp(timestamp) - } - if let _value = message { - builder.setMessage(_value) - } - if hasExpirationStartTimestamp { - builder.setExpirationStartTimestamp(expirationStartTimestamp) - } - builder.setUnidentifiedStatus(unidentifiedStatus) - if hasIsRecipientUpdate { - builder.setIsRecipientUpdate(isRecipientUpdate) - } - return builder - } - - @objc public class SSKProtoSyncMessageSentBuilder: NSObject { - - private var proto = SignalServiceProtos_SyncMessage.Sent() - - @objc fileprivate override init() {} - - @objc public func setDestination(_ valueParam: String) { - proto.destination = valueParam - } - - @objc public func setTimestamp(_ valueParam: UInt64) { - proto.timestamp = valueParam - } - - @objc public func setMessage(_ valueParam: SSKProtoDataMessage) { - proto.message = valueParam.proto - } - - @objc public func setExpirationStartTimestamp(_ valueParam: UInt64) { - proto.expirationStartTimestamp = valueParam - } - - @objc public func addUnidentifiedStatus(_ valueParam: SSKProtoSyncMessageSentUnidentifiedDeliveryStatus) { - var items = proto.unidentifiedStatus - items.append(valueParam.proto) - proto.unidentifiedStatus = items - } - - @objc public func setUnidentifiedStatus(_ wrappedItems: [SSKProtoSyncMessageSentUnidentifiedDeliveryStatus]) { - proto.unidentifiedStatus = wrappedItems.map { $0.proto } - } - - @objc public func setIsRecipientUpdate(_ valueParam: Bool) { - proto.isRecipientUpdate = valueParam - } - - @objc public func build() throws -> SSKProtoSyncMessageSent { - return try SSKProtoSyncMessageSent.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoSyncMessageSent.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_SyncMessage.Sent - - @objc public let message: SSKProtoDataMessage? - - @objc public let unidentifiedStatus: [SSKProtoSyncMessageSentUnidentifiedDeliveryStatus] - - @objc public var destination: String? { - guard proto.hasDestination else { - return nil - } - return proto.destination - } - @objc public var hasDestination: Bool { - return proto.hasDestination - } - - @objc public var timestamp: UInt64 { - return proto.timestamp - } - @objc public var hasTimestamp: Bool { - return proto.hasTimestamp - } - - @objc public var expirationStartTimestamp: UInt64 { - return proto.expirationStartTimestamp - } - @objc public var hasExpirationStartTimestamp: Bool { - return proto.hasExpirationStartTimestamp - } - - @objc public var isRecipientUpdate: Bool { - return proto.isRecipientUpdate - } - @objc public var hasIsRecipientUpdate: Bool { - return proto.hasIsRecipientUpdate - } - - private init(proto: SignalServiceProtos_SyncMessage.Sent, - message: SSKProtoDataMessage?, - unidentifiedStatus: [SSKProtoSyncMessageSentUnidentifiedDeliveryStatus]) { - self.proto = proto - self.message = message - self.unidentifiedStatus = unidentifiedStatus - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoSyncMessageSent { - let proto = try SignalServiceProtos_SyncMessage.Sent(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_SyncMessage.Sent) throws -> SSKProtoSyncMessageSent { - var message: SSKProtoDataMessage? = nil - if proto.hasMessage { - message = try SSKProtoDataMessage.parseProto(proto.message) - } - - var unidentifiedStatus: [SSKProtoSyncMessageSentUnidentifiedDeliveryStatus] = [] - unidentifiedStatus = try proto.unidentifiedStatus.map { try SSKProtoSyncMessageSentUnidentifiedDeliveryStatus.parseProto($0) } - - // MARK: - Begin Validation Logic for SSKProtoSyncMessageSent - - - // MARK: - End Validation Logic for SSKProtoSyncMessageSent - - - let result = SSKProtoSyncMessageSent(proto: proto, - message: message, - unidentifiedStatus: unidentifiedStatus) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoSyncMessageSent { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoSyncMessageSent.SSKProtoSyncMessageSentBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoSyncMessageSent? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoSyncMessageContacts - -@objc public class SSKProtoSyncMessageContacts: NSObject { - - // MARK: - SSKProtoSyncMessageContactsBuilder - - @objc public class func builder() -> SSKProtoSyncMessageContactsBuilder { - return SSKProtoSyncMessageContactsBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoSyncMessageContactsBuilder { - let builder = SSKProtoSyncMessageContactsBuilder() - if let _value = blob { - builder.setBlob(_value) - } - if hasIsComplete { - builder.setIsComplete(isComplete) - } - if let _value = data { - builder.setData(_value) - } - return builder - } - - @objc public class SSKProtoSyncMessageContactsBuilder: NSObject { - - private var proto = SignalServiceProtos_SyncMessage.Contacts() - - @objc fileprivate override init() {} - - @objc public func setBlob(_ valueParam: SSKProtoAttachmentPointer) { - proto.blob = valueParam.proto - } - - @objc public func setIsComplete(_ valueParam: Bool) { - proto.isComplete = valueParam - } - - @objc public func setData(_ valueParam: Data) { - proto.data = valueParam - } - - @objc public func build() throws -> SSKProtoSyncMessageContacts { - return try SSKProtoSyncMessageContacts.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoSyncMessageContacts.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_SyncMessage.Contacts - - @objc public let blob: SSKProtoAttachmentPointer? - - @objc public var isComplete: Bool { - return proto.isComplete - } - @objc public var hasIsComplete: Bool { - return proto.hasIsComplete - } - - @objc public var data: Data? { - guard proto.hasData else { - return nil - } - return proto.data - } - @objc public var hasData: Bool { - return proto.hasData - } - - private init(proto: SignalServiceProtos_SyncMessage.Contacts, - blob: SSKProtoAttachmentPointer?) { - self.proto = proto - self.blob = blob - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoSyncMessageContacts { - let proto = try SignalServiceProtos_SyncMessage.Contacts(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_SyncMessage.Contacts) throws -> SSKProtoSyncMessageContacts { - var blob: SSKProtoAttachmentPointer? = nil - if proto.hasBlob { - blob = try SSKProtoAttachmentPointer.parseProto(proto.blob) - } - - // MARK: - Begin Validation Logic for SSKProtoSyncMessageContacts - - - // MARK: - End Validation Logic for SSKProtoSyncMessageContacts - - - let result = SSKProtoSyncMessageContacts(proto: proto, - blob: blob) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoSyncMessageContacts { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoSyncMessageContacts.SSKProtoSyncMessageContactsBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoSyncMessageContacts? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoSyncMessageGroups - -@objc public class SSKProtoSyncMessageGroups: NSObject { - - // MARK: - SSKProtoSyncMessageGroupsBuilder - - @objc public class func builder() -> SSKProtoSyncMessageGroupsBuilder { - return SSKProtoSyncMessageGroupsBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoSyncMessageGroupsBuilder { - let builder = SSKProtoSyncMessageGroupsBuilder() - if let _value = blob { - builder.setBlob(_value) - } - if let _value = data { - builder.setData(_value) - } - return builder - } - - @objc public class SSKProtoSyncMessageGroupsBuilder: NSObject { - - private var proto = SignalServiceProtos_SyncMessage.Groups() - - @objc fileprivate override init() {} - - @objc public func setBlob(_ valueParam: SSKProtoAttachmentPointer) { - proto.blob = valueParam.proto - } - - @objc public func setData(_ valueParam: Data) { - proto.data = valueParam - } - - @objc public func build() throws -> SSKProtoSyncMessageGroups { - return try SSKProtoSyncMessageGroups.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoSyncMessageGroups.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_SyncMessage.Groups - - @objc public let blob: SSKProtoAttachmentPointer? - - @objc public var data: Data? { - guard proto.hasData else { - return nil - } - return proto.data - } - @objc public var hasData: Bool { - return proto.hasData - } - - private init(proto: SignalServiceProtos_SyncMessage.Groups, - blob: SSKProtoAttachmentPointer?) { - self.proto = proto - self.blob = blob - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoSyncMessageGroups { - let proto = try SignalServiceProtos_SyncMessage.Groups(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_SyncMessage.Groups) throws -> SSKProtoSyncMessageGroups { - var blob: SSKProtoAttachmentPointer? = nil - if proto.hasBlob { - blob = try SSKProtoAttachmentPointer.parseProto(proto.blob) - } - - // MARK: - Begin Validation Logic for SSKProtoSyncMessageGroups - - - // MARK: - End Validation Logic for SSKProtoSyncMessageGroups - - - let result = SSKProtoSyncMessageGroups(proto: proto, - blob: blob) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoSyncMessageGroups { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoSyncMessageGroups.SSKProtoSyncMessageGroupsBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoSyncMessageGroups? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoSyncMessageOpenGroupDetails - -@objc public class SSKProtoSyncMessageOpenGroupDetails: NSObject { - - // MARK: - SSKProtoSyncMessageOpenGroupDetailsBuilder - - @objc public class func builder(url: String, channelID: UInt64) -> SSKProtoSyncMessageOpenGroupDetailsBuilder { - return SSKProtoSyncMessageOpenGroupDetailsBuilder(url: url, channelID: channelID) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoSyncMessageOpenGroupDetailsBuilder { - let builder = SSKProtoSyncMessageOpenGroupDetailsBuilder(url: url, channelID: channelID) - return builder - } - - @objc public class SSKProtoSyncMessageOpenGroupDetailsBuilder: NSObject { - - private var proto = SignalServiceProtos_SyncMessage.OpenGroupDetails() - - @objc fileprivate override init() {} - - @objc fileprivate init(url: String, channelID: UInt64) { - super.init() - - setUrl(url) - setChannelID(channelID) - } - - @objc public func setUrl(_ valueParam: String) { - proto.url = valueParam - } - - @objc public func setChannelID(_ valueParam: UInt64) { - proto.channelID = valueParam - } - - @objc public func build() throws -> SSKProtoSyncMessageOpenGroupDetails { - return try SSKProtoSyncMessageOpenGroupDetails.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoSyncMessageOpenGroupDetails.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_SyncMessage.OpenGroupDetails - - @objc public let url: String - - @objc public let channelID: UInt64 - - private init(proto: SignalServiceProtos_SyncMessage.OpenGroupDetails, - url: String, - channelID: UInt64) { - self.proto = proto - self.url = url - self.channelID = channelID - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoSyncMessageOpenGroupDetails { - let proto = try SignalServiceProtos_SyncMessage.OpenGroupDetails(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_SyncMessage.OpenGroupDetails) throws -> SSKProtoSyncMessageOpenGroupDetails { - guard proto.hasURL else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: url") - } - let url = proto.url - - guard proto.hasChannelID else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: channelID") - } - let channelID = proto.channelID - - // MARK: - Begin Validation Logic for SSKProtoSyncMessageOpenGroupDetails - - - // MARK: - End Validation Logic for SSKProtoSyncMessageOpenGroupDetails - - - let result = SSKProtoSyncMessageOpenGroupDetails(proto: proto, - url: url, - channelID: channelID) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoSyncMessageOpenGroupDetails { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoSyncMessageOpenGroupDetails.SSKProtoSyncMessageOpenGroupDetailsBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoSyncMessageOpenGroupDetails? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoSyncMessageBlocked - -@objc public class SSKProtoSyncMessageBlocked: NSObject { - - // MARK: - SSKProtoSyncMessageBlockedBuilder - - @objc public class func builder() -> SSKProtoSyncMessageBlockedBuilder { - return SSKProtoSyncMessageBlockedBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoSyncMessageBlockedBuilder { - let builder = SSKProtoSyncMessageBlockedBuilder() - builder.setNumbers(numbers) - builder.setGroupIds(groupIds) - return builder - } - - @objc public class SSKProtoSyncMessageBlockedBuilder: NSObject { - - private var proto = SignalServiceProtos_SyncMessage.Blocked() - - @objc fileprivate override init() {} - - @objc public func addNumbers(_ valueParam: String) { - var items = proto.numbers - items.append(valueParam) - proto.numbers = items - } - - @objc public func setNumbers(_ wrappedItems: [String]) { - proto.numbers = wrappedItems - } - - @objc public func addGroupIds(_ valueParam: Data) { - var items = proto.groupIds - items.append(valueParam) - proto.groupIds = items - } - - @objc public func setGroupIds(_ wrappedItems: [Data]) { - proto.groupIds = wrappedItems - } - - @objc public func build() throws -> SSKProtoSyncMessageBlocked { - return try SSKProtoSyncMessageBlocked.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoSyncMessageBlocked.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_SyncMessage.Blocked - - @objc public var numbers: [String] { - return proto.numbers - } - - @objc public var groupIds: [Data] { - return proto.groupIds - } - - private init(proto: SignalServiceProtos_SyncMessage.Blocked) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoSyncMessageBlocked { - let proto = try SignalServiceProtos_SyncMessage.Blocked(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_SyncMessage.Blocked) throws -> SSKProtoSyncMessageBlocked { - // MARK: - Begin Validation Logic for SSKProtoSyncMessageBlocked - - - // MARK: - End Validation Logic for SSKProtoSyncMessageBlocked - - - let result = SSKProtoSyncMessageBlocked(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoSyncMessageBlocked { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoSyncMessageBlocked.SSKProtoSyncMessageBlockedBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoSyncMessageBlocked? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoSyncMessageRequest - -@objc public class SSKProtoSyncMessageRequest: NSObject { - - // MARK: - SSKProtoSyncMessageRequestType - - @objc public enum SSKProtoSyncMessageRequestType: Int32 { - case unknown = 0 - case contacts = 1 - case groups = 2 - case blocked = 3 - case configuration = 4 - } - - private class func SSKProtoSyncMessageRequestTypeWrap(_ value: SignalServiceProtos_SyncMessage.Request.TypeEnum) -> SSKProtoSyncMessageRequestType { - switch value { - case .unknown: return .unknown - case .contacts: return .contacts - case .groups: return .groups - case .blocked: return .blocked - case .configuration: return .configuration - } - } - - private class func SSKProtoSyncMessageRequestTypeUnwrap(_ value: SSKProtoSyncMessageRequestType) -> SignalServiceProtos_SyncMessage.Request.TypeEnum { - switch value { - case .unknown: return .unknown - case .contacts: return .contacts - case .groups: return .groups - case .blocked: return .blocked - case .configuration: return .configuration - } - } - - // MARK: - SSKProtoSyncMessageRequestBuilder - - @objc public class func builder(type: SSKProtoSyncMessageRequestType) -> SSKProtoSyncMessageRequestBuilder { - return SSKProtoSyncMessageRequestBuilder(type: type) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoSyncMessageRequestBuilder { - let builder = SSKProtoSyncMessageRequestBuilder(type: type) - return builder - } - - @objc public class SSKProtoSyncMessageRequestBuilder: NSObject { - - private var proto = SignalServiceProtos_SyncMessage.Request() - - @objc fileprivate override init() {} - - @objc fileprivate init(type: SSKProtoSyncMessageRequestType) { - super.init() - - setType(type) - } - - @objc public func setType(_ valueParam: SSKProtoSyncMessageRequestType) { - proto.type = SSKProtoSyncMessageRequestTypeUnwrap(valueParam) - } - - @objc public func build() throws -> SSKProtoSyncMessageRequest { - return try SSKProtoSyncMessageRequest.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoSyncMessageRequest.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_SyncMessage.Request - - @objc public let type: SSKProtoSyncMessageRequestType - - private init(proto: SignalServiceProtos_SyncMessage.Request, - type: SSKProtoSyncMessageRequestType) { - self.proto = proto - self.type = type - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoSyncMessageRequest { - let proto = try SignalServiceProtos_SyncMessage.Request(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_SyncMessage.Request) throws -> SSKProtoSyncMessageRequest { - guard proto.hasType else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") - } - let type = SSKProtoSyncMessageRequestTypeWrap(proto.type) - - // MARK: - Begin Validation Logic for SSKProtoSyncMessageRequest - - - // MARK: - End Validation Logic for SSKProtoSyncMessageRequest - - - let result = SSKProtoSyncMessageRequest(proto: proto, - type: type) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoSyncMessageRequest { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoSyncMessageRequest.SSKProtoSyncMessageRequestBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoSyncMessageRequest? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoSyncMessageRead - -@objc public class SSKProtoSyncMessageRead: NSObject { - - // MARK: - SSKProtoSyncMessageReadBuilder - - @objc public class func builder(sender: String, timestamp: UInt64) -> SSKProtoSyncMessageReadBuilder { - return SSKProtoSyncMessageReadBuilder(sender: sender, timestamp: timestamp) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoSyncMessageReadBuilder { - let builder = SSKProtoSyncMessageReadBuilder(sender: sender, timestamp: timestamp) - return builder - } - - @objc public class SSKProtoSyncMessageReadBuilder: NSObject { - - private var proto = SignalServiceProtos_SyncMessage.Read() - - @objc fileprivate override init() {} - - @objc fileprivate init(sender: String, timestamp: UInt64) { - super.init() - - setSender(sender) - setTimestamp(timestamp) - } - - @objc public func setSender(_ valueParam: String) { - proto.sender = valueParam - } - - @objc public func setTimestamp(_ valueParam: UInt64) { - proto.timestamp = valueParam - } - - @objc public func build() throws -> SSKProtoSyncMessageRead { - return try SSKProtoSyncMessageRead.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoSyncMessageRead.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_SyncMessage.Read - - @objc public let sender: String - - @objc public let timestamp: UInt64 - - private init(proto: SignalServiceProtos_SyncMessage.Read, - sender: String, - timestamp: UInt64) { - self.proto = proto - self.sender = sender - self.timestamp = timestamp - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoSyncMessageRead { - let proto = try SignalServiceProtos_SyncMessage.Read(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_SyncMessage.Read) throws -> SSKProtoSyncMessageRead { - guard proto.hasSender else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: sender") - } - let sender = proto.sender - - guard proto.hasTimestamp else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: timestamp") - } - let timestamp = proto.timestamp - - // MARK: - Begin Validation Logic for SSKProtoSyncMessageRead - - - // MARK: - End Validation Logic for SSKProtoSyncMessageRead - - - let result = SSKProtoSyncMessageRead(proto: proto, - sender: sender, - timestamp: timestamp) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoSyncMessageRead { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoSyncMessageRead.SSKProtoSyncMessageReadBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoSyncMessageRead? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoSyncMessageConfiguration - -@objc public class SSKProtoSyncMessageConfiguration: NSObject { - - // MARK: - SSKProtoSyncMessageConfigurationBuilder - - @objc public class func builder() -> SSKProtoSyncMessageConfigurationBuilder { - return SSKProtoSyncMessageConfigurationBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoSyncMessageConfigurationBuilder { - let builder = SSKProtoSyncMessageConfigurationBuilder() - if hasReadReceipts { - builder.setReadReceipts(readReceipts) - } - if hasUnidentifiedDeliveryIndicators { - builder.setUnidentifiedDeliveryIndicators(unidentifiedDeliveryIndicators) - } - if hasTypingIndicators { - builder.setTypingIndicators(typingIndicators) - } - if hasLinkPreviews { - builder.setLinkPreviews(linkPreviews) - } - return builder - } - - @objc public class SSKProtoSyncMessageConfigurationBuilder: NSObject { - - private var proto = SignalServiceProtos_SyncMessage.Configuration() - - @objc fileprivate override init() {} - - @objc public func setReadReceipts(_ valueParam: Bool) { - proto.readReceipts = valueParam - } - - @objc public func setUnidentifiedDeliveryIndicators(_ valueParam: Bool) { - proto.unidentifiedDeliveryIndicators = valueParam - } - - @objc public func setTypingIndicators(_ valueParam: Bool) { - proto.typingIndicators = valueParam - } - - @objc public func setLinkPreviews(_ valueParam: Bool) { - proto.linkPreviews = valueParam - } - - @objc public func build() throws -> SSKProtoSyncMessageConfiguration { - return try SSKProtoSyncMessageConfiguration.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoSyncMessageConfiguration.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_SyncMessage.Configuration - - @objc public var readReceipts: Bool { - return proto.readReceipts - } - @objc public var hasReadReceipts: Bool { - return proto.hasReadReceipts - } - - @objc public var unidentifiedDeliveryIndicators: Bool { - return proto.unidentifiedDeliveryIndicators - } - @objc public var hasUnidentifiedDeliveryIndicators: Bool { - return proto.hasUnidentifiedDeliveryIndicators - } - - @objc public var typingIndicators: Bool { - return proto.typingIndicators - } - @objc public var hasTypingIndicators: Bool { - return proto.hasTypingIndicators - } - - @objc public var linkPreviews: Bool { - return proto.linkPreviews - } - @objc public var hasLinkPreviews: Bool { - return proto.hasLinkPreviews - } - - private init(proto: SignalServiceProtos_SyncMessage.Configuration) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoSyncMessageConfiguration { - let proto = try SignalServiceProtos_SyncMessage.Configuration(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_SyncMessage.Configuration) throws -> SSKProtoSyncMessageConfiguration { - // MARK: - Begin Validation Logic for SSKProtoSyncMessageConfiguration - - - // MARK: - End Validation Logic for SSKProtoSyncMessageConfiguration - - - let result = SSKProtoSyncMessageConfiguration(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoSyncMessageConfiguration { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoSyncMessageConfiguration.SSKProtoSyncMessageConfigurationBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoSyncMessageConfiguration? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoSyncMessage - -@objc public class SSKProtoSyncMessage: NSObject { - - // MARK: - SSKProtoSyncMessageBuilder - - @objc public class func builder() -> SSKProtoSyncMessageBuilder { - return SSKProtoSyncMessageBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoSyncMessageBuilder { - let builder = SSKProtoSyncMessageBuilder() - if let _value = sent { - builder.setSent(_value) - } - if let _value = contacts { - builder.setContacts(_value) - } - if let _value = groups { - builder.setGroups(_value) - } - if let _value = request { - builder.setRequest(_value) - } - builder.setRead(read) - if let _value = blocked { - builder.setBlocked(_value) - } - if let _value = verified { - builder.setVerified(_value) - } - if let _value = configuration { - builder.setConfiguration(_value) - } - if let _value = padding { - builder.setPadding(_value) - } - builder.setOpenGroups(openGroups) - return builder - } - - @objc public class SSKProtoSyncMessageBuilder: NSObject { - - private var proto = SignalServiceProtos_SyncMessage() - - @objc fileprivate override init() {} - - @objc public func setSent(_ valueParam: SSKProtoSyncMessageSent) { - proto.sent = valueParam.proto - } - - @objc public func setContacts(_ valueParam: SSKProtoSyncMessageContacts) { - proto.contacts = valueParam.proto - } - - @objc public func setGroups(_ valueParam: SSKProtoSyncMessageGroups) { - proto.groups = valueParam.proto - } - - @objc public func setRequest(_ valueParam: SSKProtoSyncMessageRequest) { - proto.request = valueParam.proto - } - - @objc public func addRead(_ valueParam: SSKProtoSyncMessageRead) { - var items = proto.read - items.append(valueParam.proto) - proto.read = items - } - - @objc public func setRead(_ wrappedItems: [SSKProtoSyncMessageRead]) { - proto.read = wrappedItems.map { $0.proto } - } - - @objc public func setBlocked(_ valueParam: SSKProtoSyncMessageBlocked) { - proto.blocked = valueParam.proto - } - - @objc public func setVerified(_ valueParam: SSKProtoVerified) { - proto.verified = valueParam.proto - } - - @objc public func setConfiguration(_ valueParam: SSKProtoSyncMessageConfiguration) { - proto.configuration = valueParam.proto - } - - @objc public func setPadding(_ valueParam: Data) { - proto.padding = valueParam - } - - @objc public func addOpenGroups(_ valueParam: SSKProtoSyncMessageOpenGroupDetails) { - var items = proto.openGroups - items.append(valueParam.proto) - proto.openGroups = items - } - - @objc public func setOpenGroups(_ wrappedItems: [SSKProtoSyncMessageOpenGroupDetails]) { - proto.openGroups = wrappedItems.map { $0.proto } - } - - @objc public func build() throws -> SSKProtoSyncMessage { - return try SSKProtoSyncMessage.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoSyncMessage.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_SyncMessage - - @objc public let sent: SSKProtoSyncMessageSent? - - @objc public let contacts: SSKProtoSyncMessageContacts? - - @objc public let groups: SSKProtoSyncMessageGroups? - - @objc public let request: SSKProtoSyncMessageRequest? - - @objc public let read: [SSKProtoSyncMessageRead] - - @objc public let blocked: SSKProtoSyncMessageBlocked? - - @objc public let verified: SSKProtoVerified? - - @objc public let configuration: SSKProtoSyncMessageConfiguration? - - @objc public let openGroups: [SSKProtoSyncMessageOpenGroupDetails] - - @objc public var padding: Data? { - guard proto.hasPadding else { - return nil - } - return proto.padding - } - @objc public var hasPadding: Bool { - return proto.hasPadding - } - - private init(proto: SignalServiceProtos_SyncMessage, - sent: SSKProtoSyncMessageSent?, - contacts: SSKProtoSyncMessageContacts?, - groups: SSKProtoSyncMessageGroups?, - request: SSKProtoSyncMessageRequest?, - read: [SSKProtoSyncMessageRead], - blocked: SSKProtoSyncMessageBlocked?, - verified: SSKProtoVerified?, - configuration: SSKProtoSyncMessageConfiguration?, - openGroups: [SSKProtoSyncMessageOpenGroupDetails]) { - self.proto = proto - self.sent = sent - self.contacts = contacts - self.groups = groups - self.request = request - self.read = read - self.blocked = blocked - self.verified = verified - self.configuration = configuration - self.openGroups = openGroups - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoSyncMessage { - let proto = try SignalServiceProtos_SyncMessage(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_SyncMessage) throws -> SSKProtoSyncMessage { - var sent: SSKProtoSyncMessageSent? = nil - if proto.hasSent { - sent = try SSKProtoSyncMessageSent.parseProto(proto.sent) - } - - var contacts: SSKProtoSyncMessageContacts? = nil - if proto.hasContacts { - contacts = try SSKProtoSyncMessageContacts.parseProto(proto.contacts) - } - - var groups: SSKProtoSyncMessageGroups? = nil - if proto.hasGroups { - groups = try SSKProtoSyncMessageGroups.parseProto(proto.groups) - } - - var request: SSKProtoSyncMessageRequest? = nil - if proto.hasRequest { - request = try SSKProtoSyncMessageRequest.parseProto(proto.request) - } - - var read: [SSKProtoSyncMessageRead] = [] - read = try proto.read.map { try SSKProtoSyncMessageRead.parseProto($0) } - - var blocked: SSKProtoSyncMessageBlocked? = nil - if proto.hasBlocked { - blocked = try SSKProtoSyncMessageBlocked.parseProto(proto.blocked) - } - - var verified: SSKProtoVerified? = nil - if proto.hasVerified { - verified = try SSKProtoVerified.parseProto(proto.verified) - } - - var configuration: SSKProtoSyncMessageConfiguration? = nil - if proto.hasConfiguration { - configuration = try SSKProtoSyncMessageConfiguration.parseProto(proto.configuration) - } - - var openGroups: [SSKProtoSyncMessageOpenGroupDetails] = [] - openGroups = try proto.openGroups.map { try SSKProtoSyncMessageOpenGroupDetails.parseProto($0) } - - // MARK: - Begin Validation Logic for SSKProtoSyncMessage - - - // MARK: - End Validation Logic for SSKProtoSyncMessage - - - let result = SSKProtoSyncMessage(proto: proto, - sent: sent, - contacts: contacts, - groups: groups, - request: request, - read: read, - blocked: blocked, - verified: verified, - configuration: configuration, - openGroups: openGroups) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoSyncMessage { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoSyncMessage.SSKProtoSyncMessageBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoSyncMessage? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoAttachmentPointer - -@objc public class SSKProtoAttachmentPointer: NSObject { - - // MARK: - SSKProtoAttachmentPointerFlags - - @objc public enum SSKProtoAttachmentPointerFlags: Int32 { - case voiceMessage = 1 - } - - private class func SSKProtoAttachmentPointerFlagsWrap(_ value: SignalServiceProtos_AttachmentPointer.Flags) -> SSKProtoAttachmentPointerFlags { - switch value { - case .voiceMessage: return .voiceMessage - } - } - - private class func SSKProtoAttachmentPointerFlagsUnwrap(_ value: SSKProtoAttachmentPointerFlags) -> SignalServiceProtos_AttachmentPointer.Flags { - switch value { - case .voiceMessage: return .voiceMessage - } - } - - // MARK: - SSKProtoAttachmentPointerBuilder - - @objc public class func builder(id: UInt64) -> SSKProtoAttachmentPointerBuilder { - return SSKProtoAttachmentPointerBuilder(id: id) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoAttachmentPointerBuilder { - let builder = SSKProtoAttachmentPointerBuilder(id: id) - if let _value = contentType { - builder.setContentType(_value) - } - if let _value = key { - builder.setKey(_value) - } - if hasSize { - builder.setSize(size) - } - if let _value = thumbnail { - builder.setThumbnail(_value) - } - if let _value = digest { - builder.setDigest(_value) - } - if let _value = fileName { - builder.setFileName(_value) - } - if hasFlags { - builder.setFlags(flags) - } - if hasWidth { - builder.setWidth(width) - } - if hasHeight { - builder.setHeight(height) - } - if let _value = caption { - builder.setCaption(_value) - } - if let _value = url { - builder.setUrl(_value) - } - return builder - } - - @objc public class SSKProtoAttachmentPointerBuilder: NSObject { - - private var proto = SignalServiceProtos_AttachmentPointer() - - @objc fileprivate override init() {} - - @objc fileprivate init(id: UInt64) { - super.init() - - setId(id) - } - - @objc public func setId(_ valueParam: UInt64) { - proto.id = valueParam - } - - @objc public func setContentType(_ valueParam: String) { - proto.contentType = valueParam - } - - @objc public func setKey(_ valueParam: Data) { - proto.key = valueParam - } - - @objc public func setSize(_ valueParam: UInt32) { - proto.size = valueParam - } - - @objc public func setThumbnail(_ valueParam: Data) { - proto.thumbnail = valueParam - } - - @objc public func setDigest(_ valueParam: Data) { - proto.digest = valueParam - } - - @objc public func setFileName(_ valueParam: String) { - proto.fileName = valueParam - } - - @objc public func setFlags(_ valueParam: UInt32) { - proto.flags = valueParam - } - - @objc public func setWidth(_ valueParam: UInt32) { - proto.width = valueParam - } - - @objc public func setHeight(_ valueParam: UInt32) { - proto.height = valueParam - } - - @objc public func setCaption(_ valueParam: String) { - proto.caption = valueParam - } - - @objc public func setUrl(_ valueParam: String) { - proto.url = valueParam - } - - @objc public func build() throws -> SSKProtoAttachmentPointer { - return try SSKProtoAttachmentPointer.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoAttachmentPointer.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_AttachmentPointer - - @objc public let id: UInt64 - - @objc public var contentType: String? { - guard proto.hasContentType else { - return nil - } - return proto.contentType - } - @objc public var hasContentType: Bool { - return proto.hasContentType - } - - @objc public var key: Data? { - guard proto.hasKey else { - return nil - } - return proto.key - } - @objc public var hasKey: Bool { - return proto.hasKey - } - - @objc public var size: UInt32 { - return proto.size - } - @objc public var hasSize: Bool { - return proto.hasSize - } - - @objc public var thumbnail: Data? { - guard proto.hasThumbnail else { - return nil - } - return proto.thumbnail - } - @objc public var hasThumbnail: Bool { - return proto.hasThumbnail - } - - @objc public var digest: Data? { - guard proto.hasDigest else { - return nil - } - return proto.digest - } - @objc public var hasDigest: Bool { - return proto.hasDigest - } - - @objc public var fileName: String? { - guard proto.hasFileName else { - return nil - } - return proto.fileName - } - @objc public var hasFileName: Bool { - return proto.hasFileName - } - - @objc public var flags: UInt32 { - return proto.flags - } - @objc public var hasFlags: Bool { - return proto.hasFlags - } - - @objc public var width: UInt32 { - return proto.width - } - @objc public var hasWidth: Bool { - return proto.hasWidth - } - - @objc public var height: UInt32 { - return proto.height - } - @objc public var hasHeight: Bool { - return proto.hasHeight - } - - @objc public var caption: String? { - guard proto.hasCaption else { - return nil - } - return proto.caption - } - @objc public var hasCaption: Bool { - return proto.hasCaption - } - - @objc public var url: String? { - guard proto.hasURL else { - return nil - } - return proto.url - } - @objc public var hasURL: Bool { - return proto.hasURL - } - - private init(proto: SignalServiceProtos_AttachmentPointer, - id: UInt64) { - self.proto = proto - self.id = id - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoAttachmentPointer { - let proto = try SignalServiceProtos_AttachmentPointer(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_AttachmentPointer) throws -> SSKProtoAttachmentPointer { - guard proto.hasID else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") - } - let id = proto.id - - // MARK: - Begin Validation Logic for SSKProtoAttachmentPointer - - - // MARK: - End Validation Logic for SSKProtoAttachmentPointer - - - let result = SSKProtoAttachmentPointer(proto: proto, - id: id) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoAttachmentPointer { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoAttachmentPointer.SSKProtoAttachmentPointerBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoAttachmentPointer? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoGroupContext - -@objc public class SSKProtoGroupContext: NSObject { - - // MARK: - SSKProtoGroupContextType - - @objc public enum SSKProtoGroupContextType: Int32 { - case unknown = 0 - case update = 1 - case deliver = 2 - case quit = 3 - case requestInfo = 4 - } - - private class func SSKProtoGroupContextTypeWrap(_ value: SignalServiceProtos_GroupContext.TypeEnum) -> SSKProtoGroupContextType { - switch value { - case .unknown: return .unknown - case .update: return .update - case .deliver: return .deliver - case .quit: return .quit - case .requestInfo: return .requestInfo - } - } - - private class func SSKProtoGroupContextTypeUnwrap(_ value: SSKProtoGroupContextType) -> SignalServiceProtos_GroupContext.TypeEnum { - switch value { - case .unknown: return .unknown - case .update: return .update - case .deliver: return .deliver - case .quit: return .quit - case .requestInfo: return .requestInfo - } - } - - // MARK: - SSKProtoGroupContextBuilder - - @objc public class func builder(id: Data, type: SSKProtoGroupContextType) -> SSKProtoGroupContextBuilder { - return SSKProtoGroupContextBuilder(id: id, type: type) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoGroupContextBuilder { - let builder = SSKProtoGroupContextBuilder(id: id, type: type) - if let _value = name { - builder.setName(_value) - } - builder.setMembers(members) - if let _value = avatar { - builder.setAvatar(_value) - } - builder.setAdmins(admins) - return builder - } - - @objc public class SSKProtoGroupContextBuilder: NSObject { - - private var proto = SignalServiceProtos_GroupContext() - - @objc fileprivate override init() {} - - @objc fileprivate init(id: Data, type: SSKProtoGroupContextType) { - super.init() - - setId(id) - setType(type) - } - - @objc public func setId(_ valueParam: Data) { - proto.id = valueParam - } - - @objc public func setType(_ valueParam: SSKProtoGroupContextType) { - proto.type = SSKProtoGroupContextTypeUnwrap(valueParam) - } - - @objc public func setName(_ valueParam: String) { - proto.name = valueParam - } - - @objc public func addMembers(_ valueParam: String) { - var items = proto.members - items.append(valueParam) - proto.members = items - } - - @objc public func setMembers(_ wrappedItems: [String]) { - proto.members = wrappedItems - } - - @objc public func setAvatar(_ valueParam: SSKProtoAttachmentPointer) { - proto.avatar = valueParam.proto - } - - @objc public func addAdmins(_ valueParam: String) { - var items = proto.admins - items.append(valueParam) - proto.admins = items - } - - @objc public func setAdmins(_ wrappedItems: [String]) { - proto.admins = wrappedItems - } - - @objc public func build() throws -> SSKProtoGroupContext { - return try SSKProtoGroupContext.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoGroupContext.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_GroupContext - - @objc public let id: Data - - @objc public let type: SSKProtoGroupContextType - - @objc public let avatar: SSKProtoAttachmentPointer? - - @objc public var name: String? { - guard proto.hasName else { - return nil - } - return proto.name - } - @objc public var hasName: Bool { - return proto.hasName - } - - @objc public var members: [String] { - return proto.members - } - - @objc public var admins: [String] { - return proto.admins - } - - private init(proto: SignalServiceProtos_GroupContext, - id: Data, - type: SSKProtoGroupContextType, - avatar: SSKProtoAttachmentPointer?) { - self.proto = proto - self.id = id - self.type = type - self.avatar = avatar - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoGroupContext { - let proto = try SignalServiceProtos_GroupContext(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_GroupContext) throws -> SSKProtoGroupContext { - guard proto.hasID else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") - } - let id = proto.id - - guard proto.hasType else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") - } - let type = SSKProtoGroupContextTypeWrap(proto.type) - - var avatar: SSKProtoAttachmentPointer? = nil - if proto.hasAvatar { - avatar = try SSKProtoAttachmentPointer.parseProto(proto.avatar) - } - - // MARK: - Begin Validation Logic for SSKProtoGroupContext - - - // MARK: - End Validation Logic for SSKProtoGroupContext - - - let result = SSKProtoGroupContext(proto: proto, - id: id, - type: type, - avatar: avatar) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoGroupContext { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoGroupContext.SSKProtoGroupContextBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoGroupContext? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoContactDetailsAvatar - -@objc public class SSKProtoContactDetailsAvatar: NSObject { - - // MARK: - SSKProtoContactDetailsAvatarBuilder - - @objc public class func builder() -> SSKProtoContactDetailsAvatarBuilder { - return SSKProtoContactDetailsAvatarBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoContactDetailsAvatarBuilder { - let builder = SSKProtoContactDetailsAvatarBuilder() - if let _value = contentType { - builder.setContentType(_value) - } - if hasLength { - builder.setLength(length) - } - return builder - } - - @objc public class SSKProtoContactDetailsAvatarBuilder: NSObject { - - private var proto = SignalServiceProtos_ContactDetails.Avatar() - - @objc fileprivate override init() {} - - @objc public func setContentType(_ valueParam: String) { - proto.contentType = valueParam - } - - @objc public func setLength(_ valueParam: UInt32) { - proto.length = valueParam - } - - @objc public func build() throws -> SSKProtoContactDetailsAvatar { - return try SSKProtoContactDetailsAvatar.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoContactDetailsAvatar.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_ContactDetails.Avatar - - @objc public var contentType: String? { - guard proto.hasContentType else { - return nil - } - return proto.contentType - } - @objc public var hasContentType: Bool { - return proto.hasContentType - } - - @objc public var length: UInt32 { - return proto.length - } - @objc public var hasLength: Bool { - return proto.hasLength - } - - private init(proto: SignalServiceProtos_ContactDetails.Avatar) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoContactDetailsAvatar { - let proto = try SignalServiceProtos_ContactDetails.Avatar(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_ContactDetails.Avatar) throws -> SSKProtoContactDetailsAvatar { - // MARK: - Begin Validation Logic for SSKProtoContactDetailsAvatar - - - // MARK: - End Validation Logic for SSKProtoContactDetailsAvatar - - - let result = SSKProtoContactDetailsAvatar(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoContactDetailsAvatar { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoContactDetailsAvatar.SSKProtoContactDetailsAvatarBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoContactDetailsAvatar? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoContactDetails - -@objc public class SSKProtoContactDetails: NSObject { - - // MARK: - SSKProtoContactDetailsBuilder - - @objc public class func builder(number: String) -> SSKProtoContactDetailsBuilder { - return SSKProtoContactDetailsBuilder(number: number) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoContactDetailsBuilder { - let builder = SSKProtoContactDetailsBuilder(number: number) - if let _value = name { - builder.setName(_value) - } - if let _value = avatar { - builder.setAvatar(_value) - } - if let _value = color { - builder.setColor(_value) - } - if let _value = verified { - builder.setVerified(_value) - } - if let _value = profileKey { - builder.setProfileKey(_value) - } - if hasBlocked { - builder.setBlocked(blocked) - } - if hasExpireTimer { - builder.setExpireTimer(expireTimer) - } - if let _value = nickname { - builder.setNickname(_value) - } - return builder - } - - @objc public class SSKProtoContactDetailsBuilder: NSObject { - - private var proto = SignalServiceProtos_ContactDetails() - - @objc fileprivate override init() {} - - @objc fileprivate init(number: String) { - super.init() - - setNumber(number) - } - - @objc public func setNumber(_ valueParam: String) { - proto.number = valueParam - } - - @objc public func setName(_ valueParam: String) { - proto.name = valueParam - } - - @objc public func setAvatar(_ valueParam: SSKProtoContactDetailsAvatar) { - proto.avatar = valueParam.proto - } - - @objc public func setColor(_ valueParam: String) { - proto.color = valueParam - } - - @objc public func setVerified(_ valueParam: SSKProtoVerified) { - proto.verified = valueParam.proto - } - - @objc public func setProfileKey(_ valueParam: Data) { - proto.profileKey = valueParam - } - - @objc public func setBlocked(_ valueParam: Bool) { - proto.blocked = valueParam - } - - @objc public func setExpireTimer(_ valueParam: UInt32) { - proto.expireTimer = valueParam - } - - @objc public func setNickname(_ valueParam: String) { - proto.nickname = valueParam - } - - @objc public func build() throws -> SSKProtoContactDetails { - return try SSKProtoContactDetails.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoContactDetails.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_ContactDetails - - @objc public let number: String - - @objc public let avatar: SSKProtoContactDetailsAvatar? - - @objc public let verified: SSKProtoVerified? - - @objc public var name: String? { - guard proto.hasName else { - return nil - } - return proto.name - } - @objc public var hasName: Bool { - return proto.hasName - } - - @objc public var color: String? { - guard proto.hasColor else { - return nil - } - return proto.color - } - @objc public var hasColor: Bool { - return proto.hasColor - } - - @objc public var profileKey: Data? { - guard proto.hasProfileKey else { - return nil - } - return proto.profileKey - } - @objc public var hasProfileKey: Bool { - return proto.hasProfileKey - } - - @objc public var blocked: Bool { - return proto.blocked - } - @objc public var hasBlocked: Bool { - return proto.hasBlocked - } - - @objc public var expireTimer: UInt32 { - return proto.expireTimer - } - @objc public var hasExpireTimer: Bool { - return proto.hasExpireTimer - } - - @objc public var nickname: String? { - guard proto.hasNickname else { - return nil - } - return proto.nickname - } - @objc public var hasNickname: Bool { - return proto.hasNickname - } - - private init(proto: SignalServiceProtos_ContactDetails, - number: String, - avatar: SSKProtoContactDetailsAvatar?, - verified: SSKProtoVerified?) { - self.proto = proto - self.number = number - self.avatar = avatar - self.verified = verified - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoContactDetails { - let proto = try SignalServiceProtos_ContactDetails(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_ContactDetails) throws -> SSKProtoContactDetails { - guard proto.hasNumber else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: number") - } - let number = proto.number - - var avatar: SSKProtoContactDetailsAvatar? = nil - if proto.hasAvatar { - avatar = try SSKProtoContactDetailsAvatar.parseProto(proto.avatar) - } - - var verified: SSKProtoVerified? = nil - if proto.hasVerified { - verified = try SSKProtoVerified.parseProto(proto.verified) - } - - // MARK: - Begin Validation Logic for SSKProtoContactDetails - - - // MARK: - End Validation Logic for SSKProtoContactDetails - - - let result = SSKProtoContactDetails(proto: proto, - number: number, - avatar: avatar, - verified: verified) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoContactDetails { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoContactDetails.SSKProtoContactDetailsBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoContactDetails? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoGroupDetailsAvatar - -@objc public class SSKProtoGroupDetailsAvatar: NSObject { - - // MARK: - SSKProtoGroupDetailsAvatarBuilder - - @objc public class func builder() -> SSKProtoGroupDetailsAvatarBuilder { - return SSKProtoGroupDetailsAvatarBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoGroupDetailsAvatarBuilder { - let builder = SSKProtoGroupDetailsAvatarBuilder() - if let _value = contentType { - builder.setContentType(_value) - } - if hasLength { - builder.setLength(length) - } - return builder - } - - @objc public class SSKProtoGroupDetailsAvatarBuilder: NSObject { - - private var proto = SignalServiceProtos_GroupDetails.Avatar() - - @objc fileprivate override init() {} - - @objc public func setContentType(_ valueParam: String) { - proto.contentType = valueParam - } - - @objc public func setLength(_ valueParam: UInt32) { - proto.length = valueParam - } - - @objc public func build() throws -> SSKProtoGroupDetailsAvatar { - return try SSKProtoGroupDetailsAvatar.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoGroupDetailsAvatar.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_GroupDetails.Avatar - - @objc public var contentType: String? { - guard proto.hasContentType else { - return nil - } - return proto.contentType - } - @objc public var hasContentType: Bool { - return proto.hasContentType - } - - @objc public var length: UInt32 { - return proto.length - } - @objc public var hasLength: Bool { - return proto.hasLength - } - - private init(proto: SignalServiceProtos_GroupDetails.Avatar) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoGroupDetailsAvatar { - let proto = try SignalServiceProtos_GroupDetails.Avatar(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_GroupDetails.Avatar) throws -> SSKProtoGroupDetailsAvatar { - // MARK: - Begin Validation Logic for SSKProtoGroupDetailsAvatar - - - // MARK: - End Validation Logic for SSKProtoGroupDetailsAvatar - - - let result = SSKProtoGroupDetailsAvatar(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoGroupDetailsAvatar { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoGroupDetailsAvatar.SSKProtoGroupDetailsAvatarBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoGroupDetailsAvatar? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoGroupDetails - -@objc public class SSKProtoGroupDetails: NSObject { - - // MARK: - SSKProtoGroupDetailsBuilder - - @objc public class func builder(id: Data) -> SSKProtoGroupDetailsBuilder { - return SSKProtoGroupDetailsBuilder(id: id) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoGroupDetailsBuilder { - let builder = SSKProtoGroupDetailsBuilder(id: id) - if let _value = name { - builder.setName(_value) - } - builder.setMembers(members) - if let _value = avatar { - builder.setAvatar(_value) - } - if hasActive { - builder.setActive(active) - } - if hasExpireTimer { - builder.setExpireTimer(expireTimer) - } - if let _value = color { - builder.setColor(_value) - } - if hasBlocked { - builder.setBlocked(blocked) - } - builder.setAdmins(admins) - return builder - } - - @objc public class SSKProtoGroupDetailsBuilder: NSObject { - - private var proto = SignalServiceProtos_GroupDetails() - - @objc fileprivate override init() {} - - @objc fileprivate init(id: Data) { - super.init() - - setId(id) - } - - @objc public func setId(_ valueParam: Data) { - proto.id = valueParam - } - - @objc public func setName(_ valueParam: String) { - proto.name = valueParam - } - - @objc public func addMembers(_ valueParam: String) { - var items = proto.members - items.append(valueParam) - proto.members = items - } - - @objc public func setMembers(_ wrappedItems: [String]) { - proto.members = wrappedItems - } - - @objc public func setAvatar(_ valueParam: SSKProtoGroupDetailsAvatar) { - proto.avatar = valueParam.proto - } - - @objc public func setActive(_ valueParam: Bool) { - proto.active = valueParam - } - - @objc public func setExpireTimer(_ valueParam: UInt32) { - proto.expireTimer = valueParam - } - - @objc public func setColor(_ valueParam: String) { - proto.color = valueParam - } - - @objc public func setBlocked(_ valueParam: Bool) { - proto.blocked = valueParam - } - - @objc public func addAdmins(_ valueParam: String) { - var items = proto.admins - items.append(valueParam) - proto.admins = items - } - - @objc public func setAdmins(_ wrappedItems: [String]) { - proto.admins = wrappedItems - } - - @objc public func build() throws -> SSKProtoGroupDetails { - return try SSKProtoGroupDetails.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoGroupDetails.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_GroupDetails - - @objc public let id: Data - - @objc public let avatar: SSKProtoGroupDetailsAvatar? - - @objc public var name: String? { - guard proto.hasName else { - return nil - } - return proto.name - } - @objc public var hasName: Bool { - return proto.hasName - } - - @objc public var members: [String] { - return proto.members - } - - @objc public var active: Bool { - return proto.active - } - @objc public var hasActive: Bool { - return proto.hasActive - } - - @objc public var expireTimer: UInt32 { - return proto.expireTimer - } - @objc public var hasExpireTimer: Bool { - return proto.hasExpireTimer - } - - @objc public var color: String? { - guard proto.hasColor else { - return nil - } - return proto.color - } - @objc public var hasColor: Bool { - return proto.hasColor - } - - @objc public var blocked: Bool { - return proto.blocked - } - @objc public var hasBlocked: Bool { - return proto.hasBlocked - } - - @objc public var admins: [String] { - return proto.admins - } - - private init(proto: SignalServiceProtos_GroupDetails, - id: Data, - avatar: SSKProtoGroupDetailsAvatar?) { - self.proto = proto - self.id = id - self.avatar = avatar - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoGroupDetails { - let proto = try SignalServiceProtos_GroupDetails(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_GroupDetails) throws -> SSKProtoGroupDetails { - guard proto.hasID else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: id") - } - let id = proto.id - - var avatar: SSKProtoGroupDetailsAvatar? = nil - if proto.hasAvatar { - avatar = try SSKProtoGroupDetailsAvatar.parseProto(proto.avatar) - } - - // MARK: - Begin Validation Logic for SSKProtoGroupDetails - - - // MARK: - End Validation Logic for SSKProtoGroupDetails - - - let result = SSKProtoGroupDetails(proto: proto, - id: id, - avatar: avatar) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoGroupDetails { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoGroupDetails.SSKProtoGroupDetailsBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoGroupDetails? { - return try! self.build() - } -} - -#endif - -// MARK: - SSKProtoPublicChatInfo - -@objc public class SSKProtoPublicChatInfo: NSObject { - - // MARK: - SSKProtoPublicChatInfoBuilder - - @objc public class func builder() -> SSKProtoPublicChatInfoBuilder { - return SSKProtoPublicChatInfoBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SSKProtoPublicChatInfoBuilder { - let builder = SSKProtoPublicChatInfoBuilder() - if hasServerID { - builder.setServerID(serverID) - } - return builder - } - - @objc public class SSKProtoPublicChatInfoBuilder: NSObject { - - private var proto = SignalServiceProtos_PublicChatInfo() - - @objc fileprivate override init() {} - - @objc public func setServerID(_ valueParam: UInt64) { - proto.serverID = valueParam - } - - @objc public func build() throws -> SSKProtoPublicChatInfo { - return try SSKProtoPublicChatInfo.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SSKProtoPublicChatInfo.parseProto(proto).serializedData() - } - } - - fileprivate let proto: SignalServiceProtos_PublicChatInfo - - @objc public var serverID: UInt64 { - return proto.serverID - } - @objc public var hasServerID: Bool { - return proto.hasServerID - } - - private init(proto: SignalServiceProtos_PublicChatInfo) { - self.proto = proto - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SSKProtoPublicChatInfo { - let proto = try SignalServiceProtos_PublicChatInfo(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: SignalServiceProtos_PublicChatInfo) throws -> SSKProtoPublicChatInfo { - // MARK: - Begin Validation Logic for SSKProtoPublicChatInfo - - - // MARK: - End Validation Logic for SSKProtoPublicChatInfo - - - let result = SSKProtoPublicChatInfo(proto: proto) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SSKProtoPublicChatInfo { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SSKProtoPublicChatInfo.SSKProtoPublicChatInfoBuilder { - @objc public func buildIgnoringErrors() -> SSKProtoPublicChatInfo? { - return try! self.build() - } -} - -#endif diff --git a/SignalServiceKit/src/Protos/Generated/SignalIOS.pb.swift b/SignalServiceKit/src/Protos/Generated/SignalIOS.pb.swift deleted file mode 100644 index 588c729ed..000000000 --- a/SignalServiceKit/src/Protos/Generated/SignalIOS.pb.swift +++ /dev/null @@ -1,318 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: SignalIOS.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -//* -// Copyright (C) 2014-2016 Open Whisper Systems -// -// Licensed according to the LICENSE file in this repository. - -/// iOS - since we use a modern proto-compiler, we must specify -/// the legacy proto format. - -import Foundation -import SwiftProtobuf - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -struct IOSProtos_BackupSnapshot { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var entity: [IOSProtos_BackupSnapshot.BackupEntity] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct BackupEntity { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var type: IOSProtos_BackupSnapshot.BackupEntity.TypeEnum { - get {return _type ?? .unknown} - set {_type = newValue} - } - /// Returns true if `type` has been explicitly set. - var hasType: Bool {return self._type != nil} - /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {self._type = nil} - - /// @required - var entityData: Data { - get {return _entityData ?? SwiftProtobuf.Internal.emptyData} - set {_entityData = newValue} - } - /// Returns true if `entityData` has been explicitly set. - var hasEntityData: Bool {return self._entityData != nil} - /// Clears the value of `entityData`. Subsequent reads from it will return its default value. - mutating func clearEntityData() {self._entityData = nil} - - /// @required - var collection: String { - get {return _collection ?? String()} - set {_collection = newValue} - } - /// Returns true if `collection` has been explicitly set. - var hasCollection: Bool {return self._collection != nil} - /// Clears the value of `collection`. Subsequent reads from it will return its default value. - mutating func clearCollection() {self._collection = nil} - - /// @required - var key: String { - get {return _key ?? String()} - set {_key = newValue} - } - /// Returns true if `key` has been explicitly set. - var hasKey: Bool {return self._key != nil} - /// Clears the value of `key`. Subsequent reads from it will return its default value. - mutating func clearKey() {self._key = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum TypeEnum: SwiftProtobuf.Enum { - typealias RawValue = Int - case unknown // = 0 - case migration // = 1 - case thread // = 2 - case interaction // = 3 - case attachment // = 4 - case misc // = 5 - - init() { - self = .unknown - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unknown - case 1: self = .migration - case 2: self = .thread - case 3: self = .interaction - case 4: self = .attachment - case 5: self = .misc - default: return nil - } - } - - var rawValue: Int { - switch self { - case .unknown: return 0 - case .migration: return 1 - case .thread: return 2 - case .interaction: return 3 - case .attachment: return 4 - case .misc: return 5 - } - } - - } - - init() {} - - fileprivate var _type: IOSProtos_BackupSnapshot.BackupEntity.TypeEnum? = nil - fileprivate var _entityData: Data? = nil - fileprivate var _collection: String? = nil - fileprivate var _key: String? = nil - } - - init() {} -} - -#if swift(>=4.2) - -extension IOSProtos_BackupSnapshot.BackupEntity.TypeEnum: CaseIterable { - // Support synthesized by the compiler. -} - -#endif // swift(>=4.2) - -struct IOSProtos_DeviceName { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var ephemeralPublic: Data { - get {return _ephemeralPublic ?? SwiftProtobuf.Internal.emptyData} - set {_ephemeralPublic = newValue} - } - /// Returns true if `ephemeralPublic` has been explicitly set. - var hasEphemeralPublic: Bool {return self._ephemeralPublic != nil} - /// Clears the value of `ephemeralPublic`. Subsequent reads from it will return its default value. - mutating func clearEphemeralPublic() {self._ephemeralPublic = nil} - - /// @required - var syntheticIv: Data { - get {return _syntheticIv ?? SwiftProtobuf.Internal.emptyData} - set {_syntheticIv = newValue} - } - /// Returns true if `syntheticIv` has been explicitly set. - var hasSyntheticIv: Bool {return self._syntheticIv != nil} - /// Clears the value of `syntheticIv`. Subsequent reads from it will return its default value. - mutating func clearSyntheticIv() {self._syntheticIv = nil} - - /// @required - var ciphertext: Data { - get {return _ciphertext ?? SwiftProtobuf.Internal.emptyData} - set {_ciphertext = newValue} - } - /// Returns true if `ciphertext` has been explicitly set. - var hasCiphertext: Bool {return self._ciphertext != nil} - /// Clears the value of `ciphertext`. Subsequent reads from it will return its default value. - mutating func clearCiphertext() {self._ciphertext = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _ephemeralPublic: Data? = nil - fileprivate var _syntheticIv: Data? = nil - fileprivate var _ciphertext: Data? = nil -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "IOSProtos" - -extension IOSProtos_BackupSnapshot: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".BackupSnapshot" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "entity"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeRepeatedMessageField(value: &self.entity) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.entity.isEmpty { - try visitor.visitRepeatedMessageField(value: self.entity, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: IOSProtos_BackupSnapshot, rhs: IOSProtos_BackupSnapshot) -> Bool { - if lhs.entity != rhs.entity {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension IOSProtos_BackupSnapshot.BackupEntity: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = IOSProtos_BackupSnapshot.protoMessageName + ".BackupEntity" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "type"), - 2: .same(proto: "entityData"), - 3: .same(proto: "collection"), - 4: .same(proto: "key"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularEnumField(value: &self._type) - case 2: try decoder.decodeSingularBytesField(value: &self._entityData) - case 3: try decoder.decodeSingularStringField(value: &self._collection) - case 4: try decoder.decodeSingularStringField(value: &self._key) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 1) - } - if let v = self._entityData { - try visitor.visitSingularBytesField(value: v, fieldNumber: 2) - } - if let v = self._collection { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) - } - if let v = self._key { - try visitor.visitSingularStringField(value: v, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: IOSProtos_BackupSnapshot.BackupEntity, rhs: IOSProtos_BackupSnapshot.BackupEntity) -> Bool { - if lhs._type != rhs._type {return false} - if lhs._entityData != rhs._entityData {return false} - if lhs._collection != rhs._collection {return false} - if lhs._key != rhs._key {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension IOSProtos_BackupSnapshot.BackupEntity.TypeEnum: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNKNOWN"), - 1: .same(proto: "MIGRATION"), - 2: .same(proto: "THREAD"), - 3: .same(proto: "INTERACTION"), - 4: .same(proto: "ATTACHMENT"), - 5: .same(proto: "MISC"), - ] -} - -extension IOSProtos_DeviceName: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".DeviceName" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "ephemeralPublic"), - 2: .same(proto: "syntheticIv"), - 3: .same(proto: "ciphertext"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self._ephemeralPublic) - case 2: try decoder.decodeSingularBytesField(value: &self._syntheticIv) - case 3: try decoder.decodeSingularBytesField(value: &self._ciphertext) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._ephemeralPublic { - try visitor.visitSingularBytesField(value: v, fieldNumber: 1) - } - if let v = self._syntheticIv { - try visitor.visitSingularBytesField(value: v, fieldNumber: 2) - } - if let v = self._ciphertext { - try visitor.visitSingularBytesField(value: v, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: IOSProtos_DeviceName, rhs: IOSProtos_DeviceName) -> Bool { - if lhs._ephemeralPublic != rhs._ephemeralPublic {return false} - if lhs._syntheticIv != rhs._syntheticIv {return false} - if lhs._ciphertext != rhs._ciphertext {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/SignalServiceKit/src/Protos/Generated/SignalIOSProto.swift b/SignalServiceKit/src/Protos/Generated/SignalIOSProto.swift deleted file mode 100644 index 5761fbda7..000000000 --- a/SignalServiceKit/src/Protos/Generated/SignalIOSProto.swift +++ /dev/null @@ -1,409 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -// WARNING: This code is generated. Only edit within the markers. - -public enum SignalIOSProtoError: Error { - case invalidProtobuf(description: String) -} - -// MARK: - SignalIOSProtoBackupSnapshotBackupEntity - -@objc public class SignalIOSProtoBackupSnapshotBackupEntity: NSObject { - - // MARK: - SignalIOSProtoBackupSnapshotBackupEntityType - - @objc public enum SignalIOSProtoBackupSnapshotBackupEntityType: Int32 { - case unknown = 0 - case migration = 1 - case thread = 2 - case interaction = 3 - case attachment = 4 - case misc = 5 - } - - private class func SignalIOSProtoBackupSnapshotBackupEntityTypeWrap(_ value: IOSProtos_BackupSnapshot.BackupEntity.TypeEnum) -> SignalIOSProtoBackupSnapshotBackupEntityType { - switch value { - case .unknown: return .unknown - case .migration: return .migration - case .thread: return .thread - case .interaction: return .interaction - case .attachment: return .attachment - case .misc: return .misc - } - } - - private class func SignalIOSProtoBackupSnapshotBackupEntityTypeUnwrap(_ value: SignalIOSProtoBackupSnapshotBackupEntityType) -> IOSProtos_BackupSnapshot.BackupEntity.TypeEnum { - switch value { - case .unknown: return .unknown - case .migration: return .migration - case .thread: return .thread - case .interaction: return .interaction - case .attachment: return .attachment - case .misc: return .misc - } - } - - // MARK: - SignalIOSProtoBackupSnapshotBackupEntityBuilder - - @objc public class func builder(type: SignalIOSProtoBackupSnapshotBackupEntityType, entityData: Data, collection: String, key: String) -> SignalIOSProtoBackupSnapshotBackupEntityBuilder { - return SignalIOSProtoBackupSnapshotBackupEntityBuilder(type: type, entityData: entityData, collection: collection, key: key) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SignalIOSProtoBackupSnapshotBackupEntityBuilder { - let builder = SignalIOSProtoBackupSnapshotBackupEntityBuilder(type: type, entityData: entityData, collection: collection, key: key) - return builder - } - - @objc public class SignalIOSProtoBackupSnapshotBackupEntityBuilder: NSObject { - - private var proto = IOSProtos_BackupSnapshot.BackupEntity() - - @objc fileprivate override init() {} - - @objc fileprivate init(type: SignalIOSProtoBackupSnapshotBackupEntityType, entityData: Data, collection: String, key: String) { - super.init() - - setType(type) - setEntityData(entityData) - setCollection(collection) - setKey(key) - } - - @objc public func setType(_ valueParam: SignalIOSProtoBackupSnapshotBackupEntityType) { - proto.type = SignalIOSProtoBackupSnapshotBackupEntityTypeUnwrap(valueParam) - } - - @objc public func setEntityData(_ valueParam: Data) { - proto.entityData = valueParam - } - - @objc public func setCollection(_ valueParam: String) { - proto.collection = valueParam - } - - @objc public func setKey(_ valueParam: String) { - proto.key = valueParam - } - - @objc public func build() throws -> SignalIOSProtoBackupSnapshotBackupEntity { - return try SignalIOSProtoBackupSnapshotBackupEntity.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SignalIOSProtoBackupSnapshotBackupEntity.parseProto(proto).serializedData() - } - } - - fileprivate let proto: IOSProtos_BackupSnapshot.BackupEntity - - @objc public let type: SignalIOSProtoBackupSnapshotBackupEntityType - - @objc public let entityData: Data - - @objc public let collection: String - - @objc public let key: String - - private init(proto: IOSProtos_BackupSnapshot.BackupEntity, - type: SignalIOSProtoBackupSnapshotBackupEntityType, - entityData: Data, - collection: String, - key: String) { - self.proto = proto - self.type = type - self.entityData = entityData - self.collection = collection - self.key = key - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SignalIOSProtoBackupSnapshotBackupEntity { - let proto = try IOSProtos_BackupSnapshot.BackupEntity(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: IOSProtos_BackupSnapshot.BackupEntity) throws -> SignalIOSProtoBackupSnapshotBackupEntity { - guard proto.hasType else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") - } - let type = SignalIOSProtoBackupSnapshotBackupEntityTypeWrap(proto.type) - - guard proto.hasEntityData else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: entityData") - } - let entityData = proto.entityData - - guard proto.hasCollection else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: collection") - } - let collection = proto.collection - - guard proto.hasKey else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: key") - } - let key = proto.key - - // MARK: - Begin Validation Logic for SignalIOSProtoBackupSnapshotBackupEntity - - - // MARK: - End Validation Logic for SignalIOSProtoBackupSnapshotBackupEntity - - - let result = SignalIOSProtoBackupSnapshotBackupEntity(proto: proto, - type: type, - entityData: entityData, - collection: collection, - key: key) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SignalIOSProtoBackupSnapshotBackupEntity { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SignalIOSProtoBackupSnapshotBackupEntity.SignalIOSProtoBackupSnapshotBackupEntityBuilder { - @objc public func buildIgnoringErrors() -> SignalIOSProtoBackupSnapshotBackupEntity? { - return try! self.build() - } -} - -#endif - -// MARK: - SignalIOSProtoBackupSnapshot - -@objc public class SignalIOSProtoBackupSnapshot: NSObject { - - // MARK: - SignalIOSProtoBackupSnapshotBuilder - - @objc public class func builder() -> SignalIOSProtoBackupSnapshotBuilder { - return SignalIOSProtoBackupSnapshotBuilder() - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SignalIOSProtoBackupSnapshotBuilder { - let builder = SignalIOSProtoBackupSnapshotBuilder() - builder.setEntity(entity) - return builder - } - - @objc public class SignalIOSProtoBackupSnapshotBuilder: NSObject { - - private var proto = IOSProtos_BackupSnapshot() - - @objc fileprivate override init() {} - - @objc public func addEntity(_ valueParam: SignalIOSProtoBackupSnapshotBackupEntity) { - var items = proto.entity - items.append(valueParam.proto) - proto.entity = items - } - - @objc public func setEntity(_ wrappedItems: [SignalIOSProtoBackupSnapshotBackupEntity]) { - proto.entity = wrappedItems.map { $0.proto } - } - - @objc public func build() throws -> SignalIOSProtoBackupSnapshot { - return try SignalIOSProtoBackupSnapshot.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SignalIOSProtoBackupSnapshot.parseProto(proto).serializedData() - } - } - - fileprivate let proto: IOSProtos_BackupSnapshot - - @objc public let entity: [SignalIOSProtoBackupSnapshotBackupEntity] - - private init(proto: IOSProtos_BackupSnapshot, - entity: [SignalIOSProtoBackupSnapshotBackupEntity]) { - self.proto = proto - self.entity = entity - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SignalIOSProtoBackupSnapshot { - let proto = try IOSProtos_BackupSnapshot(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: IOSProtos_BackupSnapshot) throws -> SignalIOSProtoBackupSnapshot { - var entity: [SignalIOSProtoBackupSnapshotBackupEntity] = [] - entity = try proto.entity.map { try SignalIOSProtoBackupSnapshotBackupEntity.parseProto($0) } - - // MARK: - Begin Validation Logic for SignalIOSProtoBackupSnapshot - - - // MARK: - End Validation Logic for SignalIOSProtoBackupSnapshot - - - let result = SignalIOSProtoBackupSnapshot(proto: proto, - entity: entity) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SignalIOSProtoBackupSnapshot { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SignalIOSProtoBackupSnapshot.SignalIOSProtoBackupSnapshotBuilder { - @objc public func buildIgnoringErrors() -> SignalIOSProtoBackupSnapshot? { - return try! self.build() - } -} - -#endif - -// MARK: - SignalIOSProtoDeviceName - -@objc public class SignalIOSProtoDeviceName: NSObject { - - // MARK: - SignalIOSProtoDeviceNameBuilder - - @objc public class func builder(ephemeralPublic: Data, syntheticIv: Data, ciphertext: Data) -> SignalIOSProtoDeviceNameBuilder { - return SignalIOSProtoDeviceNameBuilder(ephemeralPublic: ephemeralPublic, syntheticIv: syntheticIv, ciphertext: ciphertext) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> SignalIOSProtoDeviceNameBuilder { - let builder = SignalIOSProtoDeviceNameBuilder(ephemeralPublic: ephemeralPublic, syntheticIv: syntheticIv, ciphertext: ciphertext) - return builder - } - - @objc public class SignalIOSProtoDeviceNameBuilder: NSObject { - - private var proto = IOSProtos_DeviceName() - - @objc fileprivate override init() {} - - @objc fileprivate init(ephemeralPublic: Data, syntheticIv: Data, ciphertext: Data) { - super.init() - - setEphemeralPublic(ephemeralPublic) - setSyntheticIv(syntheticIv) - setCiphertext(ciphertext) - } - - @objc public func setEphemeralPublic(_ valueParam: Data) { - proto.ephemeralPublic = valueParam - } - - @objc public func setSyntheticIv(_ valueParam: Data) { - proto.syntheticIv = valueParam - } - - @objc public func setCiphertext(_ valueParam: Data) { - proto.ciphertext = valueParam - } - - @objc public func build() throws -> SignalIOSProtoDeviceName { - return try SignalIOSProtoDeviceName.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try SignalIOSProtoDeviceName.parseProto(proto).serializedData() - } - } - - fileprivate let proto: IOSProtos_DeviceName - - @objc public let ephemeralPublic: Data - - @objc public let syntheticIv: Data - - @objc public let ciphertext: Data - - private init(proto: IOSProtos_DeviceName, - ephemeralPublic: Data, - syntheticIv: Data, - ciphertext: Data) { - self.proto = proto - self.ephemeralPublic = ephemeralPublic - self.syntheticIv = syntheticIv - self.ciphertext = ciphertext - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> SignalIOSProtoDeviceName { - let proto = try IOSProtos_DeviceName(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: IOSProtos_DeviceName) throws -> SignalIOSProtoDeviceName { - guard proto.hasEphemeralPublic else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: ephemeralPublic") - } - let ephemeralPublic = proto.ephemeralPublic - - guard proto.hasSyntheticIv else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: syntheticIv") - } - let syntheticIv = proto.syntheticIv - - guard proto.hasCiphertext else { - throw SignalIOSProtoError.invalidProtobuf(description: "\(logTag) missing required field: ciphertext") - } - let ciphertext = proto.ciphertext - - // MARK: - Begin Validation Logic for SignalIOSProtoDeviceName - - - // MARK: - End Validation Logic for SignalIOSProtoDeviceName - - - let result = SignalIOSProtoDeviceName(proto: proto, - ephemeralPublic: ephemeralPublic, - syntheticIv: syntheticIv, - ciphertext: ciphertext) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension SignalIOSProtoDeviceName { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension SignalIOSProtoDeviceName.SignalIOSProtoDeviceNameBuilder { - @objc public func buildIgnoringErrors() -> SignalIOSProtoDeviceName? { - return try! self.build() - } -} - -#endif diff --git a/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift b/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift deleted file mode 100644 index ac386523f..000000000 --- a/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift +++ /dev/null @@ -1,5199 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: SignalService.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -//* -// Copyright (C) 2014-2016 Open Whisper Systems -// -// Licensed according to the LICENSE file in this repository. - -/// iOS - since we use a modern proto-compiler, we must specify -/// the legacy proto format. - -import Foundation -import SwiftProtobuf - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -struct SignalServiceProtos_Envelope { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var type: SignalServiceProtos_Envelope.TypeEnum { - get {return _type ?? .unknown} - set {_type = newValue} - } - /// Returns true if `type` has been explicitly set. - var hasType: Bool {return self._type != nil} - /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {self._type = nil} - - var source: String { - get {return _source ?? String()} - set {_source = newValue} - } - /// Returns true if `source` has been explicitly set. - var hasSource: Bool {return self._source != nil} - /// Clears the value of `source`. Subsequent reads from it will return its default value. - mutating func clearSource() {self._source = nil} - - var sourceDevice: UInt32 { - get {return _sourceDevice ?? 0} - set {_sourceDevice = newValue} - } - /// Returns true if `sourceDevice` has been explicitly set. - var hasSourceDevice: Bool {return self._sourceDevice != nil} - /// Clears the value of `sourceDevice`. Subsequent reads from it will return its default value. - mutating func clearSourceDevice() {self._sourceDevice = nil} - - var relay: String { - get {return _relay ?? String()} - set {_relay = newValue} - } - /// Returns true if `relay` has been explicitly set. - var hasRelay: Bool {return self._relay != nil} - /// Clears the value of `relay`. Subsequent reads from it will return its default value. - mutating func clearRelay() {self._relay = nil} - - /// @required - var timestamp: UInt64 { - get {return _timestamp ?? 0} - set {_timestamp = newValue} - } - /// Returns true if `timestamp` has been explicitly set. - var hasTimestamp: Bool {return self._timestamp != nil} - /// Clears the value of `timestamp`. Subsequent reads from it will return its default value. - mutating func clearTimestamp() {self._timestamp = nil} - - /// Contains an encrypted DataMessage - var legacyMessage: Data { - get {return _legacyMessage ?? SwiftProtobuf.Internal.emptyData} - set {_legacyMessage = newValue} - } - /// Returns true if `legacyMessage` has been explicitly set. - var hasLegacyMessage: Bool {return self._legacyMessage != nil} - /// Clears the value of `legacyMessage`. Subsequent reads from it will return its default value. - mutating func clearLegacyMessage() {self._legacyMessage = nil} - - /// Contains an encrypted Content - var content: Data { - get {return _content ?? SwiftProtobuf.Internal.emptyData} - set {_content = newValue} - } - /// Returns true if `content` has been explicitly set. - var hasContent: Bool {return self._content != nil} - /// Clears the value of `content`. Subsequent reads from it will return its default value. - mutating func clearContent() {self._content = nil} - - /// We may eventually want to make this required. - var serverGuid: String { - get {return _serverGuid ?? String()} - set {_serverGuid = newValue} - } - /// Returns true if `serverGuid` has been explicitly set. - var hasServerGuid: Bool {return self._serverGuid != nil} - /// Clears the value of `serverGuid`. Subsequent reads from it will return its default value. - mutating func clearServerGuid() {self._serverGuid = nil} - - /// We may eventually want to make this required. - var serverTimestamp: UInt64 { - get {return _serverTimestamp ?? 0} - set {_serverTimestamp = newValue} - } - /// Returns true if `serverTimestamp` has been explicitly set. - var hasServerTimestamp: Bool {return self._serverTimestamp != nil} - /// Clears the value of `serverTimestamp`. Subsequent reads from it will return its default value. - mutating func clearServerTimestamp() {self._serverTimestamp = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum TypeEnum: SwiftProtobuf.Enum { - typealias RawValue = Int - case unknown // = 0 - case ciphertext // = 1 - case keyExchange // = 2 - case prekeyBundle // = 3 - case receipt // = 5 - case unidentifiedSender // = 6 - - /// Loki - case closedGroupCiphertext // = 7 - - /// Loki: Encrypted using the fallback session cipher. Contains a pre key bundle if it's a session request. - case fallbackMessage // = 101 - - init() { - self = .unknown - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unknown - case 1: self = .ciphertext - case 2: self = .keyExchange - case 3: self = .prekeyBundle - case 5: self = .receipt - case 6: self = .unidentifiedSender - case 7: self = .closedGroupCiphertext - case 101: self = .fallbackMessage - default: return nil - } - } - - var rawValue: Int { - switch self { - case .unknown: return 0 - case .ciphertext: return 1 - case .keyExchange: return 2 - case .prekeyBundle: return 3 - case .receipt: return 5 - case .unidentifiedSender: return 6 - case .closedGroupCiphertext: return 7 - case .fallbackMessage: return 101 - } - } - - } - - init() {} - - fileprivate var _type: SignalServiceProtos_Envelope.TypeEnum? = nil - fileprivate var _source: String? = nil - fileprivate var _sourceDevice: UInt32? = nil - fileprivate var _relay: String? = nil - fileprivate var _timestamp: UInt64? = nil - fileprivate var _legacyMessage: Data? = nil - fileprivate var _content: Data? = nil - fileprivate var _serverGuid: String? = nil - fileprivate var _serverTimestamp: UInt64? = nil -} - -#if swift(>=4.2) - -extension SignalServiceProtos_Envelope.TypeEnum: CaseIterable { - // Support synthesized by the compiler. -} - -#endif // swift(>=4.2) - -struct SignalServiceProtos_TypingMessage { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var timestamp: UInt64 { - get {return _timestamp ?? 0} - set {_timestamp = newValue} - } - /// Returns true if `timestamp` has been explicitly set. - var hasTimestamp: Bool {return self._timestamp != nil} - /// Clears the value of `timestamp`. Subsequent reads from it will return its default value. - mutating func clearTimestamp() {self._timestamp = nil} - - /// @required - var action: SignalServiceProtos_TypingMessage.Action { - get {return _action ?? .started} - set {_action = newValue} - } - /// Returns true if `action` has been explicitly set. - var hasAction: Bool {return self._action != nil} - /// Clears the value of `action`. Subsequent reads from it will return its default value. - mutating func clearAction() {self._action = nil} - - var groupID: Data { - get {return _groupID ?? SwiftProtobuf.Internal.emptyData} - set {_groupID = newValue} - } - /// Returns true if `groupID` has been explicitly set. - var hasGroupID: Bool {return self._groupID != nil} - /// Clears the value of `groupID`. Subsequent reads from it will return its default value. - mutating func clearGroupID() {self._groupID = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum Action: SwiftProtobuf.Enum { - typealias RawValue = Int - case started // = 0 - case stopped // = 1 - - init() { - self = .started - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .started - case 1: self = .stopped - default: return nil - } - } - - var rawValue: Int { - switch self { - case .started: return 0 - case .stopped: return 1 - } - } - - } - - init() {} - - fileprivate var _timestamp: UInt64? = nil - fileprivate var _action: SignalServiceProtos_TypingMessage.Action? = nil - fileprivate var _groupID: Data? = nil -} - -#if swift(>=4.2) - -extension SignalServiceProtos_TypingMessage.Action: CaseIterable { - // Support synthesized by the compiler. -} - -#endif // swift(>=4.2) - -struct SignalServiceProtos_Content { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var dataMessage: SignalServiceProtos_DataMessage { - get {return _dataMessage ?? SignalServiceProtos_DataMessage()} - set {_dataMessage = newValue} - } - /// Returns true if `dataMessage` has been explicitly set. - var hasDataMessage: Bool {return self._dataMessage != nil} - /// Clears the value of `dataMessage`. Subsequent reads from it will return its default value. - mutating func clearDataMessage() {self._dataMessage = nil} - - var syncMessage: SignalServiceProtos_SyncMessage { - get {return _syncMessage ?? SignalServiceProtos_SyncMessage()} - set {_syncMessage = newValue} - } - /// Returns true if `syncMessage` has been explicitly set. - var hasSyncMessage: Bool {return self._syncMessage != nil} - /// Clears the value of `syncMessage`. Subsequent reads from it will return its default value. - mutating func clearSyncMessage() {self._syncMessage = nil} - - var callMessage: SignalServiceProtos_CallMessage { - get {return _callMessage ?? SignalServiceProtos_CallMessage()} - set {_callMessage = newValue} - } - /// Returns true if `callMessage` has been explicitly set. - var hasCallMessage: Bool {return self._callMessage != nil} - /// Clears the value of `callMessage`. Subsequent reads from it will return its default value. - mutating func clearCallMessage() {self._callMessage = nil} - - var nullMessage: SignalServiceProtos_NullMessage { - get {return _nullMessage ?? SignalServiceProtos_NullMessage()} - set {_nullMessage = newValue} - } - /// Returns true if `nullMessage` has been explicitly set. - var hasNullMessage: Bool {return self._nullMessage != nil} - /// Clears the value of `nullMessage`. Subsequent reads from it will return its default value. - mutating func clearNullMessage() {self._nullMessage = nil} - - var receiptMessage: SignalServiceProtos_ReceiptMessage { - get {return _receiptMessage ?? SignalServiceProtos_ReceiptMessage()} - set {_receiptMessage = newValue} - } - /// Returns true if `receiptMessage` has been explicitly set. - var hasReceiptMessage: Bool {return self._receiptMessage != nil} - /// Clears the value of `receiptMessage`. Subsequent reads from it will return its default value. - mutating func clearReceiptMessage() {self._receiptMessage = nil} - - var typingMessage: SignalServiceProtos_TypingMessage { - get {return _typingMessage ?? SignalServiceProtos_TypingMessage()} - set {_typingMessage = newValue} - } - /// Returns true if `typingMessage` has been explicitly set. - var hasTypingMessage: Bool {return self._typingMessage != nil} - /// Clears the value of `typingMessage`. Subsequent reads from it will return its default value. - mutating func clearTypingMessage() {self._typingMessage = nil} - - /// Loki - var prekeyBundleMessage: SignalServiceProtos_PrekeyBundleMessage { - get {return _prekeyBundleMessage ?? SignalServiceProtos_PrekeyBundleMessage()} - set {_prekeyBundleMessage = newValue} - } - /// Returns true if `prekeyBundleMessage` has been explicitly set. - var hasPrekeyBundleMessage: Bool {return self._prekeyBundleMessage != nil} - /// Clears the value of `prekeyBundleMessage`. Subsequent reads from it will return its default value. - mutating func clearPrekeyBundleMessage() {self._prekeyBundleMessage = nil} - - /// Loki - var lokiDeviceLinkMessage: SignalServiceProtos_LokiDeviceLinkMessage { - get {return _lokiDeviceLinkMessage ?? SignalServiceProtos_LokiDeviceLinkMessage()} - set {_lokiDeviceLinkMessage = newValue} - } - /// Returns true if `lokiDeviceLinkMessage` has been explicitly set. - var hasLokiDeviceLinkMessage: Bool {return self._lokiDeviceLinkMessage != nil} - /// Clears the value of `lokiDeviceLinkMessage`. Subsequent reads from it will return its default value. - mutating func clearLokiDeviceLinkMessage() {self._lokiDeviceLinkMessage = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _dataMessage: SignalServiceProtos_DataMessage? = nil - fileprivate var _syncMessage: SignalServiceProtos_SyncMessage? = nil - fileprivate var _callMessage: SignalServiceProtos_CallMessage? = nil - fileprivate var _nullMessage: SignalServiceProtos_NullMessage? = nil - fileprivate var _receiptMessage: SignalServiceProtos_ReceiptMessage? = nil - fileprivate var _typingMessage: SignalServiceProtos_TypingMessage? = nil - fileprivate var _prekeyBundleMessage: SignalServiceProtos_PrekeyBundleMessage? = nil - fileprivate var _lokiDeviceLinkMessage: SignalServiceProtos_LokiDeviceLinkMessage? = nil -} - -/// Loki -struct SignalServiceProtos_PrekeyBundleMessage { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var identityKey: Data { - get {return _identityKey ?? SwiftProtobuf.Internal.emptyData} - set {_identityKey = newValue} - } - /// Returns true if `identityKey` has been explicitly set. - var hasIdentityKey: Bool {return self._identityKey != nil} - /// Clears the value of `identityKey`. Subsequent reads from it will return its default value. - mutating func clearIdentityKey() {self._identityKey = nil} - - var deviceID: UInt32 { - get {return _deviceID ?? 0} - set {_deviceID = newValue} - } - /// Returns true if `deviceID` has been explicitly set. - var hasDeviceID: Bool {return self._deviceID != nil} - /// Clears the value of `deviceID`. Subsequent reads from it will return its default value. - mutating func clearDeviceID() {self._deviceID = nil} - - var prekeyID: UInt32 { - get {return _prekeyID ?? 0} - set {_prekeyID = newValue} - } - /// Returns true if `prekeyID` has been explicitly set. - var hasPrekeyID: Bool {return self._prekeyID != nil} - /// Clears the value of `prekeyID`. Subsequent reads from it will return its default value. - mutating func clearPrekeyID() {self._prekeyID = nil} - - var signedKeyID: UInt32 { - get {return _signedKeyID ?? 0} - set {_signedKeyID = newValue} - } - /// Returns true if `signedKeyID` has been explicitly set. - var hasSignedKeyID: Bool {return self._signedKeyID != nil} - /// Clears the value of `signedKeyID`. Subsequent reads from it will return its default value. - mutating func clearSignedKeyID() {self._signedKeyID = nil} - - var prekey: Data { - get {return _prekey ?? SwiftProtobuf.Internal.emptyData} - set {_prekey = newValue} - } - /// Returns true if `prekey` has been explicitly set. - var hasPrekey: Bool {return self._prekey != nil} - /// Clears the value of `prekey`. Subsequent reads from it will return its default value. - mutating func clearPrekey() {self._prekey = nil} - - var signedKey: Data { - get {return _signedKey ?? SwiftProtobuf.Internal.emptyData} - set {_signedKey = newValue} - } - /// Returns true if `signedKey` has been explicitly set. - var hasSignedKey: Bool {return self._signedKey != nil} - /// Clears the value of `signedKey`. Subsequent reads from it will return its default value. - mutating func clearSignedKey() {self._signedKey = nil} - - var signature: Data { - get {return _signature ?? SwiftProtobuf.Internal.emptyData} - set {_signature = newValue} - } - /// Returns true if `signature` has been explicitly set. - var hasSignature: Bool {return self._signature != nil} - /// Clears the value of `signature`. Subsequent reads from it will return its default value. - mutating func clearSignature() {self._signature = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _identityKey: Data? = nil - fileprivate var _deviceID: UInt32? = nil - fileprivate var _prekeyID: UInt32? = nil - fileprivate var _signedKeyID: UInt32? = nil - fileprivate var _prekey: Data? = nil - fileprivate var _signedKey: Data? = nil - fileprivate var _signature: Data? = nil -} - -/// Loki -struct SignalServiceProtos_LokiDeviceLinkMessage { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var masterPublicKey: String { - get {return _masterPublicKey ?? String()} - set {_masterPublicKey = newValue} - } - /// Returns true if `masterPublicKey` has been explicitly set. - var hasMasterPublicKey: Bool {return self._masterPublicKey != nil} - /// Clears the value of `masterPublicKey`. Subsequent reads from it will return its default value. - mutating func clearMasterPublicKey() {self._masterPublicKey = nil} - - var slavePublicKey: String { - get {return _slavePublicKey ?? String()} - set {_slavePublicKey = newValue} - } - /// Returns true if `slavePublicKey` has been explicitly set. - var hasSlavePublicKey: Bool {return self._slavePublicKey != nil} - /// Clears the value of `slavePublicKey`. Subsequent reads from it will return its default value. - mutating func clearSlavePublicKey() {self._slavePublicKey = nil} - - var slaveSignature: Data { - get {return _slaveSignature ?? SwiftProtobuf.Internal.emptyData} - set {_slaveSignature = newValue} - } - /// Returns true if `slaveSignature` has been explicitly set. - var hasSlaveSignature: Bool {return self._slaveSignature != nil} - /// Clears the value of `slaveSignature`. Subsequent reads from it will return its default value. - mutating func clearSlaveSignature() {self._slaveSignature = nil} - - var masterSignature: Data { - get {return _masterSignature ?? SwiftProtobuf.Internal.emptyData} - set {_masterSignature = newValue} - } - /// Returns true if `masterSignature` has been explicitly set. - var hasMasterSignature: Bool {return self._masterSignature != nil} - /// Clears the value of `masterSignature`. Subsequent reads from it will return its default value. - mutating func clearMasterSignature() {self._masterSignature = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _masterPublicKey: String? = nil - fileprivate var _slavePublicKey: String? = nil - fileprivate var _slaveSignature: Data? = nil - fileprivate var _masterSignature: Data? = nil -} - -struct SignalServiceProtos_CallMessage { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var offer: SignalServiceProtos_CallMessage.Offer { - get {return _offer ?? SignalServiceProtos_CallMessage.Offer()} - set {_offer = newValue} - } - /// Returns true if `offer` has been explicitly set. - var hasOffer: Bool {return self._offer != nil} - /// Clears the value of `offer`. Subsequent reads from it will return its default value. - mutating func clearOffer() {self._offer = nil} - - var answer: SignalServiceProtos_CallMessage.Answer { - get {return _answer ?? SignalServiceProtos_CallMessage.Answer()} - set {_answer = newValue} - } - /// Returns true if `answer` has been explicitly set. - var hasAnswer: Bool {return self._answer != nil} - /// Clears the value of `answer`. Subsequent reads from it will return its default value. - mutating func clearAnswer() {self._answer = nil} - - var iceUpdate: [SignalServiceProtos_CallMessage.IceUpdate] = [] - - var hangup: SignalServiceProtos_CallMessage.Hangup { - get {return _hangup ?? SignalServiceProtos_CallMessage.Hangup()} - set {_hangup = newValue} - } - /// Returns true if `hangup` has been explicitly set. - var hasHangup: Bool {return self._hangup != nil} - /// Clears the value of `hangup`. Subsequent reads from it will return its default value. - mutating func clearHangup() {self._hangup = nil} - - var busy: SignalServiceProtos_CallMessage.Busy { - get {return _busy ?? SignalServiceProtos_CallMessage.Busy()} - set {_busy = newValue} - } - /// Returns true if `busy` has been explicitly set. - var hasBusy: Bool {return self._busy != nil} - /// Clears the value of `busy`. Subsequent reads from it will return its default value. - mutating func clearBusy() {self._busy = nil} - - /// Signal-iOS sends profile key with call messages - /// for earlier discovery - var profileKey: Data { - get {return _profileKey ?? SwiftProtobuf.Internal.emptyData} - set {_profileKey = newValue} - } - /// Returns true if `profileKey` has been explicitly set. - var hasProfileKey: Bool {return self._profileKey != nil} - /// Clears the value of `profileKey`. Subsequent reads from it will return its default value. - mutating func clearProfileKey() {self._profileKey = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct Offer { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var id: UInt64 { - get {return _id ?? 0} - set {_id = newValue} - } - /// Returns true if `id` has been explicitly set. - var hasID: Bool {return self._id != nil} - /// Clears the value of `id`. Subsequent reads from it will return its default value. - mutating func clearID() {self._id = nil} - - /// Signal-iOS renamed the description field to avoid - /// conflicts with [NSObject description]. - /// @required - var sessionDescription: String { - get {return _sessionDescription ?? String()} - set {_sessionDescription = newValue} - } - /// Returns true if `sessionDescription` has been explicitly set. - var hasSessionDescription: Bool {return self._sessionDescription != nil} - /// Clears the value of `sessionDescription`. Subsequent reads from it will return its default value. - mutating func clearSessionDescription() {self._sessionDescription = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _id: UInt64? = nil - fileprivate var _sessionDescription: String? = nil - } - - struct Answer { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var id: UInt64 { - get {return _id ?? 0} - set {_id = newValue} - } - /// Returns true if `id` has been explicitly set. - var hasID: Bool {return self._id != nil} - /// Clears the value of `id`. Subsequent reads from it will return its default value. - mutating func clearID() {self._id = nil} - - /// Signal-iOS renamed the description field to avoid - /// conflicts with [NSObject description]. - /// @required - var sessionDescription: String { - get {return _sessionDescription ?? String()} - set {_sessionDescription = newValue} - } - /// Returns true if `sessionDescription` has been explicitly set. - var hasSessionDescription: Bool {return self._sessionDescription != nil} - /// Clears the value of `sessionDescription`. Subsequent reads from it will return its default value. - mutating func clearSessionDescription() {self._sessionDescription = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _id: UInt64? = nil - fileprivate var _sessionDescription: String? = nil - } - - struct IceUpdate { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var id: UInt64 { - get {return _id ?? 0} - set {_id = newValue} - } - /// Returns true if `id` has been explicitly set. - var hasID: Bool {return self._id != nil} - /// Clears the value of `id`. Subsequent reads from it will return its default value. - mutating func clearID() {self._id = nil} - - /// @required - var sdpMid: String { - get {return _sdpMid ?? String()} - set {_sdpMid = newValue} - } - /// Returns true if `sdpMid` has been explicitly set. - var hasSdpMid: Bool {return self._sdpMid != nil} - /// Clears the value of `sdpMid`. Subsequent reads from it will return its default value. - mutating func clearSdpMid() {self._sdpMid = nil} - - /// @required - var sdpMlineIndex: UInt32 { - get {return _sdpMlineIndex ?? 0} - set {_sdpMlineIndex = newValue} - } - /// Returns true if `sdpMlineIndex` has been explicitly set. - var hasSdpMlineIndex: Bool {return self._sdpMlineIndex != nil} - /// Clears the value of `sdpMlineIndex`. Subsequent reads from it will return its default value. - mutating func clearSdpMlineIndex() {self._sdpMlineIndex = nil} - - /// @required - var sdp: String { - get {return _sdp ?? String()} - set {_sdp = newValue} - } - /// Returns true if `sdp` has been explicitly set. - var hasSdp: Bool {return self._sdp != nil} - /// Clears the value of `sdp`. Subsequent reads from it will return its default value. - mutating func clearSdp() {self._sdp = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _id: UInt64? = nil - fileprivate var _sdpMid: String? = nil - fileprivate var _sdpMlineIndex: UInt32? = nil - fileprivate var _sdp: String? = nil - } - - struct Busy { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var id: UInt64 { - get {return _id ?? 0} - set {_id = newValue} - } - /// Returns true if `id` has been explicitly set. - var hasID: Bool {return self._id != nil} - /// Clears the value of `id`. Subsequent reads from it will return its default value. - mutating func clearID() {self._id = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _id: UInt64? = nil - } - - struct Hangup { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var id: UInt64 { - get {return _id ?? 0} - set {_id = newValue} - } - /// Returns true if `id` has been explicitly set. - var hasID: Bool {return self._id != nil} - /// Clears the value of `id`. Subsequent reads from it will return its default value. - mutating func clearID() {self._id = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _id: UInt64? = nil - } - - init() {} - - fileprivate var _offer: SignalServiceProtos_CallMessage.Offer? = nil - fileprivate var _answer: SignalServiceProtos_CallMessage.Answer? = nil - fileprivate var _hangup: SignalServiceProtos_CallMessage.Hangup? = nil - fileprivate var _busy: SignalServiceProtos_CallMessage.Busy? = nil - fileprivate var _profileKey: Data? = nil -} - -struct SignalServiceProtos_ClosedGroupCiphertextMessageWrapper { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var ciphertext: Data { - get {return _ciphertext ?? SwiftProtobuf.Internal.emptyData} - set {_ciphertext = newValue} - } - /// Returns true if `ciphertext` has been explicitly set. - var hasCiphertext: Bool {return self._ciphertext != nil} - /// Clears the value of `ciphertext`. Subsequent reads from it will return its default value. - mutating func clearCiphertext() {self._ciphertext = nil} - - /// @required - var ephemeralPublicKey: Data { - get {return _ephemeralPublicKey ?? SwiftProtobuf.Internal.emptyData} - set {_ephemeralPublicKey = newValue} - } - /// Returns true if `ephemeralPublicKey` has been explicitly set. - var hasEphemeralPublicKey: Bool {return self._ephemeralPublicKey != nil} - /// Clears the value of `ephemeralPublicKey`. Subsequent reads from it will return its default value. - mutating func clearEphemeralPublicKey() {self._ephemeralPublicKey = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _ciphertext: Data? = nil - fileprivate var _ephemeralPublicKey: Data? = nil -} - -struct SignalServiceProtos_DataMessage { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var body: String { - get {return _body ?? String()} - set {_body = newValue} - } - /// Returns true if `body` has been explicitly set. - var hasBody: Bool {return self._body != nil} - /// Clears the value of `body`. Subsequent reads from it will return its default value. - mutating func clearBody() {self._body = nil} - - var attachments: [SignalServiceProtos_AttachmentPointer] = [] - - var group: SignalServiceProtos_GroupContext { - get {return _group ?? SignalServiceProtos_GroupContext()} - set {_group = newValue} - } - /// Returns true if `group` has been explicitly set. - var hasGroup: Bool {return self._group != nil} - /// Clears the value of `group`. Subsequent reads from it will return its default value. - mutating func clearGroup() {self._group = nil} - - var flags: UInt32 { - get {return _flags ?? 0} - set {_flags = newValue} - } - /// Returns true if `flags` has been explicitly set. - var hasFlags: Bool {return self._flags != nil} - /// Clears the value of `flags`. Subsequent reads from it will return its default value. - mutating func clearFlags() {self._flags = nil} - - var expireTimer: UInt32 { - get {return _expireTimer ?? 0} - set {_expireTimer = newValue} - } - /// Returns true if `expireTimer` has been explicitly set. - var hasExpireTimer: Bool {return self._expireTimer != nil} - /// Clears the value of `expireTimer`. Subsequent reads from it will return its default value. - mutating func clearExpireTimer() {self._expireTimer = nil} - - var profileKey: Data { - get {return _profileKey ?? SwiftProtobuf.Internal.emptyData} - set {_profileKey = newValue} - } - /// Returns true if `profileKey` has been explicitly set. - var hasProfileKey: Bool {return self._profileKey != nil} - /// Clears the value of `profileKey`. Subsequent reads from it will return its default value. - mutating func clearProfileKey() {self._profileKey = nil} - - var timestamp: UInt64 { - get {return _timestamp ?? 0} - set {_timestamp = newValue} - } - /// Returns true if `timestamp` has been explicitly set. - var hasTimestamp: Bool {return self._timestamp != nil} - /// Clears the value of `timestamp`. Subsequent reads from it will return its default value. - mutating func clearTimestamp() {self._timestamp = nil} - - var quote: SignalServiceProtos_DataMessage.Quote { - get {return _quote ?? SignalServiceProtos_DataMessage.Quote()} - set {_quote = newValue} - } - /// Returns true if `quote` has been explicitly set. - var hasQuote: Bool {return self._quote != nil} - /// Clears the value of `quote`. Subsequent reads from it will return its default value. - mutating func clearQuote() {self._quote = nil} - - var contact: [SignalServiceProtos_DataMessage.Contact] = [] - - var preview: [SignalServiceProtos_DataMessage.Preview] = [] - - /// Loki: The current user's profile - var profile: SignalServiceProtos_DataMessage.LokiProfile { - get {return _profile ?? SignalServiceProtos_DataMessage.LokiProfile()} - set {_profile = newValue} - } - /// Returns true if `profile` has been explicitly set. - var hasProfile: Bool {return self._profile != nil} - /// Clears the value of `profile`. Subsequent reads from it will return its default value. - mutating func clearProfile() {self._profile = nil} - - /// Loki - var closedGroupUpdate: SignalServiceProtos_DataMessage.ClosedGroupUpdate { - get {return _closedGroupUpdate ?? SignalServiceProtos_DataMessage.ClosedGroupUpdate()} - set {_closedGroupUpdate = newValue} - } - /// Returns true if `closedGroupUpdate` has been explicitly set. - var hasClosedGroupUpdate: Bool {return self._closedGroupUpdate != nil} - /// Clears the value of `closedGroupUpdate`. Subsequent reads from it will return its default value. - mutating func clearClosedGroupUpdate() {self._closedGroupUpdate = nil} - - /// Loki: Internal public chat info - var publicChatInfo: SignalServiceProtos_PublicChatInfo { - get {return _publicChatInfo ?? SignalServiceProtos_PublicChatInfo()} - set {_publicChatInfo = newValue} - } - /// Returns true if `publicChatInfo` has been explicitly set. - var hasPublicChatInfo: Bool {return self._publicChatInfo != nil} - /// Clears the value of `publicChatInfo`. Subsequent reads from it will return its default value. - mutating func clearPublicChatInfo() {self._publicChatInfo = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum Flags: SwiftProtobuf.Enum { - typealias RawValue = Int - case endSession // = 1 - case expirationTimerUpdate // = 2 - case profileKeyUpdate // = 4 - case unlinkDevice // = 128 - - init() { - self = .endSession - } - - init?(rawValue: Int) { - switch rawValue { - case 1: self = .endSession - case 2: self = .expirationTimerUpdate - case 4: self = .profileKeyUpdate - case 128: self = .unlinkDevice - default: return nil - } - } - - var rawValue: Int { - switch self { - case .endSession: return 1 - case .expirationTimerUpdate: return 2 - case .profileKeyUpdate: return 4 - case .unlinkDevice: return 128 - } - } - - } - - struct Quote { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var id: UInt64 { - get {return _id ?? 0} - set {_id = newValue} - } - /// Returns true if `id` has been explicitly set. - var hasID: Bool {return self._id != nil} - /// Clears the value of `id`. Subsequent reads from it will return its default value. - mutating func clearID() {self._id = nil} - - /// @required - var author: String { - get {return _author ?? String()} - set {_author = newValue} - } - /// Returns true if `author` has been explicitly set. - var hasAuthor: Bool {return self._author != nil} - /// Clears the value of `author`. Subsequent reads from it will return its default value. - mutating func clearAuthor() {self._author = nil} - - var text: String { - get {return _text ?? String()} - set {_text = newValue} - } - /// Returns true if `text` has been explicitly set. - var hasText: Bool {return self._text != nil} - /// Clears the value of `text`. Subsequent reads from it will return its default value. - mutating func clearText() {self._text = nil} - - var attachments: [SignalServiceProtos_DataMessage.Quote.QuotedAttachment] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct QuotedAttachment { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var contentType: String { - get {return _contentType ?? String()} - set {_contentType = newValue} - } - /// Returns true if `contentType` has been explicitly set. - var hasContentType: Bool {return self._contentType != nil} - /// Clears the value of `contentType`. Subsequent reads from it will return its default value. - mutating func clearContentType() {self._contentType = nil} - - var fileName: String { - get {return _fileName ?? String()} - set {_fileName = newValue} - } - /// Returns true if `fileName` has been explicitly set. - var hasFileName: Bool {return self._fileName != nil} - /// Clears the value of `fileName`. Subsequent reads from it will return its default value. - mutating func clearFileName() {self._fileName = nil} - - var thumbnail: SignalServiceProtos_AttachmentPointer { - get {return _thumbnail ?? SignalServiceProtos_AttachmentPointer()} - set {_thumbnail = newValue} - } - /// Returns true if `thumbnail` has been explicitly set. - var hasThumbnail: Bool {return self._thumbnail != nil} - /// Clears the value of `thumbnail`. Subsequent reads from it will return its default value. - mutating func clearThumbnail() {self._thumbnail = nil} - - var flags: UInt32 { - get {return _flags ?? 0} - set {_flags = newValue} - } - /// Returns true if `flags` has been explicitly set. - var hasFlags: Bool {return self._flags != nil} - /// Clears the value of `flags`. Subsequent reads from it will return its default value. - mutating func clearFlags() {self._flags = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum Flags: SwiftProtobuf.Enum { - typealias RawValue = Int - case voiceMessage // = 1 - - init() { - self = .voiceMessage - } - - init?(rawValue: Int) { - switch rawValue { - case 1: self = .voiceMessage - default: return nil - } - } - - var rawValue: Int { - switch self { - case .voiceMessage: return 1 - } - } - - } - - init() {} - - fileprivate var _contentType: String? = nil - fileprivate var _fileName: String? = nil - fileprivate var _thumbnail: SignalServiceProtos_AttachmentPointer? = nil - fileprivate var _flags: UInt32? = nil - } - - init() {} - - fileprivate var _id: UInt64? = nil - fileprivate var _author: String? = nil - fileprivate var _text: String? = nil - } - - struct Contact { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var name: SignalServiceProtos_DataMessage.Contact.Name { - get {return _name ?? SignalServiceProtos_DataMessage.Contact.Name()} - set {_name = newValue} - } - /// Returns true if `name` has been explicitly set. - var hasName: Bool {return self._name != nil} - /// Clears the value of `name`. Subsequent reads from it will return its default value. - mutating func clearName() {self._name = nil} - - var number: [SignalServiceProtos_DataMessage.Contact.Phone] = [] - - var email: [SignalServiceProtos_DataMessage.Contact.Email] = [] - - var address: [SignalServiceProtos_DataMessage.Contact.PostalAddress] = [] - - var avatar: SignalServiceProtos_DataMessage.Contact.Avatar { - get {return _avatar ?? SignalServiceProtos_DataMessage.Contact.Avatar()} - set {_avatar = newValue} - } - /// Returns true if `avatar` has been explicitly set. - var hasAvatar: Bool {return self._avatar != nil} - /// Clears the value of `avatar`. Subsequent reads from it will return its default value. - mutating func clearAvatar() {self._avatar = nil} - - var organization: String { - get {return _organization ?? String()} - set {_organization = newValue} - } - /// Returns true if `organization` has been explicitly set. - var hasOrganization: Bool {return self._organization != nil} - /// Clears the value of `organization`. Subsequent reads from it will return its default value. - mutating func clearOrganization() {self._organization = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct Name { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var givenName: String { - get {return _givenName ?? String()} - set {_givenName = newValue} - } - /// Returns true if `givenName` has been explicitly set. - var hasGivenName: Bool {return self._givenName != nil} - /// Clears the value of `givenName`. Subsequent reads from it will return its default value. - mutating func clearGivenName() {self._givenName = nil} - - var familyName: String { - get {return _familyName ?? String()} - set {_familyName = newValue} - } - /// Returns true if `familyName` has been explicitly set. - var hasFamilyName: Bool {return self._familyName != nil} - /// Clears the value of `familyName`. Subsequent reads from it will return its default value. - mutating func clearFamilyName() {self._familyName = nil} - - var prefix: String { - get {return _prefix ?? String()} - set {_prefix = newValue} - } - /// Returns true if `prefix` has been explicitly set. - var hasPrefix: Bool {return self._prefix != nil} - /// Clears the value of `prefix`. Subsequent reads from it will return its default value. - mutating func clearPrefix() {self._prefix = nil} - - var suffix: String { - get {return _suffix ?? String()} - set {_suffix = newValue} - } - /// Returns true if `suffix` has been explicitly set. - var hasSuffix: Bool {return self._suffix != nil} - /// Clears the value of `suffix`. Subsequent reads from it will return its default value. - mutating func clearSuffix() {self._suffix = nil} - - var middleName: String { - get {return _middleName ?? String()} - set {_middleName = newValue} - } - /// Returns true if `middleName` has been explicitly set. - var hasMiddleName: Bool {return self._middleName != nil} - /// Clears the value of `middleName`. Subsequent reads from it will return its default value. - mutating func clearMiddleName() {self._middleName = nil} - - var displayName: String { - get {return _displayName ?? String()} - set {_displayName = newValue} - } - /// Returns true if `displayName` has been explicitly set. - var hasDisplayName: Bool {return self._displayName != nil} - /// Clears the value of `displayName`. Subsequent reads from it will return its default value. - mutating func clearDisplayName() {self._displayName = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _givenName: String? = nil - fileprivate var _familyName: String? = nil - fileprivate var _prefix: String? = nil - fileprivate var _suffix: String? = nil - fileprivate var _middleName: String? = nil - fileprivate var _displayName: String? = nil - } - - struct Phone { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var value: String { - get {return _value ?? String()} - set {_value = newValue} - } - /// Returns true if `value` has been explicitly set. - var hasValue: Bool {return self._value != nil} - /// Clears the value of `value`. Subsequent reads from it will return its default value. - mutating func clearValue() {self._value = nil} - - var type: SignalServiceProtos_DataMessage.Contact.Phone.TypeEnum { - get {return _type ?? .home} - set {_type = newValue} - } - /// Returns true if `type` has been explicitly set. - var hasType: Bool {return self._type != nil} - /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {self._type = nil} - - var label: String { - get {return _label ?? String()} - set {_label = newValue} - } - /// Returns true if `label` has been explicitly set. - var hasLabel: Bool {return self._label != nil} - /// Clears the value of `label`. Subsequent reads from it will return its default value. - mutating func clearLabel() {self._label = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum TypeEnum: SwiftProtobuf.Enum { - typealias RawValue = Int - case home // = 1 - case mobile // = 2 - case work // = 3 - case custom // = 4 - - init() { - self = .home - } - - init?(rawValue: Int) { - switch rawValue { - case 1: self = .home - case 2: self = .mobile - case 3: self = .work - case 4: self = .custom - default: return nil - } - } - - var rawValue: Int { - switch self { - case .home: return 1 - case .mobile: return 2 - case .work: return 3 - case .custom: return 4 - } - } - - } - - init() {} - - fileprivate var _value: String? = nil - fileprivate var _type: SignalServiceProtos_DataMessage.Contact.Phone.TypeEnum? = nil - fileprivate var _label: String? = nil - } - - struct Email { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var value: String { - get {return _value ?? String()} - set {_value = newValue} - } - /// Returns true if `value` has been explicitly set. - var hasValue: Bool {return self._value != nil} - /// Clears the value of `value`. Subsequent reads from it will return its default value. - mutating func clearValue() {self._value = nil} - - var type: SignalServiceProtos_DataMessage.Contact.Email.TypeEnum { - get {return _type ?? .home} - set {_type = newValue} - } - /// Returns true if `type` has been explicitly set. - var hasType: Bool {return self._type != nil} - /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {self._type = nil} - - var label: String { - get {return _label ?? String()} - set {_label = newValue} - } - /// Returns true if `label` has been explicitly set. - var hasLabel: Bool {return self._label != nil} - /// Clears the value of `label`. Subsequent reads from it will return its default value. - mutating func clearLabel() {self._label = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum TypeEnum: SwiftProtobuf.Enum { - typealias RawValue = Int - case home // = 1 - case mobile // = 2 - case work // = 3 - case custom // = 4 - - init() { - self = .home - } - - init?(rawValue: Int) { - switch rawValue { - case 1: self = .home - case 2: self = .mobile - case 3: self = .work - case 4: self = .custom - default: return nil - } - } - - var rawValue: Int { - switch self { - case .home: return 1 - case .mobile: return 2 - case .work: return 3 - case .custom: return 4 - } - } - - } - - init() {} - - fileprivate var _value: String? = nil - fileprivate var _type: SignalServiceProtos_DataMessage.Contact.Email.TypeEnum? = nil - fileprivate var _label: String? = nil - } - - struct PostalAddress { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var type: SignalServiceProtos_DataMessage.Contact.PostalAddress.TypeEnum { - get {return _type ?? .home} - set {_type = newValue} - } - /// Returns true if `type` has been explicitly set. - var hasType: Bool {return self._type != nil} - /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {self._type = nil} - - var label: String { - get {return _label ?? String()} - set {_label = newValue} - } - /// Returns true if `label` has been explicitly set. - var hasLabel: Bool {return self._label != nil} - /// Clears the value of `label`. Subsequent reads from it will return its default value. - mutating func clearLabel() {self._label = nil} - - var street: String { - get {return _street ?? String()} - set {_street = newValue} - } - /// Returns true if `street` has been explicitly set. - var hasStreet: Bool {return self._street != nil} - /// Clears the value of `street`. Subsequent reads from it will return its default value. - mutating func clearStreet() {self._street = nil} - - var pobox: String { - get {return _pobox ?? String()} - set {_pobox = newValue} - } - /// Returns true if `pobox` has been explicitly set. - var hasPobox: Bool {return self._pobox != nil} - /// Clears the value of `pobox`. Subsequent reads from it will return its default value. - mutating func clearPobox() {self._pobox = nil} - - var neighborhood: String { - get {return _neighborhood ?? String()} - set {_neighborhood = newValue} - } - /// Returns true if `neighborhood` has been explicitly set. - var hasNeighborhood: Bool {return self._neighborhood != nil} - /// Clears the value of `neighborhood`. Subsequent reads from it will return its default value. - mutating func clearNeighborhood() {self._neighborhood = nil} - - var city: String { - get {return _city ?? String()} - set {_city = newValue} - } - /// Returns true if `city` has been explicitly set. - var hasCity: Bool {return self._city != nil} - /// Clears the value of `city`. Subsequent reads from it will return its default value. - mutating func clearCity() {self._city = nil} - - var region: String { - get {return _region ?? String()} - set {_region = newValue} - } - /// Returns true if `region` has been explicitly set. - var hasRegion: Bool {return self._region != nil} - /// Clears the value of `region`. Subsequent reads from it will return its default value. - mutating func clearRegion() {self._region = nil} - - var postcode: String { - get {return _postcode ?? String()} - set {_postcode = newValue} - } - /// Returns true if `postcode` has been explicitly set. - var hasPostcode: Bool {return self._postcode != nil} - /// Clears the value of `postcode`. Subsequent reads from it will return its default value. - mutating func clearPostcode() {self._postcode = nil} - - var country: String { - get {return _country ?? String()} - set {_country = newValue} - } - /// Returns true if `country` has been explicitly set. - var hasCountry: Bool {return self._country != nil} - /// Clears the value of `country`. Subsequent reads from it will return its default value. - mutating func clearCountry() {self._country = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum TypeEnum: SwiftProtobuf.Enum { - typealias RawValue = Int - case home // = 1 - case work // = 2 - case custom // = 3 - - init() { - self = .home - } - - init?(rawValue: Int) { - switch rawValue { - case 1: self = .home - case 2: self = .work - case 3: self = .custom - default: return nil - } - } - - var rawValue: Int { - switch self { - case .home: return 1 - case .work: return 2 - case .custom: return 3 - } - } - - } - - init() {} - - fileprivate var _type: SignalServiceProtos_DataMessage.Contact.PostalAddress.TypeEnum? = nil - fileprivate var _label: String? = nil - fileprivate var _street: String? = nil - fileprivate var _pobox: String? = nil - fileprivate var _neighborhood: String? = nil - fileprivate var _city: String? = nil - fileprivate var _region: String? = nil - fileprivate var _postcode: String? = nil - fileprivate var _country: String? = nil - } - - struct Avatar { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var avatar: SignalServiceProtos_AttachmentPointer { - get {return _avatar ?? SignalServiceProtos_AttachmentPointer()} - set {_avatar = newValue} - } - /// Returns true if `avatar` has been explicitly set. - var hasAvatar: Bool {return self._avatar != nil} - /// Clears the value of `avatar`. Subsequent reads from it will return its default value. - mutating func clearAvatar() {self._avatar = nil} - - var isProfile: Bool { - get {return _isProfile ?? false} - set {_isProfile = newValue} - } - /// Returns true if `isProfile` has been explicitly set. - var hasIsProfile: Bool {return self._isProfile != nil} - /// Clears the value of `isProfile`. Subsequent reads from it will return its default value. - mutating func clearIsProfile() {self._isProfile = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _avatar: SignalServiceProtos_AttachmentPointer? = nil - fileprivate var _isProfile: Bool? = nil - } - - init() {} - - fileprivate var _name: SignalServiceProtos_DataMessage.Contact.Name? = nil - fileprivate var _avatar: SignalServiceProtos_DataMessage.Contact.Avatar? = nil - fileprivate var _organization: String? = nil - } - - struct Preview { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var url: String { - get {return _url ?? String()} - set {_url = newValue} - } - /// Returns true if `url` has been explicitly set. - var hasURL: Bool {return self._url != nil} - /// Clears the value of `url`. Subsequent reads from it will return its default value. - mutating func clearURL() {self._url = nil} - - var title: String { - get {return _title ?? String()} - set {_title = newValue} - } - /// Returns true if `title` has been explicitly set. - var hasTitle: Bool {return self._title != nil} - /// Clears the value of `title`. Subsequent reads from it will return its default value. - mutating func clearTitle() {self._title = nil} - - var image: SignalServiceProtos_AttachmentPointer { - get {return _image ?? SignalServiceProtos_AttachmentPointer()} - set {_image = newValue} - } - /// Returns true if `image` has been explicitly set. - var hasImage: Bool {return self._image != nil} - /// Clears the value of `image`. Subsequent reads from it will return its default value. - mutating func clearImage() {self._image = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _url: String? = nil - fileprivate var _title: String? = nil - fileprivate var _image: SignalServiceProtos_AttachmentPointer? = nil - } - - /// Loki - struct LokiProfile { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var displayName: String { - get {return _displayName ?? String()} - set {_displayName = newValue} - } - /// Returns true if `displayName` has been explicitly set. - var hasDisplayName: Bool {return self._displayName != nil} - /// Clears the value of `displayName`. Subsequent reads from it will return its default value. - mutating func clearDisplayName() {self._displayName = nil} - - var profilePicture: String { - get {return _profilePicture ?? String()} - set {_profilePicture = newValue} - } - /// Returns true if `profilePicture` has been explicitly set. - var hasProfilePicture: Bool {return self._profilePicture != nil} - /// Clears the value of `profilePicture`. Subsequent reads from it will return its default value. - mutating func clearProfilePicture() {self._profilePicture = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _displayName: String? = nil - fileprivate var _profilePicture: String? = nil - } - - /// Loki - struct ClosedGroupUpdate { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var name: String { - get {return _name ?? String()} - set {_name = newValue} - } - /// Returns true if `name` has been explicitly set. - var hasName: Bool {return self._name != nil} - /// Clears the value of `name`. Subsequent reads from it will return its default value. - mutating func clearName() {self._name = nil} - - /// @required - var groupPublicKey: Data { - get {return _groupPublicKey ?? SwiftProtobuf.Internal.emptyData} - set {_groupPublicKey = newValue} - } - /// Returns true if `groupPublicKey` has been explicitly set. - var hasGroupPublicKey: Bool {return self._groupPublicKey != nil} - /// Clears the value of `groupPublicKey`. Subsequent reads from it will return its default value. - mutating func clearGroupPublicKey() {self._groupPublicKey = nil} - - var groupPrivateKey: Data { - get {return _groupPrivateKey ?? SwiftProtobuf.Internal.emptyData} - set {_groupPrivateKey = newValue} - } - /// Returns true if `groupPrivateKey` has been explicitly set. - var hasGroupPrivateKey: Bool {return self._groupPrivateKey != nil} - /// Clears the value of `groupPrivateKey`. Subsequent reads from it will return its default value. - mutating func clearGroupPrivateKey() {self._groupPrivateKey = nil} - - var senderKeys: [SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey] = [] - - var members: [Data] = [] - - var admins: [Data] = [] - - /// @required - var type: SignalServiceProtos_DataMessage.ClosedGroupUpdate.TypeEnum { - get {return _type ?? .new} - set {_type = newValue} - } - /// Returns true if `type` has been explicitly set. - var hasType: Bool {return self._type != nil} - /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {self._type = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum TypeEnum: SwiftProtobuf.Enum { - typealias RawValue = Int - - /// groupPublicKey, name, groupPrivateKey, senderKeys, members, admins - case new // = 0 - - /// groupPublicKey, name, senderKeys, members, admins - case info // = 1 - - /// groupPublicKey - case senderKeyRequest // = 2 - - /// groupPublicKey, senderKeys - case senderKey // = 3 - - init() { - self = .new - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .new - case 1: self = .info - case 2: self = .senderKeyRequest - case 3: self = .senderKey - default: return nil - } - } - - var rawValue: Int { - switch self { - case .new: return 0 - case .info: return 1 - case .senderKeyRequest: return 2 - case .senderKey: return 3 - } - } - - } - - struct SenderKey { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var chainKey: Data { - get {return _chainKey ?? SwiftProtobuf.Internal.emptyData} - set {_chainKey = newValue} - } - /// Returns true if `chainKey` has been explicitly set. - var hasChainKey: Bool {return self._chainKey != nil} - /// Clears the value of `chainKey`. Subsequent reads from it will return its default value. - mutating func clearChainKey() {self._chainKey = nil} - - /// @required - var keyIndex: UInt32 { - get {return _keyIndex ?? 0} - set {_keyIndex = newValue} - } - /// Returns true if `keyIndex` has been explicitly set. - var hasKeyIndex: Bool {return self._keyIndex != nil} - /// Clears the value of `keyIndex`. Subsequent reads from it will return its default value. - mutating func clearKeyIndex() {self._keyIndex = nil} - - /// @required - var publicKey: Data { - get {return _publicKey ?? SwiftProtobuf.Internal.emptyData} - set {_publicKey = newValue} - } - /// Returns true if `publicKey` has been explicitly set. - var hasPublicKey: Bool {return self._publicKey != nil} - /// Clears the value of `publicKey`. Subsequent reads from it will return its default value. - mutating func clearPublicKey() {self._publicKey = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _chainKey: Data? = nil - fileprivate var _keyIndex: UInt32? = nil - fileprivate var _publicKey: Data? = nil - } - - init() {} - - fileprivate var _name: String? = nil - fileprivate var _groupPublicKey: Data? = nil - fileprivate var _groupPrivateKey: Data? = nil - fileprivate var _type: SignalServiceProtos_DataMessage.ClosedGroupUpdate.TypeEnum? = nil - } - - init() {} - - fileprivate var _body: String? = nil - fileprivate var _group: SignalServiceProtos_GroupContext? = nil - fileprivate var _flags: UInt32? = nil - fileprivate var _expireTimer: UInt32? = nil - fileprivate var _profileKey: Data? = nil - fileprivate var _timestamp: UInt64? = nil - fileprivate var _quote: SignalServiceProtos_DataMessage.Quote? = nil - fileprivate var _profile: SignalServiceProtos_DataMessage.LokiProfile? = nil - fileprivate var _closedGroupUpdate: SignalServiceProtos_DataMessage.ClosedGroupUpdate? = nil - fileprivate var _publicChatInfo: SignalServiceProtos_PublicChatInfo? = nil -} - -#if swift(>=4.2) - -extension SignalServiceProtos_DataMessage.Flags: CaseIterable { - // Support synthesized by the compiler. -} - -extension SignalServiceProtos_DataMessage.Quote.QuotedAttachment.Flags: CaseIterable { - // Support synthesized by the compiler. -} - -extension SignalServiceProtos_DataMessage.Contact.Phone.TypeEnum: CaseIterable { - // Support synthesized by the compiler. -} - -extension SignalServiceProtos_DataMessage.Contact.Email.TypeEnum: CaseIterable { - // Support synthesized by the compiler. -} - -extension SignalServiceProtos_DataMessage.Contact.PostalAddress.TypeEnum: CaseIterable { - // Support synthesized by the compiler. -} - -extension SignalServiceProtos_DataMessage.ClosedGroupUpdate.TypeEnum: CaseIterable { - // Support synthesized by the compiler. -} - -#endif // swift(>=4.2) - -struct SignalServiceProtos_NullMessage { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var padding: Data { - get {return _padding ?? SwiftProtobuf.Internal.emptyData} - set {_padding = newValue} - } - /// Returns true if `padding` has been explicitly set. - var hasPadding: Bool {return self._padding != nil} - /// Clears the value of `padding`. Subsequent reads from it will return its default value. - mutating func clearPadding() {self._padding = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _padding: Data? = nil -} - -struct SignalServiceProtos_ReceiptMessage { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var type: SignalServiceProtos_ReceiptMessage.TypeEnum { - get {return _type ?? .delivery} - set {_type = newValue} - } - /// Returns true if `type` has been explicitly set. - var hasType: Bool {return self._type != nil} - /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {self._type = nil} - - var timestamp: [UInt64] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum TypeEnum: SwiftProtobuf.Enum { - typealias RawValue = Int - case delivery // = 0 - case read // = 1 - - init() { - self = .delivery - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .delivery - case 1: self = .read - default: return nil - } - } - - var rawValue: Int { - switch self { - case .delivery: return 0 - case .read: return 1 - } - } - - } - - init() {} - - fileprivate var _type: SignalServiceProtos_ReceiptMessage.TypeEnum? = nil -} - -#if swift(>=4.2) - -extension SignalServiceProtos_ReceiptMessage.TypeEnum: CaseIterable { - // Support synthesized by the compiler. -} - -#endif // swift(>=4.2) - -struct SignalServiceProtos_Verified { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var destination: String { - get {return _destination ?? String()} - set {_destination = newValue} - } - /// Returns true if `destination` has been explicitly set. - var hasDestination: Bool {return self._destination != nil} - /// Clears the value of `destination`. Subsequent reads from it will return its default value. - mutating func clearDestination() {self._destination = nil} - - var identityKey: Data { - get {return _identityKey ?? SwiftProtobuf.Internal.emptyData} - set {_identityKey = newValue} - } - /// Returns true if `identityKey` has been explicitly set. - var hasIdentityKey: Bool {return self._identityKey != nil} - /// Clears the value of `identityKey`. Subsequent reads from it will return its default value. - mutating func clearIdentityKey() {self._identityKey = nil} - - var state: SignalServiceProtos_Verified.State { - get {return _state ?? .default} - set {_state = newValue} - } - /// Returns true if `state` has been explicitly set. - var hasState: Bool {return self._state != nil} - /// Clears the value of `state`. Subsequent reads from it will return its default value. - mutating func clearState() {self._state = nil} - - var nullMessage: Data { - get {return _nullMessage ?? SwiftProtobuf.Internal.emptyData} - set {_nullMessage = newValue} - } - /// Returns true if `nullMessage` has been explicitly set. - var hasNullMessage: Bool {return self._nullMessage != nil} - /// Clears the value of `nullMessage`. Subsequent reads from it will return its default value. - mutating func clearNullMessage() {self._nullMessage = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum State: SwiftProtobuf.Enum { - typealias RawValue = Int - case `default` // = 0 - case verified // = 1 - case unverified // = 2 - - init() { - self = .default - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .default - case 1: self = .verified - case 2: self = .unverified - default: return nil - } - } - - var rawValue: Int { - switch self { - case .default: return 0 - case .verified: return 1 - case .unverified: return 2 - } - } - - } - - init() {} - - fileprivate var _destination: String? = nil - fileprivate var _identityKey: Data? = nil - fileprivate var _state: SignalServiceProtos_Verified.State? = nil - fileprivate var _nullMessage: Data? = nil -} - -#if swift(>=4.2) - -extension SignalServiceProtos_Verified.State: CaseIterable { - // Support synthesized by the compiler. -} - -#endif // swift(>=4.2) - -struct SignalServiceProtos_SyncMessage { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var sent: SignalServiceProtos_SyncMessage.Sent { - get {return _sent ?? SignalServiceProtos_SyncMessage.Sent()} - set {_sent = newValue} - } - /// Returns true if `sent` has been explicitly set. - var hasSent: Bool {return self._sent != nil} - /// Clears the value of `sent`. Subsequent reads from it will return its default value. - mutating func clearSent() {self._sent = nil} - - var contacts: SignalServiceProtos_SyncMessage.Contacts { - get {return _contacts ?? SignalServiceProtos_SyncMessage.Contacts()} - set {_contacts = newValue} - } - /// Returns true if `contacts` has been explicitly set. - var hasContacts: Bool {return self._contacts != nil} - /// Clears the value of `contacts`. Subsequent reads from it will return its default value. - mutating func clearContacts() {self._contacts = nil} - - var groups: SignalServiceProtos_SyncMessage.Groups { - get {return _groups ?? SignalServiceProtos_SyncMessage.Groups()} - set {_groups = newValue} - } - /// Returns true if `groups` has been explicitly set. - var hasGroups: Bool {return self._groups != nil} - /// Clears the value of `groups`. Subsequent reads from it will return its default value. - mutating func clearGroups() {self._groups = nil} - - var request: SignalServiceProtos_SyncMessage.Request { - get {return _request ?? SignalServiceProtos_SyncMessage.Request()} - set {_request = newValue} - } - /// Returns true if `request` has been explicitly set. - var hasRequest: Bool {return self._request != nil} - /// Clears the value of `request`. Subsequent reads from it will return its default value. - mutating func clearRequest() {self._request = nil} - - var read: [SignalServiceProtos_SyncMessage.Read] = [] - - var blocked: SignalServiceProtos_SyncMessage.Blocked { - get {return _blocked ?? SignalServiceProtos_SyncMessage.Blocked()} - set {_blocked = newValue} - } - /// Returns true if `blocked` has been explicitly set. - var hasBlocked: Bool {return self._blocked != nil} - /// Clears the value of `blocked`. Subsequent reads from it will return its default value. - mutating func clearBlocked() {self._blocked = nil} - - var verified: SignalServiceProtos_Verified { - get {return _verified ?? SignalServiceProtos_Verified()} - set {_verified = newValue} - } - /// Returns true if `verified` has been explicitly set. - var hasVerified: Bool {return self._verified != nil} - /// Clears the value of `verified`. Subsequent reads from it will return its default value. - mutating func clearVerified() {self._verified = nil} - - var configuration: SignalServiceProtos_SyncMessage.Configuration { - get {return _configuration ?? SignalServiceProtos_SyncMessage.Configuration()} - set {_configuration = newValue} - } - /// Returns true if `configuration` has been explicitly set. - var hasConfiguration: Bool {return self._configuration != nil} - /// Clears the value of `configuration`. Subsequent reads from it will return its default value. - mutating func clearConfiguration() {self._configuration = nil} - - var padding: Data { - get {return _padding ?? SwiftProtobuf.Internal.emptyData} - set {_padding = newValue} - } - /// Returns true if `padding` has been explicitly set. - var hasPadding: Bool {return self._padding != nil} - /// Clears the value of `padding`. Subsequent reads from it will return its default value. - mutating func clearPadding() {self._padding = nil} - - var openGroups: [SignalServiceProtos_SyncMessage.OpenGroupDetails] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct Sent { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var destination: String { - get {return _destination ?? String()} - set {_destination = newValue} - } - /// Returns true if `destination` has been explicitly set. - var hasDestination: Bool {return self._destination != nil} - /// Clears the value of `destination`. Subsequent reads from it will return its default value. - mutating func clearDestination() {self._destination = nil} - - var timestamp: UInt64 { - get {return _timestamp ?? 0} - set {_timestamp = newValue} - } - /// Returns true if `timestamp` has been explicitly set. - var hasTimestamp: Bool {return self._timestamp != nil} - /// Clears the value of `timestamp`. Subsequent reads from it will return its default value. - mutating func clearTimestamp() {self._timestamp = nil} - - var message: SignalServiceProtos_DataMessage { - get {return _message ?? SignalServiceProtos_DataMessage()} - set {_message = newValue} - } - /// Returns true if `message` has been explicitly set. - var hasMessage: Bool {return self._message != nil} - /// Clears the value of `message`. Subsequent reads from it will return its default value. - mutating func clearMessage() {self._message = nil} - - var expirationStartTimestamp: UInt64 { - get {return _expirationStartTimestamp ?? 0} - set {_expirationStartTimestamp = newValue} - } - /// Returns true if `expirationStartTimestamp` has been explicitly set. - var hasExpirationStartTimestamp: Bool {return self._expirationStartTimestamp != nil} - /// Clears the value of `expirationStartTimestamp`. Subsequent reads from it will return its default value. - mutating func clearExpirationStartTimestamp() {self._expirationStartTimestamp = nil} - - var unidentifiedStatus: [SignalServiceProtos_SyncMessage.Sent.UnidentifiedDeliveryStatus] = [] - - var isRecipientUpdate: Bool { - get {return _isRecipientUpdate ?? false} - set {_isRecipientUpdate = newValue} - } - /// Returns true if `isRecipientUpdate` has been explicitly set. - var hasIsRecipientUpdate: Bool {return self._isRecipientUpdate != nil} - /// Clears the value of `isRecipientUpdate`. Subsequent reads from it will return its default value. - mutating func clearIsRecipientUpdate() {self._isRecipientUpdate = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct UnidentifiedDeliveryStatus { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var destination: String { - get {return _destination ?? String()} - set {_destination = newValue} - } - /// Returns true if `destination` has been explicitly set. - var hasDestination: Bool {return self._destination != nil} - /// Clears the value of `destination`. Subsequent reads from it will return its default value. - mutating func clearDestination() {self._destination = nil} - - var unidentified: Bool { - get {return _unidentified ?? false} - set {_unidentified = newValue} - } - /// Returns true if `unidentified` has been explicitly set. - var hasUnidentified: Bool {return self._unidentified != nil} - /// Clears the value of `unidentified`. Subsequent reads from it will return its default value. - mutating func clearUnidentified() {self._unidentified = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _destination: String? = nil - fileprivate var _unidentified: Bool? = nil - } - - init() {} - - fileprivate var _destination: String? = nil - fileprivate var _timestamp: UInt64? = nil - fileprivate var _message: SignalServiceProtos_DataMessage? = nil - fileprivate var _expirationStartTimestamp: UInt64? = nil - fileprivate var _isRecipientUpdate: Bool? = nil - } - - struct Contacts { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var blob: SignalServiceProtos_AttachmentPointer { - get {return _blob ?? SignalServiceProtos_AttachmentPointer()} - set {_blob = newValue} - } - /// Returns true if `blob` has been explicitly set. - var hasBlob: Bool {return self._blob != nil} - /// Clears the value of `blob`. Subsequent reads from it will return its default value. - mutating func clearBlob() {self._blob = nil} - - /// Signal-iOS renamed this property. - var isComplete: Bool { - get {return _isComplete ?? false} - set {_isComplete = newValue} - } - /// Returns true if `isComplete` has been explicitly set. - var hasIsComplete: Bool {return self._isComplete != nil} - /// Clears the value of `isComplete`. Subsequent reads from it will return its default value. - mutating func clearIsComplete() {self._isComplete = nil} - - /// Loki - var data: Data { - get {return _data ?? SwiftProtobuf.Internal.emptyData} - set {_data = newValue} - } - /// Returns true if `data` has been explicitly set. - var hasData: Bool {return self._data != nil} - /// Clears the value of `data`. Subsequent reads from it will return its default value. - mutating func clearData() {self._data = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _blob: SignalServiceProtos_AttachmentPointer? = nil - fileprivate var _isComplete: Bool? = nil - fileprivate var _data: Data? = nil - } - - struct Groups { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var blob: SignalServiceProtos_AttachmentPointer { - get {return _blob ?? SignalServiceProtos_AttachmentPointer()} - set {_blob = newValue} - } - /// Returns true if `blob` has been explicitly set. - var hasBlob: Bool {return self._blob != nil} - /// Clears the value of `blob`. Subsequent reads from it will return its default value. - mutating func clearBlob() {self._blob = nil} - - /// Loki - var data: Data { - get {return _data ?? SwiftProtobuf.Internal.emptyData} - set {_data = newValue} - } - /// Returns true if `data` has been explicitly set. - var hasData: Bool {return self._data != nil} - /// Clears the value of `data`. Subsequent reads from it will return its default value. - mutating func clearData() {self._data = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _blob: SignalServiceProtos_AttachmentPointer? = nil - fileprivate var _data: Data? = nil - } - - /// Loki - struct OpenGroupDetails { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var url: String { - get {return _url ?? String()} - set {_url = newValue} - } - /// Returns true if `url` has been explicitly set. - var hasURL: Bool {return self._url != nil} - /// Clears the value of `url`. Subsequent reads from it will return its default value. - mutating func clearURL() {self._url = nil} - - /// @required - var channelID: UInt64 { - get {return _channelID ?? 0} - set {_channelID = newValue} - } - /// Returns true if `channelID` has been explicitly set. - var hasChannelID: Bool {return self._channelID != nil} - /// Clears the value of `channelID`. Subsequent reads from it will return its default value. - mutating func clearChannelID() {self._channelID = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _url: String? = nil - fileprivate var _channelID: UInt64? = nil - } - - struct Blocked { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var numbers: [String] = [] - - var groupIds: [Data] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - struct Request { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var type: SignalServiceProtos_SyncMessage.Request.TypeEnum { - get {return _type ?? .unknown} - set {_type = newValue} - } - /// Returns true if `type` has been explicitly set. - var hasType: Bool {return self._type != nil} - /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {self._type = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum TypeEnum: SwiftProtobuf.Enum { - typealias RawValue = Int - case unknown // = 0 - case contacts // = 1 - case groups // = 2 - case blocked // = 3 - case configuration // = 4 - - init() { - self = .unknown - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unknown - case 1: self = .contacts - case 2: self = .groups - case 3: self = .blocked - case 4: self = .configuration - default: return nil - } - } - - var rawValue: Int { - switch self { - case .unknown: return 0 - case .contacts: return 1 - case .groups: return 2 - case .blocked: return 3 - case .configuration: return 4 - } - } - - } - - init() {} - - fileprivate var _type: SignalServiceProtos_SyncMessage.Request.TypeEnum? = nil - } - - struct Read { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var sender: String { - get {return _sender ?? String()} - set {_sender = newValue} - } - /// Returns true if `sender` has been explicitly set. - var hasSender: Bool {return self._sender != nil} - /// Clears the value of `sender`. Subsequent reads from it will return its default value. - mutating func clearSender() {self._sender = nil} - - /// @required - var timestamp: UInt64 { - get {return _timestamp ?? 0} - set {_timestamp = newValue} - } - /// Returns true if `timestamp` has been explicitly set. - var hasTimestamp: Bool {return self._timestamp != nil} - /// Clears the value of `timestamp`. Subsequent reads from it will return its default value. - mutating func clearTimestamp() {self._timestamp = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _sender: String? = nil - fileprivate var _timestamp: UInt64? = nil - } - - struct Configuration { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var readReceipts: Bool { - get {return _readReceipts ?? false} - set {_readReceipts = newValue} - } - /// Returns true if `readReceipts` has been explicitly set. - var hasReadReceipts: Bool {return self._readReceipts != nil} - /// Clears the value of `readReceipts`. Subsequent reads from it will return its default value. - mutating func clearReadReceipts() {self._readReceipts = nil} - - var unidentifiedDeliveryIndicators: Bool { - get {return _unidentifiedDeliveryIndicators ?? false} - set {_unidentifiedDeliveryIndicators = newValue} - } - /// Returns true if `unidentifiedDeliveryIndicators` has been explicitly set. - var hasUnidentifiedDeliveryIndicators: Bool {return self._unidentifiedDeliveryIndicators != nil} - /// Clears the value of `unidentifiedDeliveryIndicators`. Subsequent reads from it will return its default value. - mutating func clearUnidentifiedDeliveryIndicators() {self._unidentifiedDeliveryIndicators = nil} - - var typingIndicators: Bool { - get {return _typingIndicators ?? false} - set {_typingIndicators = newValue} - } - /// Returns true if `typingIndicators` has been explicitly set. - var hasTypingIndicators: Bool {return self._typingIndicators != nil} - /// Clears the value of `typingIndicators`. Subsequent reads from it will return its default value. - mutating func clearTypingIndicators() {self._typingIndicators = nil} - - var linkPreviews: Bool { - get {return _linkPreviews ?? false} - set {_linkPreviews = newValue} - } - /// Returns true if `linkPreviews` has been explicitly set. - var hasLinkPreviews: Bool {return self._linkPreviews != nil} - /// Clears the value of `linkPreviews`. Subsequent reads from it will return its default value. - mutating func clearLinkPreviews() {self._linkPreviews = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _readReceipts: Bool? = nil - fileprivate var _unidentifiedDeliveryIndicators: Bool? = nil - fileprivate var _typingIndicators: Bool? = nil - fileprivate var _linkPreviews: Bool? = nil - } - - init() {} - - fileprivate var _sent: SignalServiceProtos_SyncMessage.Sent? = nil - fileprivate var _contacts: SignalServiceProtos_SyncMessage.Contacts? = nil - fileprivate var _groups: SignalServiceProtos_SyncMessage.Groups? = nil - fileprivate var _request: SignalServiceProtos_SyncMessage.Request? = nil - fileprivate var _blocked: SignalServiceProtos_SyncMessage.Blocked? = nil - fileprivate var _verified: SignalServiceProtos_Verified? = nil - fileprivate var _configuration: SignalServiceProtos_SyncMessage.Configuration? = nil - fileprivate var _padding: Data? = nil -} - -#if swift(>=4.2) - -extension SignalServiceProtos_SyncMessage.Request.TypeEnum: CaseIterable { - // Support synthesized by the compiler. -} - -#endif // swift(>=4.2) - -struct SignalServiceProtos_AttachmentPointer { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var id: UInt64 { - get {return _id ?? 0} - set {_id = newValue} - } - /// Returns true if `id` has been explicitly set. - var hasID: Bool {return self._id != nil} - /// Clears the value of `id`. Subsequent reads from it will return its default value. - mutating func clearID() {self._id = nil} - - var contentType: String { - get {return _contentType ?? String()} - set {_contentType = newValue} - } - /// Returns true if `contentType` has been explicitly set. - var hasContentType: Bool {return self._contentType != nil} - /// Clears the value of `contentType`. Subsequent reads from it will return its default value. - mutating func clearContentType() {self._contentType = nil} - - var key: Data { - get {return _key ?? SwiftProtobuf.Internal.emptyData} - set {_key = newValue} - } - /// Returns true if `key` has been explicitly set. - var hasKey: Bool {return self._key != nil} - /// Clears the value of `key`. Subsequent reads from it will return its default value. - mutating func clearKey() {self._key = nil} - - var size: UInt32 { - get {return _size ?? 0} - set {_size = newValue} - } - /// Returns true if `size` has been explicitly set. - var hasSize: Bool {return self._size != nil} - /// Clears the value of `size`. Subsequent reads from it will return its default value. - mutating func clearSize() {self._size = nil} - - var thumbnail: Data { - get {return _thumbnail ?? SwiftProtobuf.Internal.emptyData} - set {_thumbnail = newValue} - } - /// Returns true if `thumbnail` has been explicitly set. - var hasThumbnail: Bool {return self._thumbnail != nil} - /// Clears the value of `thumbnail`. Subsequent reads from it will return its default value. - mutating func clearThumbnail() {self._thumbnail = nil} - - var digest: Data { - get {return _digest ?? SwiftProtobuf.Internal.emptyData} - set {_digest = newValue} - } - /// Returns true if `digest` has been explicitly set. - var hasDigest: Bool {return self._digest != nil} - /// Clears the value of `digest`. Subsequent reads from it will return its default value. - mutating func clearDigest() {self._digest = nil} - - var fileName: String { - get {return _fileName ?? String()} - set {_fileName = newValue} - } - /// Returns true if `fileName` has been explicitly set. - var hasFileName: Bool {return self._fileName != nil} - /// Clears the value of `fileName`. Subsequent reads from it will return its default value. - mutating func clearFileName() {self._fileName = nil} - - var flags: UInt32 { - get {return _flags ?? 0} - set {_flags = newValue} - } - /// Returns true if `flags` has been explicitly set. - var hasFlags: Bool {return self._flags != nil} - /// Clears the value of `flags`. Subsequent reads from it will return its default value. - mutating func clearFlags() {self._flags = nil} - - var width: UInt32 { - get {return _width ?? 0} - set {_width = newValue} - } - /// Returns true if `width` has been explicitly set. - var hasWidth: Bool {return self._width != nil} - /// Clears the value of `width`. Subsequent reads from it will return its default value. - mutating func clearWidth() {self._width = nil} - - var height: UInt32 { - get {return _height ?? 0} - set {_height = newValue} - } - /// Returns true if `height` has been explicitly set. - var hasHeight: Bool {return self._height != nil} - /// Clears the value of `height`. Subsequent reads from it will return its default value. - mutating func clearHeight() {self._height = nil} - - var caption: String { - get {return _caption ?? String()} - set {_caption = newValue} - } - /// Returns true if `caption` has been explicitly set. - var hasCaption: Bool {return self._caption != nil} - /// Clears the value of `caption`. Subsequent reads from it will return its default value. - mutating func clearCaption() {self._caption = nil} - - /// Loki - var url: String { - get {return _url ?? String()} - set {_url = newValue} - } - /// Returns true if `url` has been explicitly set. - var hasURL: Bool {return self._url != nil} - /// Clears the value of `url`. Subsequent reads from it will return its default value. - mutating func clearURL() {self._url = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum Flags: SwiftProtobuf.Enum { - typealias RawValue = Int - case voiceMessage // = 1 - - init() { - self = .voiceMessage - } - - init?(rawValue: Int) { - switch rawValue { - case 1: self = .voiceMessage - default: return nil - } - } - - var rawValue: Int { - switch self { - case .voiceMessage: return 1 - } - } - - } - - init() {} - - fileprivate var _id: UInt64? = nil - fileprivate var _contentType: String? = nil - fileprivate var _key: Data? = nil - fileprivate var _size: UInt32? = nil - fileprivate var _thumbnail: Data? = nil - fileprivate var _digest: Data? = nil - fileprivate var _fileName: String? = nil - fileprivate var _flags: UInt32? = nil - fileprivate var _width: UInt32? = nil - fileprivate var _height: UInt32? = nil - fileprivate var _caption: String? = nil - fileprivate var _url: String? = nil -} - -#if swift(>=4.2) - -extension SignalServiceProtos_AttachmentPointer.Flags: CaseIterable { - // Support synthesized by the compiler. -} - -#endif // swift(>=4.2) - -struct SignalServiceProtos_GroupContext { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var id: Data { - get {return _id ?? SwiftProtobuf.Internal.emptyData} - set {_id = newValue} - } - /// Returns true if `id` has been explicitly set. - var hasID: Bool {return self._id != nil} - /// Clears the value of `id`. Subsequent reads from it will return its default value. - mutating func clearID() {self._id = nil} - - /// @required - var type: SignalServiceProtos_GroupContext.TypeEnum { - get {return _type ?? .unknown} - set {_type = newValue} - } - /// Returns true if `type` has been explicitly set. - var hasType: Bool {return self._type != nil} - /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {self._type = nil} - - var name: String { - get {return _name ?? String()} - set {_name = newValue} - } - /// Returns true if `name` has been explicitly set. - var hasName: Bool {return self._name != nil} - /// Clears the value of `name`. Subsequent reads from it will return its default value. - mutating func clearName() {self._name = nil} - - var members: [String] = [] - - var avatar: SignalServiceProtos_AttachmentPointer { - get {return _avatar ?? SignalServiceProtos_AttachmentPointer()} - set {_avatar = newValue} - } - /// Returns true if `avatar` has been explicitly set. - var hasAvatar: Bool {return self._avatar != nil} - /// Clears the value of `avatar`. Subsequent reads from it will return its default value. - mutating func clearAvatar() {self._avatar = nil} - - /// Loki - var admins: [String] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum TypeEnum: SwiftProtobuf.Enum { - typealias RawValue = Int - case unknown // = 0 - case update // = 1 - case deliver // = 2 - case quit // = 3 - case requestInfo // = 4 - - init() { - self = .unknown - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unknown - case 1: self = .update - case 2: self = .deliver - case 3: self = .quit - case 4: self = .requestInfo - default: return nil - } - } - - var rawValue: Int { - switch self { - case .unknown: return 0 - case .update: return 1 - case .deliver: return 2 - case .quit: return 3 - case .requestInfo: return 4 - } - } - - } - - init() {} - - fileprivate var _id: Data? = nil - fileprivate var _type: SignalServiceProtos_GroupContext.TypeEnum? = nil - fileprivate var _name: String? = nil - fileprivate var _avatar: SignalServiceProtos_AttachmentPointer? = nil -} - -#if swift(>=4.2) - -extension SignalServiceProtos_GroupContext.TypeEnum: CaseIterable { - // Support synthesized by the compiler. -} - -#endif // swift(>=4.2) - -struct SignalServiceProtos_ContactDetails { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var number: String { - get {return _number ?? String()} - set {_number = newValue} - } - /// Returns true if `number` has been explicitly set. - var hasNumber: Bool {return self._number != nil} - /// Clears the value of `number`. Subsequent reads from it will return its default value. - mutating func clearNumber() {self._number = nil} - - var name: String { - get {return _name ?? String()} - set {_name = newValue} - } - /// Returns true if `name` has been explicitly set. - var hasName: Bool {return self._name != nil} - /// Clears the value of `name`. Subsequent reads from it will return its default value. - mutating func clearName() {self._name = nil} - - var avatar: SignalServiceProtos_ContactDetails.Avatar { - get {return _avatar ?? SignalServiceProtos_ContactDetails.Avatar()} - set {_avatar = newValue} - } - /// Returns true if `avatar` has been explicitly set. - var hasAvatar: Bool {return self._avatar != nil} - /// Clears the value of `avatar`. Subsequent reads from it will return its default value. - mutating func clearAvatar() {self._avatar = nil} - - var color: String { - get {return _color ?? String()} - set {_color = newValue} - } - /// Returns true if `color` has been explicitly set. - var hasColor: Bool {return self._color != nil} - /// Clears the value of `color`. Subsequent reads from it will return its default value. - mutating func clearColor() {self._color = nil} - - var verified: SignalServiceProtos_Verified { - get {return _verified ?? SignalServiceProtos_Verified()} - set {_verified = newValue} - } - /// Returns true if `verified` has been explicitly set. - var hasVerified: Bool {return self._verified != nil} - /// Clears the value of `verified`. Subsequent reads from it will return its default value. - mutating func clearVerified() {self._verified = nil} - - var profileKey: Data { - get {return _profileKey ?? SwiftProtobuf.Internal.emptyData} - set {_profileKey = newValue} - } - /// Returns true if `profileKey` has been explicitly set. - var hasProfileKey: Bool {return self._profileKey != nil} - /// Clears the value of `profileKey`. Subsequent reads from it will return its default value. - mutating func clearProfileKey() {self._profileKey = nil} - - var blocked: Bool { - get {return _blocked ?? false} - set {_blocked = newValue} - } - /// Returns true if `blocked` has been explicitly set. - var hasBlocked: Bool {return self._blocked != nil} - /// Clears the value of `blocked`. Subsequent reads from it will return its default value. - mutating func clearBlocked() {self._blocked = nil} - - var expireTimer: UInt32 { - get {return _expireTimer ?? 0} - set {_expireTimer = newValue} - } - /// Returns true if `expireTimer` has been explicitly set. - var hasExpireTimer: Bool {return self._expireTimer != nil} - /// Clears the value of `expireTimer`. Subsequent reads from it will return its default value. - mutating func clearExpireTimer() {self._expireTimer = nil} - - /// Loki - var nickname: String { - get {return _nickname ?? String()} - set {_nickname = newValue} - } - /// Returns true if `nickname` has been explicitly set. - var hasNickname: Bool {return self._nickname != nil} - /// Clears the value of `nickname`. Subsequent reads from it will return its default value. - mutating func clearNickname() {self._nickname = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct Avatar { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var contentType: String { - get {return _contentType ?? String()} - set {_contentType = newValue} - } - /// Returns true if `contentType` has been explicitly set. - var hasContentType: Bool {return self._contentType != nil} - /// Clears the value of `contentType`. Subsequent reads from it will return its default value. - mutating func clearContentType() {self._contentType = nil} - - var length: UInt32 { - get {return _length ?? 0} - set {_length = newValue} - } - /// Returns true if `length` has been explicitly set. - var hasLength: Bool {return self._length != nil} - /// Clears the value of `length`. Subsequent reads from it will return its default value. - mutating func clearLength() {self._length = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _contentType: String? = nil - fileprivate var _length: UInt32? = nil - } - - init() {} - - fileprivate var _number: String? = nil - fileprivate var _name: String? = nil - fileprivate var _avatar: SignalServiceProtos_ContactDetails.Avatar? = nil - fileprivate var _color: String? = nil - fileprivate var _verified: SignalServiceProtos_Verified? = nil - fileprivate var _profileKey: Data? = nil - fileprivate var _blocked: Bool? = nil - fileprivate var _expireTimer: UInt32? = nil - fileprivate var _nickname: String? = nil -} - -struct SignalServiceProtos_GroupDetails { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var id: Data { - get {return _id ?? SwiftProtobuf.Internal.emptyData} - set {_id = newValue} - } - /// Returns true if `id` has been explicitly set. - var hasID: Bool {return self._id != nil} - /// Clears the value of `id`. Subsequent reads from it will return its default value. - mutating func clearID() {self._id = nil} - - var name: String { - get {return _name ?? String()} - set {_name = newValue} - } - /// Returns true if `name` has been explicitly set. - var hasName: Bool {return self._name != nil} - /// Clears the value of `name`. Subsequent reads from it will return its default value. - mutating func clearName() {self._name = nil} - - var members: [String] = [] - - var avatar: SignalServiceProtos_GroupDetails.Avatar { - get {return _avatar ?? SignalServiceProtos_GroupDetails.Avatar()} - set {_avatar = newValue} - } - /// Returns true if `avatar` has been explicitly set. - var hasAvatar: Bool {return self._avatar != nil} - /// Clears the value of `avatar`. Subsequent reads from it will return its default value. - mutating func clearAvatar() {self._avatar = nil} - - var active: Bool { - get {return _active ?? true} - set {_active = newValue} - } - /// Returns true if `active` has been explicitly set. - var hasActive: Bool {return self._active != nil} - /// Clears the value of `active`. Subsequent reads from it will return its default value. - mutating func clearActive() {self._active = nil} - - var expireTimer: UInt32 { - get {return _expireTimer ?? 0} - set {_expireTimer = newValue} - } - /// Returns true if `expireTimer` has been explicitly set. - var hasExpireTimer: Bool {return self._expireTimer != nil} - /// Clears the value of `expireTimer`. Subsequent reads from it will return its default value. - mutating func clearExpireTimer() {self._expireTimer = nil} - - var color: String { - get {return _color ?? String()} - set {_color = newValue} - } - /// Returns true if `color` has been explicitly set. - var hasColor: Bool {return self._color != nil} - /// Clears the value of `color`. Subsequent reads from it will return its default value. - mutating func clearColor() {self._color = nil} - - var blocked: Bool { - get {return _blocked ?? false} - set {_blocked = newValue} - } - /// Returns true if `blocked` has been explicitly set. - var hasBlocked: Bool {return self._blocked != nil} - /// Clears the value of `blocked`. Subsequent reads from it will return its default value. - mutating func clearBlocked() {self._blocked = nil} - - /// Loki - var admins: [String] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct Avatar { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var contentType: String { - get {return _contentType ?? String()} - set {_contentType = newValue} - } - /// Returns true if `contentType` has been explicitly set. - var hasContentType: Bool {return self._contentType != nil} - /// Clears the value of `contentType`. Subsequent reads from it will return its default value. - mutating func clearContentType() {self._contentType = nil} - - var length: UInt32 { - get {return _length ?? 0} - set {_length = newValue} - } - /// Returns true if `length` has been explicitly set. - var hasLength: Bool {return self._length != nil} - /// Clears the value of `length`. Subsequent reads from it will return its default value. - mutating func clearLength() {self._length = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _contentType: String? = nil - fileprivate var _length: UInt32? = nil - } - - init() {} - - fileprivate var _id: Data? = nil - fileprivate var _name: String? = nil - fileprivate var _avatar: SignalServiceProtos_GroupDetails.Avatar? = nil - fileprivate var _active: Bool? = nil - fileprivate var _expireTimer: UInt32? = nil - fileprivate var _color: String? = nil - fileprivate var _blocked: Bool? = nil -} - -/// Internal - DO NOT SEND -struct SignalServiceProtos_PublicChatInfo { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var serverID: UInt64 { - get {return _serverID ?? 0} - set {_serverID = newValue} - } - /// Returns true if `serverID` has been explicitly set. - var hasServerID: Bool {return self._serverID != nil} - /// Clears the value of `serverID`. Subsequent reads from it will return its default value. - mutating func clearServerID() {self._serverID = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _serverID: UInt64? = nil -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "SignalServiceProtos" - -extension SignalServiceProtos_Envelope: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Envelope" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "type"), - 2: .same(proto: "source"), - 7: .same(proto: "sourceDevice"), - 3: .same(proto: "relay"), - 5: .same(proto: "timestamp"), - 6: .same(proto: "legacyMessage"), - 8: .same(proto: "content"), - 9: .same(proto: "serverGuid"), - 10: .same(proto: "serverTimestamp"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularEnumField(value: &self._type) - case 2: try decoder.decodeSingularStringField(value: &self._source) - case 3: try decoder.decodeSingularStringField(value: &self._relay) - case 5: try decoder.decodeSingularUInt64Field(value: &self._timestamp) - case 6: try decoder.decodeSingularBytesField(value: &self._legacyMessage) - case 7: try decoder.decodeSingularUInt32Field(value: &self._sourceDevice) - case 8: try decoder.decodeSingularBytesField(value: &self._content) - case 9: try decoder.decodeSingularStringField(value: &self._serverGuid) - case 10: try decoder.decodeSingularUInt64Field(value: &self._serverTimestamp) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 1) - } - if let v = self._source { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - if let v = self._relay { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) - } - if let v = self._timestamp { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 5) - } - if let v = self._legacyMessage { - try visitor.visitSingularBytesField(value: v, fieldNumber: 6) - } - if let v = self._sourceDevice { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 7) - } - if let v = self._content { - try visitor.visitSingularBytesField(value: v, fieldNumber: 8) - } - if let v = self._serverGuid { - try visitor.visitSingularStringField(value: v, fieldNumber: 9) - } - if let v = self._serverTimestamp { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 10) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_Envelope, rhs: SignalServiceProtos_Envelope) -> Bool { - if lhs._type != rhs._type {return false} - if lhs._source != rhs._source {return false} - if lhs._sourceDevice != rhs._sourceDevice {return false} - if lhs._relay != rhs._relay {return false} - if lhs._timestamp != rhs._timestamp {return false} - if lhs._legacyMessage != rhs._legacyMessage {return false} - if lhs._content != rhs._content {return false} - if lhs._serverGuid != rhs._serverGuid {return false} - if lhs._serverTimestamp != rhs._serverTimestamp {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_Envelope.TypeEnum: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNKNOWN"), - 1: .same(proto: "CIPHERTEXT"), - 2: .same(proto: "KEY_EXCHANGE"), - 3: .same(proto: "PREKEY_BUNDLE"), - 5: .same(proto: "RECEIPT"), - 6: .same(proto: "UNIDENTIFIED_SENDER"), - 7: .same(proto: "CLOSED_GROUP_CIPHERTEXT"), - 101: .same(proto: "FALLBACK_MESSAGE"), - ] -} - -extension SignalServiceProtos_TypingMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".TypingMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "timestamp"), - 2: .same(proto: "action"), - 3: .same(proto: "groupId"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularUInt64Field(value: &self._timestamp) - case 2: try decoder.decodeSingularEnumField(value: &self._action) - case 3: try decoder.decodeSingularBytesField(value: &self._groupID) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._timestamp { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1) - } - if let v = self._action { - try visitor.visitSingularEnumField(value: v, fieldNumber: 2) - } - if let v = self._groupID { - try visitor.visitSingularBytesField(value: v, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_TypingMessage, rhs: SignalServiceProtos_TypingMessage) -> Bool { - if lhs._timestamp != rhs._timestamp {return false} - if lhs._action != rhs._action {return false} - if lhs._groupID != rhs._groupID {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_TypingMessage.Action: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "STARTED"), - 1: .same(proto: "STOPPED"), - ] -} - -extension SignalServiceProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Content" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "dataMessage"), - 2: .same(proto: "syncMessage"), - 3: .same(proto: "callMessage"), - 4: .same(proto: "nullMessage"), - 5: .same(proto: "receiptMessage"), - 6: .same(proto: "typingMessage"), - 101: .same(proto: "prekeyBundleMessage"), - 103: .same(proto: "lokiDeviceLinkMessage"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularMessageField(value: &self._dataMessage) - case 2: try decoder.decodeSingularMessageField(value: &self._syncMessage) - case 3: try decoder.decodeSingularMessageField(value: &self._callMessage) - case 4: try decoder.decodeSingularMessageField(value: &self._nullMessage) - case 5: try decoder.decodeSingularMessageField(value: &self._receiptMessage) - case 6: try decoder.decodeSingularMessageField(value: &self._typingMessage) - case 101: try decoder.decodeSingularMessageField(value: &self._prekeyBundleMessage) - case 103: try decoder.decodeSingularMessageField(value: &self._lokiDeviceLinkMessage) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._dataMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } - if let v = self._syncMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } - if let v = self._callMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } - if let v = self._nullMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } - if let v = self._receiptMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } - if let v = self._typingMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } - if let v = self._prekeyBundleMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 101) - } - if let v = self._lokiDeviceLinkMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 103) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_Content, rhs: SignalServiceProtos_Content) -> Bool { - if lhs._dataMessage != rhs._dataMessage {return false} - if lhs._syncMessage != rhs._syncMessage {return false} - if lhs._callMessage != rhs._callMessage {return false} - if lhs._nullMessage != rhs._nullMessage {return false} - if lhs._receiptMessage != rhs._receiptMessage {return false} - if lhs._typingMessage != rhs._typingMessage {return false} - if lhs._prekeyBundleMessage != rhs._prekeyBundleMessage {return false} - if lhs._lokiDeviceLinkMessage != rhs._lokiDeviceLinkMessage {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_PrekeyBundleMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".PrekeyBundleMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "identityKey"), - 2: .same(proto: "deviceID"), - 3: .same(proto: "prekeyID"), - 4: .same(proto: "signedKeyID"), - 5: .same(proto: "prekey"), - 6: .same(proto: "signedKey"), - 7: .same(proto: "signature"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self._identityKey) - case 2: try decoder.decodeSingularUInt32Field(value: &self._deviceID) - case 3: try decoder.decodeSingularUInt32Field(value: &self._prekeyID) - case 4: try decoder.decodeSingularUInt32Field(value: &self._signedKeyID) - case 5: try decoder.decodeSingularBytesField(value: &self._prekey) - case 6: try decoder.decodeSingularBytesField(value: &self._signedKey) - case 7: try decoder.decodeSingularBytesField(value: &self._signature) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._identityKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 1) - } - if let v = self._deviceID { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) - } - if let v = self._prekeyID { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) - } - if let v = self._signedKeyID { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) - } - if let v = self._prekey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 5) - } - if let v = self._signedKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 6) - } - if let v = self._signature { - try visitor.visitSingularBytesField(value: v, fieldNumber: 7) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_PrekeyBundleMessage, rhs: SignalServiceProtos_PrekeyBundleMessage) -> Bool { - if lhs._identityKey != rhs._identityKey {return false} - if lhs._deviceID != rhs._deviceID {return false} - if lhs._prekeyID != rhs._prekeyID {return false} - if lhs._signedKeyID != rhs._signedKeyID {return false} - if lhs._prekey != rhs._prekey {return false} - if lhs._signedKey != rhs._signedKey {return false} - if lhs._signature != rhs._signature {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_LokiDeviceLinkMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".LokiDeviceLinkMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "masterPublicKey"), - 2: .same(proto: "slavePublicKey"), - 3: .same(proto: "slaveSignature"), - 4: .same(proto: "masterSignature"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._masterPublicKey) - case 2: try decoder.decodeSingularStringField(value: &self._slavePublicKey) - case 3: try decoder.decodeSingularBytesField(value: &self._slaveSignature) - case 4: try decoder.decodeSingularBytesField(value: &self._masterSignature) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._masterPublicKey { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._slavePublicKey { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - if let v = self._slaveSignature { - try visitor.visitSingularBytesField(value: v, fieldNumber: 3) - } - if let v = self._masterSignature { - try visitor.visitSingularBytesField(value: v, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_LokiDeviceLinkMessage, rhs: SignalServiceProtos_LokiDeviceLinkMessage) -> Bool { - if lhs._masterPublicKey != rhs._masterPublicKey {return false} - if lhs._slavePublicKey != rhs._slavePublicKey {return false} - if lhs._slaveSignature != rhs._slaveSignature {return false} - if lhs._masterSignature != rhs._masterSignature {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".CallMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "offer"), - 2: .same(proto: "answer"), - 3: .same(proto: "iceUpdate"), - 4: .same(proto: "hangup"), - 5: .same(proto: "busy"), - 6: .same(proto: "profileKey"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularMessageField(value: &self._offer) - case 2: try decoder.decodeSingularMessageField(value: &self._answer) - case 3: try decoder.decodeRepeatedMessageField(value: &self.iceUpdate) - case 4: try decoder.decodeSingularMessageField(value: &self._hangup) - case 5: try decoder.decodeSingularMessageField(value: &self._busy) - case 6: try decoder.decodeSingularBytesField(value: &self._profileKey) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._offer { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } - if let v = self._answer { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } - if !self.iceUpdate.isEmpty { - try visitor.visitRepeatedMessageField(value: self.iceUpdate, fieldNumber: 3) - } - if let v = self._hangup { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } - if let v = self._busy { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } - if let v = self._profileKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 6) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_CallMessage, rhs: SignalServiceProtos_CallMessage) -> Bool { - if lhs._offer != rhs._offer {return false} - if lhs._answer != rhs._answer {return false} - if lhs.iceUpdate != rhs.iceUpdate {return false} - if lhs._hangup != rhs._hangup {return false} - if lhs._busy != rhs._busy {return false} - if lhs._profileKey != rhs._profileKey {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_CallMessage.Offer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_CallMessage.protoMessageName + ".Offer" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "sessionDescription"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularUInt64Field(value: &self._id) - case 2: try decoder.decodeSingularStringField(value: &self._sessionDescription) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._id { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1) - } - if let v = self._sessionDescription { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_CallMessage.Offer, rhs: SignalServiceProtos_CallMessage.Offer) -> Bool { - if lhs._id != rhs._id {return false} - if lhs._sessionDescription != rhs._sessionDescription {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_CallMessage.Answer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_CallMessage.protoMessageName + ".Answer" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "sessionDescription"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularUInt64Field(value: &self._id) - case 2: try decoder.decodeSingularStringField(value: &self._sessionDescription) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._id { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1) - } - if let v = self._sessionDescription { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_CallMessage.Answer, rhs: SignalServiceProtos_CallMessage.Answer) -> Bool { - if lhs._id != rhs._id {return false} - if lhs._sessionDescription != rhs._sessionDescription {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_CallMessage.IceUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_CallMessage.protoMessageName + ".IceUpdate" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "sdpMid"), - 3: .same(proto: "sdpMLineIndex"), - 4: .same(proto: "sdp"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularUInt64Field(value: &self._id) - case 2: try decoder.decodeSingularStringField(value: &self._sdpMid) - case 3: try decoder.decodeSingularUInt32Field(value: &self._sdpMlineIndex) - case 4: try decoder.decodeSingularStringField(value: &self._sdp) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._id { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1) - } - if let v = self._sdpMid { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - if let v = self._sdpMlineIndex { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) - } - if let v = self._sdp { - try visitor.visitSingularStringField(value: v, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_CallMessage.IceUpdate, rhs: SignalServiceProtos_CallMessage.IceUpdate) -> Bool { - if lhs._id != rhs._id {return false} - if lhs._sdpMid != rhs._sdpMid {return false} - if lhs._sdpMlineIndex != rhs._sdpMlineIndex {return false} - if lhs._sdp != rhs._sdp {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_CallMessage.Busy: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_CallMessage.protoMessageName + ".Busy" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularUInt64Field(value: &self._id) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._id { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_CallMessage.Busy, rhs: SignalServiceProtos_CallMessage.Busy) -> Bool { - if lhs._id != rhs._id {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_CallMessage.Hangup: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_CallMessage.protoMessageName + ".Hangup" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularUInt64Field(value: &self._id) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._id { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_CallMessage.Hangup, rhs: SignalServiceProtos_CallMessage.Hangup) -> Bool { - if lhs._id != rhs._id {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_ClosedGroupCiphertextMessageWrapper: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ClosedGroupCiphertextMessageWrapper" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "ciphertext"), - 2: .same(proto: "ephemeralPublicKey"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self._ciphertext) - case 2: try decoder.decodeSingularBytesField(value: &self._ephemeralPublicKey) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._ciphertext { - try visitor.visitSingularBytesField(value: v, fieldNumber: 1) - } - if let v = self._ephemeralPublicKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_ClosedGroupCiphertextMessageWrapper, rhs: SignalServiceProtos_ClosedGroupCiphertextMessageWrapper) -> Bool { - if lhs._ciphertext != rhs._ciphertext {return false} - if lhs._ephemeralPublicKey != rhs._ephemeralPublicKey {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".DataMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "body"), - 2: .same(proto: "attachments"), - 3: .same(proto: "group"), - 4: .same(proto: "flags"), - 5: .same(proto: "expireTimer"), - 6: .same(proto: "profileKey"), - 7: .same(proto: "timestamp"), - 8: .same(proto: "quote"), - 9: .same(proto: "contact"), - 10: .same(proto: "preview"), - 101: .same(proto: "profile"), - 103: .same(proto: "closedGroupUpdate"), - 999: .same(proto: "publicChatInfo"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._body) - case 2: try decoder.decodeRepeatedMessageField(value: &self.attachments) - case 3: try decoder.decodeSingularMessageField(value: &self._group) - case 4: try decoder.decodeSingularUInt32Field(value: &self._flags) - case 5: try decoder.decodeSingularUInt32Field(value: &self._expireTimer) - case 6: try decoder.decodeSingularBytesField(value: &self._profileKey) - case 7: try decoder.decodeSingularUInt64Field(value: &self._timestamp) - case 8: try decoder.decodeSingularMessageField(value: &self._quote) - case 9: try decoder.decodeRepeatedMessageField(value: &self.contact) - case 10: try decoder.decodeRepeatedMessageField(value: &self.preview) - case 101: try decoder.decodeSingularMessageField(value: &self._profile) - case 103: try decoder.decodeSingularMessageField(value: &self._closedGroupUpdate) - case 999: try decoder.decodeSingularMessageField(value: &self._publicChatInfo) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._body { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if !self.attachments.isEmpty { - try visitor.visitRepeatedMessageField(value: self.attachments, fieldNumber: 2) - } - if let v = self._group { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } - if let v = self._flags { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) - } - if let v = self._expireTimer { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 5) - } - if let v = self._profileKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 6) - } - if let v = self._timestamp { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 7) - } - if let v = self._quote { - try visitor.visitSingularMessageField(value: v, fieldNumber: 8) - } - if !self.contact.isEmpty { - try visitor.visitRepeatedMessageField(value: self.contact, fieldNumber: 9) - } - if !self.preview.isEmpty { - try visitor.visitRepeatedMessageField(value: self.preview, fieldNumber: 10) - } - if let v = self._profile { - try visitor.visitSingularMessageField(value: v, fieldNumber: 101) - } - if let v = self._closedGroupUpdate { - try visitor.visitSingularMessageField(value: v, fieldNumber: 103) - } - if let v = self._publicChatInfo { - try visitor.visitSingularMessageField(value: v, fieldNumber: 999) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage, rhs: SignalServiceProtos_DataMessage) -> Bool { - if lhs._body != rhs._body {return false} - if lhs.attachments != rhs.attachments {return false} - if lhs._group != rhs._group {return false} - if lhs._flags != rhs._flags {return false} - if lhs._expireTimer != rhs._expireTimer {return false} - if lhs._profileKey != rhs._profileKey {return false} - if lhs._timestamp != rhs._timestamp {return false} - if lhs._quote != rhs._quote {return false} - if lhs.contact != rhs.contact {return false} - if lhs.preview != rhs.preview {return false} - if lhs._profile != rhs._profile {return false} - if lhs._closedGroupUpdate != rhs._closedGroupUpdate {return false} - if lhs._publicChatInfo != rhs._publicChatInfo {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage.Flags: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "END_SESSION"), - 2: .same(proto: "EXPIRATION_TIMER_UPDATE"), - 4: .same(proto: "PROFILE_KEY_UPDATE"), - 128: .same(proto: "UNLINK_DEVICE"), - ] -} - -extension SignalServiceProtos_DataMessage.Quote: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_DataMessage.protoMessageName + ".Quote" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "author"), - 3: .same(proto: "text"), - 4: .same(proto: "attachments"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularUInt64Field(value: &self._id) - case 2: try decoder.decodeSingularStringField(value: &self._author) - case 3: try decoder.decodeSingularStringField(value: &self._text) - case 4: try decoder.decodeRepeatedMessageField(value: &self.attachments) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._id { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1) - } - if let v = self._author { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - if let v = self._text { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) - } - if !self.attachments.isEmpty { - try visitor.visitRepeatedMessageField(value: self.attachments, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage.Quote, rhs: SignalServiceProtos_DataMessage.Quote) -> Bool { - if lhs._id != rhs._id {return false} - if lhs._author != rhs._author {return false} - if lhs._text != rhs._text {return false} - if lhs.attachments != rhs.attachments {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage.Quote.QuotedAttachment: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_DataMessage.Quote.protoMessageName + ".QuotedAttachment" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "contentType"), - 2: .same(proto: "fileName"), - 3: .same(proto: "thumbnail"), - 4: .same(proto: "flags"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._contentType) - case 2: try decoder.decodeSingularStringField(value: &self._fileName) - case 3: try decoder.decodeSingularMessageField(value: &self._thumbnail) - case 4: try decoder.decodeSingularUInt32Field(value: &self._flags) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._contentType { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._fileName { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - if let v = self._thumbnail { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } - if let v = self._flags { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage.Quote.QuotedAttachment, rhs: SignalServiceProtos_DataMessage.Quote.QuotedAttachment) -> Bool { - if lhs._contentType != rhs._contentType {return false} - if lhs._fileName != rhs._fileName {return false} - if lhs._thumbnail != rhs._thumbnail {return false} - if lhs._flags != rhs._flags {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage.Quote.QuotedAttachment.Flags: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "VOICE_MESSAGE"), - ] -} - -extension SignalServiceProtos_DataMessage.Contact: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_DataMessage.protoMessageName + ".Contact" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "name"), - 3: .same(proto: "number"), - 4: .same(proto: "email"), - 5: .same(proto: "address"), - 6: .same(proto: "avatar"), - 7: .same(proto: "organization"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularMessageField(value: &self._name) - case 3: try decoder.decodeRepeatedMessageField(value: &self.number) - case 4: try decoder.decodeRepeatedMessageField(value: &self.email) - case 5: try decoder.decodeRepeatedMessageField(value: &self.address) - case 6: try decoder.decodeSingularMessageField(value: &self._avatar) - case 7: try decoder.decodeSingularStringField(value: &self._organization) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._name { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } - if !self.number.isEmpty { - try visitor.visitRepeatedMessageField(value: self.number, fieldNumber: 3) - } - if !self.email.isEmpty { - try visitor.visitRepeatedMessageField(value: self.email, fieldNumber: 4) - } - if !self.address.isEmpty { - try visitor.visitRepeatedMessageField(value: self.address, fieldNumber: 5) - } - if let v = self._avatar { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } - if let v = self._organization { - try visitor.visitSingularStringField(value: v, fieldNumber: 7) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage.Contact, rhs: SignalServiceProtos_DataMessage.Contact) -> Bool { - if lhs._name != rhs._name {return false} - if lhs.number != rhs.number {return false} - if lhs.email != rhs.email {return false} - if lhs.address != rhs.address {return false} - if lhs._avatar != rhs._avatar {return false} - if lhs._organization != rhs._organization {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage.Contact.Name: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_DataMessage.Contact.protoMessageName + ".Name" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "givenName"), - 2: .same(proto: "familyName"), - 3: .same(proto: "prefix"), - 4: .same(proto: "suffix"), - 5: .same(proto: "middleName"), - 6: .same(proto: "displayName"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._givenName) - case 2: try decoder.decodeSingularStringField(value: &self._familyName) - case 3: try decoder.decodeSingularStringField(value: &self._prefix) - case 4: try decoder.decodeSingularStringField(value: &self._suffix) - case 5: try decoder.decodeSingularStringField(value: &self._middleName) - case 6: try decoder.decodeSingularStringField(value: &self._displayName) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._givenName { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._familyName { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - if let v = self._prefix { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) - } - if let v = self._suffix { - try visitor.visitSingularStringField(value: v, fieldNumber: 4) - } - if let v = self._middleName { - try visitor.visitSingularStringField(value: v, fieldNumber: 5) - } - if let v = self._displayName { - try visitor.visitSingularStringField(value: v, fieldNumber: 6) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage.Contact.Name, rhs: SignalServiceProtos_DataMessage.Contact.Name) -> Bool { - if lhs._givenName != rhs._givenName {return false} - if lhs._familyName != rhs._familyName {return false} - if lhs._prefix != rhs._prefix {return false} - if lhs._suffix != rhs._suffix {return false} - if lhs._middleName != rhs._middleName {return false} - if lhs._displayName != rhs._displayName {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage.Contact.Phone: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_DataMessage.Contact.protoMessageName + ".Phone" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "value"), - 2: .same(proto: "type"), - 3: .same(proto: "label"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._value) - case 2: try decoder.decodeSingularEnumField(value: &self._type) - case 3: try decoder.decodeSingularStringField(value: &self._label) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._value { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 2) - } - if let v = self._label { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage.Contact.Phone, rhs: SignalServiceProtos_DataMessage.Contact.Phone) -> Bool { - if lhs._value != rhs._value {return false} - if lhs._type != rhs._type {return false} - if lhs._label != rhs._label {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage.Contact.Phone.TypeEnum: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "HOME"), - 2: .same(proto: "MOBILE"), - 3: .same(proto: "WORK"), - 4: .same(proto: "CUSTOM"), - ] -} - -extension SignalServiceProtos_DataMessage.Contact.Email: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_DataMessage.Contact.protoMessageName + ".Email" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "value"), - 2: .same(proto: "type"), - 3: .same(proto: "label"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._value) - case 2: try decoder.decodeSingularEnumField(value: &self._type) - case 3: try decoder.decodeSingularStringField(value: &self._label) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._value { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 2) - } - if let v = self._label { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage.Contact.Email, rhs: SignalServiceProtos_DataMessage.Contact.Email) -> Bool { - if lhs._value != rhs._value {return false} - if lhs._type != rhs._type {return false} - if lhs._label != rhs._label {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage.Contact.Email.TypeEnum: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "HOME"), - 2: .same(proto: "MOBILE"), - 3: .same(proto: "WORK"), - 4: .same(proto: "CUSTOM"), - ] -} - -extension SignalServiceProtos_DataMessage.Contact.PostalAddress: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_DataMessage.Contact.protoMessageName + ".PostalAddress" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "type"), - 2: .same(proto: "label"), - 3: .same(proto: "street"), - 4: .same(proto: "pobox"), - 5: .same(proto: "neighborhood"), - 6: .same(proto: "city"), - 7: .same(proto: "region"), - 8: .same(proto: "postcode"), - 9: .same(proto: "country"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularEnumField(value: &self._type) - case 2: try decoder.decodeSingularStringField(value: &self._label) - case 3: try decoder.decodeSingularStringField(value: &self._street) - case 4: try decoder.decodeSingularStringField(value: &self._pobox) - case 5: try decoder.decodeSingularStringField(value: &self._neighborhood) - case 6: try decoder.decodeSingularStringField(value: &self._city) - case 7: try decoder.decodeSingularStringField(value: &self._region) - case 8: try decoder.decodeSingularStringField(value: &self._postcode) - case 9: try decoder.decodeSingularStringField(value: &self._country) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 1) - } - if let v = self._label { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - if let v = self._street { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) - } - if let v = self._pobox { - try visitor.visitSingularStringField(value: v, fieldNumber: 4) - } - if let v = self._neighborhood { - try visitor.visitSingularStringField(value: v, fieldNumber: 5) - } - if let v = self._city { - try visitor.visitSingularStringField(value: v, fieldNumber: 6) - } - if let v = self._region { - try visitor.visitSingularStringField(value: v, fieldNumber: 7) - } - if let v = self._postcode { - try visitor.visitSingularStringField(value: v, fieldNumber: 8) - } - if let v = self._country { - try visitor.visitSingularStringField(value: v, fieldNumber: 9) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage.Contact.PostalAddress, rhs: SignalServiceProtos_DataMessage.Contact.PostalAddress) -> Bool { - if lhs._type != rhs._type {return false} - if lhs._label != rhs._label {return false} - if lhs._street != rhs._street {return false} - if lhs._pobox != rhs._pobox {return false} - if lhs._neighborhood != rhs._neighborhood {return false} - if lhs._city != rhs._city {return false} - if lhs._region != rhs._region {return false} - if lhs._postcode != rhs._postcode {return false} - if lhs._country != rhs._country {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage.Contact.PostalAddress.TypeEnum: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "HOME"), - 2: .same(proto: "WORK"), - 3: .same(proto: "CUSTOM"), - ] -} - -extension SignalServiceProtos_DataMessage.Contact.Avatar: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_DataMessage.Contact.protoMessageName + ".Avatar" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "avatar"), - 2: .same(proto: "isProfile"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularMessageField(value: &self._avatar) - case 2: try decoder.decodeSingularBoolField(value: &self._isProfile) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._avatar { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } - if let v = self._isProfile { - try visitor.visitSingularBoolField(value: v, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage.Contact.Avatar, rhs: SignalServiceProtos_DataMessage.Contact.Avatar) -> Bool { - if lhs._avatar != rhs._avatar {return false} - if lhs._isProfile != rhs._isProfile {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage.Preview: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_DataMessage.protoMessageName + ".Preview" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "url"), - 2: .same(proto: "title"), - 3: .same(proto: "image"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._url) - case 2: try decoder.decodeSingularStringField(value: &self._title) - case 3: try decoder.decodeSingularMessageField(value: &self._image) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._url { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._title { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - if let v = self._image { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage.Preview, rhs: SignalServiceProtos_DataMessage.Preview) -> Bool { - if lhs._url != rhs._url {return false} - if lhs._title != rhs._title {return false} - if lhs._image != rhs._image {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage.LokiProfile: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_DataMessage.protoMessageName + ".LokiProfile" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "displayName"), - 2: .same(proto: "profilePicture"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._displayName) - case 2: try decoder.decodeSingularStringField(value: &self._profilePicture) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._displayName { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._profilePicture { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage.LokiProfile, rhs: SignalServiceProtos_DataMessage.LokiProfile) -> Bool { - if lhs._displayName != rhs._displayName {return false} - if lhs._profilePicture != rhs._profilePicture {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage.ClosedGroupUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_DataMessage.protoMessageName + ".ClosedGroupUpdate" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "name"), - 2: .same(proto: "groupPublicKey"), - 3: .same(proto: "groupPrivateKey"), - 4: .same(proto: "senderKeys"), - 5: .same(proto: "members"), - 6: .same(proto: "admins"), - 7: .same(proto: "type"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._name) - case 2: try decoder.decodeSingularBytesField(value: &self._groupPublicKey) - case 3: try decoder.decodeSingularBytesField(value: &self._groupPrivateKey) - case 4: try decoder.decodeRepeatedMessageField(value: &self.senderKeys) - case 5: try decoder.decodeRepeatedBytesField(value: &self.members) - case 6: try decoder.decodeRepeatedBytesField(value: &self.admins) - case 7: try decoder.decodeSingularEnumField(value: &self._type) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._name { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._groupPublicKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 2) - } - if let v = self._groupPrivateKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 3) - } - if !self.senderKeys.isEmpty { - try visitor.visitRepeatedMessageField(value: self.senderKeys, fieldNumber: 4) - } - if !self.members.isEmpty { - try visitor.visitRepeatedBytesField(value: self.members, fieldNumber: 5) - } - if !self.admins.isEmpty { - try visitor.visitRepeatedBytesField(value: self.admins, fieldNumber: 6) - } - if let v = self._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 7) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage.ClosedGroupUpdate, rhs: SignalServiceProtos_DataMessage.ClosedGroupUpdate) -> Bool { - if lhs._name != rhs._name {return false} - if lhs._groupPublicKey != rhs._groupPublicKey {return false} - if lhs._groupPrivateKey != rhs._groupPrivateKey {return false} - if lhs.senderKeys != rhs.senderKeys {return false} - if lhs.members != rhs.members {return false} - if lhs.admins != rhs.admins {return false} - if lhs._type != rhs._type {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_DataMessage.ClosedGroupUpdate.TypeEnum: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "NEW"), - 1: .same(proto: "INFO"), - 2: .same(proto: "SENDER_KEY_REQUEST"), - 3: .same(proto: "SENDER_KEY"), - ] -} - -extension SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_DataMessage.ClosedGroupUpdate.protoMessageName + ".SenderKey" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "chainKey"), - 2: .same(proto: "keyIndex"), - 3: .same(proto: "publicKey"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self._chainKey) - case 2: try decoder.decodeSingularUInt32Field(value: &self._keyIndex) - case 3: try decoder.decodeSingularBytesField(value: &self._publicKey) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._chainKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 1) - } - if let v = self._keyIndex { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) - } - if let v = self._publicKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey, rhs: SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey) -> Bool { - if lhs._chainKey != rhs._chainKey {return false} - if lhs._keyIndex != rhs._keyIndex {return false} - if lhs._publicKey != rhs._publicKey {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_NullMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".NullMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "padding"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self._padding) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._padding { - try visitor.visitSingularBytesField(value: v, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_NullMessage, rhs: SignalServiceProtos_NullMessage) -> Bool { - if lhs._padding != rhs._padding {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_ReceiptMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ReceiptMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "type"), - 2: .same(proto: "timestamp"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularEnumField(value: &self._type) - case 2: try decoder.decodeRepeatedUInt64Field(value: &self.timestamp) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 1) - } - if !self.timestamp.isEmpty { - try visitor.visitRepeatedUInt64Field(value: self.timestamp, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_ReceiptMessage, rhs: SignalServiceProtos_ReceiptMessage) -> Bool { - if lhs._type != rhs._type {return false} - if lhs.timestamp != rhs.timestamp {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_ReceiptMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "DELIVERY"), - 1: .same(proto: "READ"), - ] -} - -extension SignalServiceProtos_Verified: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Verified" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "destination"), - 2: .same(proto: "identityKey"), - 3: .same(proto: "state"), - 4: .same(proto: "nullMessage"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._destination) - case 2: try decoder.decodeSingularBytesField(value: &self._identityKey) - case 3: try decoder.decodeSingularEnumField(value: &self._state) - case 4: try decoder.decodeSingularBytesField(value: &self._nullMessage) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._destination { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._identityKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 2) - } - if let v = self._state { - try visitor.visitSingularEnumField(value: v, fieldNumber: 3) - } - if let v = self._nullMessage { - try visitor.visitSingularBytesField(value: v, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_Verified, rhs: SignalServiceProtos_Verified) -> Bool { - if lhs._destination != rhs._destination {return false} - if lhs._identityKey != rhs._identityKey {return false} - if lhs._state != rhs._state {return false} - if lhs._nullMessage != rhs._nullMessage {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_Verified.State: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "DEFAULT"), - 1: .same(proto: "VERIFIED"), - 2: .same(proto: "UNVERIFIED"), - ] -} - -extension SignalServiceProtos_SyncMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".SyncMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "sent"), - 2: .same(proto: "contacts"), - 3: .same(proto: "groups"), - 4: .same(proto: "request"), - 5: .same(proto: "read"), - 6: .same(proto: "blocked"), - 7: .same(proto: "verified"), - 9: .same(proto: "configuration"), - 8: .same(proto: "padding"), - 100: .same(proto: "openGroups"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularMessageField(value: &self._sent) - case 2: try decoder.decodeSingularMessageField(value: &self._contacts) - case 3: try decoder.decodeSingularMessageField(value: &self._groups) - case 4: try decoder.decodeSingularMessageField(value: &self._request) - case 5: try decoder.decodeRepeatedMessageField(value: &self.read) - case 6: try decoder.decodeSingularMessageField(value: &self._blocked) - case 7: try decoder.decodeSingularMessageField(value: &self._verified) - case 8: try decoder.decodeSingularBytesField(value: &self._padding) - case 9: try decoder.decodeSingularMessageField(value: &self._configuration) - case 100: try decoder.decodeRepeatedMessageField(value: &self.openGroups) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._sent { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } - if let v = self._contacts { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } - if let v = self._groups { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } - if let v = self._request { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } - if !self.read.isEmpty { - try visitor.visitRepeatedMessageField(value: self.read, fieldNumber: 5) - } - if let v = self._blocked { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } - if let v = self._verified { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } - if let v = self._padding { - try visitor.visitSingularBytesField(value: v, fieldNumber: 8) - } - if let v = self._configuration { - try visitor.visitSingularMessageField(value: v, fieldNumber: 9) - } - if !self.openGroups.isEmpty { - try visitor.visitRepeatedMessageField(value: self.openGroups, fieldNumber: 100) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_SyncMessage, rhs: SignalServiceProtos_SyncMessage) -> Bool { - if lhs._sent != rhs._sent {return false} - if lhs._contacts != rhs._contacts {return false} - if lhs._groups != rhs._groups {return false} - if lhs._request != rhs._request {return false} - if lhs.read != rhs.read {return false} - if lhs._blocked != rhs._blocked {return false} - if lhs._verified != rhs._verified {return false} - if lhs._configuration != rhs._configuration {return false} - if lhs._padding != rhs._padding {return false} - if lhs.openGroups != rhs.openGroups {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_SyncMessage.Sent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_SyncMessage.protoMessageName + ".Sent" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "destination"), - 2: .same(proto: "timestamp"), - 3: .same(proto: "message"), - 4: .same(proto: "expirationStartTimestamp"), - 5: .same(proto: "unidentifiedStatus"), - 6: .same(proto: "isRecipientUpdate"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._destination) - case 2: try decoder.decodeSingularUInt64Field(value: &self._timestamp) - case 3: try decoder.decodeSingularMessageField(value: &self._message) - case 4: try decoder.decodeSingularUInt64Field(value: &self._expirationStartTimestamp) - case 5: try decoder.decodeRepeatedMessageField(value: &self.unidentifiedStatus) - case 6: try decoder.decodeSingularBoolField(value: &self._isRecipientUpdate) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._destination { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._timestamp { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 2) - } - if let v = self._message { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } - if let v = self._expirationStartTimestamp { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 4) - } - if !self.unidentifiedStatus.isEmpty { - try visitor.visitRepeatedMessageField(value: self.unidentifiedStatus, fieldNumber: 5) - } - if let v = self._isRecipientUpdate { - try visitor.visitSingularBoolField(value: v, fieldNumber: 6) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_SyncMessage.Sent, rhs: SignalServiceProtos_SyncMessage.Sent) -> Bool { - if lhs._destination != rhs._destination {return false} - if lhs._timestamp != rhs._timestamp {return false} - if lhs._message != rhs._message {return false} - if lhs._expirationStartTimestamp != rhs._expirationStartTimestamp {return false} - if lhs.unidentifiedStatus != rhs.unidentifiedStatus {return false} - if lhs._isRecipientUpdate != rhs._isRecipientUpdate {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_SyncMessage.Sent.UnidentifiedDeliveryStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_SyncMessage.Sent.protoMessageName + ".UnidentifiedDeliveryStatus" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "destination"), - 2: .same(proto: "unidentified"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._destination) - case 2: try decoder.decodeSingularBoolField(value: &self._unidentified) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._destination { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._unidentified { - try visitor.visitSingularBoolField(value: v, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_SyncMessage.Sent.UnidentifiedDeliveryStatus, rhs: SignalServiceProtos_SyncMessage.Sent.UnidentifiedDeliveryStatus) -> Bool { - if lhs._destination != rhs._destination {return false} - if lhs._unidentified != rhs._unidentified {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_SyncMessage.Contacts: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_SyncMessage.protoMessageName + ".Contacts" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "blob"), - 2: .same(proto: "isComplete"), - 101: .same(proto: "data"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularMessageField(value: &self._blob) - case 2: try decoder.decodeSingularBoolField(value: &self._isComplete) - case 101: try decoder.decodeSingularBytesField(value: &self._data) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._blob { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } - if let v = self._isComplete { - try visitor.visitSingularBoolField(value: v, fieldNumber: 2) - } - if let v = self._data { - try visitor.visitSingularBytesField(value: v, fieldNumber: 101) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_SyncMessage.Contacts, rhs: SignalServiceProtos_SyncMessage.Contacts) -> Bool { - if lhs._blob != rhs._blob {return false} - if lhs._isComplete != rhs._isComplete {return false} - if lhs._data != rhs._data {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_SyncMessage.Groups: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_SyncMessage.protoMessageName + ".Groups" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "blob"), - 101: .same(proto: "data"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularMessageField(value: &self._blob) - case 101: try decoder.decodeSingularBytesField(value: &self._data) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._blob { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } - if let v = self._data { - try visitor.visitSingularBytesField(value: v, fieldNumber: 101) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_SyncMessage.Groups, rhs: SignalServiceProtos_SyncMessage.Groups) -> Bool { - if lhs._blob != rhs._blob {return false} - if lhs._data != rhs._data {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_SyncMessage.OpenGroupDetails: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_SyncMessage.protoMessageName + ".OpenGroupDetails" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "url"), - 2: .same(proto: "channelID"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._url) - case 2: try decoder.decodeSingularUInt64Field(value: &self._channelID) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._url { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._channelID { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_SyncMessage.OpenGroupDetails, rhs: SignalServiceProtos_SyncMessage.OpenGroupDetails) -> Bool { - if lhs._url != rhs._url {return false} - if lhs._channelID != rhs._channelID {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_SyncMessage.Blocked: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_SyncMessage.protoMessageName + ".Blocked" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "numbers"), - 2: .same(proto: "groupIds"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeRepeatedStringField(value: &self.numbers) - case 2: try decoder.decodeRepeatedBytesField(value: &self.groupIds) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.numbers.isEmpty { - try visitor.visitRepeatedStringField(value: self.numbers, fieldNumber: 1) - } - if !self.groupIds.isEmpty { - try visitor.visitRepeatedBytesField(value: self.groupIds, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_SyncMessage.Blocked, rhs: SignalServiceProtos_SyncMessage.Blocked) -> Bool { - if lhs.numbers != rhs.numbers {return false} - if lhs.groupIds != rhs.groupIds {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_SyncMessage.Request: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_SyncMessage.protoMessageName + ".Request" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "type"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularEnumField(value: &self._type) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_SyncMessage.Request, rhs: SignalServiceProtos_SyncMessage.Request) -> Bool { - if lhs._type != rhs._type {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_SyncMessage.Request.TypeEnum: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNKNOWN"), - 1: .same(proto: "CONTACTS"), - 2: .same(proto: "GROUPS"), - 3: .same(proto: "BLOCKED"), - 4: .same(proto: "CONFIGURATION"), - ] -} - -extension SignalServiceProtos_SyncMessage.Read: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_SyncMessage.protoMessageName + ".Read" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "sender"), - 2: .same(proto: "timestamp"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._sender) - case 2: try decoder.decodeSingularUInt64Field(value: &self._timestamp) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._sender { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._timestamp { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_SyncMessage.Read, rhs: SignalServiceProtos_SyncMessage.Read) -> Bool { - if lhs._sender != rhs._sender {return false} - if lhs._timestamp != rhs._timestamp {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_SyncMessage.Configuration: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_SyncMessage.protoMessageName + ".Configuration" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "readReceipts"), - 2: .same(proto: "unidentifiedDeliveryIndicators"), - 3: .same(proto: "typingIndicators"), - 4: .same(proto: "linkPreviews"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularBoolField(value: &self._readReceipts) - case 2: try decoder.decodeSingularBoolField(value: &self._unidentifiedDeliveryIndicators) - case 3: try decoder.decodeSingularBoolField(value: &self._typingIndicators) - case 4: try decoder.decodeSingularBoolField(value: &self._linkPreviews) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._readReceipts { - try visitor.visitSingularBoolField(value: v, fieldNumber: 1) - } - if let v = self._unidentifiedDeliveryIndicators { - try visitor.visitSingularBoolField(value: v, fieldNumber: 2) - } - if let v = self._typingIndicators { - try visitor.visitSingularBoolField(value: v, fieldNumber: 3) - } - if let v = self._linkPreviews { - try visitor.visitSingularBoolField(value: v, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_SyncMessage.Configuration, rhs: SignalServiceProtos_SyncMessage.Configuration) -> Bool { - if lhs._readReceipts != rhs._readReceipts {return false} - if lhs._unidentifiedDeliveryIndicators != rhs._unidentifiedDeliveryIndicators {return false} - if lhs._typingIndicators != rhs._typingIndicators {return false} - if lhs._linkPreviews != rhs._linkPreviews {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_AttachmentPointer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".AttachmentPointer" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "contentType"), - 3: .same(proto: "key"), - 4: .same(proto: "size"), - 5: .same(proto: "thumbnail"), - 6: .same(proto: "digest"), - 7: .same(proto: "fileName"), - 8: .same(proto: "flags"), - 9: .same(proto: "width"), - 10: .same(proto: "height"), - 11: .same(proto: "caption"), - 101: .same(proto: "url"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularFixed64Field(value: &self._id) - case 2: try decoder.decodeSingularStringField(value: &self._contentType) - case 3: try decoder.decodeSingularBytesField(value: &self._key) - case 4: try decoder.decodeSingularUInt32Field(value: &self._size) - case 5: try decoder.decodeSingularBytesField(value: &self._thumbnail) - case 6: try decoder.decodeSingularBytesField(value: &self._digest) - case 7: try decoder.decodeSingularStringField(value: &self._fileName) - case 8: try decoder.decodeSingularUInt32Field(value: &self._flags) - case 9: try decoder.decodeSingularUInt32Field(value: &self._width) - case 10: try decoder.decodeSingularUInt32Field(value: &self._height) - case 11: try decoder.decodeSingularStringField(value: &self._caption) - case 101: try decoder.decodeSingularStringField(value: &self._url) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._id { - try visitor.visitSingularFixed64Field(value: v, fieldNumber: 1) - } - if let v = self._contentType { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - if let v = self._key { - try visitor.visitSingularBytesField(value: v, fieldNumber: 3) - } - if let v = self._size { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) - } - if let v = self._thumbnail { - try visitor.visitSingularBytesField(value: v, fieldNumber: 5) - } - if let v = self._digest { - try visitor.visitSingularBytesField(value: v, fieldNumber: 6) - } - if let v = self._fileName { - try visitor.visitSingularStringField(value: v, fieldNumber: 7) - } - if let v = self._flags { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 8) - } - if let v = self._width { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 9) - } - if let v = self._height { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 10) - } - if let v = self._caption { - try visitor.visitSingularStringField(value: v, fieldNumber: 11) - } - if let v = self._url { - try visitor.visitSingularStringField(value: v, fieldNumber: 101) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_AttachmentPointer, rhs: SignalServiceProtos_AttachmentPointer) -> Bool { - if lhs._id != rhs._id {return false} - if lhs._contentType != rhs._contentType {return false} - if lhs._key != rhs._key {return false} - if lhs._size != rhs._size {return false} - if lhs._thumbnail != rhs._thumbnail {return false} - if lhs._digest != rhs._digest {return false} - if lhs._fileName != rhs._fileName {return false} - if lhs._flags != rhs._flags {return false} - if lhs._width != rhs._width {return false} - if lhs._height != rhs._height {return false} - if lhs._caption != rhs._caption {return false} - if lhs._url != rhs._url {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_AttachmentPointer.Flags: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "VOICE_MESSAGE"), - ] -} - -extension SignalServiceProtos_GroupContext: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".GroupContext" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "type"), - 3: .same(proto: "name"), - 4: .same(proto: "members"), - 5: .same(proto: "avatar"), - 6: .same(proto: "admins"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self._id) - case 2: try decoder.decodeSingularEnumField(value: &self._type) - case 3: try decoder.decodeSingularStringField(value: &self._name) - case 4: try decoder.decodeRepeatedStringField(value: &self.members) - case 5: try decoder.decodeSingularMessageField(value: &self._avatar) - case 6: try decoder.decodeRepeatedStringField(value: &self.admins) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._id { - try visitor.visitSingularBytesField(value: v, fieldNumber: 1) - } - if let v = self._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 2) - } - if let v = self._name { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) - } - if !self.members.isEmpty { - try visitor.visitRepeatedStringField(value: self.members, fieldNumber: 4) - } - if let v = self._avatar { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } - if !self.admins.isEmpty { - try visitor.visitRepeatedStringField(value: self.admins, fieldNumber: 6) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_GroupContext, rhs: SignalServiceProtos_GroupContext) -> Bool { - if lhs._id != rhs._id {return false} - if lhs._type != rhs._type {return false} - if lhs._name != rhs._name {return false} - if lhs.members != rhs.members {return false} - if lhs._avatar != rhs._avatar {return false} - if lhs.admins != rhs.admins {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_GroupContext.TypeEnum: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNKNOWN"), - 1: .same(proto: "UPDATE"), - 2: .same(proto: "DELIVER"), - 3: .same(proto: "QUIT"), - 4: .same(proto: "REQUEST_INFO"), - ] -} - -extension SignalServiceProtos_ContactDetails: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ContactDetails" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "number"), - 2: .same(proto: "name"), - 3: .same(proto: "avatar"), - 4: .same(proto: "color"), - 5: .same(proto: "verified"), - 6: .same(proto: "profileKey"), - 7: .same(proto: "blocked"), - 8: .same(proto: "expireTimer"), - 101: .same(proto: "nickname"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._number) - case 2: try decoder.decodeSingularStringField(value: &self._name) - case 3: try decoder.decodeSingularMessageField(value: &self._avatar) - case 4: try decoder.decodeSingularStringField(value: &self._color) - case 5: try decoder.decodeSingularMessageField(value: &self._verified) - case 6: try decoder.decodeSingularBytesField(value: &self._profileKey) - case 7: try decoder.decodeSingularBoolField(value: &self._blocked) - case 8: try decoder.decodeSingularUInt32Field(value: &self._expireTimer) - case 101: try decoder.decodeSingularStringField(value: &self._nickname) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._number { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._name { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - if let v = self._avatar { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } - if let v = self._color { - try visitor.visitSingularStringField(value: v, fieldNumber: 4) - } - if let v = self._verified { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } - if let v = self._profileKey { - try visitor.visitSingularBytesField(value: v, fieldNumber: 6) - } - if let v = self._blocked { - try visitor.visitSingularBoolField(value: v, fieldNumber: 7) - } - if let v = self._expireTimer { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 8) - } - if let v = self._nickname { - try visitor.visitSingularStringField(value: v, fieldNumber: 101) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_ContactDetails, rhs: SignalServiceProtos_ContactDetails) -> Bool { - if lhs._number != rhs._number {return false} - if lhs._name != rhs._name {return false} - if lhs._avatar != rhs._avatar {return false} - if lhs._color != rhs._color {return false} - if lhs._verified != rhs._verified {return false} - if lhs._profileKey != rhs._profileKey {return false} - if lhs._blocked != rhs._blocked {return false} - if lhs._expireTimer != rhs._expireTimer {return false} - if lhs._nickname != rhs._nickname {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_ContactDetails.Avatar: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_ContactDetails.protoMessageName + ".Avatar" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "contentType"), - 2: .same(proto: "length"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._contentType) - case 2: try decoder.decodeSingularUInt32Field(value: &self._length) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._contentType { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._length { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_ContactDetails.Avatar, rhs: SignalServiceProtos_ContactDetails.Avatar) -> Bool { - if lhs._contentType != rhs._contentType {return false} - if lhs._length != rhs._length {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".GroupDetails" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "id"), - 2: .same(proto: "name"), - 3: .same(proto: "members"), - 4: .same(proto: "avatar"), - 5: .same(proto: "active"), - 6: .same(proto: "expireTimer"), - 7: .same(proto: "color"), - 8: .same(proto: "blocked"), - 9: .same(proto: "admins"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularBytesField(value: &self._id) - case 2: try decoder.decodeSingularStringField(value: &self._name) - case 3: try decoder.decodeRepeatedStringField(value: &self.members) - case 4: try decoder.decodeSingularMessageField(value: &self._avatar) - case 5: try decoder.decodeSingularBoolField(value: &self._active) - case 6: try decoder.decodeSingularUInt32Field(value: &self._expireTimer) - case 7: try decoder.decodeSingularStringField(value: &self._color) - case 8: try decoder.decodeSingularBoolField(value: &self._blocked) - case 9: try decoder.decodeRepeatedStringField(value: &self.admins) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._id { - try visitor.visitSingularBytesField(value: v, fieldNumber: 1) - } - if let v = self._name { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - if !self.members.isEmpty { - try visitor.visitRepeatedStringField(value: self.members, fieldNumber: 3) - } - if let v = self._avatar { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } - if let v = self._active { - try visitor.visitSingularBoolField(value: v, fieldNumber: 5) - } - if let v = self._expireTimer { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 6) - } - if let v = self._color { - try visitor.visitSingularStringField(value: v, fieldNumber: 7) - } - if let v = self._blocked { - try visitor.visitSingularBoolField(value: v, fieldNumber: 8) - } - if !self.admins.isEmpty { - try visitor.visitRepeatedStringField(value: self.admins, fieldNumber: 9) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_GroupDetails, rhs: SignalServiceProtos_GroupDetails) -> Bool { - if lhs._id != rhs._id {return false} - if lhs._name != rhs._name {return false} - if lhs.members != rhs.members {return false} - if lhs._avatar != rhs._avatar {return false} - if lhs._active != rhs._active {return false} - if lhs._expireTimer != rhs._expireTimer {return false} - if lhs._color != rhs._color {return false} - if lhs._blocked != rhs._blocked {return false} - if lhs.admins != rhs.admins {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_GroupDetails.Avatar: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = SignalServiceProtos_GroupDetails.protoMessageName + ".Avatar" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "contentType"), - 2: .same(proto: "length"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._contentType) - case 2: try decoder.decodeSingularUInt32Field(value: &self._length) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._contentType { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._length { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_GroupDetails.Avatar, rhs: SignalServiceProtos_GroupDetails.Avatar) -> Bool { - if lhs._contentType != rhs._contentType {return false} - if lhs._length != rhs._length {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension SignalServiceProtos_PublicChatInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".PublicChatInfo" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "serverID"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularUInt64Field(value: &self._serverID) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._serverID { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: SignalServiceProtos_PublicChatInfo, rhs: SignalServiceProtos_PublicChatInfo) -> Bool { - if lhs._serverID != rhs._serverID {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/SignalServiceKit/src/Protos/Generated/WebSocketProto.swift b/SignalServiceKit/src/Protos/Generated/WebSocketProto.swift deleted file mode 100644 index c886ccc95..000000000 --- a/SignalServiceKit/src/Protos/Generated/WebSocketProto.swift +++ /dev/null @@ -1,486 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -// WARNING: This code is generated. Only edit within the markers. - -public enum WebSocketProtoError: Error { - case invalidProtobuf(description: String) -} - -// MARK: - WebSocketProtoWebSocketRequestMessage - -@objc public class WebSocketProtoWebSocketRequestMessage: NSObject { - - // MARK: - WebSocketProtoWebSocketRequestMessageBuilder - - @objc public class func builder(verb: String, path: String, requestID: UInt64) -> WebSocketProtoWebSocketRequestMessageBuilder { - return WebSocketProtoWebSocketRequestMessageBuilder(verb: verb, path: path, requestID: requestID) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> WebSocketProtoWebSocketRequestMessageBuilder { - let builder = WebSocketProtoWebSocketRequestMessageBuilder(verb: verb, path: path, requestID: requestID) - if let _value = body { - builder.setBody(_value) - } - builder.setHeaders(headers) - return builder - } - - @objc public class WebSocketProtoWebSocketRequestMessageBuilder: NSObject { - - private var proto = WebSocketProtos_WebSocketRequestMessage() - - @objc fileprivate override init() {} - - @objc fileprivate init(verb: String, path: String, requestID: UInt64) { - super.init() - - setVerb(verb) - setPath(path) - setRequestID(requestID) - } - - @objc public func setVerb(_ valueParam: String) { - proto.verb = valueParam - } - - @objc public func setPath(_ valueParam: String) { - proto.path = valueParam - } - - @objc public func setBody(_ valueParam: Data) { - proto.body = valueParam - } - - @objc public func addHeaders(_ valueParam: String) { - var items = proto.headers - items.append(valueParam) - proto.headers = items - } - - @objc public func setHeaders(_ wrappedItems: [String]) { - proto.headers = wrappedItems - } - - @objc public func setRequestID(_ valueParam: UInt64) { - proto.requestID = valueParam - } - - @objc public func build() throws -> WebSocketProtoWebSocketRequestMessage { - return try WebSocketProtoWebSocketRequestMessage.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try WebSocketProtoWebSocketRequestMessage.parseProto(proto).serializedData() - } - } - - fileprivate let proto: WebSocketProtos_WebSocketRequestMessage - - @objc public let verb: String - - @objc public let path: String - - @objc public let requestID: UInt64 - - @objc public var body: Data? { - guard proto.hasBody else { - return nil - } - return proto.body - } - @objc public var hasBody: Bool { - return proto.hasBody - } - - @objc public var headers: [String] { - return proto.headers - } - - private init(proto: WebSocketProtos_WebSocketRequestMessage, - verb: String, - path: String, - requestID: UInt64) { - self.proto = proto - self.verb = verb - self.path = path - self.requestID = requestID - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> WebSocketProtoWebSocketRequestMessage { - let proto = try WebSocketProtos_WebSocketRequestMessage(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketRequestMessage) throws -> WebSocketProtoWebSocketRequestMessage { - guard proto.hasVerb else { - throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: verb") - } - let verb = proto.verb - - guard proto.hasPath else { - throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: path") - } - let path = proto.path - - guard proto.hasRequestID else { - throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: requestID") - } - let requestID = proto.requestID - - // MARK: - Begin Validation Logic for WebSocketProtoWebSocketRequestMessage - - - // MARK: - End Validation Logic for WebSocketProtoWebSocketRequestMessage - - - let result = WebSocketProtoWebSocketRequestMessage(proto: proto, - verb: verb, - path: path, - requestID: requestID) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension WebSocketProtoWebSocketRequestMessage { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension WebSocketProtoWebSocketRequestMessage.WebSocketProtoWebSocketRequestMessageBuilder { - @objc public func buildIgnoringErrors() -> WebSocketProtoWebSocketRequestMessage? { - return try! self.build() - } -} - -#endif - -// MARK: - WebSocketProtoWebSocketResponseMessage - -@objc public class WebSocketProtoWebSocketResponseMessage: NSObject { - - // MARK: - WebSocketProtoWebSocketResponseMessageBuilder - - @objc public class func builder(requestID: UInt64, status: UInt32) -> WebSocketProtoWebSocketResponseMessageBuilder { - return WebSocketProtoWebSocketResponseMessageBuilder(requestID: requestID, status: status) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> WebSocketProtoWebSocketResponseMessageBuilder { - let builder = WebSocketProtoWebSocketResponseMessageBuilder(requestID: requestID, status: status) - if let _value = message { - builder.setMessage(_value) - } - builder.setHeaders(headers) - if let _value = body { - builder.setBody(_value) - } - return builder - } - - @objc public class WebSocketProtoWebSocketResponseMessageBuilder: NSObject { - - private var proto = WebSocketProtos_WebSocketResponseMessage() - - @objc fileprivate override init() {} - - @objc fileprivate init(requestID: UInt64, status: UInt32) { - super.init() - - setRequestID(requestID) - setStatus(status) - } - - @objc public func setRequestID(_ valueParam: UInt64) { - proto.requestID = valueParam - } - - @objc public func setStatus(_ valueParam: UInt32) { - proto.status = valueParam - } - - @objc public func setMessage(_ valueParam: String) { - proto.message = valueParam - } - - @objc public func addHeaders(_ valueParam: String) { - var items = proto.headers - items.append(valueParam) - proto.headers = items - } - - @objc public func setHeaders(_ wrappedItems: [String]) { - proto.headers = wrappedItems - } - - @objc public func setBody(_ valueParam: Data) { - proto.body = valueParam - } - - @objc public func build() throws -> WebSocketProtoWebSocketResponseMessage { - return try WebSocketProtoWebSocketResponseMessage.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try WebSocketProtoWebSocketResponseMessage.parseProto(proto).serializedData() - } - } - - fileprivate let proto: WebSocketProtos_WebSocketResponseMessage - - @objc public let requestID: UInt64 - - @objc public let status: UInt32 - - @objc public var message: String? { - guard proto.hasMessage else { - return nil - } - return proto.message - } - @objc public var hasMessage: Bool { - return proto.hasMessage - } - - @objc public var headers: [String] { - return proto.headers - } - - @objc public var body: Data? { - guard proto.hasBody else { - return nil - } - return proto.body - } - @objc public var hasBody: Bool { - return proto.hasBody - } - - private init(proto: WebSocketProtos_WebSocketResponseMessage, - requestID: UInt64, - status: UInt32) { - self.proto = proto - self.requestID = requestID - self.status = status - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> WebSocketProtoWebSocketResponseMessage { - let proto = try WebSocketProtos_WebSocketResponseMessage(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketResponseMessage) throws -> WebSocketProtoWebSocketResponseMessage { - guard proto.hasRequestID else { - throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: requestID") - } - let requestID = proto.requestID - - guard proto.hasStatus else { - throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: status") - } - let status = proto.status - - // MARK: - Begin Validation Logic for WebSocketProtoWebSocketResponseMessage - - - // MARK: - End Validation Logic for WebSocketProtoWebSocketResponseMessage - - - let result = WebSocketProtoWebSocketResponseMessage(proto: proto, - requestID: requestID, - status: status) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension WebSocketProtoWebSocketResponseMessage { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension WebSocketProtoWebSocketResponseMessage.WebSocketProtoWebSocketResponseMessageBuilder { - @objc public func buildIgnoringErrors() -> WebSocketProtoWebSocketResponseMessage? { - return try! self.build() - } -} - -#endif - -// MARK: - WebSocketProtoWebSocketMessage - -@objc public class WebSocketProtoWebSocketMessage: NSObject { - - // MARK: - WebSocketProtoWebSocketMessageType - - @objc public enum WebSocketProtoWebSocketMessageType: Int32 { - case unknown = 0 - case request = 1 - case response = 2 - } - - private class func WebSocketProtoWebSocketMessageTypeWrap(_ value: WebSocketProtos_WebSocketMessage.TypeEnum) -> WebSocketProtoWebSocketMessageType { - switch value { - case .unknown: return .unknown - case .request: return .request - case .response: return .response - } - } - - private class func WebSocketProtoWebSocketMessageTypeUnwrap(_ value: WebSocketProtoWebSocketMessageType) -> WebSocketProtos_WebSocketMessage.TypeEnum { - switch value { - case .unknown: return .unknown - case .request: return .request - case .response: return .response - } - } - - // MARK: - WebSocketProtoWebSocketMessageBuilder - - @objc public class func builder(type: WebSocketProtoWebSocketMessageType) -> WebSocketProtoWebSocketMessageBuilder { - return WebSocketProtoWebSocketMessageBuilder(type: type) - } - - // asBuilder() constructs a builder that reflects the proto's contents. - @objc public func asBuilder() -> WebSocketProtoWebSocketMessageBuilder { - let builder = WebSocketProtoWebSocketMessageBuilder(type: type) - if let _value = request { - builder.setRequest(_value) - } - if let _value = response { - builder.setResponse(_value) - } - return builder - } - - @objc public class WebSocketProtoWebSocketMessageBuilder: NSObject { - - private var proto = WebSocketProtos_WebSocketMessage() - - @objc fileprivate override init() {} - - @objc fileprivate init(type: WebSocketProtoWebSocketMessageType) { - super.init() - - setType(type) - } - - @objc public func setType(_ valueParam: WebSocketProtoWebSocketMessageType) { - proto.type = WebSocketProtoWebSocketMessageTypeUnwrap(valueParam) - } - - @objc public func setRequest(_ valueParam: WebSocketProtoWebSocketRequestMessage) { - proto.request = valueParam.proto - } - - @objc public func setResponse(_ valueParam: WebSocketProtoWebSocketResponseMessage) { - proto.response = valueParam.proto - } - - @objc public func build() throws -> WebSocketProtoWebSocketMessage { - return try WebSocketProtoWebSocketMessage.parseProto(proto) - } - - @objc public func buildSerializedData() throws -> Data { - return try WebSocketProtoWebSocketMessage.parseProto(proto).serializedData() - } - } - - fileprivate let proto: WebSocketProtos_WebSocketMessage - - @objc public let type: WebSocketProtoWebSocketMessageType - - @objc public let request: WebSocketProtoWebSocketRequestMessage? - - @objc public let response: WebSocketProtoWebSocketResponseMessage? - - private init(proto: WebSocketProtos_WebSocketMessage, - type: WebSocketProtoWebSocketMessageType, - request: WebSocketProtoWebSocketRequestMessage?, - response: WebSocketProtoWebSocketResponseMessage?) { - self.proto = proto - self.type = type - self.request = request - self.response = response - } - - @objc - public func serializedData() throws -> Data { - return try self.proto.serializedData() - } - - @objc public class func parseData(_ serializedData: Data) throws -> WebSocketProtoWebSocketMessage { - let proto = try WebSocketProtos_WebSocketMessage(serializedData: serializedData) - return try parseProto(proto) - } - - fileprivate class func parseProto(_ proto: WebSocketProtos_WebSocketMessage) throws -> WebSocketProtoWebSocketMessage { - guard proto.hasType else { - throw WebSocketProtoError.invalidProtobuf(description: "\(logTag) missing required field: type") - } - let type = WebSocketProtoWebSocketMessageTypeWrap(proto.type) - - var request: WebSocketProtoWebSocketRequestMessage? = nil - if proto.hasRequest { - request = try WebSocketProtoWebSocketRequestMessage.parseProto(proto.request) - } - - var response: WebSocketProtoWebSocketResponseMessage? = nil - if proto.hasResponse { - response = try WebSocketProtoWebSocketResponseMessage.parseProto(proto.response) - } - - // MARK: - Begin Validation Logic for WebSocketProtoWebSocketMessage - - - // MARK: - End Validation Logic for WebSocketProtoWebSocketMessage - - - let result = WebSocketProtoWebSocketMessage(proto: proto, - type: type, - request: request, - response: response) - return result - } - - @objc public override var debugDescription: String { - return "\(proto)" - } -} - -#if DEBUG - -extension WebSocketProtoWebSocketMessage { - @objc public func serializedDataIgnoringErrors() -> Data? { - return try! self.serializedData() - } -} - -extension WebSocketProtoWebSocketMessage.WebSocketProtoWebSocketMessageBuilder { - @objc public func buildIgnoringErrors() -> WebSocketProtoWebSocketMessage? { - return try! self.build() - } -} - -#endif diff --git a/SignalServiceKit/src/Protos/Generated/WebSocketResources.pb.swift b/SignalServiceKit/src/Protos/Generated/WebSocketResources.pb.swift deleted file mode 100644 index e713065fd..000000000 --- a/SignalServiceKit/src/Protos/Generated/WebSocketResources.pb.swift +++ /dev/null @@ -1,378 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: WebSocketResources.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -//* -// Copyright (C) 2014-2016 Open Whisper Systems -// -// Licensed according to the LICENSE file in this repository. - -/// iOS - since we use a modern proto-compiler, we must specify -/// the legacy proto format. - -import Foundation -import SwiftProtobuf - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -struct WebSocketProtos_WebSocketRequestMessage { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var verb: String { - get {return _verb ?? String()} - set {_verb = newValue} - } - /// Returns true if `verb` has been explicitly set. - var hasVerb: Bool {return self._verb != nil} - /// Clears the value of `verb`. Subsequent reads from it will return its default value. - mutating func clearVerb() {self._verb = nil} - - /// @required - var path: String { - get {return _path ?? String()} - set {_path = newValue} - } - /// Returns true if `path` has been explicitly set. - var hasPath: Bool {return self._path != nil} - /// Clears the value of `path`. Subsequent reads from it will return its default value. - mutating func clearPath() {self._path = nil} - - var body: Data { - get {return _body ?? SwiftProtobuf.Internal.emptyData} - set {_body = newValue} - } - /// Returns true if `body` has been explicitly set. - var hasBody: Bool {return self._body != nil} - /// Clears the value of `body`. Subsequent reads from it will return its default value. - mutating func clearBody() {self._body = nil} - - var headers: [String] = [] - - /// @required - var requestID: UInt64 { - get {return _requestID ?? 0} - set {_requestID = newValue} - } - /// Returns true if `requestID` has been explicitly set. - var hasRequestID: Bool {return self._requestID != nil} - /// Clears the value of `requestID`. Subsequent reads from it will return its default value. - mutating func clearRequestID() {self._requestID = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _verb: String? = nil - fileprivate var _path: String? = nil - fileprivate var _body: Data? = nil - fileprivate var _requestID: UInt64? = nil -} - -struct WebSocketProtos_WebSocketResponseMessage { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var requestID: UInt64 { - get {return _requestID ?? 0} - set {_requestID = newValue} - } - /// Returns true if `requestID` has been explicitly set. - var hasRequestID: Bool {return self._requestID != nil} - /// Clears the value of `requestID`. Subsequent reads from it will return its default value. - mutating func clearRequestID() {self._requestID = nil} - - /// @required - var status: UInt32 { - get {return _status ?? 0} - set {_status = newValue} - } - /// Returns true if `status` has been explicitly set. - var hasStatus: Bool {return self._status != nil} - /// Clears the value of `status`. Subsequent reads from it will return its default value. - mutating func clearStatus() {self._status = nil} - - var message: String { - get {return _message ?? String()} - set {_message = newValue} - } - /// Returns true if `message` has been explicitly set. - var hasMessage: Bool {return self._message != nil} - /// Clears the value of `message`. Subsequent reads from it will return its default value. - mutating func clearMessage() {self._message = nil} - - var headers: [String] = [] - - var body: Data { - get {return _body ?? SwiftProtobuf.Internal.emptyData} - set {_body = newValue} - } - /// Returns true if `body` has been explicitly set. - var hasBody: Bool {return self._body != nil} - /// Clears the value of `body`. Subsequent reads from it will return its default value. - mutating func clearBody() {self._body = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _requestID: UInt64? = nil - fileprivate var _status: UInt32? = nil - fileprivate var _message: String? = nil - fileprivate var _body: Data? = nil -} - -struct WebSocketProtos_WebSocketMessage { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// @required - var type: WebSocketProtos_WebSocketMessage.TypeEnum { - get {return _type ?? .unknown} - set {_type = newValue} - } - /// Returns true if `type` has been explicitly set. - var hasType: Bool {return self._type != nil} - /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {self._type = nil} - - var request: WebSocketProtos_WebSocketRequestMessage { - get {return _request ?? WebSocketProtos_WebSocketRequestMessage()} - set {_request = newValue} - } - /// Returns true if `request` has been explicitly set. - var hasRequest: Bool {return self._request != nil} - /// Clears the value of `request`. Subsequent reads from it will return its default value. - mutating func clearRequest() {self._request = nil} - - var response: WebSocketProtos_WebSocketResponseMessage { - get {return _response ?? WebSocketProtos_WebSocketResponseMessage()} - set {_response = newValue} - } - /// Returns true if `response` has been explicitly set. - var hasResponse: Bool {return self._response != nil} - /// Clears the value of `response`. Subsequent reads from it will return its default value. - mutating func clearResponse() {self._response = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum TypeEnum: SwiftProtobuf.Enum { - typealias RawValue = Int - case unknown // = 0 - case request // = 1 - case response // = 2 - - init() { - self = .unknown - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unknown - case 1: self = .request - case 2: self = .response - default: return nil - } - } - - var rawValue: Int { - switch self { - case .unknown: return 0 - case .request: return 1 - case .response: return 2 - } - } - - } - - init() {} - - fileprivate var _type: WebSocketProtos_WebSocketMessage.TypeEnum? = nil - fileprivate var _request: WebSocketProtos_WebSocketRequestMessage? = nil - fileprivate var _response: WebSocketProtos_WebSocketResponseMessage? = nil -} - -#if swift(>=4.2) - -extension WebSocketProtos_WebSocketMessage.TypeEnum: CaseIterable { - // Support synthesized by the compiler. -} - -#endif // swift(>=4.2) - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "WebSocketProtos" - -extension WebSocketProtos_WebSocketRequestMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".WebSocketRequestMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "verb"), - 2: .same(proto: "path"), - 3: .same(proto: "body"), - 5: .same(proto: "headers"), - 4: .same(proto: "requestId"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._verb) - case 2: try decoder.decodeSingularStringField(value: &self._path) - case 3: try decoder.decodeSingularBytesField(value: &self._body) - case 4: try decoder.decodeSingularUInt64Field(value: &self._requestID) - case 5: try decoder.decodeRepeatedStringField(value: &self.headers) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._verb { - try visitor.visitSingularStringField(value: v, fieldNumber: 1) - } - if let v = self._path { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } - if let v = self._body { - try visitor.visitSingularBytesField(value: v, fieldNumber: 3) - } - if let v = self._requestID { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 4) - } - if !self.headers.isEmpty { - try visitor.visitRepeatedStringField(value: self.headers, fieldNumber: 5) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: WebSocketProtos_WebSocketRequestMessage, rhs: WebSocketProtos_WebSocketRequestMessage) -> Bool { - if lhs._verb != rhs._verb {return false} - if lhs._path != rhs._path {return false} - if lhs._body != rhs._body {return false} - if lhs.headers != rhs.headers {return false} - if lhs._requestID != rhs._requestID {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension WebSocketProtos_WebSocketResponseMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".WebSocketResponseMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "requestId"), - 2: .same(proto: "status"), - 3: .same(proto: "message"), - 5: .same(proto: "headers"), - 4: .same(proto: "body"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularUInt64Field(value: &self._requestID) - case 2: try decoder.decodeSingularUInt32Field(value: &self._status) - case 3: try decoder.decodeSingularStringField(value: &self._message) - case 4: try decoder.decodeSingularBytesField(value: &self._body) - case 5: try decoder.decodeRepeatedStringField(value: &self.headers) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._requestID { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1) - } - if let v = self._status { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) - } - if let v = self._message { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) - } - if let v = self._body { - try visitor.visitSingularBytesField(value: v, fieldNumber: 4) - } - if !self.headers.isEmpty { - try visitor.visitRepeatedStringField(value: self.headers, fieldNumber: 5) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: WebSocketProtos_WebSocketResponseMessage, rhs: WebSocketProtos_WebSocketResponseMessage) -> Bool { - if lhs._requestID != rhs._requestID {return false} - if lhs._status != rhs._status {return false} - if lhs._message != rhs._message {return false} - if lhs.headers != rhs.headers {return false} - if lhs._body != rhs._body {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension WebSocketProtos_WebSocketMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".WebSocketMessage" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "type"), - 2: .same(proto: "request"), - 3: .same(proto: "response"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularEnumField(value: &self._type) - case 2: try decoder.decodeSingularMessageField(value: &self._request) - case 3: try decoder.decodeSingularMessageField(value: &self._response) - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if let v = self._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 1) - } - if let v = self._request { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } - if let v = self._response { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: WebSocketProtos_WebSocketMessage, rhs: WebSocketProtos_WebSocketMessage) -> Bool { - if lhs._type != rhs._type {return false} - if lhs._request != rhs._request {return false} - if lhs._response != rhs._response {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension WebSocketProtos_WebSocketMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNKNOWN"), - 1: .same(proto: "REQUEST"), - 2: .same(proto: "RESPONSE"), - ] -} diff --git a/SignalServiceKit/src/SSKEnvironment.h b/SignalServiceKit/src/SSKEnvironment.h deleted file mode 100644 index 8dc5179d2..000000000 --- a/SignalServiceKit/src/SSKEnvironment.h +++ /dev/null @@ -1,118 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSSyncManagerProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -@class ContactDiscoveryService; -@class ContactsUpdater; -@class OWS2FAManager; -@class OWSAttachmentDownloads; -@class OWSBatchMessageProcessor; -@class OWSBlockingManager; -@class OWSDisappearingMessagesJob; -@class OWSIdentityManager; -@class OWSMessageDecrypter; -@class OWSMessageManager; -@class OWSMessageReceiver; -@class OWSMessageSender; -@class OWSOutgoingReceiptManager; -@class OWSPrimaryStorage; -@class OWSReadReceiptManager; -@class SSKMessageSenderJobQueue; -@class TSAccountManager; -@class TSNetworkManager; -@class TSSocketManager; -@class YapDatabaseConnection; - -@protocol ContactsManagerProtocol; -@protocol NotificationsProtocol; -@protocol OWSCallMessageHandler; -@protocol ProfileManagerProtocol; -@protocol OWSUDManager; -@protocol SSKReachabilityManager; -@protocol OWSSyncManagerProtocol; -@protocol OWSTypingIndicators; - -@interface SSKEnvironment : NSObject - -- (instancetype)initWithContactsManager:(id)contactsManager - messageSender:(OWSMessageSender *)messageSender - messageSenderJobQueue:(SSKMessageSenderJobQueue *)messageSenderJobQueue - profileManager:(id)profileManager - primaryStorage:(OWSPrimaryStorage *)primaryStorage - contactsUpdater:(ContactsUpdater *)contactsUpdater - networkManager:(TSNetworkManager *)networkManager - messageManager:(OWSMessageManager *)messageManager - blockingManager:(OWSBlockingManager *)blockingManager - identityManager:(OWSIdentityManager *)identityManager - udManager:(id)udManager - messageDecrypter:(OWSMessageDecrypter *)messageDecrypter - batchMessageProcessor:(OWSBatchMessageProcessor *)batchMessageProcessor - messageReceiver:(OWSMessageReceiver *)messageReceiver - socketManager:(TSSocketManager *)socketManager - tsAccountManager:(TSAccountManager *)tsAccountManager - ows2FAManager:(OWS2FAManager *)ows2FAManager - disappearingMessagesJob:(OWSDisappearingMessagesJob *)disappearingMessagesJob - contactDiscoveryService:(ContactDiscoveryService *)contactDiscoveryService - readReceiptManager:(OWSReadReceiptManager *)readReceiptManager - outgoingReceiptManager:(OWSOutgoingReceiptManager *)outgoingReceiptManager - reachabilityManager:(id)reachabilityManager - syncManager:(id)syncManager - typingIndicators:(id)typingIndicators - attachmentDownloads:(OWSAttachmentDownloads *)attachmentDownloads NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -@property (nonatomic, readonly, class) SSKEnvironment *shared; - -+ (void)setShared:(SSKEnvironment *)env; - -#ifdef DEBUG -// Should only be called by tests. -+ (void)clearSharedForTests; -#endif - -@property (nonatomic, readonly) id contactsManager; -@property (nonatomic, readonly) OWSMessageSender *messageSender; -@property (nonatomic, readonly) SSKMessageSenderJobQueue *messageSenderJobQueue; -@property (nonatomic, readonly) id profileManager; -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; -@property (nonatomic, readonly) ContactsUpdater *contactsUpdater; -@property (nonatomic, readonly) TSNetworkManager *networkManager; -@property (nonatomic, readonly) OWSMessageManager *messageManager; -@property (nonatomic, readonly) OWSBlockingManager *blockingManager; -@property (nonatomic, readonly) OWSIdentityManager *identityManager; -@property (nonatomic, readonly) id udManager; -@property (nonatomic, readonly) OWSMessageDecrypter *messageDecrypter; -@property (nonatomic, readonly) OWSBatchMessageProcessor *batchMessageProcessor; -@property (nonatomic, readonly) OWSMessageReceiver *messageReceiver; -@property (nonatomic, readonly) TSSocketManager *socketManager; -@property (nonatomic, readonly) TSAccountManager *tsAccountManager; -@property (nonatomic, readonly) OWS2FAManager *ows2FAManager; -@property (nonatomic, readonly) OWSDisappearingMessagesJob *disappearingMessagesJob; -@property (nonatomic, readonly) ContactDiscoveryService *contactDiscoveryService; -@property (nonatomic, readonly) OWSReadReceiptManager *readReceiptManager; -@property (nonatomic, readonly) OWSOutgoingReceiptManager *outgoingReceiptManager; -@property (nonatomic, readonly) id syncManager; -@property (nonatomic, readonly) id reachabilityManager; -@property (nonatomic, readonly) id typingIndicators; -@property (nonatomic, readonly) OWSAttachmentDownloads *attachmentDownloads; - -// This property is configured after Environment is created. -@property (atomic, nullable) id callMessageHandler; -// This property is configured after Environment is created. -@property (atomic, nullable) id notificationsManager; - -@property (atomic, readonly) YapDatabaseConnection *objectReadWriteConnection; -@property (atomic, readonly) YapDatabaseConnection *sessionStoreDBConnection; -@property (atomic, readonly) YapDatabaseConnection *migrationDBConnection; -@property (atomic, readonly) YapDatabaseConnection *analyticsDBConnection; - -- (BOOL)isComplete; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/SSKEnvironment.m b/SignalServiceKit/src/SSKEnvironment.m deleted file mode 100644 index 82419e756..000000000 --- a/SignalServiceKit/src/SSKEnvironment.m +++ /dev/null @@ -1,244 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "SSKEnvironment.h" -#import "AppContext.h" -#import "OWSPrimaryStorage.h" - -NS_ASSUME_NONNULL_BEGIN - -static SSKEnvironment *sharedSSKEnvironment; - -@interface SSKEnvironment () - -@property (nonatomic) id contactsManager; -@property (nonatomic) OWSMessageSender *messageSender; -@property (nonatomic) id profileManager; -@property (nonatomic) OWSPrimaryStorage *primaryStorage; -@property (nonatomic) ContactsUpdater *contactsUpdater; -@property (nonatomic) TSNetworkManager *networkManager; -@property (nonatomic) OWSMessageManager *messageManager; -@property (nonatomic) OWSBlockingManager *blockingManager; -@property (nonatomic) OWSIdentityManager *identityManager; -@property (nonatomic) id udManager; -@property (nonatomic) OWSMessageDecrypter *messageDecrypter; -@property (nonatomic) OWSBatchMessageProcessor *batchMessageProcessor; -@property (nonatomic) OWSMessageReceiver *messageReceiver; -@property (nonatomic) TSSocketManager *socketManager; -@property (nonatomic) TSAccountManager *tsAccountManager; -@property (nonatomic) OWS2FAManager *ows2FAManager; -@property (nonatomic) OWSDisappearingMessagesJob *disappearingMessagesJob; -@property (nonatomic) ContactDiscoveryService *contactDiscoveryService; -@property (nonatomic) OWSReadReceiptManager *readReceiptManager; -@property (nonatomic) OWSOutgoingReceiptManager *outgoingReceiptManager; -@property (nonatomic) id syncManager; -@property (nonatomic) id reachabilityManager; -@property (nonatomic) id typingIndicators; -@property (nonatomic) OWSAttachmentDownloads *attachmentDownloads; - -@end - -#pragma mark - - -@implementation SSKEnvironment - -@synthesize callMessageHandler = _callMessageHandler; -@synthesize notificationsManager = _notificationsManager; -@synthesize objectReadWriteConnection = _objectReadWriteConnection; -@synthesize sessionStoreDBConnection = _sessionStoreDBConnection; -@synthesize migrationDBConnection = _migrationDBConnection; -@synthesize analyticsDBConnection = _analyticsDBConnection; - -- (instancetype)initWithContactsManager:(id)contactsManager - messageSender:(OWSMessageSender *)messageSender - messageSenderJobQueue:(SSKMessageSenderJobQueue *)messageSenderJobQueue - profileManager:(id)profileManager - primaryStorage:(OWSPrimaryStorage *)primaryStorage - contactsUpdater:(ContactsUpdater *)contactsUpdater - networkManager:(TSNetworkManager *)networkManager - messageManager:(OWSMessageManager *)messageManager - blockingManager:(OWSBlockingManager *)blockingManager - identityManager:(OWSIdentityManager *)identityManager - udManager:(id)udManager - messageDecrypter:(OWSMessageDecrypter *)messageDecrypter - batchMessageProcessor:(OWSBatchMessageProcessor *)batchMessageProcessor - messageReceiver:(OWSMessageReceiver *)messageReceiver - socketManager:(TSSocketManager *)socketManager - tsAccountManager:(TSAccountManager *)tsAccountManager - ows2FAManager:(OWS2FAManager *)ows2FAManager - disappearingMessagesJob:(OWSDisappearingMessagesJob *)disappearingMessagesJob - contactDiscoveryService:(ContactDiscoveryService *)contactDiscoveryService - readReceiptManager:(OWSReadReceiptManager *)readReceiptManager - outgoingReceiptManager:(OWSOutgoingReceiptManager *)outgoingReceiptManager - reachabilityManager:(id)reachabilityManager - syncManager:(id)syncManager - typingIndicators:(id)typingIndicators - attachmentDownloads:(OWSAttachmentDownloads *)attachmentDownloads -{ - self = [super init]; - if (!self) { - return self; - } - - OWSAssertDebug(contactsManager); - OWSAssertDebug(messageSender); - OWSAssertDebug(messageSenderJobQueue); - OWSAssertDebug(profileManager); - OWSAssertDebug(primaryStorage); - OWSAssertDebug(contactsUpdater); - OWSAssertDebug(networkManager); - OWSAssertDebug(messageManager); - OWSAssertDebug(blockingManager); - OWSAssertDebug(identityManager); - OWSAssertDebug(udManager); - OWSAssertDebug(messageDecrypter); - OWSAssertDebug(batchMessageProcessor); - OWSAssertDebug(messageReceiver); - OWSAssertDebug(socketManager); - OWSAssertDebug(tsAccountManager); - OWSAssertDebug(ows2FAManager); - OWSAssertDebug(disappearingMessagesJob); - OWSAssertDebug(contactDiscoveryService); - OWSAssertDebug(readReceiptManager); - OWSAssertDebug(outgoingReceiptManager); - OWSAssertDebug(syncManager); - OWSAssertDebug(reachabilityManager); - OWSAssertDebug(typingIndicators); - OWSAssertDebug(attachmentDownloads); - - _contactsManager = contactsManager; - _messageSender = messageSender; - _messageSenderJobQueue = messageSenderJobQueue; - _profileManager = profileManager; - _primaryStorage = primaryStorage; - _contactsUpdater = contactsUpdater; - _networkManager = networkManager; - _messageManager = messageManager; - _blockingManager = blockingManager; - _identityManager = identityManager; - _udManager = udManager; - _messageDecrypter = messageDecrypter; - _batchMessageProcessor = batchMessageProcessor; - _messageReceiver = messageReceiver; - _socketManager = socketManager; - _tsAccountManager = tsAccountManager; - _ows2FAManager = ows2FAManager; - _disappearingMessagesJob = disappearingMessagesJob; - _contactDiscoveryService = contactDiscoveryService; - _readReceiptManager = readReceiptManager; - _outgoingReceiptManager = outgoingReceiptManager; - _syncManager = syncManager; - _reachabilityManager = reachabilityManager; - _typingIndicators = typingIndicators; - _attachmentDownloads = attachmentDownloads; - - return self; -} - -+ (instancetype)shared -{ - OWSAssertDebug(sharedSSKEnvironment); - - return sharedSSKEnvironment; -} - -+ (void)setShared:(SSKEnvironment *)env -{ - OWSAssertDebug(env); - OWSAssertDebug(!sharedSSKEnvironment || CurrentAppContext().isRunningTests); - - sharedSSKEnvironment = env; -} - -+ (void)clearSharedForTests -{ - sharedSSKEnvironment = nil; -} - -#pragma mark - Mutable Accessors -/* -- (nullable id)callMessageHandler -{ - @synchronized(self) { - OWSAssertDebug(_callMessageHandler); - - return _callMessageHandler; - } -} - -- (void)setCallMessageHandler:(nullable id)callMessageHandler -{ - @synchronized(self) { - OWSAssertDebug(callMessageHandler); - OWSAssertDebug(!_callMessageHandler); - - _callMessageHandler = callMessageHandler; - } -} - */ - -- (nullable id)notificationsManager -{ - @synchronized(self) { - OWSAssertDebug(_notificationsManager); - - return _notificationsManager; - } -} - -- (void)setNotificationsManager:(nullable id)notificationsManager -{ - @synchronized(self) { - OWSAssertDebug(notificationsManager); - OWSAssertDebug(!_notificationsManager); - - _notificationsManager = notificationsManager; - } -} - -- (BOOL)isComplete -{ - return self.notificationsManager != nil; -} - -- (YapDatabaseConnection *)objectReadWriteConnection -{ - @synchronized(self) { - if (!_objectReadWriteConnection) { - _objectReadWriteConnection = self.primaryStorage.newDatabaseConnection; - } - return _objectReadWriteConnection; - } -} - -- (YapDatabaseConnection *)sessionStoreDBConnection { - @synchronized(self) { - if (!_sessionStoreDBConnection) { - _sessionStoreDBConnection = self.primaryStorage.newDatabaseConnection; - } - return _sessionStoreDBConnection; - } -} - -- (YapDatabaseConnection *)migrationDBConnection { - @synchronized(self) { - if (!_migrationDBConnection) { - _migrationDBConnection = self.primaryStorage.newDatabaseConnection; - } - return _migrationDBConnection; - } -} - -- (YapDatabaseConnection *)analyticsDBConnection { - @synchronized(self) { - if (!_analyticsDBConnection) { - _analyticsDBConnection = self.primaryStorage.newDatabaseConnection; - } - return _analyticsDBConnection; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Security/OWSFingerprint.h b/SignalServiceKit/src/Security/OWSFingerprint.h deleted file mode 100644 index de5cef652..000000000 --- a/SignalServiceKit/src/Security/OWSFingerprint.h +++ /dev/null @@ -1,50 +0,0 @@ -// Created by Michael Kirk on 9/14/16. -// Copyright © 2016 Open Whisper Systems. All rights reserved. - -NS_ASSUME_NONNULL_BEGIN - -@class UIImage; - -@interface OWSFingerprint : NSObject - -#pragma mark - Initializers - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithMyStableId:(NSString *)myStableId - myIdentityKey:(NSData *)myIdentityKeyWithoutKeyType - theirStableId:(NSString *)theirStableId - theirIdentityKey:(NSData *)theirIdentityKeyWithoutKeyType - theirName:(NSString *)theirName - hashIterations:(uint32_t)hashIterations NS_DESIGNATED_INITIALIZER; - -+ (instancetype)fingerprintWithMyStableId:(NSString *)myStableId - myIdentityKey:(NSData *)myIdentityKeyWithoutKeyType - theirStableId:(NSString *)theirStableId - theirIdentityKey:(NSData *)theirIdentityKeyWithoutKeyType - theirName:(NSString *)theirName - hashIterations:(uint32_t)hashIterations; - -+ (instancetype)fingerprintWithMyStableId:(NSString *)myStableId - myIdentityKey:(NSData *)myIdentityKeyWithoutKeyType - theirStableId:(NSString *)theirStableId - theirIdentityKey:(NSData *)theirIdentityKeyWithoutKeyType - theirName:(NSString *)theirName; - -#pragma mark - Properties - -@property (nonatomic, readonly) NSData *myStableIdData; -@property (nonatomic, readonly) NSData *myIdentityKey; -@property (nonatomic, readonly) NSString *theirStableId; -@property (nonatomic, readonly) NSData *theirStableIdData; -@property (nonatomic, readonly) NSData *theirIdentityKey; -@property (nonatomic, readonly) NSString *displayableText; -@property (nullable, nonatomic, readonly) UIImage *image; - -#pragma mark - Instance Methods - -- (BOOL)matchesLogicalFingerprintsData:(NSData *)data error:(NSError **)error; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Security/OWSFingerprint.m b/SignalServiceKit/src/Security/OWSFingerprint.m deleted file mode 100644 index e681caf33..000000000 --- a/SignalServiceKit/src/Security/OWSFingerprint.m +++ /dev/null @@ -1,334 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSFingerprint.h" -#import "OWSError.h" -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -static uint32_t const OWSFingerprintHashingVersion = 0; -static uint32_t const OWSFingerprintScannableFormatVersion = 1; -static uint32_t const OWSFingerprintDefaultHashIterations = 5200; - -@interface OWSFingerprint () - -@property (nonatomic, readonly) NSUInteger hashIterations; -@property (nonatomic, readonly) NSString *text; -@property (nonatomic, readonly) NSData *myFingerprintData; -@property (nonatomic, readonly) NSData *theirFingerprintData; -@property (nonatomic, readonly) NSString *theirName; - -@end - -#pragma mark - - -@implementation OWSFingerprint - -- (instancetype)initWithMyStableId:(NSString *)myStableId - myIdentityKey:(NSData *)myIdentityKeyWithoutKeyType - theirStableId:(NSString *)theirStableId - theirIdentityKey:(NSData *)theirIdentityKeyWithoutKeyType - theirName:(NSString *)theirName - hashIterations:(uint32_t)hashIterations -{ - OWSAssertDebug(theirIdentityKeyWithoutKeyType.length == 32); - OWSAssertDebug(myIdentityKeyWithoutKeyType.length == 32); - - self = [super init]; - if (!self) { - return self; - } - - _myStableIdData = [myStableId dataUsingEncoding:NSUTF8StringEncoding]; - _myIdentityKey = [myIdentityKeyWithoutKeyType prependKeyType]; - _theirStableId = theirStableId; - _theirStableIdData = [theirStableId dataUsingEncoding:NSUTF8StringEncoding]; - _theirIdentityKey = [theirIdentityKeyWithoutKeyType prependKeyType]; - _theirName = theirName; - _hashIterations = hashIterations; - - _myFingerprintData = [self dataForStableId:_myStableIdData publicKey:_myIdentityKey]; - _theirFingerprintData = [self dataForStableId:_theirStableIdData publicKey:_theirIdentityKey]; - - return self; -} - -+ (instancetype)fingerprintWithMyStableId:(NSString *)myStableId - myIdentityKey:(NSData *)myIdentityKeyWithoutKeyType - theirStableId:(NSString *)theirStableId - theirIdentityKey:(NSData *)theirIdentityKeyWithoutKeyType - theirName:(NSString *)theirName - hashIterations:(uint32_t)hashIterations -{ - return [[self alloc] initWithMyStableId:myStableId - myIdentityKey:myIdentityKeyWithoutKeyType - theirStableId:theirStableId - theirIdentityKey:theirIdentityKeyWithoutKeyType - theirName:theirName - hashIterations:hashIterations]; -} - -+ (instancetype)fingerprintWithMyStableId:(NSString *)myStableId - myIdentityKey:(NSData *)myIdentityKeyWithoutKeyType - theirStableId:(NSString *)theirStableId - theirIdentityKey:(NSData *)theirIdentityKeyWithoutKeyType - theirName:(NSString *)theirName -{ - return [[self alloc] initWithMyStableId:myStableId - myIdentityKey:myIdentityKeyWithoutKeyType - theirStableId:theirStableId - theirIdentityKey:theirIdentityKeyWithoutKeyType - theirName:theirName - hashIterations:OWSFingerprintDefaultHashIterations]; -} - -- (BOOL)matchesLogicalFingerprintsData:(NSData *)data error:(NSError **)error -{ - OWSAssertDebug(data.length > 0); - OWSAssertDebug(error); - - *error = nil; - FingerprintProtoLogicalFingerprints *_Nullable logicalFingerprints; - logicalFingerprints = [FingerprintProtoLogicalFingerprints parseData:data error:error]; - if (!logicalFingerprints || *error) { - OWSFailDebug(@"fingerprint failure: %@", *error); - - NSString *description = NSLocalizedString(@"PRIVACY_VERIFICATION_FAILURE_INVALID_QRCODE", @"alert body"); - *error = OWSErrorWithCodeDescription(OWSErrorCodePrivacyVerificationFailure, description); - return NO; - } - - if (logicalFingerprints.version < OWSFingerprintScannableFormatVersion) { - OWSLogWarn(@"Verification failed. They're running an old version."); - NSString *description - = NSLocalizedString(@"PRIVACY_VERIFICATION_FAILED_WITH_OLD_REMOTE_VERSION", @"alert body"); - *error = OWSErrorWithCodeDescription(OWSErrorCodePrivacyVerificationFailure, description); - return NO; - } - - if (logicalFingerprints.version > OWSFingerprintScannableFormatVersion) { - OWSLogWarn(@"Verification failed. We're running an old version."); - NSString *description = NSLocalizedString(@"PRIVACY_VERIFICATION_FAILED_WITH_OLD_LOCAL_VERSION", @"alert body"); - *error = OWSErrorWithCodeDescription(OWSErrorCodePrivacyVerificationFailure, description); - return NO; - } - - // Their local is *our* remote. - FingerprintProtoLogicalFingerprint *localFingerprint = logicalFingerprints.remoteFingerprint; - FingerprintProtoLogicalFingerprint *remoteFingerprint = logicalFingerprints.localFingerprint; - - if (![remoteFingerprint.identityData isEqual:[self scannableData:self.theirFingerprintData]]) { - OWSLogWarn(@"Verification failed. We have the wrong fingerprint for them"); - NSString *descriptionFormat = NSLocalizedString(@"PRIVACY_VERIFICATION_FAILED_I_HAVE_WRONG_KEY_FOR_THEM", - @"Alert body when verifying with {{contact name}}"); - NSString *description = [NSString stringWithFormat:descriptionFormat, self.theirName]; - *error = OWSErrorWithCodeDescription(OWSErrorCodePrivacyVerificationFailure, description); - return NO; - } - - if (![localFingerprint.identityData isEqual:[self scannableData:self.myFingerprintData]]) { - OWSLogWarn(@"Verification failed. They have the wrong fingerprint for us"); - NSString *descriptionFormat = NSLocalizedString(@"PRIVACY_VERIFICATION_FAILED_THEY_HAVE_WRONG_KEY_FOR_ME", - @"Alert body when verifying with {{contact name}}"); - NSString *description = [NSString stringWithFormat:descriptionFormat, self.theirName]; - *error = OWSErrorWithCodeDescription(OWSErrorCodePrivacyVerificationFailure, description); - return NO; - } - - OWSLogWarn(@"Verification Succeeded."); - return YES; -} - -- (NSString *)text -{ - NSString *myDisplayString = [self stringForFingerprintData:self.myFingerprintData]; - NSString *theirDisplayString = [self stringForFingerprintData:self.theirFingerprintData]; - - if ([theirDisplayString compare:myDisplayString] == NSOrderedAscending) { - return [NSString stringWithFormat:@"%@%@", theirDisplayString, myDisplayString]; - } else { - return [NSString stringWithFormat:@"%@%@", myDisplayString, theirDisplayString]; - } -} - -/** - * Formats numeric fingerprint, 3 lines in groups of 5 digits. - */ -- (NSString *)displayableText -{ - NSString *input = self.text; - - NSMutableArray *lines = [NSMutableArray new]; - - NSUInteger lineLength = self.text.length / 3; - for (uint i = 0; i < 3; i++) { - NSString *line = [input substringWithRange:NSMakeRange(i * lineLength, lineLength)]; - - NSMutableArray *chunks = [NSMutableArray new]; - for (uint i = 0; i < line.length / 5; i++) { - NSString *nextChunk = [line substringWithRange:NSMakeRange(i * 5, 5)]; - [chunks addObject:nextChunk]; - } - [lines addObject:[chunks componentsJoinedByString:@" "]]; - } - - return [lines componentsJoinedByString:@"\n"]; -} - - -- (NSData *)dataFromShort:(uint32_t)aShort -{ - uint8_t bytes[] = { - ((uint8_t)(aShort & 0xFF00) >> 8), - (uint8_t)(aShort & 0x00FF) - }; - - return [NSData dataWithBytes:bytes length:2]; -} - -/** - * An identifier for a mutable public key, belonging to an immutable identifier (stableId). - * - * This method is intended to be somewhat expensive to produce in order to be brute force adverse. - * - * @param stableIdData - * Immutable global identifier e.g. Signal Identifier, an e164 formatted phone number encoded as UTF-8 data - * @param publicKey - * The current public key for - * @return - * All-number textual representation - */ -- (NSData *)dataForStableId:(NSData *)stableIdData publicKey:(NSData *)publicKey -{ - OWSAssertDebug(stableIdData); - OWSAssertDebug(publicKey); - - NSData *versionData = [self dataFromShort:OWSFingerprintHashingVersion]; - NSMutableData *hash = [versionData mutableCopy]; - [hash appendData:publicKey]; - [hash appendData:stableIdData]; - - NSMutableData *_Nullable digestData = [[NSMutableData alloc] initWithLength:CC_SHA512_DIGEST_LENGTH]; - if (!digestData) { - @throw [NSException exceptionWithName:NSGenericException reason:@"Couldn't allocate buffer." userInfo:nil]; - } - for (int i = 0; i < self.hashIterations; i++) { - [hash appendData:publicKey]; - - if (hash.length >= UINT32_MAX) { - @throw [NSException exceptionWithName:@"Oversize Data" reason:@"Oversize hash." userInfo:nil]; - } - - CC_SHA512(hash.bytes, (uint32_t)hash.length, digestData.mutableBytes); - // TODO get rid of this loop-allocation - hash = [digestData mutableCopy]; - } - - return [hash copy]; -} - - -- (NSString *)stringForFingerprintData:(NSData *)data -{ - OWSAssertDebug(data); - - return [NSString stringWithFormat:@"%@%@%@%@%@%@", - [self encodedChunkFromData:data offset:0], - [self encodedChunkFromData:data offset:5], - [self encodedChunkFromData:data offset:10], - [self encodedChunkFromData:data offset:15], - [self encodedChunkFromData:data offset:20], - [self encodedChunkFromData:data offset:25]]; -} - -- (NSString *)encodedChunkFromData:(NSData *)data offset:(uint)offset -{ - OWSAssertDebug(data); - - uint8_t fiveBytes[5]; - [data getBytes:fiveBytes range:NSMakeRange(offset, 5)]; - - int chunk = [self uint64From5Bytes:fiveBytes] % 100000; - return [NSString stringWithFormat:@"%05d", chunk]; -} - -- (int64_t)uint64From5Bytes:(uint8_t[])bytes -{ - int64_t result = ((bytes[0] & 0xffLL) << 32) | - ((bytes[1] & 0xffLL) << 24) | - ((bytes[2] & 0xffLL) << 16) | - ((bytes[3] & 0xffLL) << 8) | - ((bytes[4] & 0xffLL)); - - return result; -} - -- (NSData *)scannableData:(NSData *)data -{ - return [data subdataWithRange:NSMakeRange(0, 32)]; -} - -- (nullable UIImage *)image -{ - FingerprintProtoLogicalFingerprintBuilder *remoteFingerprintBuilder = - [FingerprintProtoLogicalFingerprint builderWithIdentityData:[self scannableData:self.theirFingerprintData]]; - NSError *error; - FingerprintProtoLogicalFingerprint *_Nullable remoteFingerprint = - [remoteFingerprintBuilder buildAndReturnError:&error]; - if (!remoteFingerprint || error) { - OWSFailDebug(@"could not build proto: %@", error); - return nil; - } - - FingerprintProtoLogicalFingerprintBuilder *localFingerprintBuilder = - [FingerprintProtoLogicalFingerprint builderWithIdentityData:[self scannableData:self.myFingerprintData]]; - FingerprintProtoLogicalFingerprint *_Nullable localFingerprint = - [localFingerprintBuilder buildAndReturnError:&error]; - if (!localFingerprint || error) { - OWSFailDebug(@"could not build proto: %@", error); - return nil; - } - - FingerprintProtoLogicalFingerprintsBuilder *logicalFingerprintsBuilder = - [FingerprintProtoLogicalFingerprints builderWithVersion:OWSFingerprintScannableFormatVersion - localFingerprint:localFingerprint - remoteFingerprint:remoteFingerprint]; - - // Build ByteMode QR (Latin-1 encodable data) - NSData *_Nullable fingerprintData = [logicalFingerprintsBuilder buildSerializedDataAndReturnError:&error]; - if (!fingerprintData || error) { - OWSFailDebug(@"could not serialize proto: %@", error); - return nil; - } - - OWSLogDebug(@"Building fingerprint with data: %@", fingerprintData); - - CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"]; - [filter setDefaults]; - [filter setValue:fingerprintData forKey:@"inputMessage"]; - - CIImage *ciImage = [filter outputImage]; - if (!ciImage) { - OWSLogError(@"Failed to create QR image from fingerprint text: %@", self.text); - return nil; - } - - // UIImages backed by a CIImage won't render without antialiasing, so we convert the backign image to a CGImage, - // which can be scaled crisply. - CIContext *context = [CIContext contextWithOptions:nil]; - CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent]; - UIImage *qrImage = [UIImage imageWithCGImage:cgImage]; - CGImageRelease(cgImage); - - return qrImage; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Security/OWSFingerprintBuilder.h b/SignalServiceKit/src/Security/OWSFingerprintBuilder.h deleted file mode 100644 index ced8c7a50..000000000 --- a/SignalServiceKit/src/Security/OWSFingerprintBuilder.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class TSAccountManager; -@class OWSFingerprint; -@protocol ContactsManagerProtocol; - -@interface OWSFingerprintBuilder : NSObject - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithAccountManager:(TSAccountManager *)accountManager - contactsManager:(id)contactsManager NS_DESIGNATED_INITIALIZER; - -/** - * Builds a fingerprint combining your current credentials with their most recently accepted credentials. - */ -- (nullable OWSFingerprint *)fingerprintWithTheirSignalId:(NSString *)theirSignalId; - -/** - * Builds a fingerprint combining your current credentials with the specified identity key. - * You can use this to present a new identity key for verification. - */ -- (OWSFingerprint *)fingerprintWithTheirSignalId:(NSString *)theirSignalId theirIdentityKey:(NSData *)theirIdentityKey; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Security/OWSFingerprintBuilder.m b/SignalServiceKit/src/Security/OWSFingerprintBuilder.m deleted file mode 100644 index 8ad4f31c4..000000000 --- a/SignalServiceKit/src/Security/OWSFingerprintBuilder.m +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSFingerprintBuilder.h" -#import "ContactsManagerProtocol.h" -#import "OWSFingerprint.h" -#import "OWSIdentityManager.h" -#import "TSAccountManager.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSFingerprintBuilder () - -@property (nonatomic, readonly) TSAccountManager *accountManager; -@property (nonatomic, readonly) id contactsManager; - -@end - -@implementation OWSFingerprintBuilder - -- (instancetype)initWithAccountManager:(TSAccountManager *)accountManager - contactsManager:(id)contactsManager -{ - self = [super init]; - if (!self) { - return self; - } - - _accountManager = accountManager; - _contactsManager = contactsManager; - - return self; -} - -- (nullable OWSFingerprint *)fingerprintWithTheirSignalId:(NSString *)theirSignalId -{ - NSData *_Nullable theirIdentityKey = [[OWSIdentityManager sharedManager] identityKeyForRecipientId:theirSignalId]; - - if (theirIdentityKey == nil) { - OWSFailDebug(@"Missing their identity key"); - return nil; - } - - return [self fingerprintWithTheirSignalId:theirSignalId theirIdentityKey:theirIdentityKey]; -} - -- (OWSFingerprint *)fingerprintWithTheirSignalId:(NSString *)theirSignalId theirIdentityKey:(NSData *)theirIdentityKey -{ - NSString *theirName = [self.contactsManager displayNameForPhoneIdentifier:theirSignalId]; - - NSString *mySignalId = [self.accountManager localNumber]; - NSData *myIdentityKey = [[OWSIdentityManager sharedManager] identityKeyPair].publicKey; - - return [OWSFingerprint fingerprintWithMyStableId:mySignalId - myIdentityKey:myIdentityKey - theirStableId:theirSignalId - theirIdentityKey:theirIdentityKey - theirName:theirName]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Security/OWSHTTPSecurityPolicy.h b/SignalServiceKit/src/Security/OWSHTTPSecurityPolicy.h deleted file mode 100644 index 2980c6112..000000000 --- a/SignalServiceKit/src/Security/OWSHTTPSecurityPolicy.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -extern NSData *SSKTextSecureServiceCertificateData(void); - -@interface OWSHTTPSecurityPolicy : AFSecurityPolicy - -+ (instancetype)sharedPolicy; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Security/OWSHTTPSecurityPolicy.m b/SignalServiceKit/src/Security/OWSHTTPSecurityPolicy.m deleted file mode 100644 index 72ea99e57..000000000 --- a/SignalServiceKit/src/Security/OWSHTTPSecurityPolicy.m +++ /dev/null @@ -1,105 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSHTTPSecurityPolicy.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSHTTPSecurityPolicy - -+ (instancetype)sharedPolicy { - static OWSHTTPSecurityPolicy *httpSecurityPolicy = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - httpSecurityPolicy = [[self alloc] initWithOWSPolicy]; - }); - return httpSecurityPolicy; -} - -- (instancetype)initWithOWSPolicy { - self = [[super class] defaultPolicy]; - - if (self) { - self.pinnedCertificates = [NSSet setWithArray:@[ - [self.class certificateDataForService:@"textsecure"] - ]]; - } - - return self; -} - -+ (NSData *)dataFromCertificateFileForService:(NSString *)service -{ - NSBundle *bundle = [NSBundle bundleForClass:self.class]; - NSString *path = [bundle pathForResource:service ofType:@"cer"]; - - if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { - OWSFail(@"Missing signing certificate for service %@", service); - } - - NSData *data = [NSData dataWithContentsOfFile:path]; - OWSAssert(data.length > 0); - - return data; -} - -+ (NSData *)certificateDataForService:(NSString *)service { - SecCertificateRef certRef = [self certificateForService:service]; - return (__bridge_transfer NSData *)SecCertificateCopyData(certRef); -} - -+ (SecCertificateRef)certificateForService:(NSString *)service -{ - NSData *certificateData = [self dataFromCertificateFileForService:service]; - return SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(certificateData)); -} - -- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(nullable NSString *)domain -{ - NSMutableArray *policies = [NSMutableArray array]; - [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)]; - - if (SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies) != errSecSuccess) { - OWSLogError(@"The trust policy couldn't be set."); - return NO; - } - - NSMutableArray *pinnedCertificates = [NSMutableArray array]; - for (NSData *certificateData in self.pinnedCertificates) { - [pinnedCertificates - addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)]; - } - - if (SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates) != errSecSuccess) { - OWSLogError(@"The anchor certificates couldn't be set."); - return NO; - } - - if (!AFServerTrustIsValid(serverTrust)) { - return NO; - } - - return YES; -} - -static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) { - BOOL isValid = NO; - SecTrustResultType result; - __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out); - - isValid = (result == kSecTrustResultUnspecified); - -_out: - return isValid; -} - -NSData *SSKTextSecureServiceCertificateData() -{ - return [OWSHTTPSecurityPolicy dataFromCertificateFileForService:@"textsecure"]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Security/OWSRecipientIdentity.h b/SignalServiceKit/src/Security/OWSRecipientIdentity.h deleted file mode 100644 index a209b3854..000000000 --- a/SignalServiceKit/src/Security/OWSRecipientIdentity.h +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSYapDatabaseObject.h" - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSUInteger, OWSVerificationState) { - OWSVerificationStateDefault, - OWSVerificationStateVerified, - OWSVerificationStateNoLongerVerified, -}; - -@class SSKProtoVerified; - -NSString *OWSVerificationStateToString(OWSVerificationState verificationState); -SSKProtoVerified *_Nullable BuildVerifiedProtoWithRecipientId(NSString *destinationRecipientId, - NSData *identityKey, - OWSVerificationState verificationState, - NSUInteger paddingBytesLength); - -@interface OWSRecipientIdentity : TSYapDatabaseObject - -@property (nonatomic, readonly) NSString *recipientId; -@property (nonatomic, readonly) NSData *identityKey; -@property (nonatomic, readonly) NSDate *createdAt; -@property (nonatomic, readonly) BOOL isFirstKnownKey; - -#pragma mark - Verification State - -@property (atomic, readonly) OWSVerificationState verificationState; - -- (void)updateWithVerificationState:(OWSVerificationState)verificationState - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -#pragma mark - Initializers - -- (instancetype)initWithUniqueId:(NSString *_Nullable)uniqueId NS_UNAVAILABLE; - -- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithRecipientId:(NSString *)recipientId - identityKey:(NSData *)identityKey - isFirstKnownKey:(BOOL)isFirstKnownKey - createdAt:(NSDate *)createdAt - verificationState:(OWSVerificationState)verificationState NS_DESIGNATED_INITIALIZER; - -#pragma mark - debug - -+ (void)printAllIdentities; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Security/OWSRecipientIdentity.m b/SignalServiceKit/src/Security/OWSRecipientIdentity.m deleted file mode 100644 index 79ed2d660..000000000 --- a/SignalServiceKit/src/Security/OWSRecipientIdentity.m +++ /dev/null @@ -1,184 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSRecipientIdentity.h" -#import "OWSIdentityManager.h" -#import "OWSPrimaryStorage+SessionStore.h" -#import "OWSPrimaryStorage.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *OWSVerificationStateToString(OWSVerificationState verificationState) -{ - switch (verificationState) { - case OWSVerificationStateDefault: - return @"OWSVerificationStateDefault"; - case OWSVerificationStateVerified: - return @"OWSVerificationStateVerified"; - case OWSVerificationStateNoLongerVerified: - return @"OWSVerificationStateNoLongerVerified"; - } -} - -SSKProtoVerifiedState OWSVerificationStateToProtoState(OWSVerificationState verificationState) -{ - switch (verificationState) { - case OWSVerificationStateDefault: - return SSKProtoVerifiedStateDefault; - case OWSVerificationStateVerified: - return SSKProtoVerifiedStateVerified; - case OWSVerificationStateNoLongerVerified: - return SSKProtoVerifiedStateUnverified; - } -} - -SSKProtoVerified *_Nullable BuildVerifiedProtoWithRecipientId(NSString *destinationRecipientId, - NSData *identityKey, - OWSVerificationState verificationState, - NSUInteger paddingBytesLength) -{ - OWSCAssertDebug(identityKey.length == kIdentityKeyLength); - OWSCAssertDebug(destinationRecipientId.length > 0); - // we only sync user's marking as un/verified. Never sync the conflicted state, the sibling device - // will figure that out on it's own. - OWSCAssertDebug(verificationState != OWSVerificationStateNoLongerVerified); - - SSKProtoVerifiedBuilder *verifiedBuilder = [SSKProtoVerified builderWithDestination:destinationRecipientId]; - verifiedBuilder.identityKey = identityKey; - verifiedBuilder.state = OWSVerificationStateToProtoState(verificationState); - - if (paddingBytesLength > 0) { - // We add the same amount of padding in the VerificationStateSync message and it's coresponding NullMessage so - // that the sync message is indistinguishable from an outgoing Sent transcript corresponding to the NullMessage. - // We pad the NullMessage so as to obscure it's content. The sync message (like all sync messages) will be - // *additionally* padded by the superclass while being sent. The end result is we send a NullMessage of a - // non-distinct size, and a verification sync which is ~1-512 bytes larger then that. - verifiedBuilder.nullMessage = [Cryptography generateRandomBytes:paddingBytesLength]; - } - - NSError *error; - SSKProtoVerified *_Nullable verifiedProto = [verifiedBuilder buildAndReturnError:&error]; - if (error || !verifiedProto) { - OWSCFailDebug(@"%@ could not build protobuf: %@", @"[BuildVerifiedProtoWithRecipientId]", error); - return nil; - } - return verifiedProto; -} - -@interface OWSRecipientIdentity () - -@property (atomic) OWSVerificationState verificationState; - -@end - -/** - * Record for a recipients identity key and some meta data around it used to make trust decisions. - * - * NOTE: Instances of this class MUST only be retrieved/persisted via it's internal `dbConnection`, - * which makes some special accomodations to enforce consistency. - */ -@implementation OWSRecipientIdentity - -- (instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - - if (self) { - if (![coder decodeObjectForKey:@"verificationState"]) { - _verificationState = OWSVerificationStateDefault; - } - } - - return self; -} - -- (instancetype)initWithRecipientId:(NSString *)recipientId - identityKey:(NSData *)identityKey - isFirstKnownKey:(BOOL)isFirstKnownKey - createdAt:(NSDate *)createdAt - verificationState:(OWSVerificationState)verificationState -{ - self = [super initWithUniqueId:recipientId]; - if (!self) { - return self; - } - - _recipientId = recipientId; - _identityKey = identityKey; - _isFirstKnownKey = isFirstKnownKey; - _createdAt = createdAt; - _verificationState = verificationState; - - return self; -} - -- (void)updateWithVerificationState:(OWSVerificationState)verificationState - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - // Ensure changes are persisted without clobbering any work done on another thread or instance. - [self updateWithChangeBlock:^(OWSRecipientIdentity *_Nonnull obj) { - obj.verificationState = verificationState; - } - transaction:transaction]; -} - -- (void)updateWithChangeBlock:(void (^)(OWSRecipientIdentity *obj))changeBlock - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - changeBlock(self); - - OWSRecipientIdentity *latest = [[self class] fetchObjectWithUniqueID:self.uniqueId transaction:transaction]; - if (latest == nil) { - [self saveWithTransaction:transaction]; - return; - } - - changeBlock(latest); - [latest saveWithTransaction:transaction]; -} - -- (void)updateWithChangeBlock:(void (^)(OWSRecipientIdentity *obj))changeBlock -{ - changeBlock(self); - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - OWSRecipientIdentity *latest = [[self class] fetchObjectWithUniqueID:self.uniqueId transaction:transaction]; - if (latest == nil) { - [self saveWithTransaction:transaction]; - return; - } - - changeBlock(latest); - [latest saveWithTransaction:transaction]; - }]; -} - -#pragma mark - debug - -+ (void)printAllIdentities -{ - OWSLogInfo(@"### All Recipient Identities ###"); - __block int count = 0; - [self enumerateCollectionObjectsUsingBlock:^(id obj, BOOL *stop) { - count++; - if (![obj isKindOfClass:[self class]]) { - OWSFailDebug(@"unexpected object in collection: %@", obj); - return; - } - OWSRecipientIdentity *recipientIdentity = (OWSRecipientIdentity *)obj; - - OWSLogInfo(@"Identity %d: %@", count, recipientIdentity.debugDescription); - }]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/SessionServiceKit.h b/SignalServiceKit/src/SessionServiceKit.h deleted file mode 100644 index 51c23a2cf..000000000 --- a/SignalServiceKit/src/SessionServiceKit.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -// Anything used by Swift outside of the framework must be imported. -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import diff --git a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+Calling.h b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+Calling.h deleted file mode 100644 index 10172082e..000000000 --- a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+Calling.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSPrimaryStorage.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSPrimaryStorage (Calling) - -// phoneNumber is an e164 formatted phone number. -// -// callKitId is expected to have CallKitCallManager.kAnonymousCallHandlePrefix. -- (void)setPhoneNumber:(NSString *)phoneNumber forCallKitId:(NSString *)callKitId; - -// returns an e164 formatted phone number or nil if no -// record can be found. -// -// callKitId is expected to have CallKitCallManager.kAnonymousCallHandlePrefix. -- (NSString *)phoneNumberForCallKitId:(NSString *)callKitId; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+Calling.m b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+Calling.m deleted file mode 100644 index 0feb28397..000000000 --- a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+Calling.m +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSPrimaryStorage+Calling.h" -#import "YapDatabaseConnection+OWS.h" - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSPrimaryStorageCallKitIdToPhoneNumberCollection = @"TSStorageManagerCallKitIdToPhoneNumberCollection"; - -@implementation OWSPrimaryStorage (Calling) - -- (void)setPhoneNumber:(NSString *)phoneNumber forCallKitId:(NSString *)callKitId -{ - OWSAssertDebug(phoneNumber.length > 0); - OWSAssertDebug(callKitId.length > 0); - - [self.dbReadWriteConnection setObject:phoneNumber - forKey:callKitId - inCollection:OWSPrimaryStorageCallKitIdToPhoneNumberCollection]; -} - -- (NSString *)phoneNumberForCallKitId:(NSString *)callKitId -{ - OWSAssertDebug(callKitId.length > 0); - - return - [self.dbReadConnection objectForKey:callKitId inCollection:OWSPrimaryStorageCallKitIdToPhoneNumberCollection]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.h b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.h deleted file mode 100644 index d076d0f9f..000000000 --- a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSPrimaryStorage.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSPrimaryStorage (PreKeyStore) - -- (NSArray *)generatePreKeyRecords; -- (NSArray *)generatePreKeyRecords:(int)batchSize; -- (void)storePreKeyRecords:(NSArray *)preKeyRecords NS_SWIFT_NAME(storePreKeyRecords(_:)); - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.m b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.m deleted file mode 100644 index 70e92b2a3..000000000 --- a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+PreKeyStore.m +++ /dev/null @@ -1,114 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSPrimaryStorage+PreKeyStore.h" -#import "OWSPrimaryStorage+keyFromIntLong.h" -#import "TSStorageKeys.h" -#import "YapDatabaseConnection+OWS.h" -#import -#import -#import - -#define OWSPrimaryStoragePreKeyStoreCollection @"TSStorageManagerPreKeyStoreCollection" -#define TSNextPrekeyIdKey @"TSStorageInternalSettingsNextPreKeyId" -#define BATCH_SIZE 100 - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSPrimaryStorage (PreKeyStore) - -- (NSArray *)generatePreKeyRecords -{ - return [self generatePreKeyRecords:BATCH_SIZE]; -} - -- (NSArray *)generatePreKeyRecords:(int)batchSize -{ - NSMutableArray *preKeyRecords = [NSMutableArray array]; - - @synchronized(self) - { - int preKeyId = [self nextPreKeyId:batchSize]; - - OWSLogInfo(@"building %d new preKeys starting from preKeyId: %d", batchSize, preKeyId); - for (int i = 0; i < batchSize; i++) { - ECKeyPair *keyPair = [Curve25519 generateKeyPair]; - PreKeyRecord *record = [[PreKeyRecord alloc] initWithId:preKeyId keyPair:keyPair]; - - [preKeyRecords addObject:record]; - preKeyId++; - } - - [self.dbReadWriteConnection setInt:preKeyId - forKey:TSNextPrekeyIdKey - inCollection:TSStorageInternalSettingsCollection]; - } - return preKeyRecords; -} - -- (void)storePreKeyRecords:(NSArray *)preKeyRecords -{ - for (PreKeyRecord *record in preKeyRecords) { - [self.dbReadWriteConnection setObject:record - forKey:[self keyFromInt:record.Id] - inCollection:OWSPrimaryStoragePreKeyStoreCollection]; - } -} - -- (PreKeyRecord *)throws_loadPreKey:(int)preKeyId -{ - PreKeyRecord *preKeyRecord = [self.dbReadConnection preKeyRecordForKey:[self keyFromInt:preKeyId] - inCollection:OWSPrimaryStoragePreKeyStoreCollection]; - - if (!preKeyRecord) { - OWSRaiseException(InvalidKeyIdException, @"No pre key found matching key id"); - } else { - return preKeyRecord; - } -} - -- (void)storePreKey:(int)preKeyId preKeyRecord:(PreKeyRecord *)record -{ - [self.dbReadWriteConnection setObject:record - forKey:[self keyFromInt:preKeyId] - inCollection:OWSPrimaryStoragePreKeyStoreCollection]; -} - -- (BOOL)containsPreKey:(int)preKeyId -{ - PreKeyRecord *preKeyRecord = [self.dbReadConnection preKeyRecordForKey:[self keyFromInt:preKeyId] - inCollection:OWSPrimaryStoragePreKeyStoreCollection]; - return (preKeyRecord != nil); -} - -- (void)removePreKey:(int)preKeyId protocolContext:(nullable id)protocolContext -{ - if ([protocolContext isKindOfClass:YapDatabaseReadWriteTransaction.class]) { - [(YapDatabaseReadWriteTransaction *)protocolContext removeObjectForKey:[self keyFromInt:preKeyId] inCollection:OWSPrimaryStoragePreKeyStoreCollection]; - } else { - [self.dbReadWriteConnection removeObjectForKey:[self keyFromInt:preKeyId] inCollection:OWSPrimaryStoragePreKeyStoreCollection]; - } -} - -- (int)nextPreKeyId:(int)batchSize -{ - int lastPreKeyId = - [self.dbReadConnection intForKey:TSNextPrekeyIdKey inCollection:TSStorageInternalSettingsCollection]; - - if (lastPreKeyId < 1) { - // One-time prekey ids must be > 0 and < kPreKeyOfLastResortId. - lastPreKeyId = 1 + arc4random_uniform(kPreKeyOfLastResortId - (batchSize + 1)); - } else if (lastPreKeyId > kPreKeyOfLastResortId - batchSize) { - // We want to "overflow" to 1 when we reach the "prekey of last resort" id - // to avoid biasing towards higher values. - lastPreKeyId = 1; - } - OWSCAssertDebug(lastPreKeyId > 0 && lastPreKeyId < kPreKeyOfLastResortId); - - return lastPreKeyId; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SessionStore.h b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SessionStore.h deleted file mode 100644 index db6007207..000000000 --- a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SessionStore.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSPrimaryStorage.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSPrimaryStorage (SessionStore) - -- (void)archiveAllSessionsForContact:(NSString *)contactIdentifier protocolContext:(nullable id)protocolContext; - -#pragma mark - Debug - -- (void)resetSessionStore:(YapDatabaseReadWriteTransaction *)transaction; - -#if DEBUG -- (void)snapshotSessionStore:(YapDatabaseReadWriteTransaction *)transaction; -- (void)restoreSessionStore:(YapDatabaseReadWriteTransaction *)transaction; -#endif - -- (void)printAllSessions; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SessionStore.m b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SessionStore.m deleted file mode 100644 index 9dea9ebc8..000000000 --- a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SessionStore.m +++ /dev/null @@ -1,273 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSPrimaryStorage+SessionStore.h" -#import "OWSFileSystem.h" -#import "SSKEnvironment.h" -#import "YapDatabaseConnection+OWS.h" -#import "YapDatabaseTransaction+OWS.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSPrimaryStorageSessionStoreCollection = @"TSStorageManagerSessionStoreCollection"; -NSString *const kSessionStoreDBConnectionKey = @"kSessionStoreDBConnectionKey"; - -@implementation OWSPrimaryStorage (SessionStore) - -/** - * Special purpose dbConnection which disables the object cache to better enforce transaction semantics on the store. - * Note that it's still technically possible to access this collection from a different collection, - * but that should be considered a bug. - */ -+ (YapDatabaseConnection *)sessionStoreDBConnection -{ - return SSKEnvironment.shared.sessionStoreDBConnection; -} - -- (YapDatabaseConnection *)sessionStoreDBConnection -{ - return [[self class] sessionStoreDBConnection]; -} - -#pragma mark - SessionStore - -- (SessionRecord *)loadSession:(NSString *)contactIdentifier - deviceId:(int)deviceId - protocolContext:(nullable id)protocolContext -{ - OWSAssertDebug(contactIdentifier.length > 0); - OWSAssertDebug(deviceId >= 0); - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); - - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - NSDictionary *_Nullable dictionary = - [transaction objectForKey:contactIdentifier inCollection:OWSPrimaryStorageSessionStoreCollection]; - - SessionRecord *record; - - if (dictionary) { - record = [dictionary objectForKey:@(deviceId)]; - } - - if (!record) { - return [SessionRecord new]; - } - - return record; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (NSArray *)subDevicesSessions:(NSString *)contactIdentifier protocolContext:(nullable id)protocolContext -{ - OWSAssertDebug(contactIdentifier.length > 0); - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); - - // Deprecated. We aren't currently using this anywhere, but it's "required" by the SessionStore protocol. - // If we are going to start using it I'd want to re-verify it works as intended. - OWSFailDebug(@"subDevicesSessions is deprecated"); - - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - NSDictionary *_Nullable dictionary = - [transaction objectForKey:contactIdentifier inCollection:OWSPrimaryStorageSessionStoreCollection]; - - return dictionary ? dictionary.allKeys : @[]; -} -#pragma clang diagnostic pop - -- (void)storeSession:(NSString *)contactIdentifier - deviceId:(int)deviceId - session:(SessionRecord *)session - protocolContext:protocolContext -{ - OWSAssertDebug(contactIdentifier.length > 0); - OWSAssertDebug(deviceId >= 0); - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); - - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - // We need to ensure subsequent usage of this SessionRecord does not consider this session as "fresh". Normally this - // is achieved by marking things as "not fresh" at the point of deserialization - when we fetch a SessionRecord from - // YapDB (initWithCoder:). However, because YapDB has an object cache, rather than fetching/deserializing, it's - // possible we'd get back *this* exact instance of the object (which, at this point, is still potentially "fresh"), - // thus we explicitly mark this instance as "unfresh", any time we save. - // NOTE: this may no longer be necessary now that we have a non-caching session db connection. - [session markAsUnFresh]; - - NSDictionary *immutableDictionary = - [transaction objectForKey:contactIdentifier inCollection:OWSPrimaryStorageSessionStoreCollection]; - - NSMutableDictionary *dictionary - = (immutableDictionary ? [immutableDictionary mutableCopy] : [NSMutableDictionary new]); - - [dictionary setObject:session forKey:@(deviceId)]; - - [transaction setObject:[dictionary copy] - forKey:contactIdentifier - inCollection:OWSPrimaryStorageSessionStoreCollection]; -} - -- (BOOL)containsSession:(NSString *)contactIdentifier - deviceId:(int)deviceId - protocolContext:(nullable id)protocolContext -{ - OWSAssertDebug(contactIdentifier.length > 0); - OWSAssertDebug(deviceId >= 0); - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); - - return [self loadSession:contactIdentifier deviceId:deviceId protocolContext:protocolContext] - .sessionState.hasSenderChain; -} - -- (void)deleteSessionForContact:(NSString *)contactIdentifier - deviceId:(int)deviceId - protocolContext:(nullable id)protocolContext -{ - OWSAssertDebug(contactIdentifier.length > 0); - OWSAssertDebug(deviceId >= 0); - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); - - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - OWSLogInfo( - @"[OWSPrimaryStorage (SessionStore)] deleting session for contact: %@ device: %d", contactIdentifier, deviceId); - - NSDictionary *immutableDictionary = - [transaction objectForKey:contactIdentifier inCollection:OWSPrimaryStorageSessionStoreCollection]; - - NSMutableDictionary *dictionary - = (immutableDictionary ? [immutableDictionary mutableCopy] : [NSMutableDictionary new]); - - [dictionary removeObjectForKey:@(deviceId)]; - - [transaction setObject:[dictionary copy] - forKey:contactIdentifier - inCollection:OWSPrimaryStorageSessionStoreCollection]; -} - -- (void)deleteAllSessionsForContact:(NSString *)contactIdentifier protocolContext:(nullable id)protocolContext -{ - OWSAssertDebug(contactIdentifier.length > 0); - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); - - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - OWSLogInfo(@"[OWSPrimaryStorage (SessionStore)] deleting all sessions for contact:%@", contactIdentifier); - - [transaction removeObjectForKey:contactIdentifier inCollection:OWSPrimaryStorageSessionStoreCollection]; -} - -- (void)archiveAllSessionsForContact:(NSString *)contactIdentifier protocolContext:(nullable id)protocolContext -{ - OWSAssertDebug(contactIdentifier.length > 0); - OWSAssertDebug([protocolContext isKindOfClass:[YapDatabaseReadWriteTransaction class]]); - - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - OWSLogInfo(@"[OWSPrimaryStorage (SessionStore)] archiving all sessions for contact: %@", contactIdentifier); - - __block NSDictionary *sessionRecords = - [transaction objectForKey:contactIdentifier inCollection:OWSPrimaryStorageSessionStoreCollection]; - - for (id deviceId in sessionRecords) { - id object = sessionRecords[deviceId]; - if (![object isKindOfClass:[SessionRecord class]]) { - OWSFailDebug(@"Unexpected object in session dict: %@", [object class]); - continue; - } - - SessionRecord *sessionRecord = (SessionRecord *)object; - [sessionRecord archiveCurrentState]; - } - - [transaction setObject:sessionRecords - forKey:contactIdentifier - inCollection:OWSPrimaryStorageSessionStoreCollection]; -} - -#pragma mark - debug - -- (void)resetSessionStore:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - OWSLogWarn(@"resetting session store"); - - [transaction removeAllObjectsInCollection:OWSPrimaryStorageSessionStoreCollection]; -} - -- (void)printAllSessions -{ - NSString *tag = @"[OWSPrimaryStorage (SessionStore)]"; - [self.sessionStoreDBConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - OWSLogDebug(@"%@ All Sessions:", tag); - [transaction - enumerateKeysAndObjectsInCollection:OWSPrimaryStorageSessionStoreCollection - usingBlock:^(NSString *_Nonnull key, - id _Nonnull deviceSessionsObject, - BOOL *_Nonnull stop) { - if (![deviceSessionsObject isKindOfClass:[NSDictionary class]]) { - OWSFailDebug(@"%@ Unexpected type: %@ in collection.", - tag, - [deviceSessionsObject class]); - return; - } - NSDictionary *deviceSessions = (NSDictionary *)deviceSessionsObject; - - OWSLogDebug(@"%@ Sessions for recipient: %@", tag, key); - [deviceSessions enumerateKeysAndObjectsUsingBlock:^( - id _Nonnull key, id _Nonnull sessionRecordObject, BOOL *_Nonnull stop) { - if (![sessionRecordObject isKindOfClass:[SessionRecord class]]) { - OWSFailDebug(@"%@ Unexpected type: %@ in collection.", - tag, - [sessionRecordObject class]); - return; - } - SessionRecord *sessionRecord = (SessionRecord *)sessionRecordObject; - SessionState *activeState = [sessionRecord sessionState]; - NSArray *previousStates = - [sessionRecord previousSessionStates]; - OWSLogDebug(@"%@ Device: %@ SessionRecord: %@ activeSessionState: " - @"%@ previousSessionStates: %@", - tag, - key, - sessionRecord, - activeState, - previousStates); - }]; - }]; - }]; -} - -#if DEBUG -- (NSString *)snapshotFilePath -{ - // Prefix name with period "." so that backups will ignore these snapshots. - NSString *dirPath = [OWSFileSystem appDocumentDirectoryPath]; - return [dirPath stringByAppendingPathComponent:@".session-snapshot"]; -} - -- (void)snapshotSessionStore:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - [transaction snapshotCollection:OWSPrimaryStorageSessionStoreCollection snapshotFilePath:self.snapshotFilePath]; -} - -- (void)restoreSessionStore:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - [transaction restoreSnapshotOfCollection:OWSPrimaryStorageSessionStoreCollection - snapshotFilePath:self.snapshotFilePath]; -} -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.h b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.h deleted file mode 100644 index 5f25bc827..000000000 --- a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSPrimaryStorage.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -// Used for testing -extern NSString *const OWSPrimaryStorageSignedPreKeyStoreCollection; - -@interface OWSPrimaryStorage (SignedPreKeyStore) - -- (SignedPreKeyRecord *)generateRandomSignedRecord; - -- (nullable SignedPreKeyRecord *)loadSignedPrekeyOrNil:(int)signedPreKeyId; - -// Returns nil if no current signed prekey id is found. -- (nullable NSNumber *)currentSignedPrekeyId; -- (void)setCurrentSignedPrekeyId:(int)value; -- (nullable SignedPreKeyRecord *)currentSignedPreKey; - -#pragma mark - Prekey update failures - -- (int)prekeyUpdateFailureCount; -- (void)clearPrekeyUpdateFailureCount; -- (int)incrementPrekeyUpdateFailureCount; - -- (nullable NSDate *)firstPrekeyUpdateFailureDate; -- (void)setFirstPrekeyUpdateFailureDate:(nonnull NSDate *)value; -- (void)clearFirstPrekeyUpdateFailureDate; - -#pragma mark - Debugging - -- (void)logSignedPreKeyReport; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.m b/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.m deleted file mode 100644 index 413ddfe67..000000000 --- a/SignalServiceKit/src/Storage/AxolotlStore/OWSPrimaryStorage+SignedPreKeyStore.m +++ /dev/null @@ -1,225 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSPrimaryStorage+SignedPreKeyStore.h" -#import "OWSIdentityManager.h" -#import "OWSPrimaryStorage+PreKeyStore.h" -#import "OWSPrimaryStorage+keyFromIntLong.h" -#import "YapDatabaseConnection+OWS.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSPrimaryStorageSignedPreKeyStoreCollection = @"TSStorageManagerSignedPreKeyStoreCollection"; -NSString *const OWSPrimaryStorageSignedPreKeyMetadataCollection = @"TSStorageManagerSignedPreKeyMetadataCollection"; -NSString *const OWSPrimaryStorageKeyPrekeyUpdateFailureCount = @"prekeyUpdateFailureCount"; -NSString *const OWSPrimaryStorageKeyFirstPrekeyUpdateFailureDate = @"firstPrekeyUpdateFailureDate"; -NSString *const OWSPrimaryStorageKeyPrekeyCurrentSignedPrekeyId = @"currentSignedPrekeyId"; - -@implementation OWSPrimaryStorage (SignedPreKeyStore) - -- (SignedPreKeyRecord *)generateRandomSignedRecord -{ - ECKeyPair *keyPair = [Curve25519 generateKeyPair]; - - // Signed prekey ids must be > 0. - int preKeyId = 1 + arc4random_uniform(INT32_MAX - 1); - ECKeyPair *_Nullable identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair]; - OWSAssert(identityKeyPair); - - @try { - NSData *signature = [Ed25519 throws_sign:keyPair.publicKey.prependKeyType withKeyPair:identityKeyPair]; - return [[SignedPreKeyRecord alloc] initWithId:preKeyId - keyPair:keyPair - signature:signature - generatedAt:[NSDate date]]; - } @catch (NSException *exception) { - // throws_sign only throws when the data to sign is empty or `keyPair` is nil. - // Neither of which should happen. - OWSFail(@"exception: %@", exception); - return nil; - } -} - -- (SignedPreKeyRecord *)throws_loadSignedPrekey:(int)signedPreKeyId -{ - SignedPreKeyRecord *preKeyRecord = - [self.dbReadConnection signedPreKeyRecordForKey:[self keyFromInt:signedPreKeyId] - inCollection:OWSPrimaryStorageSignedPreKeyStoreCollection]; - - if (!preKeyRecord) { - OWSRaiseException(InvalidKeyIdException, @"No signed pre key found matching key id"); - } else { - return preKeyRecord; - } -} - -- (nullable SignedPreKeyRecord *)loadSignedPrekeyOrNil:(int)signedPreKeyId -{ - return [self.dbReadConnection signedPreKeyRecordForKey:[self keyFromInt:signedPreKeyId] - inCollection:OWSPrimaryStorageSignedPreKeyStoreCollection]; -} - -- (NSArray *)loadSignedPreKeys -{ - NSMutableArray *signedPreKeyRecords = [NSMutableArray array]; - - YapDatabaseConnection *conn = [self newDatabaseConnection]; - - [conn readWithBlock:^(YapDatabaseReadTransaction *transaction) { - [transaction enumerateRowsInCollection:OWSPrimaryStorageSignedPreKeyStoreCollection - usingBlock:^(NSString *key, id object, id metadata, BOOL *stop) { - [signedPreKeyRecords addObject:object]; - }]; - }]; - - return signedPreKeyRecords; -} - -- (void)storeSignedPreKey:(int)signedPreKeyId signedPreKeyRecord:(SignedPreKeyRecord *)signedPreKeyRecord -{ - [self.dbReadWriteConnection setObject:signedPreKeyRecord - forKey:[self keyFromInt:signedPreKeyId] - inCollection:OWSPrimaryStorageSignedPreKeyStoreCollection]; -} - -- (BOOL)containsSignedPreKey:(int)signedPreKeyId -{ - PreKeyRecord *preKeyRecord = - [self.dbReadConnection signedPreKeyRecordForKey:[self keyFromInt:signedPreKeyId] - inCollection:OWSPrimaryStorageSignedPreKeyStoreCollection]; - return (preKeyRecord != nil); -} - -- (void)removeSignedPreKey:(int)signedPrekeyId -{ - [self.dbReadWriteConnection removeObjectForKey:[self keyFromInt:signedPrekeyId] - inCollection:OWSPrimaryStorageSignedPreKeyStoreCollection]; -} - -- (nullable NSNumber *)currentSignedPrekeyId -{ - return [self.dbReadConnection objectForKey:OWSPrimaryStorageKeyPrekeyCurrentSignedPrekeyId - inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; -} - -- (void)setCurrentSignedPrekeyId:(int)value -{ - [self.dbReadWriteConnection setObject:@(value) - forKey:OWSPrimaryStorageKeyPrekeyCurrentSignedPrekeyId - inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; -} - -- (nullable SignedPreKeyRecord *)currentSignedPreKey -{ - __block SignedPreKeyRecord *_Nullable currentRecord; - - [self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - NSNumber *_Nullable preKeyId = [transaction objectForKey:OWSPrimaryStorageKeyPrekeyCurrentSignedPrekeyId - inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; - - if (preKeyId == nil) { - return; - } - - currentRecord = - [transaction objectForKey:preKeyId.stringValue inCollection:OWSPrimaryStorageSignedPreKeyStoreCollection]; - }]; - - return currentRecord; -} - -#pragma mark - Prekey update failures - -- (int)prekeyUpdateFailureCount -{ - NSNumber *_Nullable value = [self.dbReadConnection objectForKey:OWSPrimaryStorageKeyPrekeyUpdateFailureCount - inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; - // Will default to zero. - return [value intValue]; -} - -- (void)clearPrekeyUpdateFailureCount -{ - [self.dbReadWriteConnection removeObjectForKey:OWSPrimaryStorageKeyPrekeyUpdateFailureCount - inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; -} - -- (int)incrementPrekeyUpdateFailureCount -{ - return [self.dbReadWriteConnection incrementIntForKey:OWSPrimaryStorageKeyPrekeyUpdateFailureCount - inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; -} - -- (nullable NSDate *)firstPrekeyUpdateFailureDate -{ - return [self.dbReadConnection dateForKey:OWSPrimaryStorageKeyFirstPrekeyUpdateFailureDate - inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; -} - -- (void)setFirstPrekeyUpdateFailureDate:(nonnull NSDate *)value -{ - [self.dbReadWriteConnection setDate:value - forKey:OWSPrimaryStorageKeyFirstPrekeyUpdateFailureDate - inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; -} - -- (void)clearFirstPrekeyUpdateFailureDate -{ - [self.dbReadWriteConnection removeObjectForKey:OWSPrimaryStorageKeyFirstPrekeyUpdateFailureDate - inCollection:OWSPrimaryStorageSignedPreKeyMetadataCollection]; -} - -#pragma mark - Debugging - -- (void)logSignedPreKeyReport -{ - NSString *tag = @"[OWSPrimaryStorage (SignedPreKeyStore)]"; - - NSNumber *currentId = [self currentSignedPrekeyId]; - NSDate *firstPrekeyUpdateFailureDate = [self firstPrekeyUpdateFailureDate]; - NSUInteger prekeyUpdateFailureCount = [self prekeyUpdateFailureCount]; - - [self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - __block int i = 0; - - OWSLogInfo(@"%@ SignedPreKeys Report:", tag); - OWSLogInfo(@"%@ currentId: %@", tag, currentId); - OWSLogInfo(@"%@ firstPrekeyUpdateFailureDate: %@", tag, firstPrekeyUpdateFailureDate); - OWSLogInfo(@"%@ prekeyUpdateFailureCount: %lu", tag, (unsigned long)prekeyUpdateFailureCount); - - NSUInteger count = [transaction numberOfKeysInCollection:OWSPrimaryStorageSignedPreKeyStoreCollection]; - OWSLogInfo(@"%@ All Keys (count: %lu):", tag, (unsigned long)count); - - [transaction - enumerateKeysAndObjectsInCollection:OWSPrimaryStorageSignedPreKeyStoreCollection - usingBlock:^( - NSString *_Nonnull key, id _Nonnull signedPreKeyObject, BOOL *_Nonnull stop) { - i++; - if (![signedPreKeyObject isKindOfClass:[SignedPreKeyRecord class]]) { - OWSFailDebug(@"%@ Was expecting SignedPreKeyRecord, but found: %@", - tag, - [signedPreKeyObject class]); - return; - } - SignedPreKeyRecord *signedPreKeyRecord - = (SignedPreKeyRecord *)signedPreKeyObject; - OWSLogInfo(@"%@ #%d { - - private let indexBlock: (T, YapDatabaseReadTransaction) -> String - - public init(indexBlock: @escaping (T, YapDatabaseReadTransaction) -> String) { - self.indexBlock = indexBlock - } - - public func index(_ item: T, transaction: YapDatabaseReadTransaction) -> String { - return normalize(indexingText: indexBlock(item, transaction)) - } - - private func normalize(indexingText: String) -> String { - return FullTextSearchFinder.normalize(text: indexingText) - } -} - -@objc -public class FullTextSearchFinder: NSObject { - - // MARK: - Dependencies - - private static var tsAccountManager: TSAccountManager { - return TSAccountManager.sharedInstance() - } - - // MARK: - Querying - - // We want to match by prefix for "search as you type" functionality. - // SQLite does not support suffix or contains matches. - public class func query(searchText: String) -> String { - // 1. Normalize the search text. - // - // TODO: We could arguably convert to lowercase since the search - // is case-insensitive. - let normalizedSearchText = FullTextSearchFinder.normalize(text: searchText) - - // 2. Split the non-numeric text into query terms (or tokens). - let nonNumericText = String(String.UnicodeScalarView(normalizedSearchText.unicodeScalars.lazy.map { - if CharacterSet.decimalDigits.contains($0) { - return " " - } else { - return $0 - } - })) - var queryTerms = nonNumericText.split(separator: " ") - - // 3. Add an additional numeric-only query term. - let digitsOnlyScalars = normalizedSearchText.unicodeScalars.lazy.filter { - CharacterSet.decimalDigits.contains($0) - } - let digitsOnly: Substring = Substring(String(String.UnicodeScalarView(digitsOnlyScalars))) - queryTerms.append(digitsOnly) - - // 4. De-duplicate and sort query terms. - // Duplicate terms are redundant. - // Sorting terms makes the output of this method deterministic and easier to test, - // and the order won't affect the search results. - queryTerms = Array(Set(queryTerms)).sorted() - - // 5. Filter the query terms. - let filteredQueryTerms = queryTerms.filter { - // Ignore empty terms. - $0.count > 0 - }.map { - // Allow partial match of each term. - // - // Note that we use double-quotes to enclose each search term. - // Quoted search terms can include a few more characters than - // "bareword" (non-quoted) search terms. This shouldn't matter, - // since we're filtering all of the affected characters, but - // quoting protects us from any bugs in that logic. - "\"\($0)\"*" - } - - // 6. Join terms into query string. - let query = filteredQueryTerms.joined(separator: " ") - return query - } - - public func enumerateObjects(searchText: String, transaction: YapDatabaseReadTransaction, block: @escaping (Any, String) -> Void) { - guard let ext: YapDatabaseFullTextSearchTransaction = ext(transaction: transaction) else { - owsFailDebug("ext was unexpectedly nil") - return - } - - let query = FullTextSearchFinder.query(searchText: searchText) - - Logger.verbose("query: \(query)") - - let maxSearchResults = 500 - var searchResultCount = 0 - let snippetOptions = YapDatabaseFullTextSearchSnippetOptions() - snippetOptions.startMatchText = "" - snippetOptions.endMatchText = "" - ext.enumerateKeysAndObjects(matching: query, with: snippetOptions) { (snippet: String, _: String, _: String, object: Any, stop: UnsafeMutablePointer) in - guard searchResultCount < maxSearchResults else { - stop.pointee = true - return - } - searchResultCount += 1 - - block(object, snippet) - } - } - - // MARK: - Normalization - - fileprivate static var charactersToRemove: CharacterSet = { - // * We want to strip punctuation - and our definition of "punctuation" - // is broader than `CharacterSet.punctuationCharacters`. - // * FTS should be robust to (i.e. ignore) illegal and control characters, - // but it's safer if we filter them ourselves as well. - var charactersToFilter = CharacterSet.punctuationCharacters - charactersToFilter.formUnion(CharacterSet.illegalCharacters) - charactersToFilter.formUnion(CharacterSet.controlCharacters) - - // We want to strip all ASCII characters except: - // * Letters a-z, A-Z - // * Numerals 0-9 - // * Whitespace - var asciiToFilter = CharacterSet(charactersIn: UnicodeScalar(0x0)!.. String { - // 1. Filter out invalid characters. - let filtered = text.removeCharacters(characterSet: charactersToRemove) - - // 2. Simplify whitespace. - let simplified = filtered.replaceCharacters(characterSet: .whitespacesAndNewlines, - replacement: " ") - - // 3. Strip leading & trailing whitespace last, since we may replace - // filtered characters with whitespace. - return simplified.trimmingCharacters(in: .whitespacesAndNewlines) - } - - // MARK: - Index Building - - private class var contactsManager: ContactsManagerProtocol { - return SSKEnvironment.shared.contactsManager - } - - private static let groupThreadIndexer: SearchIndexer = SearchIndexer { (groupThread: TSGroupThread, transaction: YapDatabaseReadTransaction) in - let groupName = groupThread.groupModel.groupName ?? "" - - let memberStrings = groupThread.groupModel.groupMemberIds.map { recipientId in - recipientIndexer.index(recipientId, transaction: transaction) - }.joined(separator: " ") - - return "\(groupName) \(memberStrings)" - } - - private static let contactThreadIndexer: SearchIndexer = SearchIndexer { (contactThread: TSContactThread, transaction: YapDatabaseReadTransaction) in - let recipientId = contactThread.contactIdentifier() - var result = recipientIndexer.index(recipientId, transaction: transaction) - - if IsNoteToSelfEnabled(), - let localNumber = tsAccountManager.storedOrCachedLocalNumber(transaction), - localNumber == recipientId { - - let noteToSelfLabel = NSLocalizedString("NOTE_TO_SELF", comment: "Label for 1:1 conversation with yourself.") - result += " \(noteToSelfLabel)" - } - - return result - } - - private static let recipientIndexer: SearchIndexer = SearchIndexer { (recipientId: String, transaction: YapDatabaseReadTransaction) in - let displayName = contactsManager.displayName(forPhoneIdentifier: recipientId, transaction: transaction) - - let nationalNumber: String = { (recipientId: String) -> String in - - guard let phoneNumber = PhoneNumber(fromE164: recipientId) else { - owsFailDebug("unexpected unparseable recipientId: \(recipientId)") - return "" - } - - guard let digitScalars = phoneNumber.nationalNumber?.unicodeScalars.filter({ CharacterSet.decimalDigits.contains($0) }) else { - owsFailDebug("unexpected unparseable recipientId: \(recipientId)") - return "" - } - - return String(String.UnicodeScalarView(digitScalars)) - }(recipientId) - - return "\(recipientId) \(nationalNumber) \(displayName)" - } - - private static let messageIndexer: SearchIndexer = SearchIndexer { (message: TSMessage, transaction: YapDatabaseReadTransaction) in - if let bodyText = message.bodyText(with: transaction) { - return bodyText - } - return "" - } - - private class func indexContent(object: Any, transaction: YapDatabaseReadTransaction) -> String? { - if let groupThread = object as? TSGroupThread { - return self.groupThreadIndexer.index(groupThread, transaction: transaction) - } else if let contactThread = object as? TSContactThread { - guard contactThread.shouldThreadBeVisible && !contactThread.isSlaveThread else { - // If we've never sent/received a message in a TSContactThread, - // then we want it to appear in the "Other Contacts" section rather - // than in the "Conversations" section. - return nil - } - return self.contactThreadIndexer.index(contactThread, transaction: transaction) - } else if let message = object as? TSMessage { - return self.messageIndexer.index(message, transaction: transaction) - } else if let signalAccount = object as? SignalAccount { - return self.recipientIndexer.index(signalAccount.recipientId, transaction: transaction) - } else { - return nil - } - } - - // MARK: - Extension Registration - - private static let dbExtensionName: String = "FullTextSearchFinderExtension" - - private func ext(transaction: YapDatabaseReadTransaction) -> YapDatabaseFullTextSearchTransaction? { - return transaction.ext(FullTextSearchFinder.dbExtensionName) as? YapDatabaseFullTextSearchTransaction - } - - @objc - public class func asyncRegisterDatabaseExtension(storage: OWSStorage) { - storage.asyncRegister(dbExtensionConfig, withName: dbExtensionName) - } - - // Only for testing. - public class func ensureDatabaseExtensionRegistered(storage: OWSStorage) { - guard storage.registeredExtension(dbExtensionName) == nil else { - return - } - - storage.register(dbExtensionConfig, withName: dbExtensionName) - } - - private class var dbExtensionConfig: YapDatabaseFullTextSearch { - AssertIsOnMainThread() - - let contentColumnName = "content" - - let handler = YapDatabaseFullTextSearchHandler.withObjectBlock { (transaction: YapDatabaseReadTransaction, dict: NSMutableDictionary, _: String, _: String, object: Any) in - dict[contentColumnName] = indexContent(object: object, transaction: transaction) - } - - // update search index on contact name changes? - - return YapDatabaseFullTextSearch(columnNames: ["content"], - options: nil, - handler: handler, - ftsVersion: YapDatabaseFullTextSearchFTS5Version, - versionTag: "1") - } -} diff --git a/SignalServiceKit/src/Storage/OWSIncomingMessageFinder.h b/SignalServiceKit/src/Storage/OWSIncomingMessageFinder.h deleted file mode 100644 index d62800957..000000000 --- a/SignalServiceKit/src/Storage/OWSIncomingMessageFinder.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class OWSStorage; -@class YapDatabaseReadTransaction; - -@interface OWSIncomingMessageFinder : NSObject - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -+ (NSString *)databaseExtensionName; -+ (void)asyncRegisterExtensionWithPrimaryStorage:(OWSStorage *)storage; - -/** - * Detects existance of a duplicate incoming message. - */ -- (BOOL)existsMessageWithTimestamp:(uint64_t)timestamp - sourceId:(NSString *)sourceId - sourceDeviceId:(uint32_t)sourceDeviceId - transaction:(YapDatabaseReadTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/OWSIncomingMessageFinder.m b/SignalServiceKit/src/Storage/OWSIncomingMessageFinder.m deleted file mode 100644 index f8f5cd9d9..000000000 --- a/SignalServiceKit/src/Storage/OWSIncomingMessageFinder.m +++ /dev/null @@ -1,150 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSIncomingMessageFinder.h" -#import "OWSPrimaryStorage.h" -#import "TSIncomingMessage.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSIncomingMessageFinderExtensionName = @"OWSIncomingMessageFinderExtensionName"; - -NSString *const OWSIncomingMessageFinderColumnTimestamp = @"OWSIncomingMessageFinderColumnTimestamp"; -NSString *const OWSIncomingMessageFinderColumnSourceId = @"OWSIncomingMessageFinderColumnSourceId"; -NSString *const OWSIncomingMessageFinderColumnSourceDeviceId = @"OWSIncomingMessageFinderColumnSourceDeviceId"; - -@interface OWSIncomingMessageFinder () - -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@end - -@implementation OWSIncomingMessageFinder - -@synthesize dbConnection = _dbConnection; - -#pragma mark - init - -- (instancetype)init -{ - OWSAssertDebug([OWSPrimaryStorage sharedManager]); - - return [self initWithPrimaryStorage:[OWSPrimaryStorage sharedManager]]; -} - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - if (!self) { - return self; - } - - _primaryStorage = primaryStorage; - - return self; -} - -#pragma mark - properties - -- (YapDatabaseConnection *)dbConnection -{ - @synchronized(self) { - if (!_dbConnection) { - _dbConnection = [self.primaryStorage newDatabaseConnection]; - } - } - return _dbConnection; -} - -#pragma mark - YAP integration - -+ (YapDatabaseSecondaryIndex *)indexExtension -{ - YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new]; - - [setup addColumn:OWSIncomingMessageFinderColumnTimestamp withType:YapDatabaseSecondaryIndexTypeInteger]; - [setup addColumn:OWSIncomingMessageFinderColumnSourceId withType:YapDatabaseSecondaryIndexTypeText]; - [setup addColumn:OWSIncomingMessageFinderColumnSourceDeviceId withType:YapDatabaseSecondaryIndexTypeInteger]; - - YapDatabaseSecondaryIndexWithObjectBlock block = ^(YapDatabaseReadTransaction *transaction, - NSMutableDictionary *dict, - NSString *collection, - NSString *key, - id object) { - if ([object isKindOfClass:[TSIncomingMessage class]]) { - TSIncomingMessage *incomingMessage = (TSIncomingMessage *)object; - - // On new messages authorId should be set on all incoming messages, but there was a time when authorId was - // only set on incoming group messages. - NSObject *authorIdOrNull = incomingMessage.authorId ? incomingMessage.authorId : [NSNull null]; - [dict setObject:@(incomingMessage.timestamp) forKey:OWSIncomingMessageFinderColumnTimestamp]; - [dict setObject:authorIdOrNull forKey:OWSIncomingMessageFinderColumnSourceId]; - [dict setObject:@(incomingMessage.sourceDeviceId) forKey:OWSIncomingMessageFinderColumnSourceDeviceId]; - } - }; - - YapDatabaseSecondaryIndexHandler *handler = [YapDatabaseSecondaryIndexHandler withObjectBlock:block]; - - return [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:handler versionTag:nil]; -} - -+ (NSString *)databaseExtensionName -{ - return OWSIncomingMessageFinderExtensionName; -} - -+ (void)asyncRegisterExtensionWithPrimaryStorage:(OWSStorage *)storage -{ - OWSLogInfo(@"registering async."); - [storage asyncRegisterExtension:self.indexExtension withName:OWSIncomingMessageFinderExtensionName]; -} - -#ifdef DEBUG -// We should not normally hit this, as we should have prefer registering async, but it is useful for testing. -- (void)registerExtension -{ - OWSLogError(@"registering SYNC. We should prefer async when possible."); - [self.primaryStorage registerExtension:self.class.indexExtension withName:OWSIncomingMessageFinderExtensionName]; -} -#endif - -#pragma mark - instance methods - -- (BOOL)existsMessageWithTimestamp:(uint64_t)timestamp - sourceId:(NSString *)sourceId - sourceDeviceId:(uint32_t)sourceDeviceId - transaction:(YapDatabaseReadTransaction *)transaction -{ -#ifdef DEBUG - if (![self.primaryStorage registeredExtension:OWSIncomingMessageFinderExtensionName]) { - OWSFailDebug(@"but extension is not registered"); - - // we should be initializing this at startup rather than have an unexpectedly slow lazy setup at random. - [self registerExtension]; - } -#endif - - NSString *queryFormat = [NSString stringWithFormat:@"WHERE %@ = ? AND %@ = ? AND %@ = ?", - OWSIncomingMessageFinderColumnTimestamp, - OWSIncomingMessageFinderColumnSourceId, - OWSIncomingMessageFinderColumnSourceDeviceId]; - // YapDatabaseQuery params must be objects - YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:queryFormat, @(timestamp), sourceId, @(sourceDeviceId)]; - - NSUInteger count; - BOOL success = [[transaction ext:OWSIncomingMessageFinderExtensionName] getNumberOfRows:&count matchingQuery:query]; - if (!success) { - OWSFailDebug(@"Could not execute query"); - return NO; - } - - return count > 0; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/OWSMediaGalleryFinder.h b/SignalServiceKit/src/Storage/OWSMediaGalleryFinder.h deleted file mode 100644 index c583b33ac..000000000 --- a/SignalServiceKit/src/Storage/OWSMediaGalleryFinder.h +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class OWSStorage; -@class TSAttachment; -@class TSThread; -@class YapDatabaseAutoViewTransaction; -@class YapDatabaseConnection; -@class YapDatabaseReadTransaction; -@class YapDatabaseViewRowChange; - -@interface OWSMediaGalleryFinder : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithThread:(TSThread *)thread NS_DESIGNATED_INITIALIZER; - -// How many media items a thread has -- (NSUInteger)mediaCountWithTransaction:(YapDatabaseReadTransaction *)transaction NS_SWIFT_NAME(mediaCount(transaction:)); - -// The ordinal position of an attachment within a thread's media gallery -- (nullable NSNumber *)mediaIndexForAttachment:(TSAttachment *)attachment - transaction:(YapDatabaseReadTransaction *)transaction - NS_SWIFT_NAME(mediaIndex(attachment:transaction:)); - -- (nullable TSAttachment *)oldestMediaAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction - NS_SWIFT_NAME(oldestMediaAttachment(transaction:)); -- (nullable TSAttachment *)mostRecentMediaAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction - NS_SWIFT_NAME(mostRecentMediaAttachment(transaction:)); - -- (void)enumerateMediaAttachmentsWithRange:(NSRange)range - transaction:(YapDatabaseReadTransaction *)transaction - block:(void (^)(TSAttachment *))attachmentBlock - NS_SWIFT_NAME(enumerateMediaAttachments(range:transaction:block:)); - -- (BOOL)hasMediaChangesInNotifications:(NSArray *)notifications - dbConnection:(YapDatabaseConnection *)dbConnection; - -#pragma mark - Extension registration - -@property (nonatomic, readonly) NSString *mediaGroup; -- (YapDatabaseAutoViewTransaction *)galleryExtensionWithTransaction:(YapDatabaseReadTransaction *)transaction - NS_SWIFT_NAME(galleryExtension(transaction:)); -+ (NSString *)databaseExtensionName; -+ (void)asyncRegisterDatabaseExtensionsWithPrimaryStorage:(OWSStorage *)storage; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/OWSMediaGalleryFinder.m b/SignalServiceKit/src/Storage/OWSMediaGalleryFinder.m deleted file mode 100644 index 7a22c368a..000000000 --- a/SignalServiceKit/src/Storage/OWSMediaGalleryFinder.m +++ /dev/null @@ -1,218 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSMediaGalleryFinder.h" -#import "OWSStorage.h" -#import "TSAttachmentStream.h" -#import "TSMessage.h" -#import "TSThread.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -static NSString *const OWSMediaGalleryFinderExtensionName = @"OWSMediaGalleryFinderExtensionName"; - -@interface OWSMediaGalleryFinder () - -@property (nonatomic, readonly) TSThread *thread; - -@end - -@implementation OWSMediaGalleryFinder - -- (instancetype)initWithThread:(TSThread *)thread -{ - self = [super init]; - if (!self) { - return self; - } - - _thread = thread; - - return self; -} - -#pragma mark - Public Finder Methods - -- (NSUInteger)mediaCountWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - return [[self galleryExtensionWithTransaction:transaction] numberOfItemsInGroup:self.mediaGroup]; -} - -- (nullable NSNumber *)mediaIndexForAttachment:(TSAttachment *)attachment - transaction:(YapDatabaseReadTransaction *)transaction -{ - NSString *groupId; - NSUInteger index; - - BOOL wasFound = [[self galleryExtensionWithTransaction:transaction] getGroup:&groupId - index:&index - forKey:attachment.uniqueId - inCollection:[TSAttachment collection]]; - - if (!wasFound) { - return nil; - } - - OWSAssertDebug([self.mediaGroup isEqual:groupId]); - - return @(index); -} - -- (nullable TSAttachment *)oldestMediaAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - return [[self galleryExtensionWithTransaction:transaction] firstObjectInGroup:self.mediaGroup]; -} - -- (nullable TSAttachment *)mostRecentMediaAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - return [[self galleryExtensionWithTransaction:transaction] lastObjectInGroup:self.mediaGroup]; -} - -- (void)enumerateMediaAttachmentsWithRange:(NSRange)range - transaction:(YapDatabaseReadTransaction *)transaction - block:(void (^)(TSAttachment *))attachmentBlock -{ - - [[self galleryExtensionWithTransaction:transaction] - enumerateKeysAndObjectsInGroup:self.mediaGroup - withOptions:0 - range:range - usingBlock:^(NSString *_Nonnull collection, - NSString *_Nonnull key, - id _Nonnull object, - NSUInteger index, - BOOL *_Nonnull stop) { - OWSAssertDebug([object isKindOfClass:[TSAttachment class]]); - attachmentBlock((TSAttachment *)object); - }]; -} - -- (BOOL)hasMediaChangesInNotifications:(NSArray *)notifications - dbConnection:(YapDatabaseConnection *)dbConnection -{ - YapDatabaseAutoViewConnection *extConnection = [dbConnection ext:OWSMediaGalleryFinderExtensionName]; - OWSAssert(extConnection); - - return [extConnection hasChangesForGroup:self.mediaGroup inNotifications:notifications]; -} - -#pragma mark - Util - -- (YapDatabaseAutoViewTransaction *)galleryExtensionWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - YapDatabaseAutoViewTransaction *extension = [transaction extension:OWSMediaGalleryFinderExtensionName]; - OWSAssertDebug(extension); - - return extension; -} - -+ (NSString *)mediaGroupWithThreadId:(NSString *)threadId -{ - return [NSString stringWithFormat:@"%@-media", threadId]; -} - -- (NSString *)mediaGroup -{ - return [[self class] mediaGroupWithThreadId:self.thread.uniqueId]; -} - -#pragma mark - Extension registration - -+ (NSString *)databaseExtensionName -{ - return OWSMediaGalleryFinderExtensionName; -} - -+ (void)asyncRegisterDatabaseExtensionsWithPrimaryStorage:(OWSStorage *)storage -{ - [storage asyncRegisterExtension:[self mediaGalleryDatabaseExtension] - withName:OWSMediaGalleryFinderExtensionName]; -} - -+ (YapDatabaseAutoView *)mediaGalleryDatabaseExtension -{ - YapDatabaseViewSorting *sorting = - [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *_Nonnull transaction, - NSString *_Nonnull group, - NSString *_Nonnull collection1, - NSString *_Nonnull key1, - id _Nonnull object1, - NSString *_Nonnull collection2, - NSString *_Nonnull key2, - id _Nonnull object2) { - if (![object1 isKindOfClass:[TSAttachment class]]) { - OWSFailDebug(@"Unexpected object while sorting: %@", [object1 class]); - return NSOrderedSame; - } - TSAttachment *attachment1 = (TSAttachment *)object1; - - if (![object2 isKindOfClass:[TSAttachment class]]) { - OWSFailDebug(@"Unexpected object while sorting: %@", [object2 class]); - return NSOrderedSame; - } - TSAttachment *attachment2 = (TSAttachment *)object2; - - TSMessage *_Nullable message1 = [attachment1 fetchAlbumMessageWithTransaction:transaction]; - TSMessage *_Nullable message2 = [attachment2 fetchAlbumMessageWithTransaction:transaction]; - if (message1 == nil || message2 == nil) { - OWSFailDebug(@"couldn't find albumMessage"); - return NSOrderedSame; - } - - if ([message1.uniqueId isEqualToString:message2.uniqueId]) { - NSUInteger index1 = [message1.attachmentIds indexOfObject:attachment1.uniqueId]; - NSUInteger index2 = [message1.attachmentIds indexOfObject:attachment2.uniqueId]; - - if (index1 == NSNotFound || index2 == NSNotFound) { - OWSFailDebug(@"couldn't find attachmentId in it's albumMessage"); - return NSOrderedSame; - } - return [@(index1) compare:@(index2)]; - } else { - return [message1 compareForSorting:message2]; - } - }]; - - YapDatabaseViewGrouping *grouping = - [YapDatabaseViewGrouping withObjectBlock:^NSString *_Nullable(YapDatabaseReadTransaction *_Nonnull transaction, - NSString *_Nonnull collection, - NSString *_Nonnull key, - id _Nonnull object) { - // Don't include nil or not yet downloaded attachments. - if (![object isKindOfClass:[TSAttachmentStream class]]) { - return nil; - } - - TSAttachmentStream *attachment = (TSAttachmentStream *)object; - if (attachment.albumMessageId == nil) { - return nil; - } - - if (!attachment.isValidVisualMedia) { - return nil; - } - - TSMessage *message = [attachment fetchAlbumMessageWithTransaction:transaction]; - if (message == nil) { - OWSFailDebug(@"message was unexpectedly nil"); - return nil; - } - - return [self mediaGroupWithThreadId:message.uniqueThreadId]; - }]; - - YapDatabaseViewOptions *options = [YapDatabaseViewOptions new]; - options.allowedCollections = - [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:TSAttachment.collection]]; - - return [[YapDatabaseAutoView alloc] initWithGrouping:grouping sorting:sorting versionTag:@"4" options:options]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/OWSPrimaryStorage.h b/SignalServiceKit/src/Storage/OWSPrimaryStorage.h deleted file mode 100644 index dd1cf65b3..000000000 --- a/SignalServiceKit/src/Storage/OWSPrimaryStorage.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSStorage.h" - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const OWSUIDatabaseConnectionWillUpdateNotification; -extern NSString *const OWSUIDatabaseConnectionDidUpdateNotification; -extern NSString *const OWSUIDatabaseConnectionWillUpdateExternallyNotification; -extern NSString *const OWSUIDatabaseConnectionDidUpdateExternallyNotification; -extern NSString *const OWSUIDatabaseConnectionNotificationsKey; - -@interface OWSPrimaryStorage : OWSStorage - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initStorage; - -+ (instancetype)sharedManager NS_SWIFT_NAME(shared()); - -@property (nonatomic, readonly) YapDatabaseConnection *uiDatabaseConnection; -@property (nonatomic, readonly) YapDatabaseConnection *dbReadConnection; -@property (nonatomic, readonly) YapDatabaseConnection *dbReadWriteConnection; - -- (void)updateUIDatabaseConnectionToLatest; - -+ (YapDatabaseConnection *)dbReadConnection; -+ (YapDatabaseConnection *)dbReadWriteConnection; - -+ (nullable NSError *)migrateToSharedData; - -+ (NSString *)databaseFilePath; - -+ (NSString *)legacyDatabaseFilePath; -+ (NSString *)legacyDatabaseFilePath_SHM; -+ (NSString *)legacyDatabaseFilePath_WAL; -+ (NSString *)sharedDataDatabaseFilePath; -+ (NSString *)sharedDataDatabaseFilePath_SHM; -+ (NSString *)sharedDataDatabaseFilePath_WAL; - -+ (void)protectFiles; - -#pragma mark - Misc. - -- (void)touchDbAsync; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/OWSPrimaryStorage.m b/SignalServiceKit/src/Storage/OWSPrimaryStorage.m deleted file mode 100644 index a2d756655..000000000 --- a/SignalServiceKit/src/Storage/OWSPrimaryStorage.m +++ /dev/null @@ -1,461 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSPrimaryStorage.h" -#import "AppContext.h" -#import "OWSAnalytics.h" -#import "OWSBatchMessageProcessor.h" -#import "OWSDisappearingMessagesFinder.h" -#import "OWSFailedAttachmentDownloadsJob.h" -#import "OWSFailedMessagesJob.h" -#import "OWSFileSystem.h" -#import "OWSIncomingMessageFinder.h" -#import "OWSIncompleteCallsJob.h" -#import "OWSMediaGalleryFinder.h" -#import "OWSMessageReceiver.h" -#import "OWSStorage+Subclass.h" -#import "SSKEnvironment.h" -#import "TSDatabaseSecondaryIndexes.h" -#import "TSDatabaseView.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSUIDatabaseConnectionWillUpdateNotification = @"OWSUIDatabaseConnectionWillUpdateNotification"; -NSString *const OWSUIDatabaseConnectionDidUpdateNotification = @"OWSUIDatabaseConnectionDidUpdateNotification"; -NSString *const OWSUIDatabaseConnectionWillUpdateExternallyNotification = @"OWSUIDatabaseConnectionWillUpdateExternallyNotification"; -NSString *const OWSUIDatabaseConnectionDidUpdateExternallyNotification = @"OWSUIDatabaseConnectionDidUpdateExternallyNotification"; - -NSString *const OWSUIDatabaseConnectionNotificationsKey = @"OWSUIDatabaseConnectionNotificationsKey"; - -void VerifyRegistrationsForPrimaryStorage(OWSStorage *storage) -{ - OWSCAssertDebug(storage); - - [[storage newDatabaseConnection] asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction) { - for (NSString *extensionName in storage.registeredExtensionNames) { - OWSLogVerbose(@"Verifying database extension: %@", extensionName); - YapDatabaseViewTransaction *_Nullable viewTransaction = [transaction ext:extensionName]; - if (!viewTransaction) { - OWSCFailDebug(@"VerifyRegistrationsForPrimaryStorage missing database extension: %@", extensionName); - - [OWSStorage incrementVersionOfDatabaseExtension:extensionName]; - } - } - }]; -} - -#pragma mark - - -@interface OWSPrimaryStorage () - -@property (atomic) BOOL areAsyncRegistrationsComplete; -@property (atomic) BOOL areSyncRegistrationsComplete; -@property (nonatomic, readonly) YapDatabaseConnectionPool *dbReadPool; - -@end - -#pragma mark - - -@implementation OWSPrimaryStorage - -@synthesize uiDatabaseConnection = _uiDatabaseConnection; - -+ (instancetype)sharedManager -{ - OWSAssertDebug(SSKEnvironment.shared.primaryStorage); - - return SSKEnvironment.shared.primaryStorage; -} - -- (instancetype)initStorage -{ - self = [super initStorage]; - - if (self) { - [self loadDatabase]; - - _dbReadPool = [[YapDatabaseConnectionPool alloc] initWithDatabase:self.database]; - _dbReadWriteConnection = [self newDatabaseConnection]; - _uiDatabaseConnection = [self newDatabaseConnection]; - - // Increase object cache limit. Default is 250. - _uiDatabaseConnection.objectCacheLimit = 500; - [_uiDatabaseConnection beginLongLivedReadTransaction]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(yapDatabaseModified:) - name:YapDatabaseModifiedNotification - object:self.dbNotificationObject]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(yapDatabaseModifiedExternally:) - name:YapDatabaseModifiedExternallyNotification - object:nil]; - - OWSSingletonAssert(); - } - - return self; -} - -- (void)dealloc -{ - // Surface memory leaks by logging the deallocation of this class. - OWSLogVerbose(@"Dealloc: %@", self.class); - - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)yapDatabaseModifiedExternally:(NSNotification *)notification -{ - // Notify observers we're about to update the database connection - [[NSNotificationCenter defaultCenter] postNotificationName:OWSUIDatabaseConnectionWillUpdateExternallyNotification object:self.dbNotificationObject]; - - // Move uiDatabaseConnection to the latest commit. - // Do so atomically, and fetch all the notifications for each commit we jump. - NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction]; - - // Notify observers that the uiDatabaseConnection was updated - NSDictionary *userInfo = @{ OWSUIDatabaseConnectionNotificationsKey: notifications }; - [[NSNotificationCenter defaultCenter] postNotificationName:OWSUIDatabaseConnectionDidUpdateExternallyNotification - object:self.dbNotificationObject - userInfo:userInfo]; -} - -- (void)yapDatabaseModified:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - OWSLogVerbose(@""); - [self updateUIDatabaseConnectionToLatest]; -} - -- (void)updateUIDatabaseConnectionToLatest -{ - OWSAssertIsOnMainThread(); - - // Notify observers we're about to update the database connection - [[NSNotificationCenter defaultCenter] postNotificationName:OWSUIDatabaseConnectionWillUpdateNotification object:self.dbNotificationObject]; - - // Move uiDatabaseConnection to the latest commit. - // Do so atomically, and fetch all the notifications for each commit we jump. - NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction]; - - // Notify observers that the uiDatabaseConnection was updated - NSDictionary *userInfo = @{ OWSUIDatabaseConnectionNotificationsKey: notifications }; - [[NSNotificationCenter defaultCenter] postNotificationName:OWSUIDatabaseConnectionDidUpdateNotification - object:self.dbNotificationObject - userInfo:userInfo]; -} - -- (YapDatabaseConnection *)uiDatabaseConnection -{ - OWSAssertIsOnMainThread(); - return _uiDatabaseConnection; -} - -- (void)resetStorage -{ - _dbReadPool = nil; - _uiDatabaseConnection = nil; - _dbReadWriteConnection = nil; - - [super resetStorage]; -} - -- (void)runSyncRegistrations -{ - // Synchronously register extensions which are essential for views. - [TSDatabaseView registerCrossProcessNotifier:self]; - - // See comments on OWSDatabaseConnection. - // - // In the absence of finding documentation that can shed light on the issue we've been - // seeing, this issue only seems to affect sync and not async registrations. We've always - // been opening write transactions before the async registrations complete without negative - // consequences. - OWSAssertDebug(!self.areSyncRegistrationsComplete); - self.areSyncRegistrationsComplete = YES; -} - -- (void)runAsyncRegistrationsWithCompletion:(void (^_Nonnull)(void))completion -{ - OWSAssertDebug(completion); - OWSAssertDebug(self.database); - - OWSLogVerbose(@"async registrations enqueuing."); - - // Asynchronously register other extensions. - // - // All sync registrations must be done before all async registrations, - // or the sync registrations will block on the async registrations. - [TSDatabaseView asyncRegisterLegacyThreadInteractionsDatabaseView:self]; - [TSDatabaseView asyncRegisterThreadInteractionsDatabaseView:self]; - [TSDatabaseView asyncRegisterThreadDatabaseView:self]; - [TSDatabaseView asyncRegisterUnreadDatabaseView:self]; - [self asyncRegisterExtension:[TSDatabaseSecondaryIndexes registerTimeStampIndex] - withName:[TSDatabaseSecondaryIndexes registerTimeStampIndexExtensionName]]; - - [OWSMessageReceiver asyncRegisterDatabaseExtension:self]; - [OWSBatchMessageProcessor asyncRegisterDatabaseExtension:self]; - - [TSDatabaseView asyncRegisterUnseenDatabaseView:self]; - [TSDatabaseView asyncRegisterThreadOutgoingMessagesDatabaseView:self]; - [TSDatabaseView asyncRegisterThreadSpecialMessagesDatabaseView:self]; - - [FullTextSearchFinder asyncRegisterDatabaseExtensionWithStorage:self]; - [OWSIncomingMessageFinder asyncRegisterExtensionWithPrimaryStorage:self]; - [TSDatabaseView asyncRegisterSecondaryDevicesDatabaseView:self]; - [OWSDisappearingMessagesFinder asyncRegisterDatabaseExtensions:self]; - [OWSFailedMessagesJob asyncRegisterDatabaseExtensionsWithPrimaryStorage:self]; - [OWSIncompleteCallsJob asyncRegisterDatabaseExtensionsWithPrimaryStorage:self]; - [OWSFailedAttachmentDownloadsJob asyncRegisterDatabaseExtensionsWithPrimaryStorage:self]; - [OWSMediaGalleryFinder asyncRegisterDatabaseExtensionsWithPrimaryStorage:self]; - [TSDatabaseView asyncRegisterLazyRestoreAttachmentsDatabaseView:self]; - [SSKJobRecordFinder asyncRegisterDatabaseExtensionObjCWithStorage:self]; - - // Loki - [LKDeviceLinkIndex asyncRegisterDatabaseExtensions:self]; - - [self.database - flushExtensionRequestsWithCompletionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) - completionBlock:^{ - OWSAssertDebug(!self.areAsyncRegistrationsComplete); - OWSLogVerbose(@"async registrations complete."); - - self.areAsyncRegistrationsComplete = YES; - - completion(); - - [self verifyDatabaseViews]; - }]; -} - -- (void)verifyDatabaseViews -{ - VerifyRegistrationsForPrimaryStorage(self); -} - -+ (void)protectFiles -{ - OWSLogInfo(@"Database file size: %@", [OWSFileSystem fileSizeOfPath:self.sharedDataDatabaseFilePath]); - OWSLogInfo(@"\t SHM file size: %@", [OWSFileSystem fileSizeOfPath:self.sharedDataDatabaseFilePath_SHM]); - OWSLogInfo(@"\t WAL file size: %@", [OWSFileSystem fileSizeOfPath:self.sharedDataDatabaseFilePath_WAL]); - - // Protect the entire new database directory. - [OWSFileSystem protectFileOrFolderAtPath:self.sharedDataDatabaseDirPath]; -} - -+ (NSString *)legacyDatabaseDirPath -{ - return [OWSFileSystem appDocumentDirectoryPath]; -} - -+ (NSString *)sharedDataDatabaseDirPath -{ - NSString *databaseDirPath = [[OWSFileSystem appSharedDataDirectoryPath] stringByAppendingPathComponent:@"database"]; - - if (![OWSFileSystem ensureDirectoryExists:databaseDirPath]) { - OWSFail(@"Could not create new database directory"); - } - return databaseDirPath; -} - -+ (NSString *)databaseFilename -{ - return @"Signal.sqlite"; -} - -+ (NSString *)databaseFilename_SHM -{ - return [self.databaseFilename stringByAppendingString:@"-shm"]; -} - -+ (NSString *)databaseFilename_WAL -{ - return [self.databaseFilename stringByAppendingString:@"-wal"]; -} - -+ (NSString *)legacyDatabaseFilePath -{ - return [self.legacyDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename]; -} - -+ (NSString *)legacyDatabaseFilePath_SHM -{ - return [self.legacyDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename_SHM]; -} - -+ (NSString *)legacyDatabaseFilePath_WAL -{ - return [self.legacyDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename_WAL]; -} - -+ (NSString *)sharedDataDatabaseFilePath -{ - return [self.sharedDataDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename]; -} - -+ (NSString *)sharedDataDatabaseFilePath_SHM -{ - return [self.sharedDataDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename_SHM]; -} - -+ (NSString *)sharedDataDatabaseFilePath_WAL -{ - return [self.sharedDataDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename_WAL]; -} - -+ (nullable NSError *)migrateToSharedData -{ - OWSLogInfo(@""); - - // Given how sensitive this migration is, we verbosely - // log the contents of all involved paths before and after. - NSArray *paths = @[ - self.legacyDatabaseFilePath, - self.legacyDatabaseFilePath_SHM, - self.legacyDatabaseFilePath_WAL, - self.sharedDataDatabaseFilePath, - self.sharedDataDatabaseFilePath_SHM, - self.sharedDataDatabaseFilePath_WAL, - ]; - NSFileManager *fileManager = [NSFileManager defaultManager]; - for (NSString *path in paths) { - if ([fileManager fileExistsAtPath:path]) { - OWSLogInfo(@"before migrateToSharedData: %@, %@", path, [OWSFileSystem fileSizeOfPath:path]); - } - } - - // We protect the db files here, which is somewhat redundant with what will happen in - // `moveAppFilePath:` which also ensures file protection. - // However that method dispatches async, since it can take a while with large attachment directories. - // - // Since we only have three files here it'll be quick to do it sync, and we want to make - // sure it happens as part of the migration. - // - // FileProtection attributes move with the file, so we do it on the legacy files before moving - // them. - [OWSFileSystem protectFileOrFolderAtPath:self.legacyDatabaseFilePath]; - [OWSFileSystem protectFileOrFolderAtPath:self.legacyDatabaseFilePath_SHM]; - [OWSFileSystem protectFileOrFolderAtPath:self.legacyDatabaseFilePath_WAL]; - - NSError *_Nullable error = nil; - if ([fileManager fileExistsAtPath:self.legacyDatabaseFilePath] && - [fileManager fileExistsAtPath:self.sharedDataDatabaseFilePath]) { - // In the case that we have a "database conflict" (i.e. database files - // in the src and dst locations), ensure database integrity by renaming - // all of the dst database files. - for (NSString *filePath in @[ - self.sharedDataDatabaseFilePath, - self.sharedDataDatabaseFilePath_SHM, - self.sharedDataDatabaseFilePath_WAL, - ]) { - error = [OWSFileSystem renameFilePathUsingRandomExtension:filePath]; - if (error) { - return error; - } - } - } - - error = - [OWSFileSystem moveAppFilePath:self.legacyDatabaseFilePath sharedDataFilePath:self.sharedDataDatabaseFilePath]; - if (error) { - return error; - } - error = [OWSFileSystem moveAppFilePath:self.legacyDatabaseFilePath_SHM - sharedDataFilePath:self.sharedDataDatabaseFilePath_SHM]; - if (error) { - return error; - } - error = [OWSFileSystem moveAppFilePath:self.legacyDatabaseFilePath_WAL - sharedDataFilePath:self.sharedDataDatabaseFilePath_WAL]; - if (error) { - return error; - } - - for (NSString *path in paths) { - if ([fileManager fileExistsAtPath:path]) { - OWSLogInfo(@"after migrateToSharedData: %@, %@", path, [OWSFileSystem fileSizeOfPath:path]); - } - } - - return nil; -} - -+ (NSString *)databaseFilePath -{ - OWSLogVerbose(@"databasePath: %@", OWSPrimaryStorage.sharedDataDatabaseFilePath); - - return self.sharedDataDatabaseFilePath; -} - -+ (NSString *)databaseFilePath_SHM -{ - return self.sharedDataDatabaseFilePath_SHM; -} - -+ (NSString *)databaseFilePath_WAL -{ - return self.sharedDataDatabaseFilePath_WAL; -} - -- (NSString *)databaseFilePath -{ - return OWSPrimaryStorage.databaseFilePath; -} - -- (NSString *)databaseFilePath_SHM -{ - return OWSPrimaryStorage.databaseFilePath_SHM; -} - -- (NSString *)databaseFilePath_WAL -{ - return OWSPrimaryStorage.databaseFilePath_WAL; -} - -- (NSString *)databaseFilename_SHM -{ - return OWSPrimaryStorage.databaseFilename_SHM; -} - -- (NSString *)databaseFilename_WAL -{ - return OWSPrimaryStorage.databaseFilename_WAL; -} - -+ (YapDatabaseConnection *)dbReadConnection -{ - return OWSPrimaryStorage.sharedManager.dbReadConnection; -} - -- (YapDatabaseConnection *)dbReadConnection -{ - return self.dbReadPool.connection; -} - -+ (YapDatabaseConnection *)dbReadWriteConnection -{ - return OWSPrimaryStorage.sharedManager.dbReadWriteConnection; -} - -#pragma mark - Misc. - -- (void)touchDbAsync -{ - OWSLogInfo(@""); - - // There appears to be a bug in YapDatabase that sometimes delays modifications - // made in another process (e.g. the SAE) from showing up in other processes. - // There's a simple workaround: a trivial write to the database flushes changes - // made from other processes. - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [transaction setObject:[NSUUID UUID].UUIDString forKey:@"conversation_view_noop_mod" inCollection:@"temp"]; - }]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/OWSStorage+Subclass.h b/SignalServiceKit/src/Storage/OWSStorage+Subclass.h deleted file mode 100644 index 5808c2863..000000000 --- a/SignalServiceKit/src/Storage/OWSStorage+Subclass.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSStorage.h" - -NS_ASSUME_NONNULL_BEGIN - -@class YapDatabase; - -@interface OWSStorage (Subclass) - -@property (atomic, nullable, readonly) YapDatabase *database; - -- (void)loadDatabase; - -- (void)runSyncRegistrations; -// completion will be invoked _off_ the main thread. -- (void)runAsyncRegistrationsWithCompletion:(void (^_Nonnull)(void))completion; - -- (BOOL)areAsyncRegistrationsComplete; -- (BOOL)areSyncRegistrationsComplete; - -- (NSString *)databaseFilePath; -- (NSString *)databaseFilePath_SHM; -- (NSString *)databaseFilePath_WAL; - -- (void)resetStorage; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/OWSStorage.h b/SignalServiceKit/src/Storage/OWSStorage.h deleted file mode 100644 index 386794369..000000000 --- a/SignalServiceKit/src/Storage/OWSStorage.h +++ /dev/null @@ -1,116 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const StorageIsReadyNotification; - -@class YapDatabaseExtension; - -@protocol OWSDatabaseConnectionDelegate - -- (BOOL)areAllRegistrationsComplete; - -@end - -#pragma mark - - -@interface OWSDatabaseConnection : YapDatabaseConnection - -@property (atomic, weak) id delegate; - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithDatabase:(YapDatabase *)database - delegate:(id)delegate NS_DESIGNATED_INITIALIZER; - -@end - -#pragma mark - - -@interface OWSDatabase : YapDatabase - -- (instancetype)init NS_UNAVAILABLE; - -- (id)initWithPath:(NSString *)inPath - serializer:(nullable YapDatabaseSerializer)inSerializer - deserializer:(YapDatabaseDeserializer)inDeserializer - options:(YapDatabaseOptions *)inOptions - delegate:(id)delegate NS_DESIGNATED_INITIALIZER; - -@end - -#pragma mark - - -typedef void (^OWSStorageMigrationBlock)(void); - -@interface OWSStorage : NSObject - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initStorage NS_DESIGNATED_INITIALIZER; - -// Returns YES if _ALL_ storage classes have completed both their -// sync _AND_ async view registrations. -+ (BOOL)isStorageReady; - -// This object can be used to filter database notifications. -@property (nonatomic, readonly, nullable) id dbNotificationObject; - -// migrationBlock will be invoked _off_ the main thread. -+ (void)registerExtensionsWithMigrationBlock:(OWSStorageMigrationBlock)migrationBlock; - -#ifdef DEBUG -- (void)closeStorageForTests; -#endif - -+ (void)resetAllStorage; - -- (YapDatabaseConnection *)newDatabaseConnection; - -+ (YapDatabaseOptions *)defaultDatabaseOptions; - -#pragma mark - Extension Registration - -+ (void)incrementVersionOfDatabaseExtension:(NSString *)extensionName; - -- (BOOL)registerExtension:(YapDatabaseExtension *)extension withName:(NSString *)extensionName; - -- (void)asyncRegisterExtension:(YapDatabaseExtension *)extension withName:(NSString *)extensionName; -- (void)asyncRegisterExtension:(YapDatabaseExtension *)extension - withName:(NSString *)extensionName - completion:(nullable dispatch_block_t)completion; - -- (nullable id)registeredExtension:(NSString *)extensionName; - -- (NSArray *)registeredExtensionNames; - -#pragma mark - - -- (unsigned long long)databaseFileSize; -- (unsigned long long)databaseWALFileSize; -- (unsigned long long)databaseSHMFileSize; - -- (YapDatabaseConnection *)registrationConnection; - -#pragma mark - Password - -/** - * Returns NO if: - * - * - Keychain is locked because device has just been restarted. - * - Password could not be retrieved because of a keychain error. - */ -+ (BOOL)isDatabasePasswordAccessible; - -+ (nullable NSData *)tryToLoadDatabaseLegacyPassphrase:(NSError **)errorHandle; -+ (void)removeLegacyPassphrase; - -+ (void)storeDatabaseCipherKeySpec:(NSData *)cipherKeySpecData; - -- (void)logFileSizes; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/OWSStorage.m b/SignalServiceKit/src/Storage/OWSStorage.m deleted file mode 100644 index 7f83dad12..000000000 --- a/SignalServiceKit/src/Storage/OWSStorage.m +++ /dev/null @@ -1,946 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSStorage.h" -#import "AppContext.h" -#import "NSNotificationCenter+OWS.h" -#import "NSUserDefaults+OWS.h" -#import "OWSBackgroundTask.h" -#import "OWSFileSystem.h" -#import "OWSPrimaryStorage.h" -#import "OWSStorage+Subclass.h" -#import "TSAttachmentStream.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const StorageIsReadyNotification = @"StorageIsReadyNotification"; -NSString *const OWSResetStorageNotification = @"OWSResetStorageNotification"; - -static NSString *keychainService = @"TSKeyChainService"; -static NSString *keychainDBLegacyPassphrase = @"TSDatabasePass"; -static NSString *keychainDBCipherKeySpec = @"OWSDatabaseCipherKeySpec"; - -const NSUInteger kDatabasePasswordLength = 30; - -typedef NSData *_Nullable (^LoadDatabaseMetadataBlock)(NSError **_Nullable); -typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void); - -NSString *const kNSUserDefaults_DatabaseExtensionVersionMap = @"kNSUserDefaults_DatabaseExtensionVersionMap"; - -#pragma mark - - -@interface YapDatabaseConnection () - -- (id)initWithDatabase:(YapDatabase *)database; - -@end - -#pragma mark - - -@implementation OWSDatabaseConnection - -- (id)initWithDatabase:(YapDatabase *)database delegate:(id)delegate -{ - self = [super initWithDatabase:database]; - - if (!self) { - return self; - } - - OWSAssertDebug(delegate); - - self.delegate = delegate; - - return self; -} - -// Assert that the database is in a ready state (specifically that any sync database -// view registrations have completed and any async registrations have been started) -// before creating write transactions. -// -// Creating write transactions before the _sync_ database views are registered -// causes YapDatabase to rebuild all of our database views, which is catastrophic. -// Specifically, it causes YDB's "view version" checks to fail. -- (void)readWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block -{ - id delegate = self.delegate; - OWSAssertDebug(delegate); - OWSAssertDebug(delegate.areAllRegistrationsComplete); - - OWSBackgroundTask *_Nullable backgroundTask = nil; - if (CurrentAppContext().isMainApp && !CurrentAppContext().isRunningTests) { - backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - } - [super readWriteWithBlock:block]; - backgroundTask = nil; -} - -- (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block -{ - [self asyncReadWriteWithBlock:block completionQueue:NULL completionBlock:NULL]; -} - -- (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block - completionBlock:(nullable dispatch_block_t)completionBlock -{ - [self asyncReadWriteWithBlock:block completionQueue:NULL completionBlock:completionBlock]; -} - -- (void)asyncReadWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block - completionQueue:(nullable dispatch_queue_t)completionQueue - completionBlock:(nullable dispatch_block_t)completionBlock -{ - id delegate = self.delegate; - OWSAssertDebug(delegate); - OWSAssertDebug(delegate.areAllRegistrationsComplete); - - __block OWSBackgroundTask *_Nullable backgroundTask = nil; - if (CurrentAppContext().isMainApp) { - backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - } - [super asyncReadWriteWithBlock:block completionQueue:completionQueue completionBlock:^{ - if (completionBlock) { - completionBlock(); - } - backgroundTask = nil; - }]; -} - -@end - -#pragma mark - - -// This class is only used in DEBUG builds. -@interface YapDatabase () - -- (void)addConnection:(YapDatabaseConnection *)connection; - -- (YapDatabaseConnection *)registrationConnection; - -@end - -#pragma mark - - -@interface OWSDatabase () - -@property (atomic, weak) id delegate; - -@end - -#pragma mark - - -@implementation OWSDatabase - -- (id)initWithPath:(NSString *)inPath - serializer:(nullable YapDatabaseSerializer)inSerializer - deserializer:(YapDatabaseDeserializer)inDeserializer - options:(YapDatabaseOptions *)inOptions - delegate:(id)delegate -{ - self = [super initWithPath:inPath serializer:inSerializer deserializer:inDeserializer options:inOptions]; - - if (!self) { - return self; - } - - OWSAssertDebug(delegate); - - self.delegate = delegate; - - return self; -} - -// This clobbers the superclass implementation to include asserts which -// ensure that the database is in a ready state before creating write transactions. -// -// See comments in OWSDatabaseConnection. -- (YapDatabaseConnection *)newConnection -{ - id delegate = self.delegate; - OWSAssertDebug(delegate); - - OWSDatabaseConnection *connection = [[OWSDatabaseConnection alloc] initWithDatabase:self delegate:delegate]; - [self addConnection:connection]; - return connection; -} - -- (YapDatabaseConnection *)registrationConnection -{ - YapDatabaseConnection *connection = [super registrationConnection]; - return connection; -} - -@end - -#pragma mark - - -@interface OWSUnknownDBObject : TSYapDatabaseObject - -@end - -#pragma mark - - -/** - * A default object to return when we can't deserialize an object from YapDB. This can prevent crashes when - * old objects linger after their definition file is removed. The danger is that, the objects can lay in wait - * until the next time a DB extension is added and we necessarily enumerate the entire DB. - */ -@implementation OWSUnknownDBObject - -- (void)encodeWithCoder:(NSCoder *)aCoder -{ - OWSFailDebug(@"Tried to save object from unknown collection"); - - return [super encodeWithCoder:aCoder]; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - return self; -} - -- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSFailDebug(@"Tried to save unknown object"); - - // No-op. -} - -- (void)touchWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSFailDebug(@"Tried to touch unknown object"); - - // No-op. -} - -- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSFailDebug(@"Tried to remove unknown object"); - - // No-op. -} - -@end - -#pragma mark - - -@interface OWSUnarchiverDelegate : NSObject - -@end - -#pragma mark - - -@implementation OWSUnarchiverDelegate - -- (nullable Class)unarchiver:(NSKeyedUnarchiver *)unarchiver - cannotDecodeObjectOfClassName:(NSString *)name - originalClasses:(NSArray *)classNames -{ - if ([name isEqualToString:@"TSRecipient"]) { - OWSLogError(@"Could not decode object: %@", name); - } else { - NSLog(@"Could not decode object: %@", name); - } - OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotDecodeClass]); - return [OWSUnknownDBObject class]; -} - -@end - -#pragma mark - - -@interface OWSStorage () - -@property (atomic, nullable) YapDatabase *database; - -@property (nonatomic) NSMutableArray *extensionNames; - -@end - -#pragma mark - - -@implementation OWSStorage - -- (instancetype)initStorage -{ - self = [super init]; - - if (self) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(resetStorage) - name:OWSResetStorageNotification - object:nil]; - - self.extensionNames = [NSMutableArray new]; - } - - return self; -} - -- (void)dealloc -{ - // Surface memory leaks by logging the deallocation of this class. - OWSLogVerbose(@"Dealloc: %@", self.class); - - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)loadDatabase -{ - if (![self tryToLoadDatabase]) { - // Failing to load the database is catastrophic. - // - // The best we can try to do is to discard the current database - // and behave like a clean install. - OWSFailDebug(@"Could not load database"); - OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotLoadDatabase]); - - // Try to reset app by deleting all databases. - // - // TODO: Possibly clean up all app files. - // [OWSStorage deleteDatabaseFiles]; - - if (![self tryToLoadDatabase]) { - OWSFailDebug(@"Could not load database (second try)"); - OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotLoadDatabaseSecondAttempt]); - - // Sleep to give analytics events time to be delivered. - [NSThread sleepForTimeInterval:15.0f]; - - OWSFail(@"Failed to initialize database."); - } - } -} - -- (nullable id)dbNotificationObject -{ - OWSAssertDebug(self.database); - - return self.database; -} - -- (BOOL)areAsyncRegistrationsComplete -{ - OWSAbstractMethod(); - - return NO; -} - -- (BOOL)areSyncRegistrationsComplete -{ - OWSAbstractMethod(); - - return NO; -} - -- (BOOL)areAllRegistrationsComplete -{ - return self.areSyncRegistrationsComplete && self.areAsyncRegistrationsComplete; -} - -- (void)runSyncRegistrations -{ - OWSAbstractMethod(); -} - -- (void)runAsyncRegistrationsWithCompletion:(void (^_Nonnull)(void))completion -{ - OWSAbstractMethod(); -} - -+ (void)registerExtensionsWithMigrationBlock:(OWSStorageMigrationBlock)migrationBlock -{ - OWSAssertDebug(migrationBlock); - - __block OWSBackgroundTask *_Nullable backgroundTask = - [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - - [OWSPrimaryStorage.sharedManager runSyncRegistrations]; - - [OWSPrimaryStorage.sharedManager runAsyncRegistrationsWithCompletion:^{ - OWSAssertDebug(self.isStorageReady); - - [self postRegistrationCompleteNotification]; - - migrationBlock(); - - backgroundTask = nil; - }]; -} - -- (YapDatabaseConnection *)registrationConnection -{ - return self.database.registrationConnection; -} - -// Returns YES IFF all registrations are complete. -+ (void)postRegistrationCompleteNotification -{ - OWSAssertDebug(self.isStorageReady); - - OWSLogInfo(@""); - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [[NSNotificationCenter defaultCenter] postNotificationNameAsync:StorageIsReadyNotification - object:nil - userInfo:nil]; - }); -} - -+ (BOOL)isStorageReady -{ - return OWSPrimaryStorage.sharedManager.areAllRegistrationsComplete; -} - -+ (YapDatabaseOptions *)defaultDatabaseOptions -{ - YapDatabaseOptions *options = [[YapDatabaseOptions alloc] init]; - options.corruptAction = YapDatabaseCorruptAction_Fail; - options.enableMultiProcessSupport = YES; - - // We leave a portion of the header decrypted so that iOS will recognize the file - // as a SQLite database. Otherwise, because the database lives in a shared data container, - // and our usage of sqlite's write-ahead logging retains a lock on the database, the OS - // would kill the app/share extension as soon as it is backgrounded. - options.cipherUnencryptedHeaderLength = kSqliteHeaderLength; - - // If we want to migrate to the new cipher defaults in SQLCipher4+ we'll need to do a one time - // migration. See the `PRAGMA cipher_migrate` documentation for details. - // https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_migrate - options.legacyCipherCompatibilityVersion = 3; - - // If any of these asserts fails, we need to verify and update - // OWSDatabaseConverter which assumes the values of these options. - OWSAssertDebug(options.cipherDefaultkdfIterNumber == 0); - OWSAssertDebug(options.kdfIterNumber == 0); - OWSAssertDebug(options.cipherPageSize == 0); - OWSAssertDebug(options.pragmaPageSize == 0); - OWSAssertDebug(options.pragmaJournalSizeLimit == 0); - OWSAssertDebug(options.pragmaMMapSize == 0); - - return options; -} - -- (BOOL)tryToLoadDatabase -{ - __weak OWSStorage *weakSelf = self; - YapDatabaseOptions *options = [self.class defaultDatabaseOptions]; - options.cipherKeySpecBlock = ^{ - // NOTE: It's critical that we don't capture a reference to self - // (e.g. by using OWSAssertDebug()) or this database will contain a - // circular reference and will leak. - OWSStorage *strongSelf = weakSelf; - OWSCAssertDebug(strongSelf); - - // Rather than compute this once and capture the value of the key - // in the closure, we prefer to fetch the key from the keychain multiple times - // in order to keep the key out of application memory. - NSData *databaseKeySpec = [strongSelf databaseKeySpec]; - OWSCAssertDebug(databaseKeySpec.length == kSQLCipherKeySpecLength); - return databaseKeySpec; - }; - - // Sanity checking elsewhere asserts we should only regenerate key specs when - // there is no existing database, so rather than lazily generate in the cipherKeySpecBlock - // we must ensure the keyspec exists before we create the database. - [self ensureDatabaseKeySpecExists]; - - OWSDatabase *database = [[OWSDatabase alloc] initWithPath:[self databaseFilePath] - serializer:nil - deserializer:[[self class] logOnFailureDeserializer] - options:options - delegate:self]; - - if (!database) { - return NO; - } - - _database = database; - - return YES; -} - -/** - * NSCoding sometimes throws exceptions killing our app. We want to log that exception. - **/ -+ (YapDatabaseDeserializer)logOnFailureDeserializer -{ - OWSUnarchiverDelegate *unarchiverDelegate = [OWSUnarchiverDelegate new]; - - return ^id(NSString __unused *collection, NSString __unused *key, NSData *data) { - if (!data || data.length <= 0) { - OWSFailDebug(@"can't deserialize null object: %@", collection); - return [OWSUnknownDBObject new]; - } - - @try { - NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; - unarchiver.delegate = unarchiverDelegate; - return [unarchiver decodeObjectForKey:@"root"]; - } @catch (NSException *exception) { - // Sync log in case we bail - OWSProdCritical([OWSAnalyticsEvents storageErrorDeserialization]); - @throw exception; - } - }; -} - -- (YapDatabaseConnection *)newDatabaseConnection -{ - YapDatabaseConnection *dbConnection = self.database.newConnection; - if (!dbConnection) { - OWSFail(@"Storage could not open new database connection."); - } - return dbConnection; -} - -#pragma mark - Extension Registration - -+ (void)incrementVersionOfDatabaseExtension:(NSString *)extensionName -{ - OWSLogError(@"%@", extensionName); - - // Don't increment version of a given extension more than once - // per launch. - static NSMutableSet *incrementedViewSet = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - incrementedViewSet = [NSMutableSet new]; - }); - @synchronized(incrementedViewSet) { - if ([incrementedViewSet containsObject:extensionName]) { - OWSLogInfo(@"Ignoring redundant increment: %@", extensionName); - return; - } - [incrementedViewSet addObject:extensionName]; - } - - NSUserDefaults *appUserDefaults = [NSUserDefaults appUserDefaults]; - OWSAssertDebug(appUserDefaults); - NSMutableDictionary *_Nullable versionMap = - [[appUserDefaults valueForKey:kNSUserDefaults_DatabaseExtensionVersionMap] mutableCopy]; - if (!versionMap) { - versionMap = [NSMutableDictionary new]; - } - NSNumber *_Nullable versionSuffix = versionMap[extensionName]; - versionMap[extensionName] = @(versionSuffix.intValue + 1); - [appUserDefaults setValue:versionMap forKey:kNSUserDefaults_DatabaseExtensionVersionMap]; - [appUserDefaults synchronize]; -} - -- (nullable NSString *)appendSuffixToDatabaseExtensionVersionIfNecessary:(nullable NSString *)versionTag - extensionName:(NSString *)extensionName -{ - OWSAssertIsOnMainThread(); - - NSUserDefaults *appUserDefaults = [NSUserDefaults appUserDefaults]; - OWSAssertDebug(appUserDefaults); - NSDictionary *_Nullable versionMap = - [appUserDefaults valueForKey:kNSUserDefaults_DatabaseExtensionVersionMap]; - NSNumber *_Nullable versionSuffix = versionMap[extensionName]; - - if (versionSuffix) { - NSString *result = - [NSString stringWithFormat:@"%@.%@", (versionTag.length < 1 ? @"0" : versionTag), versionSuffix]; - OWSLogWarn(@"database extension version: %@ + %@ -> %@", versionTag, versionSuffix, result); - return result; - } - return versionTag; -} - -- (YapDatabaseExtension *)updateExtensionVersion:(YapDatabaseExtension *)extension withName:(NSString *)extensionName -{ - OWSAssertDebug(extension); - OWSAssertDebug(extensionName.length > 0); - - if ([extension isKindOfClass:[YapDatabaseAutoView class]]) { - YapDatabaseAutoView *databaseView = (YapDatabaseAutoView *)extension; - YapDatabaseAutoView *databaseViewCopy = [[YapDatabaseAutoView alloc] - initWithGrouping:databaseView.grouping - sorting:databaseView.sorting - versionTag:[self appendSuffixToDatabaseExtensionVersionIfNecessary:databaseView.versionTag - extensionName:extensionName] - options:databaseView.options]; - return databaseViewCopy; - } else if ([extension isKindOfClass:[YapDatabaseSecondaryIndex class]]) { - YapDatabaseSecondaryIndex *secondaryIndex = (YapDatabaseSecondaryIndex *)extension; - OWSAssertDebug(secondaryIndex->setup); - OWSAssertDebug(secondaryIndex->handler); - YapDatabaseSecondaryIndex *secondaryIndexCopy = [[YapDatabaseSecondaryIndex alloc] - initWithSetup:secondaryIndex->setup - handler:secondaryIndex->handler - versionTag:[self appendSuffixToDatabaseExtensionVersionIfNecessary:secondaryIndex.versionTag - extensionName:extensionName] - options:secondaryIndex->options]; - return secondaryIndexCopy; - } else if ([extension isKindOfClass:[YapDatabaseFullTextSearch class]]) { - YapDatabaseFullTextSearch *fullTextSearch = (YapDatabaseFullTextSearch *)extension; - - NSString *versionTag = [self appendSuffixToDatabaseExtensionVersionIfNecessary:fullTextSearch.versionTag extensionName:extensionName]; - YapDatabaseFullTextSearch *fullTextSearchCopy = - [[YapDatabaseFullTextSearch alloc] initWithColumnNames:fullTextSearch->columnNames.array - options:fullTextSearch->options - handler:fullTextSearch->handler - ftsVersion:fullTextSearch->ftsVersion - versionTag:versionTag]; - - return fullTextSearchCopy; - } else if ([extension isKindOfClass:[YapDatabaseCrossProcessNotification class]]) { - // versionTag doesn't matter for YapDatabaseCrossProcessNotification. - return extension; - } else { - // This method needs to be able to update the versionTag of all extensions. - // If we start using other extension types, we need to modify this method to - // handle them as well. - OWSFailDebug(@"Unknown extension type: %@", [extension class]); - - return extension; - } -} - -- (BOOL)registerExtension:(YapDatabaseExtension *)extension withName:(NSString *)extensionName -{ - extension = [self updateExtensionVersion:extension withName:extensionName]; - - OWSAssertDebug(![self.extensionNames containsObject:extensionName]); - [self.extensionNames addObject:extensionName]; - - return [self.database registerExtension:extension withName:extensionName]; -} - -- (void)asyncRegisterExtension:(YapDatabaseExtension *)extension - withName:(NSString *)extensionName -{ - [self asyncRegisterExtension:extension withName:extensionName completion:nil]; -} - -- (void)asyncRegisterExtension:(YapDatabaseExtension *)extension - withName:(NSString *)extensionName - completion:(nullable dispatch_block_t)completion -{ - extension = [self updateExtensionVersion:extension withName:extensionName]; - - OWSAssertDebug(![self.extensionNames containsObject:extensionName]); - [self.extensionNames addObject:extensionName]; - - [self.database asyncRegisterExtension:extension - withName:extensionName - completionBlock:^(BOOL ready) { - if (!ready) { - OWSFailDebug(@"asyncRegisterExtension failed: %@", extensionName); - } else { - OWSLogVerbose(@"asyncRegisterExtension succeeded: %@", extensionName); - } - - dispatch_async(dispatch_get_main_queue(), ^{ - if (completion) { - completion(); - } - }); - }]; -} - -- (nullable id)registeredExtension:(NSString *)extensionName -{ - return [self.database registeredExtension:extensionName]; -} - -- (NSArray *)registeredExtensionNames -{ - return [self.extensionNames copy]; -} - -#pragma mark - Password - -+ (void)deleteDatabaseFiles -{ - [OWSFileSystem deleteFile:[OWSPrimaryStorage legacyDatabaseFilePath]]; - [OWSFileSystem deleteFile:[OWSPrimaryStorage legacyDatabaseFilePath_SHM]]; - [OWSFileSystem deleteFile:[OWSPrimaryStorage legacyDatabaseFilePath_WAL]]; - [OWSFileSystem deleteFile:[OWSPrimaryStorage sharedDataDatabaseFilePath]]; - [OWSFileSystem deleteFile:[OWSPrimaryStorage sharedDataDatabaseFilePath_SHM]]; - [OWSFileSystem deleteFile:[OWSPrimaryStorage sharedDataDatabaseFilePath_WAL]]; -} - -- (void)closeStorageForTests -{ - [self resetStorage]; - - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)resetStorage -{ - self.database = nil; - - [OWSStorage deleteDatabaseFiles]; -} - -+ (void)resetAllStorage -{ - [[NSNotificationCenter defaultCenter] postNotificationName:OWSResetStorageNotification object:nil]; - - // This might be redundant but in the spirit of thoroughness... - [self deleteDatabaseFiles]; - - [self deleteDBKeys]; - - if (CurrentAppContext().isMainApp) { - [TSAttachmentStream deleteAttachments]; - } - - // TODO: Delete Profiles on Disk? -} - -#pragma mark - Password - -- (NSString *)databaseFilePath -{ - OWSAbstractMethod(); - - return @""; -} - -- (NSString *)databaseFilePath_SHM -{ - OWSAbstractMethod(); - - return @""; -} - -- (NSString *)databaseFilePath_WAL -{ - OWSAbstractMethod(); - - return @""; -} - -#pragma mark - Keychain - -+ (BOOL)isDatabasePasswordAccessible -{ - NSError *error; - NSData *cipherKeySpec = [self tryToLoadDatabaseCipherKeySpec:&error]; - - if (cipherKeySpec && !error) { - return YES; - } - - if (error) { - OWSLogWarn(@"Database key couldn't be accessed: %@", error.localizedDescription); - } - - return NO; -} - -+ (nullable NSData *)tryToLoadDatabaseLegacyPassphrase:(NSError **)errorHandle -{ - return [self tryToLoadKeyChainValue:keychainDBLegacyPassphrase errorHandle:errorHandle]; -} - -+ (nullable NSData *)tryToLoadDatabaseCipherKeySpec:(NSError **)errorHandle -{ - NSData *_Nullable data = [self tryToLoadKeyChainValue:keychainDBCipherKeySpec errorHandle:errorHandle]; - OWSAssertDebug(!data || data.length == kSQLCipherKeySpecLength); - - return data; -} - -+ (void)storeDatabaseCipherKeySpec:(NSData *)cipherKeySpecData -{ - OWSAssertDebug(cipherKeySpecData.length == kSQLCipherKeySpecLength); - - [self storeKeyChainValue:cipherKeySpecData keychainKey:keychainDBCipherKeySpec]; -} - -+ (void)removeLegacyPassphrase -{ - OWSLogInfo(@"removing legacy passphrase"); - - NSError *_Nullable error; - BOOL result = [CurrentAppContext().keychainStorage removeWithService:keychainService - key:keychainDBLegacyPassphrase - error:&error]; - if (error || !result) { - OWSFailDebug(@"could not remove legacy passphrase."); - } -} - -- (void)ensureDatabaseKeySpecExists -{ - NSError *error; - NSData *_Nullable keySpec = [[self class] tryToLoadDatabaseCipherKeySpec:&error]; - - if (error || (keySpec.length != kSQLCipherKeySpecLength)) { - // Because we use kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, - // the keychain will be inaccessible after device restart until - // device is unlocked for the first time. If the app receives - // a push notification, we won't be able to access the keychain to - // process that notification, so we should just terminate by throwing - // an uncaught exception. - NSString *errorDescription = [NSString - stringWithFormat:@"CipherKeySpec inaccessible. New install or no unlock since device restart? Error: %@", - error]; - if (CurrentAppContext().isMainApp) { - UIApplicationState applicationState = CurrentAppContext().reportedApplicationState; - errorDescription = [errorDescription - stringByAppendingFormat:@", ApplicationState: %@", NSStringForUIApplicationState(applicationState)]; - } - OWSLogError(@"%@", errorDescription); - [DDLog flushLog]; - - if (CurrentAppContext().isMainApp) { - if (CurrentAppContext().isInBackground) { - // Rather than crash here, we should have already detected the situation earlier - // and exited gracefully (in the app delegate) using isDatabasePasswordAccessible. - // This is a last ditch effort to avoid blowing away the user's database. - [self raiseKeySpecInaccessibleExceptionWithErrorDescription:errorDescription]; - } - } else { - [self raiseKeySpecInaccessibleExceptionWithErrorDescription:@"CipherKeySpec inaccessible; not main app."]; - } - - // At this point, either this is a new install so there's no existing password to retrieve - // or the keychain has become corrupt. Either way, we want to get back to a - // "known good state" and behave like a new install. - BOOL doesDBExist = [NSFileManager.defaultManager fileExistsAtPath:[self databaseFilePath]]; - if (doesDBExist) { - OWSFailDebug(@"Could not load database metadata"); - OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotLoadDatabaseSecondAttempt]); - } - - if (!CurrentAppContext().isRunningTests) { - // Try to reset app by deleting database. - [OWSStorage resetAllStorage]; - } - - keySpec = [Randomness generateRandomBytes:(int)kSQLCipherKeySpecLength]; - [[self class] storeDatabaseCipherKeySpec:keySpec]; - } -} - -- (NSData *)databaseKeySpec -{ - NSError *error; - NSData *_Nullable keySpec = [[self class] tryToLoadDatabaseCipherKeySpec:&error]; - - if (error) { - OWSLogError(@"failed to fetch databaseKeySpec with error: %@", error); - [self raiseKeySpecInaccessibleExceptionWithErrorDescription:@"CipherKeySpec inaccessible"]; - } - - if (keySpec.length != kSQLCipherKeySpecLength) { - OWSLogError(@"keyspec had length: %lu", (unsigned long)keySpec.length); - [self raiseKeySpecInaccessibleExceptionWithErrorDescription:@"CipherKeySpec invalid"]; - } - - return keySpec; -} - -- (void)raiseKeySpecInaccessibleExceptionWithErrorDescription:(NSString *)errorDescription -{ - OWSAssertDebug(CurrentAppContext().isMainApp && CurrentAppContext().isInBackground); - - // Sleep to give analytics events time to be delivered. - [NSThread sleepForTimeInterval:5.0f]; - - // Presumably this happened in response to a push notification. It's possible that the keychain is corrupted - // but it could also just be that the user hasn't yet unlocked their device since our password is - // kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly - OWSFail(@"%@", errorDescription); -} - -+ (void)deleteDBKeys -{ - NSError *_Nullable error; - BOOL result = [CurrentAppContext().keychainStorage removeWithService:keychainService - key:keychainDBLegacyPassphrase - error:&error]; - if (error || !result) { - OWSFailDebug(@"could not remove legacy passphrase."); - } - result = [CurrentAppContext().keychainStorage removeWithService:keychainService - key:keychainDBCipherKeySpec - error:&error]; - if (error || !result) { - OWSFailDebug(@"could not remove cipher key spec."); - } -} - -- (unsigned long long)databaseFileSize -{ - return [OWSFileSystem fileSizeOfPath:self.databaseFilePath].unsignedLongLongValue; -} - -- (unsigned long long)databaseWALFileSize -{ - return [OWSFileSystem fileSizeOfPath:self.databaseFilePath_WAL].unsignedLongLongValue; -} - -- (unsigned long long)databaseSHMFileSize -{ - return [OWSFileSystem fileSizeOfPath:self.databaseFilePath_SHM].unsignedLongLongValue; -} - -+ (nullable NSData *)tryToLoadKeyChainValue:(NSString *)keychainKey errorHandle:(NSError **)errorHandle -{ - OWSAssertDebug(keychainKey.length > 0); - OWSAssertDebug(errorHandle); - - NSData *_Nullable data = - [CurrentAppContext().keychainStorage dataForService:keychainService key:keychainKey error:errorHandle]; - if (*errorHandle || !data) { - OWSLogWarn(@"could not load keychain value."); - } - return data; -} - -+ (void)storeKeyChainValue:(NSData *)data keychainKey:(NSString *)keychainKey -{ - OWSAssertDebug(keychainKey.length > 0); - OWSAssertDebug(data.length > 0); - - NSError *error; - BOOL success = - [CurrentAppContext().keychainStorage setWithData:data service:keychainService key:keychainKey error:&error]; - if (!success || error) { - OWSFailDebug(@"Could not store database metadata"); - OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotStoreKeychainValue]); - - // Sleep to give analytics events time to be delivered. - [NSThread sleepForTimeInterval:15.0f]; - - OWSFail(@"Setting keychain value failed with error: %@", error); - } else { - OWSLogWarn(@"Successfully set new keychain value."); - } -} - -- (void)logFileSizes -{ - OWSLogInfo(@"Database file size: %@", [OWSFileSystem fileSizeOfPath:self.databaseFilePath]); - OWSLogInfo(@"\t SHM file size: %@", [OWSFileSystem fileSizeOfPath:self.databaseFilePath_SHM]); - OWSLogInfo(@"\t WAL file size: %@", [OWSFileSystem fileSizeOfPath:self.databaseFilePath_WAL]); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/SSKIncrementingIdFinder.swift b/SignalServiceKit/src/Storage/SSKIncrementingIdFinder.swift deleted file mode 100644 index 84e6a33af..000000000 --- a/SignalServiceKit/src/Storage/SSKIncrementingIdFinder.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -@objc -public class SSKIncrementingIdFinder: NSObject { - - @objc - public static let collectionName = "IncrementingIdCollection" - - @objc - public class func previousId(key: String, transaction: YapDatabaseReadTransaction) -> UInt64 { - let previousId: UInt64 = transaction.object(forKey: key, inCollection: collectionName) as? UInt64 ?? 0 - return previousId - } - - @objc - public class func nextId(key: String, transaction: YapDatabaseReadWriteTransaction) -> UInt64 { - let previousId: UInt64 = transaction.object(forKey: key, inCollection: collectionName) as? UInt64 ?? 0 - let nextId: UInt64 = previousId + 1 - - transaction.setObject(nextId, forKey: key, inCollection: collectionName) - return nextId - } -} diff --git a/SignalServiceKit/src/Storage/SSKJobRecord.h b/SignalServiceKit/src/Storage/SSKJobRecord.h deleted file mode 100644 index c14a04bca..000000000 --- a/SignalServiceKit/src/Storage/SSKJobRecord.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSYapDatabaseObject.h" - -NS_ASSUME_NONNULL_BEGIN - -extern NSErrorDomain const SSKJobRecordErrorDomain; - -typedef NS_ERROR_ENUM(SSKJobRecordErrorDomain, JobRecordError){ - JobRecordError_AssertionError = 100, - JobRecordError_IllegalStateTransition, -}; - -typedef NS_ENUM(NSUInteger, SSKJobRecordStatus) { - SSKJobRecordStatus_Unknown, - SSKJobRecordStatus_Ready, - SSKJobRecordStatus_Running, - SSKJobRecordStatus_PermanentlyFailed, - SSKJobRecordStatus_Obsolete -}; - -#pragma mark - - -@interface SSKJobRecord : TSYapDatabaseObject - -@property (nonatomic) NSUInteger failureCount; -@property (nonatomic) NSString *label; - -- (instancetype)initWithLabel:(NSString *)label NS_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithUniqueId:(NSString *_Nullable)uniqueId NS_UNAVAILABLE; -- (instancetype)init NS_UNAVAILABLE; - -@property (readonly, nonatomic) SSKJobRecordStatus status; -@property (nonatomic, readonly) UInt64 sortId; - -- (BOOL)saveAsStartedWithTransaction:(YapDatabaseReadWriteTransaction *)transaction - error:(NSError **)outError NS_SWIFT_NAME(saveAsStarted(transaction:)); - -- (void)saveAsPermanentlyFailedWithTransaction:(YapDatabaseReadWriteTransaction *)transaction - NS_SWIFT_NAME(saveAsPermanentlyFailed(transaction:)); - -- (void)saveAsObsoleteWithTransaction:(YapDatabaseReadWriteTransaction *)transaction - NS_SWIFT_NAME(saveAsObsolete(transaction:)); - -- (BOOL)saveRunningAsReadyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction - error:(NSError **)outError NS_SWIFT_NAME(saveRunningAsReady(transaction:)); - -- (BOOL)addFailureWithWithTransaction:(YapDatabaseReadWriteTransaction *)transaction - error:(NSError **)outError NS_SWIFT_NAME(addFailure(transaction:)); - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/SSKJobRecord.m b/SignalServiceKit/src/Storage/SSKJobRecord.m deleted file mode 100644 index 615ceefd9..000000000 --- a/SignalServiceKit/src/Storage/SSKJobRecord.m +++ /dev/null @@ -1,127 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "SSKJobRecord.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -NSErrorDomain const SSKJobRecordErrorDomain = @"SignalServiceKit.JobRecord"; - -#pragma mark - -@interface SSKJobRecord () - -@property (nonatomic) SSKJobRecordStatus status; -@property (nonatomic) UInt64 sortId; - -@end - -@implementation SSKJobRecord - -- (instancetype)initWithLabel:(NSString *)label -{ - self = [super init]; - if (!self) { - return self; - } - - _status = SSKJobRecordStatus_Ready; - _label = label; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -#pragma mark - TSYapDatabaseObject Overrides - -+ (NSString *)collection -{ - // To avoid a plethora of identical JobRecord subclasses, all job records share - // a common collection and JobQueue's distinguish their behavior by the job's - // `label` - return @"JobRecord"; -} - -- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (self.sortId == 0) { - self.sortId = [SSKIncrementingIdFinder nextIdWithKey:self.class.collection transaction:transaction]; - } - [super saveWithTransaction:transaction]; -} - -#pragma mark - - -- (BOOL)saveAsStartedWithTransaction:(YapDatabaseReadWriteTransaction *)transaction error:(NSError **)outError -{ - if (self.status != SSKJobRecordStatus_Ready) { - *outError = - [NSError errorWithDomain:SSKJobRecordErrorDomain code:JobRecordError_IllegalStateTransition userInfo:nil]; - return NO; - } - self.status = SSKJobRecordStatus_Running; - [self saveWithTransaction:transaction]; - - return YES; -} - -- (void)saveAsPermanentlyFailedWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - self.status = SSKJobRecordStatus_PermanentlyFailed; - [self saveWithTransaction:transaction]; -} - -- (void)saveAsObsoleteWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - self.status = SSKJobRecordStatus_Obsolete; - [self saveWithTransaction:transaction]; -} - -- (BOOL)saveRunningAsReadyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction error:(NSError **)outError -{ - switch (self.status) { - case SSKJobRecordStatus_Running: { - self.status = SSKJobRecordStatus_Ready; - [self saveWithTransaction:transaction]; - return YES; - } - case SSKJobRecordStatus_Ready: - case SSKJobRecordStatus_PermanentlyFailed: - case SSKJobRecordStatus_Obsolete: - case SSKJobRecordStatus_Unknown: { - *outError = [NSError errorWithDomain:SSKJobRecordErrorDomain - code:JobRecordError_IllegalStateTransition - userInfo:nil]; - return NO; - } - } -} - -- (BOOL)addFailureWithWithTransaction:(YapDatabaseReadWriteTransaction *)transaction error:(NSError **)outError -{ - switch (self.status) { - case SSKJobRecordStatus_Running: { - self.failureCount++; - [self saveWithTransaction:transaction]; - return YES; - } - case SSKJobRecordStatus_Ready: - case SSKJobRecordStatus_PermanentlyFailed: - case SSKJobRecordStatus_Obsolete: - case SSKJobRecordStatus_Unknown: { - *outError = [NSError errorWithDomain:SSKJobRecordErrorDomain - code:JobRecordError_IllegalStateTransition - userInfo:nil]; - return NO; - } - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/SSKKeychainStorage.swift b/SignalServiceKit/src/Storage/SSKKeychainStorage.swift deleted file mode 100644 index 168609bed..000000000 --- a/SignalServiceKit/src/Storage/SSKKeychainStorage.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import SAMKeychain - -public enum KeychainStorageError: Error { - case failure(description: String) -} - -// MARK: - - -@objc public protocol SSKKeychainStorage: class { - - @objc func string(forService service: String, key: String) throws -> String - - @objc(setString:service:key:error:) func set(string: String, service: String, key: String) throws - - @objc func data(forService service: String, key: String) throws -> Data - - @objc func set(data: Data, service: String, key: String) throws - - @objc func remove(service: String, key: String) throws -} - -// MARK: - - -@objc -public class SSKDefaultKeychainStorage: NSObject, SSKKeychainStorage { - - @objc public static let shared = SSKDefaultKeychainStorage() - - // Force usage as a singleton - override private init() { - super.init() - - SwiftSingletons.register(self) - } - - @objc public func string(forService service: String, key: String) throws -> String { - var error: NSError? - let result = SAMKeychain.password(forService: service, account: key, error: &error) - if let error = error { - throw KeychainStorageError.failure(description: "\(logTag) error retrieving string: \(error)") - } - guard let string = result else { - throw KeychainStorageError.failure(description: "\(logTag) could not retrieve string") - } - return string - } - - @objc public func set(string: String, service: String, key: String) throws { - - SAMKeychain.setAccessibilityType(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) - - var error: NSError? - let result = SAMKeychain.setPassword(string, forService: service, account: key, error: &error) - if let error = error { - throw KeychainStorageError.failure(description: "\(logTag) error setting string: \(error)") - } - guard result else { - throw KeychainStorageError.failure(description: "\(logTag) could not set string") - } - } - - @objc public func data(forService service: String, key: String) throws -> Data { - var error: NSError? - let result = SAMKeychain.passwordData(forService: service, account: key, error: &error) - if let error = error { - throw KeychainStorageError.failure(description: "\(logTag) error retrieving data: \(error)") - } - guard let data = result else { - throw KeychainStorageError.failure(description: "\(logTag) could not retrieve data") - } - return data - } - - @objc public func set(data: Data, service: String, key: String) throws { - - SAMKeychain.setAccessibilityType(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) - - var error: NSError? - let result = SAMKeychain.setPasswordData(data, forService: service, account: key, error: &error) - if let error = error { - throw KeychainStorageError.failure(description: "\(logTag) error setting data: \(error)") - } - guard result else { - throw KeychainStorageError.failure(description: "\(logTag) could not set data") - } - } - - @objc public func remove(service: String, key: String) throws { - var error: NSError? - let result = SAMKeychain.deletePassword(forService: service, account: key, error: &error) - if let error = error { - // If deletion failed because the specified item could not be found in the keychain, consider it success. - if error.code == errSecItemNotFound { - Logger.info("Keychain delete failed; item not found.") - return - } - throw KeychainStorageError.failure(description: "\(logTag) error removing data: \(error)") - } - guard result else { - throw KeychainStorageError.failure(description: "\(logTag) could not remove data") - } - } -} diff --git a/SignalServiceKit/src/Storage/SSKMessageSenderJobRecord.h b/SignalServiceKit/src/Storage/SSKMessageSenderJobRecord.h deleted file mode 100644 index 039bbd9f8..000000000 --- a/SignalServiceKit/src/Storage/SSKMessageSenderJobRecord.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "SSKJobRecord.h" - -NS_ASSUME_NONNULL_BEGIN - -@class TSOutgoingMessage; - -@interface SSKMessageSenderJobRecord : SSKJobRecord - -@property (nonatomic, readonly, nullable) NSString *messageId; -@property (nonatomic, readonly, nullable) NSString *threadId; -@property (nonatomic, readonly, nullable) TSOutgoingMessage *invisibleMessage; -@property (nonatomic, readonly) BOOL removeMessageAfterSending; - -- (nullable instancetype)initWithMessage:(TSOutgoingMessage *)message - removeMessageAfterSending:(BOOL)removeMessageAfterSending - label:(NSString *)label - error:(NSError **)outError NS_DESIGNATED_INITIALIZER; - -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -- (instancetype)initWithLabel:(nullable NSString *)label NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/SSKMessageSenderJobRecord.m b/SignalServiceKit/src/Storage/SSKMessageSenderJobRecord.m deleted file mode 100644 index d7d92a480..000000000 --- a/SignalServiceKit/src/Storage/SSKMessageSenderJobRecord.m +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "SSKMessageSenderJobRecord.h" -#import "TSOutgoingMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation SSKMessageSenderJobRecord - -#pragma mark - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (nullable instancetype)initWithMessage:(TSOutgoingMessage *)message - removeMessageAfterSending:(BOOL)removeMessageAfterSending - label:(NSString *)label - error:(NSError **)outError; -{ - self = [super initWithLabel:label]; - if (!self) { - return self; - } - - if (message.shouldBeSaved) { - _messageId = message.uniqueId; - if (_messageId == nil) { - *outError = [NSError errorWithDomain:SSKJobRecordErrorDomain - code:JobRecordError_AssertionError - userInfo:@{ NSDebugDescriptionErrorKey : @"messageId wasn't set" }]; - return nil; - } - _invisibleMessage = nil; - } else { - _messageId = nil; - _invisibleMessage = message; - } - - _removeMessageAfterSending = removeMessageAfterSending; - _threadId = message.uniqueThreadId; - - return self; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/TSDatabaseSecondaryIndexes.h b/SignalServiceKit/src/Storage/TSDatabaseSecondaryIndexes.h deleted file mode 100644 index f2e377654..000000000 --- a/SignalServiceKit/src/Storage/TSDatabaseSecondaryIndexes.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface TSDatabaseSecondaryIndexes : NSObject - -+ (NSString *)registerTimeStampIndexExtensionName; - -+ (YapDatabaseSecondaryIndex *)registerTimeStampIndex; - -+ (void)enumerateMessagesWithTimestamp:(uint64_t)timestamp - withBlock:(void (^)(NSString *collection, NSString *key, BOOL *stop))block - usingTransaction:(YapDatabaseReadTransaction *)transaction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/TSDatabaseSecondaryIndexes.m b/SignalServiceKit/src/Storage/TSDatabaseSecondaryIndexes.m deleted file mode 100644 index d31c677d9..000000000 --- a/SignalServiceKit/src/Storage/TSDatabaseSecondaryIndexes.m +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSDatabaseSecondaryIndexes.h" -#import "OWSStorage.h" -#import "TSInteraction.h" - -NS_ASSUME_NONNULL_BEGIN - -#define TSTimeStampSQLiteIndex @"messagesTimeStamp" - -@implementation TSDatabaseSecondaryIndexes - -+ (NSString *)registerTimeStampIndexExtensionName -{ - return @"idx"; -} - -+ (YapDatabaseSecondaryIndex *)registerTimeStampIndex { - YapDatabaseSecondaryIndexSetup *setup = [[YapDatabaseSecondaryIndexSetup alloc] init]; - [setup addColumn:TSTimeStampSQLiteIndex withType:YapDatabaseSecondaryIndexTypeReal]; - - YapDatabaseSecondaryIndexWithObjectBlock block = - ^(YapDatabaseReadTransaction *transaction, NSMutableDictionary *dict, NSString *collection, NSString *key, id object) { - - if ([object isKindOfClass:[TSInteraction class]]) { - TSInteraction *interaction = (TSInteraction *)object; - - [dict setObject:@(interaction.timestamp) forKey:TSTimeStampSQLiteIndex]; - } - }; - - YapDatabaseSecondaryIndexHandler *handler = [YapDatabaseSecondaryIndexHandler withObjectBlock:block]; - - YapDatabaseSecondaryIndex *secondaryIndex = - [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:handler versionTag:nil]; - - return secondaryIndex; -} - - -+ (void)enumerateMessagesWithTimestamp:(uint64_t)timestamp - withBlock:(void (^)(NSString *collection, NSString *key, BOOL *stop))block - usingTransaction:(YapDatabaseReadTransaction *)transaction -{ - NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ = %lld", TSTimeStampSQLiteIndex, timestamp]; - YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; - [[transaction ext:[self registerTimeStampIndexExtensionName]] enumerateKeysMatchingQuery:query usingBlock:block]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/TSDatabaseView.h b/SignalServiceKit/src/Storage/TSDatabaseView.h deleted file mode 100644 index 65fa2571a..000000000 --- a/SignalServiceKit/src/Storage/TSDatabaseView.h +++ /dev/null @@ -1,74 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSStorage.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const TSInboxGroup; -extern NSString *const TSArchiveGroup; -extern NSString *const TSUnreadIncomingMessagesGroup; -extern NSString *const TSSecondaryDevicesGroup; - -extern NSString *const TSThreadDatabaseViewExtensionName; - -extern NSString *const TSMessageDatabaseViewExtensionName; -extern NSString *const TSMessageDatabaseViewExtensionName_Legacy; - -extern NSString *const TSUnreadDatabaseViewExtensionName; -extern NSString *const TSUnseenDatabaseViewExtensionName; -extern NSString *const TSThreadOutgoingMessageDatabaseViewExtensionName; -extern NSString *const TSThreadSpecialMessagesDatabaseViewExtensionName; - -extern NSString *const TSSecondaryDevicesDatabaseViewExtensionName; - -extern NSString *const TSLazyRestoreAttachmentsGroup; -extern NSString *const TSLazyRestoreAttachmentsDatabaseViewExtensionName; - -@interface TSDatabaseView : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -#pragma mark - Views - -// Returns the "unseen" database view if it is ready; -// otherwise it returns the "unread" database view. -+ (id)unseenDatabaseViewExtension:(YapDatabaseReadTransaction *)transaction; - -+ (id)threadOutgoingMessageDatabaseView:(YapDatabaseReadTransaction *)transaction; - -+ (id)threadSpecialMessagesDatabaseView:(YapDatabaseReadTransaction *)transaction; - -#pragma mark - Registration - -+ (void)registerCrossProcessNotifier:(OWSStorage *)storage; - -// This method must be called _AFTER_ asyncRegisterThreadInteractionsDatabaseView. -+ (void)asyncRegisterThreadDatabaseView:(OWSStorage *)storage; - -+ (void)asyncRegisterThreadInteractionsDatabaseView:(OWSStorage *)storage; -+ (void)asyncRegisterLegacyThreadInteractionsDatabaseView:(OWSStorage *)storage; - -+ (void)asyncRegisterThreadOutgoingMessagesDatabaseView:(OWSStorage *)storage; - -// Instances of OWSReadTracking for wasRead is NO and shouldAffectUnreadCounts is YES. -// -// Should be used for "unread message counts". -+ (void)asyncRegisterUnreadDatabaseView:(OWSStorage *)storage; - -// Should be used for "unread indicator". -// -// Instances of OWSReadTracking for wasRead is NO. -+ (void)asyncRegisterUnseenDatabaseView:(OWSStorage *)storage; - -+ (void)asyncRegisterThreadSpecialMessagesDatabaseView:(OWSStorage *)storage; - -+ (void)asyncRegisterSecondaryDevicesDatabaseView:(OWSStorage *)storage; - -+ (void)asyncRegisterLazyRestoreAttachmentsDatabaseView:(OWSStorage *)storage; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/TSDatabaseView.m b/SignalServiceKit/src/Storage/TSDatabaseView.m deleted file mode 100644 index 868b06c74..000000000 --- a/SignalServiceKit/src/Storage/TSDatabaseView.m +++ /dev/null @@ -1,514 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSDatabaseView.h" -#import "OWSDevice.h" -#import "OWSReadTracking.h" -#import "TSAttachment.h" -#import "TSAttachmentPointer.h" -#import "TSIncomingMessage.h" -#import "TSInvalidIdentityKeyErrorMessage.h" -#import "TSOutgoingMessage.h" -#import "TSThread.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const TSInboxGroup = @"TSInboxGroup"; -NSString *const TSArchiveGroup = @"TSArchiveGroup"; - -NSString *const TSUnreadIncomingMessagesGroup = @"TSUnreadIncomingMessagesGroup"; -NSString *const TSSecondaryDevicesGroup = @"TSSecondaryDevicesGroup"; - -// YAPDB BUG: when changing from non-persistent to persistent view, we had to rename TSThreadDatabaseViewExtensionName -// -> TSThreadDatabaseViewExtensionName2 to work around https://github.com/yapstudios/YapDatabase/issues/324 -NSString *const TSThreadDatabaseViewExtensionName = @"TSThreadDatabaseViewExtensionName2"; - -// We sort interactions by a monotonically increasing counter. -// -// Previously we sorted the interactions database by local timestamp, which was problematic if the local clock changed. -// We need to maintain the legacy extension for purposes of migration. -// -// The "Legacy" sorting extension name constant has the same value as always, so that it won't need to be rebuilt, while -// the "Modern" sorting extension name constant has the same symbol name that we've always used for sorting -// interactions, so that the callsites won't need to change. -NSString *const TSMessageDatabaseViewExtensionName = @"TSMessageDatabaseViewExtensionName_Monotonic"; -NSString *const TSMessageDatabaseViewExtensionName_Legacy = @"TSMessageDatabaseViewExtensionName"; - -NSString *const TSThreadOutgoingMessageDatabaseViewExtensionName = @"TSThreadOutgoingMessageDatabaseViewExtensionName"; -NSString *const TSUnreadDatabaseViewExtensionName = @"TSUnreadDatabaseViewExtensionName"; -NSString *const TSUnseenDatabaseViewExtensionName = @"TSUnseenDatabaseViewExtensionName"; -NSString *const TSThreadSpecialMessagesDatabaseViewExtensionName = @"TSThreadSpecialMessagesDatabaseViewExtensionName"; -NSString *const TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesDatabaseViewExtensionName"; -NSString *const TSLazyRestoreAttachmentsDatabaseViewExtensionName - = @"TSLazyRestoreAttachmentsDatabaseViewExtensionName"; -NSString *const TSLazyRestoreAttachmentsGroup = @"TSLazyRestoreAttachmentsGroup"; - -@interface OWSStorage (TSDatabaseView) - -- (BOOL)registerExtension:(YapDatabaseExtension *)extension withName:(NSString *)extensionName; - -@end - -#pragma mark - - -@implementation TSDatabaseView - -+ (void)registerCrossProcessNotifier:(OWSStorage *)storage -{ - OWSAssertDebug(storage); - - // I don't think the identifier and name of this extension matter for our purposes, - // so long as they don't conflict with any other extension names. - YapDatabaseExtension *extension = - [[YapDatabaseCrossProcessNotification alloc] initWithIdentifier:@"SignalCrossProcessNotifier"]; - [storage registerExtension:extension withName:@"SignalCrossProcessNotifier"]; -} - -+ (void)registerMessageDatabaseViewWithName:(NSString *)viewName - viewGrouping:(YapDatabaseViewGrouping *)viewGrouping - version:(NSString *)version - storage:(OWSStorage *)storage -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(viewName.length > 0); - OWSAssertDebug((viewGrouping)); - OWSAssertDebug(storage); - - YapDatabaseView *existingView = [storage registeredExtension:viewName]; - if (existingView) { - OWSFailDebug(@"Registered database view twice: %@", viewName); - return; - } - - YapDatabaseViewSorting *viewSorting = [self messagesSorting]; - - YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init]; - options.isPersistent = YES; - options.allowedCollections = - [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[TSInteraction collection]]]; - - YapDatabaseView *view = [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping - sorting:viewSorting - versionTag:version - options:options]; - [storage asyncRegisterExtension:view withName:viewName]; -} - -+ (void)asyncRegisterUnreadDatabaseView:(OWSStorage *)storage -{ - YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *( - YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { - if ([object conformsToProtocol:@protocol(OWSReadTracking)]) { - id possiblyRead = (id)object; - if (!possiblyRead.wasRead && possiblyRead.shouldAffectUnreadCounts) { - return possiblyRead.uniqueThreadId; - } - } - return nil; - }]; - - [self registerMessageDatabaseViewWithName:TSUnreadDatabaseViewExtensionName - viewGrouping:viewGrouping - version:@"2" - storage:storage]; -} - -+ (void)asyncRegisterUnseenDatabaseView:(OWSStorage *)storage -{ - YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *( - YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { - if ([object conformsToProtocol:@protocol(OWSReadTracking)]) { - id possiblyRead = (id)object; - if (!possiblyRead.wasRead) { - return possiblyRead.uniqueThreadId; - } - } - return nil; - }]; - - [self registerMessageDatabaseViewWithName:TSUnseenDatabaseViewExtensionName - viewGrouping:viewGrouping - version:@"2" - storage:storage]; -} - -+ (void)asyncRegisterThreadSpecialMessagesDatabaseView:(OWSStorage *)storage -{ - YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *( - YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { - if (![object isKindOfClass:[TSInteraction class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object class], collection); - return nil; - } - TSInteraction *interaction = (TSInteraction *)object; - if ([interaction isDynamicInteraction]) { - return interaction.uniqueThreadId; - } else if ([object isKindOfClass:[TSInvalidIdentityKeyErrorMessage class]]) { - return interaction.uniqueThreadId; - } else if ([object isKindOfClass:[TSErrorMessage class]]) { - TSErrorMessage *errorMessage = (TSErrorMessage *)object; - if (errorMessage.errorType == TSErrorMessageNonBlockingIdentityChange) { - return errorMessage.uniqueThreadId; - } - } - return nil; - }]; - - [self registerMessageDatabaseViewWithName:TSThreadSpecialMessagesDatabaseViewExtensionName - viewGrouping:viewGrouping - version:@"2" - storage:storage]; -} - -+ (void)asyncRegisterLegacyThreadInteractionsDatabaseView:(OWSStorage *)storage -{ - OWSAssertIsOnMainThread(); - OWSAssert(storage); - - YapDatabaseView *existingView = [storage registeredExtension:TSMessageDatabaseViewExtensionName_Legacy]; - if (existingView) { - OWSFailDebug(@"Registered database view twice: %@", TSMessageDatabaseViewExtensionName_Legacy); - return; - } - - YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *( - YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { - if (![object isKindOfClass:[TSInteraction class]]) { - OWSFailDebug(@"%@ Unexpected entity %@ in collection: %@", self.logTag, [object class], collection); - return nil; - } - TSInteraction *interaction = (TSInteraction *)object; - - return interaction.uniqueThreadId; - }]; - - YapDatabaseViewSorting *viewSorting = - [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, - NSString *group, - NSString *collection1, - NSString *key1, - id object1, - NSString *collection2, - NSString *key2, - id object2) { - if (![object1 isKindOfClass:[TSInteraction class]]) { - OWSFailDebug(@"%@ Unexpected entity %@ in collection: %@", self.logTag, [object1 class], collection1); - return NSOrderedSame; - } - if (![object2 isKindOfClass:[TSInteraction class]]) { - OWSFailDebug(@"%@ Unexpected entity %@ in collection: %@", self.logTag, [object2 class], collection2); - return NSOrderedSame; - } - TSInteraction *interaction1 = (TSInteraction *)object1; - TSInteraction *interaction2 = (TSInteraction *)object2; - - // Legit usage of timestampForLegacySorting since we're registering the - // legacy extension - uint64_t timestamp1 = interaction1.timestampForLegacySorting; - uint64_t timestamp2 = interaction2.timestampForLegacySorting; - - if (timestamp1 > timestamp2) { - return NSOrderedDescending; - } else if (timestamp1 < timestamp2) { - return NSOrderedAscending; - } else { - return NSOrderedSame; - } - }]; - - YapDatabaseViewOptions *options = [YapDatabaseViewOptions new]; - options.isPersistent = YES; - options.allowedCollections = - [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[TSInteraction collection]]]; - - YapDatabaseView *view = - [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping sorting:viewSorting versionTag:@"1" options:options]; - - [storage asyncRegisterExtension:view withName:TSMessageDatabaseViewExtensionName_Legacy]; -} - -+ (void)asyncRegisterThreadInteractionsDatabaseView:(OWSStorage *)storage -{ - YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *( - YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { - if (![object isKindOfClass:[TSInteraction class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object class], collection); - return nil; - } - TSInteraction *interaction = (TSInteraction *)object; - - return interaction.uniqueThreadId; - }]; - - [self registerMessageDatabaseViewWithName:TSMessageDatabaseViewExtensionName - viewGrouping:viewGrouping - version:@"2" - storage:storage]; -} - -+ (void)asyncRegisterThreadOutgoingMessagesDatabaseView:(OWSStorage *)storage -{ - YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *( - YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { - if ([object isKindOfClass:[TSOutgoingMessage class]]) { - return ((TSOutgoingMessage *)object).uniqueThreadId; - } - return nil; - }]; - - [self registerMessageDatabaseViewWithName:TSThreadOutgoingMessageDatabaseViewExtensionName - viewGrouping:viewGrouping - version:@"3" - storage:storage]; -} - -+ (void)asyncRegisterThreadDatabaseView:(OWSStorage *)storage -{ - YapDatabaseView *threadView = [storage registeredExtension:TSThreadDatabaseViewExtensionName]; - if (threadView) { - OWSFailDebug(@"Registered database view twice: %@", TSThreadDatabaseViewExtensionName); - return; - } - - YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *( - YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { - if (![object isKindOfClass:[TSThread class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object class], collection); - return nil; - } - TSThread *thread = (TSThread *)object; - if (thread.isSlaveThread) { return nil; } - - if (thread.shouldThreadBeVisible) { - // Do nothing; we never hide threads that have ever had a message. - } else { - YapDatabaseViewTransaction *viewTransaction = [transaction ext:TSMessageDatabaseViewExtensionName]; - OWSAssertDebug(viewTransaction); - NSUInteger threadMessageCount = [viewTransaction numberOfItemsInGroup:thread.uniqueId]; - if (threadMessageCount < 1) { - return nil; - } - } - - return [thread isArchivedWithTransaction:transaction] ? TSArchiveGroup : TSInboxGroup; - }]; - - YapDatabaseViewSorting *viewSorting = [self threadSorting]; - - YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init]; - options.isPersistent = YES; - options.allowedCollections = - [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[TSThread collection]]]; - - YapDatabaseView *databaseView = - [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping sorting:viewSorting versionTag:@"4" options:options]; - - [storage asyncRegisterExtension:databaseView withName:TSThreadDatabaseViewExtensionName]; -} - -+ (YapDatabaseViewSorting *)threadSorting { - return [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, - NSString *group, - NSString *collection1, - NSString *key1, - id object1, - NSString *collection2, - NSString *key2, - id object2) { - if (![object1 isKindOfClass:[TSThread class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object1 class], collection1); - return NSOrderedSame; - } - if (![object2 isKindOfClass:[TSThread class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object2 class], collection2); - return NSOrderedSame; - } - TSThread *thread1 = (TSThread *)object1; - TSThread *thread2 = (TSThread *)object2; - if ([group isEqualToString:TSArchiveGroup] || [group isEqualToString:TSInboxGroup]) { - - TSInteraction *_Nullable lastInteractionForInbox1 = - [thread1 lastInteractionForInboxWithTransaction:transaction]; - NSDate *date1 = lastInteractionForInbox1 ? lastInteractionForInbox1.receivedAtDate : thread1.creationDate; - - TSInteraction *_Nullable lastInteractionForInbox2 = - [thread2 lastInteractionForInboxWithTransaction:transaction]; - NSDate *date2 = lastInteractionForInbox2 ? lastInteractionForInbox2.receivedAtDate : thread2.creationDate; - - return [date1 compare:date2]; - } - - return NSOrderedSame; - }]; -} - -+ (YapDatabaseViewSorting *)messagesSorting { - return [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, - NSString *group, - NSString *collection1, - NSString *key1, - id object1, - NSString *collection2, - NSString *key2, - id object2) { - if (![object1 isKindOfClass:[TSInteraction class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object1 class], collection1); - return NSOrderedSame; - } - if (![object2 isKindOfClass:[TSInteraction class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object2 class], collection2); - return NSOrderedSame; - } - TSInteraction *message1 = (TSInteraction *)object1; - TSInteraction *message2 = (TSInteraction *)object2; - - return [message1 compareForSorting:message2]; - }]; -} - -+ (void)asyncRegisterSecondaryDevicesDatabaseView:(OWSStorage *)storage -{ - YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *_Nullable( - YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { - if (![object isKindOfClass:[OWSDevice class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object class], collection); - return nil; - } - OWSDevice *device = (OWSDevice *)object; - if (![device isPrimaryDevice]) { - return TSSecondaryDevicesGroup; - } - return nil; - }]; - - YapDatabaseViewSorting *viewSorting = [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult( - YapDatabaseReadTransaction *transaction, - NSString *group, - NSString *collection1, - NSString *key1, - id object1, - NSString *collection2, - NSString *key2, - id object2) { - if (![object1 isKindOfClass:[OWSDevice class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object1 class], collection1); - return NSOrderedSame; - } - if (![object2 isKindOfClass:[OWSDevice class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object2 class], collection2); - return NSOrderedSame; - } - OWSDevice *device1 = (OWSDevice *)object1; - OWSDevice *device2 = (OWSDevice *)object2; - - return [device2.createdAt compare:device1.createdAt]; - }]; - - YapDatabaseViewOptions *options = [YapDatabaseViewOptions new]; - options.isPersistent = YES; - - NSSet *deviceCollection = [NSSet setWithObject:[OWSDevice collection]]; - options.allowedCollections = [[YapWhitelistBlacklist alloc] initWithWhitelist:deviceCollection]; - - YapDatabaseView *view = - [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping sorting:viewSorting versionTag:@"3" options:options]; - - [storage asyncRegisterExtension:view withName:TSSecondaryDevicesDatabaseViewExtensionName]; -} - -+ (void)asyncRegisterLazyRestoreAttachmentsDatabaseView:(OWSStorage *)storage -{ - YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *_Nullable( - YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { - if (![object isKindOfClass:[TSAttachment class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object class], collection); - return nil; - } - if (![object isKindOfClass:[TSAttachmentPointer class]]) { - return nil; - } - TSAttachmentPointer *attachmentPointer = (TSAttachmentPointer *)object; - if (attachmentPointer.lazyRestoreFragment) { - return TSLazyRestoreAttachmentsGroup; - } else { - return nil; - } - }]; - - YapDatabaseViewSorting *viewSorting = - [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, - NSString *group, - NSString *collection1, - NSString *key1, - id object1, - NSString *collection2, - NSString *key2, - id object2) { - if (![object1 isKindOfClass:[TSAttachmentPointer class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object1 class], collection1); - return NSOrderedSame; - } - if (![object2 isKindOfClass:[TSAttachmentPointer class]]) { - OWSFailDebug(@"Unexpected entity %@ in collection: %@", [object2 class], collection2); - return NSOrderedSame; - } - - // Specific ordering doesn't matter; we just need a stable ordering. - TSAttachmentPointer *attachmentPointer1 = (TSAttachmentPointer *)object1; - TSAttachmentPointer *attachmentPointer2 = (TSAttachmentPointer *)object2; - return [attachmentPointer1.uniqueId compare:attachmentPointer2.uniqueId]; - }]; - - YapDatabaseViewOptions *options = [YapDatabaseViewOptions new]; - options.isPersistent = YES; - options.allowedCollections = - [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[TSAttachment collection]]]; - YapDatabaseView *view = - [[YapDatabaseAutoView alloc] initWithGrouping:viewGrouping sorting:viewSorting versionTag:@"4" options:options]; - [storage asyncRegisterExtension:view withName:TSLazyRestoreAttachmentsDatabaseViewExtensionName]; -} - -+ (id)unseenDatabaseViewExtension:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - id _Nullable result = [transaction ext:TSUnseenDatabaseViewExtensionName]; - OWSAssertDebug(result); - - // TODO: I believe we can now safely remove this? - if (!result) { - result = [transaction ext:TSUnreadDatabaseViewExtensionName]; - OWSAssertDebug(result); - } - - return result; -} - -// MJK TODO - dynamic interactions -+ (id)threadOutgoingMessageDatabaseView:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - id result = [transaction ext:TSThreadOutgoingMessageDatabaseViewExtensionName]; - OWSAssertDebug(result); - - return result; -} - -+ (id)threadSpecialMessagesDatabaseView:(YapDatabaseReadTransaction *)transaction -{ - OWSAssertDebug(transaction); - - id result = [transaction ext:TSThreadSpecialMessagesDatabaseViewExtensionName]; - OWSAssertDebug(result); - - return result; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/TSStorageHeaders.h b/SignalServiceKit/src/Storage/TSStorageHeaders.h deleted file mode 100644 index 7f606aea9..000000000 --- a/SignalServiceKit/src/Storage/TSStorageHeaders.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#ifndef Signal_TSStorageHeaders_h -#define Signal_TSStorageHeaders_h -#import "OWSIdentityManager.h" -#import "OWSPrimaryStorage+PreKeyStore.h" -#import "OWSPrimaryStorage+SessionStore.h" -#import "OWSPrimaryStorage+SignedPreKeyStore.h" -#import "OWSPrimaryStorage+keyFromIntLong.h" -#import "OWSPrimaryStorage.h" - -#endif diff --git a/SignalServiceKit/src/Storage/TSStorageKeys.h b/SignalServiceKit/src/Storage/TSStorageKeys.h deleted file mode 100644 index 68068758d..000000000 --- a/SignalServiceKit/src/Storage/TSStorageKeys.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -#ifndef TextSecureKit_TSStorageKeys_h -#define TextSecureKit_TSStorageKeys_h - -/** - * Preferences exposed to the user - */ - -#pragma mark User Preferences - -#define TSStorageUserPreferencesCollection @"TSStorageUserPreferencesCollection" - - -/** - * Internal settings of the application, not exposed to the user. - */ - -#pragma mark Internal Settings - -#define TSStorageInternalSettingsCollection @"TSStorageInternalSettingsCollection" -#define TSStorageInternalSettingsVersion @"TSLastLaunchedVersion" - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/TSYapDatabaseObject.h b/SignalServiceKit/src/Storage/TSYapDatabaseObject.h deleted file mode 100644 index cb95b2ee4..000000000 --- a/SignalServiceKit/src/Storage/TSYapDatabaseObject.h +++ /dev/null @@ -1,166 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class YapDatabaseConnection; -@class YapDatabaseReadTransaction; -@class YapDatabaseReadWriteTransaction; - -@interface TSYapDatabaseObject : MTLModel - -- (instancetype)init NS_DESIGNATED_INITIALIZER; - -/** - * Initializes a new database object with a unique identifier - * - * @param uniqueId Key used for the key-value store - * - * @return Initialized object - */ -- (instancetype)initWithUniqueId:(NSString *_Nullable)uniqueId NS_DESIGNATED_INITIALIZER; - -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - -/** - * Returns the collection to which the object belongs. - * - * @return Key (string) identifying the collection - */ -+ (NSString *)collection; - -/** - * Get the number of keys in the models collection. Be aware that if there - * are multiple object types in this collection that the count will include - * the count of other objects in the same collection. - * - * @return The number of keys in the classes collection. - */ -+ (NSUInteger)numberOfKeysInCollection; -+ (NSUInteger)numberOfKeysInCollectionWithTransaction:(YapDatabaseReadTransaction *)transaction; - -/** - * Removes all objects in the classes collection. - */ -+ (void)removeAllObjectsInCollection; - -/** - * A memory intesive method to get all objects in the collection. You should prefer using enumeration over this method - * whenever feasible. See `enumerateObjectsInCollectionUsingBlock` - * - * @return All objects in the classes collection. - */ -+ (NSArray *)allObjectsInCollection; - -/** - * Enumerates all objects in collection. - */ -+ (void)enumerateCollectionObjectsUsingBlock:(void (^)(id obj, BOOL *stop))block; -+ (void)enumerateCollectionObjectsWithTransaction:(YapDatabaseReadTransaction *)transaction - usingBlock:(void (^)(id object, BOOL *stop))block; - -/** - * @return Shared database connections for reading and writing. - */ -- (YapDatabaseConnection *)dbReadConnection; -+ (YapDatabaseConnection *)dbReadConnection; -- (YapDatabaseConnection *)dbReadWriteConnection; -+ (YapDatabaseConnection *)dbReadWriteConnection; - -- (OWSPrimaryStorage *)primaryStorage; -+ (OWSPrimaryStorage *)primaryStorage; - -/** - * Fetches the object with the provided identifier - * - * @param uniqueID Unique identifier of the entry in a collection - * @param transaction Transaction used for fetching the object - * - * @return Instance of the object or nil if non-existent - */ -+ (nullable instancetype)fetchObjectWithUniqueID:(NSString *)uniqueID - transaction:(YapDatabaseReadTransaction *)transaction - NS_SWIFT_NAME(fetch(uniqueId:transaction:)); -+ (nullable instancetype)fetchObjectWithUniqueID:(NSString *)uniqueID NS_SWIFT_NAME(fetch(uniqueId:)); - -/** - * Saves the object with the shared readWrite connection. - * - * This method will block if another readWrite transaction is open. - */ -- (void)save; - -/** - * Assign the latest persisted values from the database. - */ -- (void)reload; -- (void)reloadWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (void)reloadWithTransaction:(YapDatabaseReadTransaction *)transaction ignoreMissing:(BOOL)ignoreMissing; - -/** - * Saves the object with the shared readWrite connection - does not block. - * - * Be mindful that this method may clobber other changes persisted - * while waiting to open the readWrite transaction. - * - * @param completionBlock is called on the main thread - */ -- (void)saveAsyncWithCompletionBlock:(void (^_Nullable)(void))completionBlock; - -/** - * Saves the object with the provided transaction - * - * @param transaction Database transaction - */ -- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -/** - * `touch` is a cheap way to fire a YapDatabaseModified notification to redraw anythign depending on the model. - */ -- (void)touch; -- (void)touchWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -/** - * The unique identifier of the stored object - */ -@property (nonatomic, nullable) NSString *uniqueId; - -- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; -- (void)remove; - -#pragma mark - Update With... - -// This method is used by "updateWith..." methods. -// -// This model may be updated from many threads. We don't want to save -// our local copy (this instance) since it may be out of date. We also -// want to avoid re-saving a model that has been deleted. Therefore, we -// use "updateWith..." methods to: -// -// a) Update a property of this instance. -// b) If a copy of this model exists in the database, load an up-to-date copy, -// and update and save that copy. -// b) If a copy of this model _DOES NOT_ exist in the database, do _NOT_ save -// this local instance. -// -// After "updateWith...": -// -// a) Any copy of this model in the database will have been updated. -// b) The local property on this instance will always have been updated. -// c) Other properties on this instance may be out of date. -// -// All mutable properties of this class have been made read-only to -// prevent accidentally modifying them directly. -// -// This isn't a perfect arrangement, but in practice this will prevent -// data loss and will resolve all known issues. -- (void)applyChangeToSelfAndLatestCopy:(YapDatabaseReadWriteTransaction *)transaction - changeBlock:(void (^)(id))changeBlock; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/TSYapDatabaseObject.m b/SignalServiceKit/src/Storage/TSYapDatabaseObject.m deleted file mode 100644 index caf98e414..000000000 --- a/SignalServiceKit/src/Storage/TSYapDatabaseObject.m +++ /dev/null @@ -1,250 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSYapDatabaseObject.h" -#import "OWSPrimaryStorage.h" -#import "SSKEnvironment.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation TSYapDatabaseObject - -- (instancetype)init -{ - return [self initWithUniqueId:[[NSUUID UUID] UUIDString]]; -} - -- (instancetype)initWithUniqueId:(NSString *_Nullable)aUniqueId -{ - self = [super init]; - if (!self) { - return self; - } - - _uniqueId = aUniqueId; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - self = [super initWithCoder:coder]; - if (!self) { - return self; - } - - return self; -} - -- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [transaction setObject:self forKey:self.uniqueId inCollection:[[self class] collection]]; -} - -- (void)save -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self saveWithTransaction:transaction]; - }]; -} - -- (void)saveAsyncWithCompletionBlock:(void (^_Nullable)(void))completionBlock -{ - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [self saveWithTransaction:transaction]; - } completion:completionBlock]; -} - -- (void)touchWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [transaction touchObjectForKey:self.uniqueId inCollection:[self.class collection]]; -} - -- (void)touch -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self touchWithTransaction:transaction]; - }]; -} - -- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [transaction removeObjectForKey:self.uniqueId inCollection:[[self class] collection]]; -} - -- (void)remove -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self removeWithTransaction:transaction]; - }]; -} - -- (YapDatabaseConnection *)dbReadConnection -{ - return [[self class] dbReadConnection]; -} - -- (YapDatabaseConnection *)dbReadWriteConnection -{ - return [[self class] dbReadWriteConnection]; -} - -- (OWSPrimaryStorage *)primaryStorage -{ - return [[self class] primaryStorage]; -} - -#pragma mark Class Methods - -+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey -{ - if ([propertyKey isEqualToString:@"TAG"]) { - return MTLPropertyStorageNone; - } else { - return [super storageBehaviorForPropertyWithKey:propertyKey]; - } -} - -+ (YapDatabaseConnection *)dbReadConnection -{ - OWSJanksUI(); - - // We use TSYapDatabaseObject's dbReadWriteConnection (not OWSPrimaryStorage's - // dbReadConnection) for consistency, since we tend to [TSYapDatabaseObject - // save] and want to write to the same connection we read from. To get true - // consistency, we'd want to update entities by reading & writing from within - // the same transaction, but that'll be a big refactor. - return self.dbReadWriteConnection; -} - -+ (YapDatabaseConnection *)dbReadWriteConnection -{ - OWSJanksUI(); - - return SSKEnvironment.shared.objectReadWriteConnection; -} - -+ (OWSPrimaryStorage *)primaryStorage -{ - return [OWSPrimaryStorage sharedManager]; -} - -+ (NSString *)collection -{ - return NSStringFromClass([self class]); -} - -+ (NSUInteger)numberOfKeysInCollection -{ - __block NSUInteger count; - [[self dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *transaction) { - count = [self numberOfKeysInCollectionWithTransaction:transaction]; - }]; - return count; -} - -+ (NSUInteger)numberOfKeysInCollectionWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - return [transaction numberOfKeysInCollection:[self collection]]; -} - -+ (NSArray *)allObjectsInCollection -{ - __block NSMutableArray *all = [[NSMutableArray alloc] initWithCapacity:[self numberOfKeysInCollection]]; - [self enumerateCollectionObjectsUsingBlock:^(id object, BOOL *stop) { - [all addObject:object]; - }]; - return [all copy]; -} - -+ (void)enumerateCollectionObjectsUsingBlock:(void (^)(id object, BOOL *stop))block -{ - [[self dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *transaction) { - [self enumerateCollectionObjectsWithTransaction:transaction usingBlock:block]; - }]; -} - -+ (void)enumerateCollectionObjectsWithTransaction:(YapDatabaseReadTransaction *)transaction - usingBlock:(void (^)(id object, BOOL *stop))block -{ - // Ignoring most of the YapDB parameters, and just passing through the ones we usually use. - void (^yapBlock)(NSString *key, id object, id metadata, BOOL *stop) - = ^void(NSString *key, id object, id metadata, BOOL *stop) { - block(object, stop); - }; - - [transaction enumerateRowsInCollection:[self collection] usingBlock:yapBlock]; -} - -+ (void)removeAllObjectsInCollection -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [transaction removeAllObjectsInCollection:[self collection]]; - }]; -} - -+ (nullable instancetype)fetchObjectWithUniqueID:(NSString *)uniqueID - transaction:(YapDatabaseReadTransaction *)transaction -{ - return [transaction objectForKey:uniqueID inCollection:[self collection]]; -} - -+ (nullable instancetype)fetchObjectWithUniqueID:(NSString *)uniqueID -{ - __block id _Nullable object = nil; - [[self dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *transaction) { - object = [transaction objectForKey:uniqueID inCollection:[self collection]]; - }]; - return object; -} - -#pragma mark - Update With... - -- (void)applyChangeToSelfAndLatestCopy:(YapDatabaseReadWriteTransaction *)transaction - changeBlock:(void (^)(id))changeBlock -{ - OWSAssertDebug(transaction); - - changeBlock(self); - - NSString *collection = [[self class] collection]; - id latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection]; - if (latestInstance) { - changeBlock(latestInstance); - [latestInstance saveWithTransaction:transaction]; - } -} - -#pragma mark Reload - -- (void)reload -{ - [self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - [self reloadWithTransaction:transaction]; - }]; -} - -- (void)reloadWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - [self reloadWithTransaction:transaction ignoreMissing:NO]; -} - -- (void)reloadWithTransaction:(YapDatabaseReadTransaction *)transaction ignoreMissing:(BOOL)ignoreMissing -{ - TSYapDatabaseObject *latest = [[self class] fetchObjectWithUniqueID:self.uniqueId transaction:transaction]; - if (!latest) { - if (!ignoreMissing) { - OWSFailDebug(@"`latest` was unexpectedly nil"); - } - return; - } - - [self setValuesForKeysWithDictionary:latest.dictionaryValue]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.h b/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.h deleted file mode 100644 index 96b27b277..000000000 --- a/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import - -@class ECKeyPair; -@class PreKeyRecord; -@class SignedPreKeyRecord; -@class PreKeyBundle; - -NS_ASSUME_NONNULL_BEGIN - -@interface YapDatabaseConnection (OWS) - -- (BOOL)hasObjectForKey:(NSString *)key inCollection:(NSString *)collection; -- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(BOOL)defaultValue; -- (double)doubleForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(double)defaultValue; -- (int)intForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable id)objectForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable NSDate *)dateForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable NSDictionary *)dictionaryForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable NSString *)stringForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable NSData *)dataForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable ECKeyPair *)keyPairForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable PreKeyRecord *)preKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable PreKeyBundle *)preKeyBundleForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable SignedPreKeyRecord *)signedPreKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection; - -- (NSUInteger)numberOfKeysInCollection:(NSString *)collection; - -#pragma mark - - -- (void)setObject:(id)object forKey:(NSString *)key inCollection:(NSString *)collection; -- (void)setBool:(BOOL)value forKey:(NSString *)key inCollection:(NSString *)collection; -- (void)setDouble:(double)value forKey:(NSString *)key inCollection:(NSString *)collection; -- (void)removeObjectForKey:(NSString *)string inCollection:(NSString *)collection; -- (void)setInt:(int)integer forKey:(NSString *)key inCollection:(NSString *)collection; -- (void)setDate:(NSDate *)value forKey:(NSString *)key inCollection:(NSString *)collection; -- (int)incrementIntForKey:(NSString *)key inCollection:(NSString *)collection; - -- (void)purgeCollection:(NSString *)collection; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.m b/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.m deleted file mode 100644 index b260bc704..000000000 --- a/SignalServiceKit/src/Storage/YapDatabaseConnection+OWS.m +++ /dev/null @@ -1,194 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "YapDatabaseConnection+OWS.h" -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation YapDatabaseConnection (OWS) - -- (BOOL)hasObjectForKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - return nil != [self objectForKey:key inCollection:collection]; -} - -- (nullable id)objectForKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - __block NSString *_Nullable object; - - [self readWithBlock:^(YapDatabaseReadTransaction *transaction) { - object = [transaction objectForKey:key inCollection:collection]; - }]; - - return object; -} - -- (nullable id)objectForKey:(NSString *)key inCollection:(NSString *)collection ofExpectedType:(Class)class -{ - id _Nullable value = [self objectForKey:key inCollection:collection]; - OWSAssertDebug(!value || [value isKindOfClass:class]); - return value; -} - -- (nullable NSDictionary *)dictionaryForKey:(NSString *)key inCollection:(NSString *)collection -{ - return [self objectForKey:key inCollection:collection ofExpectedType:[NSDictionary class]]; -} - -- (nullable NSString *)stringForKey:(NSString *)key inCollection:(NSString *)collection -{ - return [self objectForKey:key inCollection:collection ofExpectedType:[NSString class]]; -} - -- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(BOOL)defaultValue -{ - NSNumber *_Nullable value = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]]; - return value ? [value boolValue] : defaultValue; -} - -- (double)doubleForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(double)defaultValue -{ - NSNumber *_Nullable value = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]]; - return value ? [value doubleValue] : defaultValue; -} - -- (nullable NSData *)dataForKey:(NSString *)key inCollection:(NSString *)collection -{ - return [self objectForKey:key inCollection:collection ofExpectedType:[NSData class]]; -} - -- (nullable ECKeyPair *)keyPairForKey:(NSString *)key inCollection:(NSString *)collection -{ - return [self objectForKey:key inCollection:collection ofExpectedType:[ECKeyPair class]]; -} - -- (nullable PreKeyRecord *)preKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection -{ - return [self objectForKey:key inCollection:collection ofExpectedType:[PreKeyRecord class]]; -} - -- (nullable PreKeyBundle *)preKeyBundleForKey:(NSString *)key inCollection:(NSString *)collection -{ - return [self objectForKey:key inCollection:collection ofExpectedType:PreKeyBundle.class]; -} - -- (nullable SignedPreKeyRecord *)signedPreKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection -{ - return [self objectForKey:key inCollection:collection ofExpectedType:[SignedPreKeyRecord class]]; -} - -- (int)intForKey:(NSString *)key inCollection:(NSString *)collection -{ - NSNumber *_Nullable number = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]]; - return [number intValue]; -} - -- (nullable NSDate *)dateForKey:(NSString *)key inCollection:(NSString *)collection -{ - NSNumber *_Nullable value = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]]; - if (value) { - return [NSDate dateWithTimeIntervalSince1970:value.doubleValue]; - } else { - return nil; - } -} - -#pragma mark - - -- (NSUInteger)numberOfKeysInCollection:(NSString *)collection -{ - __block NSUInteger result; - [self readWithBlock:^(YapDatabaseReadTransaction *transaction) { - result = [transaction numberOfKeysInCollection:collection]; - }]; - return result; -} - -- (void)purgeCollection:(NSString *)collection -{ - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [transaction removeAllObjectsInCollection:collection]; - }]; -} - -- (void)setObject:(id)object forKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [transaction setObject:object forKey:key inCollection:collection]; - }]; -} - -- (void)setBool:(BOOL)value forKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - NSNumber *_Nullable oldValue = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]]; - if (oldValue && [@(value) isEqual:oldValue]) { - // Skip redundant writes. - return; - } - - [self setObject:@(value) forKey:key inCollection:collection]; -} - -- (void)setDouble:(double)value forKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - [self setObject:@(value) forKey:key inCollection:collection]; -} - -- (void)removeObjectForKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [transaction removeObjectForKey:key inCollection:collection]; - }]; -} - -- (void)setInt:(int)value forKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - [self setObject:@(value) forKey:key inCollection:collection]; -} - -- (int)incrementIntForKey:(NSString *)key inCollection:(NSString *)collection -{ - __block int value = 0; - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - value = [[transaction objectForKey:key inCollection:collection] intValue]; - value++; - [transaction setObject:@(value) forKey:key inCollection:collection]; - }]; - return value; -} - -- (void)setDate:(NSDate *)value forKey:(NSString *)key inCollection:(NSString *)collection -{ - [self setObject:@(value.timeIntervalSince1970) forKey:key inCollection:collection]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.h b/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.h deleted file mode 100644 index fb9f6524c..000000000 --- a/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import - -@class ECKeyPair; -@class PreKeyRecord; -@class PreKeyBundle; -@class SignedPreKeyRecord; - -NS_ASSUME_NONNULL_BEGIN - -@interface YapDatabaseReadTransaction (OWS) - -- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(BOOL)defaultValue; -- (int)intForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable NSDate *)dateForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable NSDictionary *)dictionaryForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable NSString *)stringForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable NSData *)dataForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable ECKeyPair *)keyPairForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable PreKeyRecord *)preKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable PreKeyBundle *)preKeyBundleForKey:(NSString *)key inCollection:(NSString *)collection; -- (nullable SignedPreKeyRecord *)signedPreKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection; - -@end - -#pragma mark - - -@interface YapDatabaseReadWriteTransaction (OWS) - -#pragma mark - Debug - -#if DEBUG -- (void)snapshotCollection:(NSString *)collection snapshotFilePath:(NSString *)snapshotFilePath; -- (void)restoreSnapshotOfCollection:(NSString *)collection snapshotFilePath:(NSString *)snapshotFilePath; -#endif - -- (void)setBool:(BOOL)value forKey:(NSString *)key inCollection:(NSString *)collection; -- (void)setDate:(NSDate *)value forKey:(NSString *)key inCollection:(NSString *)collection; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.m b/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.m deleted file mode 100644 index b461030e1..000000000 --- a/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.m +++ /dev/null @@ -1,171 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "YapDatabaseTransaction+OWS.h" -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation YapDatabaseReadTransaction (OWS) - -- (nullable id)objectForKey:(NSString *)key inCollection:(NSString *)collection ofExpectedType:(Class) class { - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - id _Nullable value = [self objectForKey:key inCollection:collection]; - OWSAssertDebug(!value || [value isKindOfClass:class]); - return value; -} - - - (nullable NSDictionary *)dictionaryForKey : (NSString *)key inCollection : (NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - return [self objectForKey:key inCollection:collection ofExpectedType:[NSDictionary class]]; -} - -- (nullable NSString *)stringForKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - return [self objectForKey:key inCollection:collection ofExpectedType:[NSString class]]; -} - -- (BOOL)boolForKey:(NSString *)key inCollection:(NSString *)collection defaultValue:(BOOL)defaultValue -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - NSNumber *_Nullable value = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]]; - return value ? [value boolValue] : defaultValue; -} - -- (nullable NSData *)dataForKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - return [self objectForKey:key inCollection:collection ofExpectedType:[NSData class]]; -} - -- (nullable ECKeyPair *)keyPairForKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - return [self objectForKey:key inCollection:collection ofExpectedType:[ECKeyPair class]]; -} - -- (nullable PreKeyRecord *)preKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - return [self objectForKey:key inCollection:collection ofExpectedType:[PreKeyRecord class]]; -} - -- (nullable PreKeyBundle *)preKeyBundleForKey:(NSString *)key inCollection:(NSString *)collection -{ - return [self objectForKey:key inCollection:collection ofExpectedType:PreKeyBundle.class]; -} - -- (nullable SignedPreKeyRecord *)signedPreKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - return [self objectForKey:key inCollection:collection ofExpectedType:[SignedPreKeyRecord class]]; -} - -- (int)intForKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - NSNumber *_Nullable number = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]]; - return [number intValue]; -} - -- (nullable NSDate *)dateForKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - NSNumber *_Nullable value = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]]; - if (value) { - return [NSDate dateWithTimeIntervalSince1970:value.doubleValue]; - } else { - return nil; - } -} - -@end - -#pragma mark - - -@implementation YapDatabaseReadWriteTransaction (OWS) - -#pragma mark - Debug - -#if DEBUG -- (void)snapshotCollection:(NSString *)collection snapshotFilePath:(NSString *)snapshotFilePath -{ - OWSAssertDebug(collection.length > 0); - OWSAssertDebug(snapshotFilePath.length > 0); - - NSMutableDictionary *snapshot = [NSMutableDictionary new]; - [self enumerateKeysAndObjectsInCollection:collection - usingBlock:^(NSString *_Nonnull key, id _Nonnull value, BOOL *_Nonnull stop) { - snapshot[key] = value; - }]; - NSData *_Nullable data = [NSKeyedArchiver archivedDataWithRootObject:snapshot]; - OWSAssertDebug(data); - BOOL success = [data writeToFile:snapshotFilePath atomically:YES]; - OWSAssertDebug(success); -} - -- (void)restoreSnapshotOfCollection:(NSString *)collection snapshotFilePath:(NSString *)snapshotFilePath -{ - OWSAssertDebug(collection.length > 0); - OWSAssertDebug(snapshotFilePath.length > 0); - - NSData *_Nullable data = [NSData dataWithContentsOfFile:snapshotFilePath]; - OWSAssertDebug(data); - NSMutableDictionary *_Nullable snapshot = [NSKeyedUnarchiver unarchiveObjectWithData:data]; - OWSAssertDebug(snapshot); - - [self removeAllObjectsInCollection:collection]; - [snapshot enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, id _Nonnull value, BOOL *_Nonnull stop) { - [self setObject:value forKey:key inCollection:collection]; - }]; -} -#endif - -- (void)setBool:(BOOL)value forKey:(NSString *)key inCollection:(NSString *)collection -{ - OWSAssertDebug(key.length > 0); - OWSAssertDebug(collection.length > 0); - - NSNumber *_Nullable oldValue = [self objectForKey:key inCollection:collection ofExpectedType:[NSNumber class]]; - if (oldValue && [@(value) isEqual:oldValue]) { - // Skip redundant writes. - return; - } - - [self setObject:@(value) forKey:key inCollection:collection]; -} - -- (void)setDate:(NSDate *)value forKey:(NSString *)key inCollection:(NSString *)collection -{ - [self setObject:@(value.timeIntervalSince1970) forKey:key inCollection:collection]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TSConstants.h b/SignalServiceKit/src/TSConstants.h deleted file mode 100644 index 8b7629190..000000000 --- a/SignalServiceKit/src/TSConstants.h +++ /dev/null @@ -1,74 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -#ifndef TextSecureKit_Constants_h -#define TextSecureKit_Constants_h - -typedef NS_ENUM(NSInteger, TSWhisperMessageType) { - TSUnknownMessageType = 0, - TSEncryptedWhisperMessageType = 1, - TSIgnoreOnIOSWhisperMessageType = 2, // on droid this is the prekey bundle message irrelevant for us - TSPreKeyWhisperMessageType = 3, - TSUnencryptedWhisperMessageType = 4, - TSUnidentifiedSenderMessageType = 6, - TSClosedGroupCiphertextMessageType = 7, - TSFallbackMessageType = 101 // Loki: Encrypted using the fallback session cipher. Contains a pre key bundle if it's a session request. -}; - -#pragma mark Server Address - -#define textSecureHTTPTimeOut 10 - -#define kLegalTermsUrlString @"https://getsession.org/privacy-policy/" - -//#ifndef DEBUG - -// Production -#define textSecureWebSocketAPI @"wss://textsecure-service.whispersystems.org/v1/websocket/" -#define textSecureCDNServerURL @"https://cdn.signal.org" -// Use same reflector for service and CDN -#define textSecureServiceReflectorHost @"europe-west1-signal-cdn-reflector.cloudfunctions.net" -#define textSecureCDNReflectorHost @"europe-west1-signal-cdn-reflector.cloudfunctions.net" -#define contactDiscoveryURL @"https://api.directory.signal.org" -#define kUDTrustRoot @"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF" -#define USING_PRODUCTION_SERVICE - -//#else - -// Staging -//#define textSecureWebSocketAPI @"wss://textsecure-service-staging.whispersystems.org/v1/websocket/" -//#define textSecureServerURL @"https://textsecure-service-staging.whispersystems.org/" -//#define textSecureCDNServerURL @"https://cdn-staging.signal.org" -//#define textSecureServiceReflectorHost @"meek-signal-service-staging.appspot.com"; -//#define textSecureCDNReflectorHost @"meek-signal-cdn-staging.appspot.com"; -//#define contactDiscoveryURL @"https://api-staging.directory.signal.org" -//#define kUDTrustRoot @"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx" - -//#endif - -BOOL IsUsingProductionService(void); - -#define textSecureAccountsAPI @"v1/accounts" -#define textSecureAttributesAPI @"/attributes/" - -#define textSecureMessagesAPI @"v1/messages/" -#define textSecureKeysAPI @"v2/keys" -#define textSecureSignedKeysAPI @"v2/keys/signed" -#define textSecureDirectoryAPI @"v1/directory" -#define textSecureAttachmentsAPI @"v1/attachments" -#define textSecureDeviceProvisioningCodeAPI @"v1/devices/provisioning/code" -#define textSecureDeviceProvisioningAPIFormat @"v1/provisioning/%@" -#define textSecureDevicesAPIFormat @"v1/devices/%@" -#define textSecureProfileAPIFormat @"v1/profile/%@" -#define textSecureSetProfileNameAPIFormat @"v1/profile/name/%@" -#define textSecureProfileAvatarFormAPI @"v1/profile/form/avatar" -#define textSecure2FAAPI @"/v1/accounts/pin" - -#define SignalApplicationGroup @"group.com.loki-project.loki-messenger" - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TSConstants.m b/SignalServiceKit/src/TSConstants.m deleted file mode 100644 index b82b9e0e7..000000000 --- a/SignalServiceKit/src/TSConstants.m +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSConstants.h" - -NS_ASSUME_NONNULL_BEGIN - -BOOL IsUsingProductionService() -{ -#ifdef USING_PRODUCTION_SERVICE - return YES; -#else - return NO; -#endif -} - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TSPrefix.h b/SignalServiceKit/src/TSPrefix.h deleted file mode 100644 index 3446f84a7..000000000 --- a/SignalServiceKit/src/TSPrefix.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -@import CocoaLumberjack; - -#ifdef DEBUG -static const NSUInteger ddLogLevel = DDLogLevelAll; -#else -static const NSUInteger ddLogLevel = DDLogLevelInfo; -#endif -#import "OWSAnalytics.h" -#import "NSArray+Functional.h" -#import "NSSet+Functional.h" -#import "NSObject+Casting.h" -#import "SSKAsserts.h" -#import "TSConstants.h" -#import -#import diff --git a/SignalServiceKit/src/TestUtils/Factories.swift b/SignalServiceKit/src/TestUtils/Factories.swift deleted file mode 100644 index b82ea591b..000000000 --- a/SignalServiceKit/src/TestUtils/Factories.swift +++ /dev/null @@ -1,558 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation - -/// Factories for creating some default TSYapDatabaseObjects. -/// -/// To customize properties applied by the factory (e.g. `someProperty`) -/// the factory needs a `public var somePropertyBuilder: () -> (SomePropertyType)` -/// which is then used in the `create` method. -/// -/// Examples: -/// -/// Create one empty thread: -/// -/// let oneThread = ContactThreadFactory().create() -/// -/// Create 12 thread's with 100 messages each -/// -/// let factory = ContractThreadFactory() -/// factory.messageCount = 100 -/// factory.create(count: 12) -/// -/// Create 100 messages in an existing thread -/// -/// let existingThread: TSThread = getSomeExistingThread() -/// let messageFactory = TSIncomingMessageFactory() -/// messageFactory.threadCreator = { _ in return existingThread } -/// messageFactory.create(count: 100) -/// -public protocol Factory { - associatedtype ObjectType: TSYapDatabaseObject - - var dbConnection: YapDatabaseConnection { get } - - func readWrite(block: @escaping (YapDatabaseReadWriteTransaction) -> Void) - - // MARK: Factory Methods - func create() -> ObjectType - func create(transaction: YapDatabaseReadWriteTransaction) -> ObjectType - - func create(count: UInt) -> [ObjectType] - func create(count: UInt, transaction: YapDatabaseReadWriteTransaction) -> [ObjectType] -} - -public extension Factory { - - static public var dbConnection: YapDatabaseConnection { - return OWSPrimaryStorage.shared().dbReadWriteConnection - } - - public var dbConnection: YapDatabaseConnection { - return OWSPrimaryStorage.shared().dbReadWriteConnection - } - - static public func readWrite(block: @escaping (YapDatabaseReadWriteTransaction) -> Void) { - dbConnection.readWrite(block) - } - - public func readWrite(block: @escaping (YapDatabaseReadWriteTransaction) -> Void) { - dbConnection.readWrite(block) - } - - // MARK: Factory Methods - - public func create() -> ObjectType { - var item: ObjectType! - self.readWrite { transaction in - item = self.create(transaction: transaction) - } - return item - } - - public func create(count: UInt) -> [ObjectType] { - var items: [ObjectType] = [] - self.readWrite { transaction in - items = self.create(count: count, transaction: transaction) - } - return items - } - - public func create(count: UInt, transaction: YapDatabaseReadWriteTransaction) -> [ObjectType] { - return (0.. TSContactThread { - let threadId = generateContactThreadId() - let thread = TSContactThread.getOrCreateThread(withContactId: threadId, transaction: transaction) - - let incomingMessageFactory = IncomingMessageFactory() - incomingMessageFactory.threadCreator = { _ in return thread } - - let outgoingMessageFactory = OutgoingMessageFactory() - outgoingMessageFactory.threadCreator = { _ in return thread } - - (0.. String { - return CommonGenerator.contactId - } -} - -@objc -public class OutgoingMessageFactory: NSObject, Factory { - - // MARK: Factory - - @objc - public func build(transaction: YapDatabaseReadWriteTransaction) -> TSOutgoingMessage { - let item = TSOutgoingMessage(outgoingMessageWithTimestamp: timestampBuilder(), - in: threadCreator(transaction), - messageBody: messageBodyBuilder(), - attachmentIds: attachmentIdsBuilder(), - expiresInSeconds: expiresInSecondsBuilder(), - expireStartedAt: expireStartedAtBuilder(), - isVoiceMessage: isVoiceMessageBuilder(), - groupMetaMessage: groupMetaMessageBuilder(), - quotedMessage: quotedMessageBuilder(), - contactShare: contactShareBuilder(), - linkPreview: linkPreviewBuilder()) - - return item - } - - @objc - public func create(transaction: YapDatabaseReadWriteTransaction) -> TSOutgoingMessage { - let item = self.build(transaction: transaction) - item.save(with: transaction) - - return item - } - - // MARK: Dependent Factories - - @objc - public var threadCreator: (YapDatabaseReadWriteTransaction) -> TSThread = { transaction in - ContactThreadFactory().create(transaction: transaction) - } - - // MARK: Generators - - @objc - public var timestampBuilder: () -> UInt64 = { - return NSDate.ows_millisecondTimeStamp() - } - - @objc - public var messageBodyBuilder: () -> String = { - return CommonGenerator.paragraph - } - - @objc - var attachmentIdsBuilder: () -> NSMutableArray = { - return [] - } - - @objc - public var expiresInSecondsBuilder: () -> UInt32 = { - return 0 - } - - @objc - public var expireStartedAtBuilder: () -> UInt64 = { - return 0 - } - - @objc - public var isVoiceMessageBuilder: () -> Bool = { - return false - } - - @objc - public var groupMetaMessageBuilder: () -> TSGroupMetaMessage = { - return .unspecified - } - - @objc - public var quotedMessageBuilder: () -> TSQuotedMessage? = { - return nil - } - - @objc - public var contactShareBuilder: () -> OWSContact? = { - return nil - } - - @objc - public var linkPreviewBuilder: () -> OWSLinkPreview? = { - return nil - } - - // MARK: Delivery Receipts - - @objc - public func buildDeliveryReceipt() -> OWSReceiptsForSenderMessage { - var item: OWSReceiptsForSenderMessage! - self.readWrite { transaction in - item = self.buildDeliveryReceipt(transaction: transaction) - } - return item - } - - @objc - public func buildDeliveryReceipt(transaction: YapDatabaseReadWriteTransaction) -> OWSReceiptsForSenderMessage { - let item = OWSReceiptsForSenderMessage.deliveryReceiptsForSenderMessage(with: threadCreator(transaction), - messageTimestamps: messageTimestampsBuilder()) - return item - } - - @objc - public var messageTimestampsBuilder: () -> [NSNumber] = { - return [1] - } -} - -@objc -public class IncomingMessageFactory: NSObject, Factory { - - // MARK: Factory - - @objc - public func create(transaction: YapDatabaseReadWriteTransaction) -> TSIncomingMessage { - - let thread = threadCreator(transaction) - - let item = TSIncomingMessage(incomingMessageWithTimestamp: timestampBuilder(), - in: thread, - authorId: authorIdBuilder(thread), - sourceDeviceId: sourceDeviceIdBuilder(), - messageBody: messageBodyBuilder(), - attachmentIds: attachmentIdsBuilder(), - expiresInSeconds: expiresInSecondsBuilder(), - quotedMessage: quotedMessageBuilder(), - contactShare: contactShareBuilder(), - linkPreview: linkPreviewBuilder(), - serverTimestamp: serverTimestampBuilder(), - wasReceivedByUD: wasReceivedByUDBuilder()) - - item.save(with: transaction) - - return item - } - - // MARK: Dependent Factories - - @objc - public var threadCreator: (YapDatabaseReadWriteTransaction) -> TSThread = { transaction in - ContactThreadFactory().create(transaction: transaction) - } - - // MARK: Generators - - @objc - public var timestampBuilder: () -> UInt64 = { - return NSDate.ows_millisecondTimeStamp() - } - - @objc - public var messageBodyBuilder: () -> String = { - return CommonGenerator.paragraph - } - - @objc - public var authorIdBuilder: (TSThread) -> String = { thread in - switch thread { - case let contactThread as TSContactThread: - return contactThread.contactIdentifier() - case let groupThread as TSGroupThread: - return groupThread.recipientIdentifiers.ows_randomElement() ?? CommonGenerator.contactId - default: - owsFailDebug("unexpected thread type") - return CommonGenerator.contactId - } - } - - @objc - public var sourceDeviceIdBuilder: () -> UInt32 = { - return 1 - } - - @objc - public var attachmentIdsBuilder: () -> [String] = { - return [] - } - - @objc - public var expiresInSecondsBuilder: () -> UInt32 = { - return 0 - } - - @objc - public var quotedMessageBuilder: () -> TSQuotedMessage? = { - return nil - } - - @objc - public var contactShareBuilder: () -> OWSContact? = { - return nil - } - - @objc - public var linkPreviewBuilder: () -> OWSLinkPreview? = { - return nil - } - - @objc - public var serverTimestampBuilder: () -> NSNumber? = { - return nil - } - - @objc - public var wasReceivedByUDBuilder: () -> Bool = { - return false - } -} - -@objc -class GroupThreadFactory: NSObject, Factory { - - @objc - public var messageCount: UInt = 0 - - @objc - public func create(transaction: YapDatabaseReadWriteTransaction) -> TSGroupThread { - let thread = TSGroupThread.getOrCreateThread(with: groupModelBuilder(self), - transaction: transaction) - thread.save(with: transaction) - - let incomingMessageFactory = IncomingMessageFactory() - incomingMessageFactory.threadCreator = { _ in return thread } - - let outgoingMessageFactory = OutgoingMessageFactory() - outgoingMessageFactory.threadCreator = { _ in return thread } - - (0.. TSGroupModel = { groupThreadFactory in - return TSGroupModel(title: groupThreadFactory.titleBuilder(), - memberIds: groupThreadFactory.memberIdsBuilder(), - image: groupThreadFactory.imageBuilder(), - groupId: groupThreadFactory.groupIdBuilder(), - groupType: .closedGroup, - adminIds: []) - } - - @objc - public var titleBuilder: () -> String? = { - return CommonGenerator.words(count: 3) - } - - @objc - public var groupIdBuilder: () -> Data = { - return Randomness.generateRandomBytes(Int32(kGroupIdLength))! - } - - @objc - public var imageBuilder: () -> UIImage? = { - return nil - } - - @objc - public var memberIdsBuilder: () -> [RecipientIdentifier] = { - let groupSize = arc4random_uniform(10) - return (0.. TSAttachmentStream { - var item: TSAttachmentStream! - readWrite { transaction in - item = create(contentType: contentType, dataSource: dataSource, transaction: transaction) - } - return item - } - - @objc - class public func create(contentType: String, dataSource: DataSource, transaction: YapDatabaseReadWriteTransaction) -> TSAttachmentStream { - let factory = AttachmentStreamFactory() - factory.contentTypeBuilder = { return contentType } - factory.byteCountBuilder = { return UInt32(dataSource.dataLength()) } - factory.sourceFilenameBuilder = { return dataSource.sourceFilename ?? "fake-filename.dat" } - - let attachmentStream = factory.build(transaction: transaction) - dataSource.write(toPath: attachmentStream.originalFilePath!) - - attachmentStream.save(with: transaction) - - return attachmentStream - } - - // MARK: Factory - - @objc - public func create(transaction: YapDatabaseReadWriteTransaction) -> TSAttachmentStream { - let attachmentStream = build(transaction: transaction) - attachmentStream.save(with: transaction) - - return attachmentStream - } - - @objc - public func build(transaction: YapDatabaseReadTransaction) -> TSAttachmentStream { - return build() - } - - @objc - public func build() -> TSAttachmentStream { - let attachmentStream = TSAttachmentStream(contentType: contentTypeBuilder(), - byteCount: byteCountBuilder(), - sourceFilename: sourceFilenameBuilder(), - caption: captionBuilder(), - albumMessageId: albumMessageIdBuilder()) - - return attachmentStream - } - - // MARK: Properties - - @objc - public var contentTypeBuilder: () -> String = { - return OWSMimeTypeApplicationOctetStream - } - - @objc - public var byteCountBuilder: () -> UInt32 = { - return 0 - } - - @objc - public var sourceFilenameBuilder: () -> String? = { - return "fake_file.dat" - } - - @objc - public var captionBuilder: () -> String? = { - return nil - } - - @objc - public var albumMessageIdBuilder: () -> String? = { - return nil - } -} - -extension Array { - public func ows_randomElement() -> Element? { - guard self.count > 0 else { - return nil - } - let index = arc4random_uniform(UInt32(self.count)) - return self[Int(index)] - } -} - -struct CommonGenerator { - - static public var contactId: String { - let digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] - - let randomDigits = (0..<10).map { _ in return digits.ows_randomElement()! } - - return "+1".appending(randomDigits.joined()) - } - - // Body Content - - static let sentences = [ - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rutrum, nulla vitae pretium hendrerit, tellus turpis pharetra libero, vitae sodales tortor ante vel sem.", - "In a time of universal deceit - telling the truth is a revolutionary act.", - "If you want a vision of the future, imagine a boot stamping on a human face - forever.", - "Who controls the past controls the future. Who controls the present controls the past.", - "All animals are equal, but some animals are more equal than others.", - "War is peace. Freedom is slavery. Ignorance is strength.", - "All the war-propaganda, all the screaming and lies and hatred, comes invariably from people who are not fighting.", - "Political language. . . is designed to make lies sound truthful and murder respectable, and to give an appearance of solidity to pure wind.", - "The nationalist not only does not disapprove of atrocities committed by his own side, but he has a remarkable capacity for not even hearing about them.", - "Every generation imagines itself to be more intelligent than the one that went before it, and wiser than the one that comes after it.", - "War against a foreign country only happens when the moneyed classes think they are going to profit from it.", - "People have only as much liberty as they have the intelligence to want and the courage to take.", - "You cannot buy the revolution. You cannot make the revolution. You can only be the revolution. It is in your spirit, or it is nowhere.", - "That is what I have always understood to be the essence of anarchism: the conviction that the burden of proof has to be placed on authority, and that it should be dismantled if that burden cannot be met.", - "Ask for work. If they don't give you work, ask for bread. If they do not give you work or bread, then take bread.", - "Every society has the criminals it deserves.", - "Anarchism is founded on the observation that since few men are wise enough to rule themselves, even fewer are wise enough to rule others.", - "If you would know who controls you see who you may not criticise.", - "At one time in the world there were woods that no one owned." - ] - - static public var word: String { - return String(sentence.split(separator: " ").first!) - } - - static public func words(count: Int) -> String { - var result: [String] = [] - - while result.count < count { - let remaining = count - result.count - result += sentence.split(separator: " ").prefix(remaining).map { String($0) } - } - - return result.joined(separator: " ") - } - - static public var sentence: String { - return sentences.ows_randomElement()! - } - - static public func sentences(count: UInt) -> [String] { - return (0.. String { - return sentences(count: sentenceCount).joined(separator: " ") - } -} diff --git a/SignalServiceKit/src/TestUtils/FakeContactsManager.swift b/SignalServiceKit/src/TestUtils/FakeContactsManager.swift deleted file mode 100644 index 47abf2479..000000000 --- a/SignalServiceKit/src/TestUtils/FakeContactsManager.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// -import Contacts - -@objc(OWSFakeContactsManager) -public class FakeContactsManager: NSObject, ContactsManagerProtocol { - public func displayName(forPhoneIdentifier recipientId: String?) -> String { - return "Fake name" - } - - public func displayName(forPhoneIdentifier recipientId: String?, transaction: YapDatabaseReadTransaction) -> String { - return self.displayName(forPhoneIdentifier: recipientId) - } - - public func signalAccounts() -> [SignalAccount] { - return [] - } - - public func isSystemContact(_ recipientId: String) -> Bool { - return true - } - - public func isSystemContact(withSignalAccount recipientId: String) -> Bool { - return true - } - - public func compare(signalAccount left: SignalAccount, with right: SignalAccount) -> ComparisonResult { - // If this method ends up being used by the tests, we should provide a better implementation. - assertionFailure("TODO") - return ComparisonResult.orderedAscending - } - - public func cnContact(withId contactId: String?) -> CNContact? { - return nil - } - - public func avatarData(forCNContactId contactId: String?) -> Data? { - return nil - } - - public func avatarImage(forCNContactId contactId: String?) -> UIImage? { - return nil - } -} diff --git a/SignalServiceKit/src/TestUtils/MockSSKEnvironment.h b/SignalServiceKit/src/TestUtils/MockSSKEnvironment.h deleted file mode 100644 index 844568a01..000000000 --- a/SignalServiceKit/src/TestUtils/MockSSKEnvironment.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "SSKEnvironment.h" - -NS_ASSUME_NONNULL_BEGIN - -// This should only be used in the tests. -#ifdef DEBUG - -@interface SSKEnvironment (MockSSKEnvironment) - -// Redeclare these properties as mutable so that tests can replace singletons. -@property (nonatomic) id contactsManager; -@property (nonatomic) OWSMessageSender *messageSender; -@property (nonatomic) id profileManager; -@property (nonatomic) OWSPrimaryStorage *primaryStorage; -@property (nonatomic) ContactsUpdater *contactsUpdater; -@property (nonatomic) TSNetworkManager *networkManager; -@property (nonatomic) OWSMessageManager *messageManager; -@property (nonatomic) OWSBlockingManager *blockingManager; -@property (nonatomic) OWSIdentityManager *identityManager; -@property (nonatomic) id udManager; -@property (nonatomic) OWSMessageDecrypter *messageDecrypter; -@property (nonatomic) OWSBatchMessageProcessor *batchMessageProcessor; -@property (nonatomic) OWSMessageReceiver *messageReceiver; -@property (nonatomic) TSSocketManager *socketManager; -@property (nonatomic) TSAccountManager *tsAccountManager; -@property (nonatomic) OWS2FAManager *ows2FAManager; -@property (nonatomic) OWSDisappearingMessagesJob *disappearingMessagesJob; -@property (nonatomic) ContactDiscoveryService *contactDiscoveryService; -@property (nonatomic) OWSReadReceiptManager *readReceiptManager; -@property (nonatomic) OWSOutgoingReceiptManager *outgoingReceiptManager; - -@end - -#pragma mark - - -@interface MockSSKEnvironment : SSKEnvironment - -+ (void)activate; - -- (instancetype)init; - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m b/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m deleted file mode 100644 index d599848dc..000000000 --- a/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m +++ /dev/null @@ -1,135 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "MockSSKEnvironment.h" -#import "ContactDiscoveryService.h" -#import "OWS2FAManager.h" -#import "OWSAttachmentDownloads.h" -#import "OWSBatchMessageProcessor.h" -#import "OWSBlockingManager.h" -#import "OWSDisappearingMessagesJob.h" -#import "OWSFakeCallMessageHandler.h" -#import "OWSFakeContactsUpdater.h" -#import "OWSFakeMessageSender.h" -#import "OWSFakeNetworkManager.h" -#import "OWSFakeProfileManager.h" -#import "OWSIdentityManager.h" -#import "OWSMessageDecrypter.h" -#import "OWSMessageManager.h" -#import "OWSMessageReceiver.h" -#import "OWSOutgoingReceiptManager.h" -#import "OWSPrimaryStorage.h" -#import "OWSReadReceiptManager.h" -#import "TSAccountManager.h" -#import "TSSocketManager.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -@interface OWSPrimaryStorage (Tests) - -@property (atomic) BOOL areAsyncRegistrationsComplete; -@property (atomic) BOOL areSyncRegistrationsComplete; - -@end - -#pragma mark - - -@implementation MockSSKEnvironment - -+ (void)activate -{ - MockSSKEnvironment *instance = [self new]; - [self setShared:instance]; - [instance configure]; -} - -- (instancetype)init -{ - OWSPrimaryStorage *primaryStorage = [MockSSKEnvironment createPrimaryStorageForTests]; - id contactsManager = [OWSFakeContactsManager new]; - TSNetworkManager *networkManager = [OWSFakeNetworkManager new]; - OWSMessageSender *messageSender = [OWSFakeMessageSender new]; - SSKMessageSenderJobQueue *messageSenderJobQueue = [SSKMessageSenderJobQueue new]; - - OWSMessageManager *messageManager = [[OWSMessageManager alloc] initWithPrimaryStorage:primaryStorage]; - OWSBlockingManager *blockingManager = [[OWSBlockingManager alloc] initWithPrimaryStorage:primaryStorage]; - OWSIdentityManager *identityManager = [[OWSIdentityManager alloc] initWithPrimaryStorage:primaryStorage]; - id udManager = [[OWSUDManagerImpl alloc] initWithPrimaryStorage:primaryStorage]; - OWSMessageDecrypter *messageDecrypter = [[OWSMessageDecrypter alloc] initWithPrimaryStorage:primaryStorage]; - OWSBatchMessageProcessor *batchMessageProcessor = - [[OWSBatchMessageProcessor alloc] initWithPrimaryStorage:primaryStorage]; - OWSMessageReceiver *messageReceiver = [[OWSMessageReceiver alloc] initWithPrimaryStorage:primaryStorage]; - TSSocketManager *socketManager = [[TSSocketManager alloc] init]; - TSAccountManager *tsAccountManager = [[TSAccountManager alloc] initWithPrimaryStorage:primaryStorage]; - OWS2FAManager *ows2FAManager = [[OWS2FAManager alloc] initWithPrimaryStorage:primaryStorage]; - OWSDisappearingMessagesJob *disappearingMessagesJob = - [[OWSDisappearingMessagesJob alloc] initWithPrimaryStorage:primaryStorage]; - ContactDiscoveryService *contactDiscoveryService = [[ContactDiscoveryService alloc] initDefault]; - OWSReadReceiptManager *readReceiptManager = [[OWSReadReceiptManager alloc] initWithPrimaryStorage:primaryStorage]; - OWSOutgoingReceiptManager *outgoingReceiptManager = - [[OWSOutgoingReceiptManager alloc] initWithPrimaryStorage:primaryStorage]; - id reachabilityManager = [SSKReachabilityManagerImpl new]; - id syncManager = [[OWSMockSyncManager alloc] init]; - id typingIndicators = [[OWSTypingIndicatorsImpl alloc] init]; - OWSAttachmentDownloads *attachmentDownloads = [[OWSAttachmentDownloads alloc] init]; - - self = [super initWithContactsManager:contactsManager - messageSender:messageSender - messageSenderJobQueue:messageSenderJobQueue - profileManager:[OWSFakeProfileManager new] - primaryStorage:primaryStorage - contactsUpdater:[OWSFakeContactsUpdater new] - networkManager:networkManager - messageManager:messageManager - blockingManager:blockingManager - identityManager:identityManager - udManager:udManager - messageDecrypter:messageDecrypter - batchMessageProcessor:batchMessageProcessor - messageReceiver:messageReceiver - socketManager:socketManager - tsAccountManager:tsAccountManager - ows2FAManager:ows2FAManager - disappearingMessagesJob:disappearingMessagesJob - contactDiscoveryService:contactDiscoveryService - readReceiptManager:readReceiptManager - outgoingReceiptManager:outgoingReceiptManager - reachabilityManager:reachabilityManager - syncManager:syncManager - typingIndicators:typingIndicators - attachmentDownloads:attachmentDownloads]; - - if (!self) { - return nil; - } - - self.callMessageHandler = [OWSFakeCallMessageHandler new]; - self.notificationsManager = [NoopNotificationsManager new]; - return self; -} - -+ (OWSPrimaryStorage *)createPrimaryStorageForTests -{ - OWSPrimaryStorage *primaryStorage = [[OWSPrimaryStorage alloc] initStorage]; - [OWSPrimaryStorage protectFiles]; - return primaryStorage; -} - -- (void)configure -{ - __block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - [OWSStorage registerExtensionsWithMigrationBlock:^() { - dispatch_semaphore_signal(semaphore); - }]; - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); -} - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/NoopNotificationsManager.swift b/SignalServiceKit/src/TestUtils/NoopNotificationsManager.swift deleted file mode 100644 index cb1c34536..000000000 --- a/SignalServiceKit/src/TestUtils/NoopNotificationsManager.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -@objc -public class NoopNotificationsManager: NSObject, NotificationsProtocol { - - public func notifyUser(for incomingMessage: TSIncomingMessage, in thread: TSThread, transaction: YapDatabaseReadTransaction) { - owsFailDebug("") - } - - public func notifyUser(for error: TSErrorMessage, thread: TSThread, transaction: YapDatabaseReadWriteTransaction) { - Logger.warn("skipping notification for: \(error.description)") - } - - public func notifyUser(forThreadlessErrorMessage error: TSErrorMessage, transaction: YapDatabaseReadWriteTransaction) { - Logger.warn("skipping notification for: \(error.description)") - } - - public func clearAllNotifications() { - Logger.warn("clearAllNotifications") - } -} diff --git a/SignalServiceKit/src/TestUtils/OWSFakeCallMessageHandler.h b/SignalServiceKit/src/TestUtils/OWSFakeCallMessageHandler.h deleted file mode 100644 index c2364cae7..000000000 --- a/SignalServiceKit/src/TestUtils/OWSFakeCallMessageHandler.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSCallMessageHandler.h" - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -@interface OWSFakeCallMessageHandler : NSObject - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/OWSFakeCallMessageHandler.m b/SignalServiceKit/src/TestUtils/OWSFakeCallMessageHandler.m deleted file mode 100644 index 06cd641f6..000000000 --- a/SignalServiceKit/src/TestUtils/OWSFakeCallMessageHandler.m +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSFakeCallMessageHandler.h" - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -@implementation OWSFakeCallMessageHandler - -- (void)receivedOffer:(SSKProtoCallMessageOffer *)offer fromCallerId:(NSString *)callerId -{ - OWSLogInfo(@""); -} - -- (void)receivedAnswer:(SSKProtoCallMessageAnswer *)answer fromCallerId:(NSString *)callerId -{ - OWSLogInfo(@""); -} - -- (void)receivedIceUpdate:(SSKProtoCallMessageIceUpdate *)iceUpdate fromCallerId:(NSString *)callerId -{ - OWSLogInfo(@""); -} - -- (void)receivedHangup:(SSKProtoCallMessageHangup *)hangup fromCallerId:(NSString *)callerId -{ - OWSLogInfo(@""); -} - -- (void)receivedBusy:(SSKProtoCallMessageBusy *)busy fromCallerId:(NSString *)callerId -{ - OWSLogInfo(@""); -} - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/OWSFakeContactsUpdater.h b/SignalServiceKit/src/TestUtils/OWSFakeContactsUpdater.h deleted file mode 100644 index 9d3bd0f7c..000000000 --- a/SignalServiceKit/src/TestUtils/OWSFakeContactsUpdater.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "ContactsUpdater.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSFakeContactsUpdater : ContactsUpdater - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/OWSFakeContactsUpdater.m b/SignalServiceKit/src/TestUtils/OWSFakeContactsUpdater.m deleted file mode 100644 index 9b3373777..000000000 --- a/SignalServiceKit/src/TestUtils/OWSFakeContactsUpdater.m +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSFakeContactsUpdater.h" -#import "SignalRecipient.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSFakeContactsUpdater - -//- (nullable SignalRecipient *)synchronousLookup:(NSString *)identifier error:(NSError **)error -//{ -// OWSLogInfo(@"[OWSFakeContactsUpdater] Faking contact lookup."); -// return [[SignalRecipient alloc] initWithTextSecureIdentifier:@"fake-recipient-id" -// relay:nil]; -//} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/OWSFakeMessageSender.h b/SignalServiceKit/src/TestUtils/OWSFakeMessageSender.h deleted file mode 100644 index bda37439f..000000000 --- a/SignalServiceKit/src/TestUtils/OWSFakeMessageSender.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageSender.h" - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -typedef void (^messageBlock)(TSOutgoingMessage *); - -@interface OWSFakeMessageSender : OWSMessageSender - -@property (nonatomic, nullable) NSError *stubbedFailingError; - -@property (nonatomic, nullable) messageBlock sendMessageWasCalledBlock; -@property (nonatomic, nullable) messageBlock sendAttachmentWasCalledBlock; -@property (nonatomic, nullable) messageBlock sendTemporaryAttachmentWasCalledBlock; - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/OWSFakeMessageSender.m b/SignalServiceKit/src/TestUtils/OWSFakeMessageSender.m deleted file mode 100644 index 0ef14732a..000000000 --- a/SignalServiceKit/src/TestUtils/OWSFakeMessageSender.m +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSFakeMessageSender.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -@implementation OWSFakeMessageSender - -- (void)sendMessage:(TSOutgoingMessage *)message - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - if (self.stubbedFailingError) { - failureHandler(self.stubbedFailingError); - } else { - successHandler(); - } - if (self.sendMessageWasCalledBlock) { - self.sendMessageWasCalledBlock(message); - } -} - -- (void)sendMessage:(OWSMessageSend *)messageSend -{ - if (self.stubbedFailingError) { - messageSend.failure(self.stubbedFailingError); - } else { - messageSend.success(); - } - if (self.sendMessageWasCalledBlock) { - self.sendMessageWasCalledBlock(messageSend.message); - } -} - -- (void)sendAttachment:(DataSource *)dataSource - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - inMessage:(TSOutgoingMessage *)outgoingMessage - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - if (self.stubbedFailingError) { - failureHandler(self.stubbedFailingError); - } else { - successHandler(); - } - if (self.sendAttachmentWasCalledBlock) { - self.sendAttachmentWasCalledBlock(outgoingMessage); - } -} - -- (void)sendTemporaryAttachment:(DataSource *)dataSource - contentType:(NSString *)contentType - inMessage:(TSOutgoingMessage *)outgoingMessage - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - if (self.stubbedFailingError) { - failureHandler(self.stubbedFailingError); - } else { - successHandler(); - } - if (self.sendTemporaryAttachmentWasCalledBlock) { - self.sendTemporaryAttachmentWasCalledBlock(outgoingMessage); - } -} - - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/OWSFakeNetworkManager.h b/SignalServiceKit/src/TestUtils/OWSFakeNetworkManager.h deleted file mode 100644 index 73b76d209..000000000 --- a/SignalServiceKit/src/TestUtils/OWSFakeNetworkManager.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSNetworkManager.h" - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -@interface OWSFakeNetworkManager : TSNetworkManager - -- (instancetype)init; - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/OWSFakeNetworkManager.m b/SignalServiceKit/src/TestUtils/OWSFakeNetworkManager.m deleted file mode 100644 index 03fcc23c8..000000000 --- a/SignalServiceKit/src/TestUtils/OWSFakeNetworkManager.m +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSFakeNetworkManager.h" - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -@implementation OWSFakeNetworkManager - -- (void)makeRequest:(TSRequest *)request - success:(void (^)(NSURLSessionDataTask *task, id responseObject))success - failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure -{ - NSLog(@"[OWSFakeNetworkManager] Ignoring unhandled request: %@", request); -} - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.h b/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.h deleted file mode 100644 index d2cf1bbb0..000000000 --- a/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "ProfileManagerProtocol.h" - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -@interface OWSFakeProfileManager : NSObject - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m b/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m deleted file mode 100644 index 762edd6cd..000000000 --- a/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m +++ /dev/null @@ -1,102 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSFakeProfileManager.h" -#import "TSThread.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -@interface OWSFakeProfileManager () - -@property (nonatomic, readonly) NSMutableDictionary *profileKeys; -@property (nonatomic, readonly) NSMutableSet *recipientWhitelist; -@property (nonatomic, readonly) NSMutableSet *threadWhitelist; -@property (nonatomic, readonly) OWSAES256Key *localProfileKey; - -@end - -#pragma mark - - -@implementation OWSFakeProfileManager - -@synthesize localProfileKey = _localProfileKey; - -- (instancetype)init -{ - self = [super init]; - if (!self) { - return self; - } - - _profileKeys = [NSMutableDictionary new]; - _recipientWhitelist = [NSMutableSet new]; - _threadWhitelist = [NSMutableSet new]; - - return self; -} - -- (OWSAES256Key *)localProfileKey -{ - if (_localProfileKey == nil) { - _localProfileKey = [OWSAES256Key generateRandomKey]; - } - return _localProfileKey; -} - -- (void)setProfileKeyData:(NSData *)profileKey forRecipientId:(NSString *)recipientId -{ - OWSAES256Key *_Nullable key = [OWSAES256Key keyWithData:profileKey]; - OWSAssert(key); - self.profileKeys[recipientId] = key; -} - -- (nullable NSData *)profileKeyDataForRecipientId:(NSString *)recipientId -{ - return self.profileKeys[recipientId].keyData; -} - -- (BOOL)isUserInProfileWhitelist:(NSString *)recipientId -{ - return [self.recipientWhitelist containsObject:recipientId]; -} - -- (BOOL)isThreadInProfileWhitelist:(TSThread *)thread -{ - return [self.threadWhitelist containsObject:thread.uniqueId]; -} - -- (void)addUserToProfileWhitelist:(NSString *)recipientId -{ - [self.recipientWhitelist addObject:recipientId]; -} - -- (void)addGroupIdToProfileWhitelist:(NSData *)groupId -{ - [self.threadWhitelist addObject:groupId.hexadecimalString]; -} - -- (void)fetchLocalUsersProfile -{ - // Do nothing. -} - -- (void)fetchProfileForRecipientId:(nonnull NSString *)recipientId -{ - // Do nothing. -} - -- (void)ensureLocalProfileCached -{ - // Do nothing. -} - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/OWSMockSyncManager.swift b/SignalServiceKit/src/TestUtils/OWSMockSyncManager.swift deleted file mode 100644 index 2a4b7874b..000000000 --- a/SignalServiceKit/src/TestUtils/OWSMockSyncManager.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit - -#if DEBUG - -@objc -public class OWSMockSyncManager: NSObject, OWSSyncManagerProtocol { - public func syncAllOpenGroups() -> AnyPromise { - Logger.info("") - - return AnyPromise() - } - - public func syncAllGroups() -> AnyPromise { - Logger.info("") - - return AnyPromise() - } - - public func syncGroup(for thread: TSGroupThread) -> AnyPromise { - Logger.info("") - - return AnyPromise() - } - - @objc public func sendConfigurationSyncMessage() { - Logger.info("") - } - - @objc public func syncLocalContact() -> AnyPromise { - Logger.info("") - - return AnyPromise() - } - - @objc public func syncContact(_ hexEncodedPubKey: String, transaction: YapDatabaseReadTransaction) -> AnyPromise { - Logger.info("") - - return AnyPromise() - } - - @objc public func syncAllContacts() -> AnyPromise { - Logger.info("") - - return AnyPromise() - } - - @objc public func syncContacts(for signalAccounts: [SignalAccount]) -> AnyPromise { - Logger.info("") - - return AnyPromise() - } -} - -#endif diff --git a/SignalServiceKit/src/TestUtils/TestAppContext.h b/SignalServiceKit/src/TestUtils/TestAppContext.h deleted file mode 100644 index 0f32d440c..000000000 --- a/SignalServiceKit/src/TestUtils/TestAppContext.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "AppContext.h" - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -@interface TestAppContext : NSObject - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/TestAppContext.m b/SignalServiceKit/src/TestUtils/TestAppContext.m deleted file mode 100644 index c0bc0ae61..000000000 --- a/SignalServiceKit/src/TestUtils/TestAppContext.m +++ /dev/null @@ -1,155 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TestAppContext.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -@interface TestAppContext () - -@property (nonatomic) SSKTestKeychainStorage *testKeychainStorage; -@property (nonatomic) NSString *mockAppDocumentDirectoryPath; -@property (nonatomic) NSString *mockAppSharedDataDirectoryPath; -@property (nonatomic) NSUserDefaults *appUserDefaults; - -@end - -#pragma mark - - -@implementation TestAppContext - -@synthesize mainWindow = _mainWindow; -@synthesize appLaunchTime = _appLaunchTime; - -- (instancetype)init -{ - self = [super init]; - if (!self) { - return self; - } - - self.testKeychainStorage = [SSKTestKeychainStorage new]; - - NSString *temporaryDirectory = OWSTemporaryDirectory(); - self.mockAppDocumentDirectoryPath = [temporaryDirectory stringByAppendingPathComponent:NSUUID.UUID.UUIDString]; - self.mockAppSharedDataDirectoryPath = [temporaryDirectory stringByAppendingPathComponent:NSUUID.UUID.UUIDString]; - self.appUserDefaults = [[NSUserDefaults alloc] init]; - _appLaunchTime = [NSDate new]; - - return self; -} - -- (UIApplicationState)reportedApplicationState -{ - return UIApplicationStateActive; -} - -#pragma mark - - -- (BOOL)isMainApp -{ - return YES; -} - -- (BOOL)isMainAppAndActive -{ - return YES; -} - -- (BOOL)isRTL -{ - return NO; -} - -- (void)setStatusBarHidden:(BOOL)isHidden animated:(BOOL)isAnimated -{ -} - -- (CGFloat)statusBarHeight -{ - return 20; -} - -- (BOOL)isInBackground -{ - return NO; -} - -- (BOOL)isAppForegroundAndActive -{ - return YES; -} - -- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler: - (BackgroundTaskExpirationHandler)expirationHandler -{ - return UIBackgroundTaskInvalid; -} - -- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)backgroundTaskIdentifier -{ -} - -- (void)ensureSleepBlocking:(BOOL)shouldBeBlocking blockingObjects:(NSArray *)blockingObjects -{ -} - -- (void)setMainAppBadgeNumber:(NSInteger)value -{ -} - -- (nullable UIViewController *)frontmostViewController -{ - return nil; -} - -- (nullable UIAlertAction *)openSystemSettingsAction -{ - return nil; -} - -- (BOOL)isRunningTests -{ - return YES; -} - -- (void)setNetworkActivityIndicatorVisible:(BOOL)value -{ -} - -#pragma mark - - -- (void)runNowOrWhenMainAppIsActive:(AppActiveBlock)block -{ - block(); -} - -- (void)runAppActiveBlocks -{ -} - -- (id)keychainStorage -{ - return self.testKeychainStorage; -} - -- (NSString *)appDocumentDirectoryPath -{ - return self.mockAppDocumentDirectoryPath; -} - -- (NSString *)appSharedDataDirectoryPath -{ - return self.mockAppSharedDataDirectoryPath; -} - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/TestUtils/TestKeychainStorage.swift b/SignalServiceKit/src/TestUtils/TestKeychainStorage.swift deleted file mode 100644 index 8127738ea..000000000 --- a/SignalServiceKit/src/TestUtils/TestKeychainStorage.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -#if DEBUG - -@objc -public class SSKTestKeychainStorage: NSObject, SSKKeychainStorage { - - private var dataMap = [String: Data]() - - override init() { - super.init() - } - - @objc public func string(forService service: String, key: String) throws -> String { - let data = try self.data(forService: service, key: key) - guard let string = String(bytes: data, encoding: String.Encoding.utf8) else { - throw KeychainStorageError.failure(description: "\(logTag) could not retrieve string") - } - return string - } - - @objc public func set(string: String, service: String, key: String) throws { - guard let data = string.data(using: String.Encoding.utf8) else { - throw KeychainStorageError.failure(description: "\(logTag) could not store data") - } - try set(data: data, service: service, key: key) - } - - private func key(forService service: String, key: String) -> String { - return "\(service) \(key)" - } - - @objc public func data(forService service: String, key: String) throws -> Data { - let key = self.key(forService: service, key: key) - guard let data = dataMap[key] else { - throw KeychainStorageError.failure(description: "\(logTag) could not retrieve data") - } - return data - } - - @objc public func set(data: Data, service: String, key: String) throws { - let key = self.key(forService: service, key: key) - dataMap[key] = data - } - - @objc public func remove(service: String, key: String) throws { - let key = self.key(forService: service, key: key) - dataMap.removeValue(forKey: key) - } -} - -#endif diff --git a/SignalServiceKit/src/Util/AppContext.h b/SignalServiceKit/src/Util/AppContext.h deleted file mode 100755 index 1d84450e6..000000000 --- a/SignalServiceKit/src/Util/AppContext.h +++ /dev/null @@ -1,128 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -static inline BOOL OWSIsDebugBuild() -{ -#ifdef DEBUG - return YES; -#else - return NO; -#endif -} - -// These are fired whenever the corresponding "main app" or "app extension" -// notification is fired. -// -// 1. This saves you the work of observing both. -// 2. This allows us to ensure that any critical work (e.g. re-opening -// databases) has been done before app re-enters foreground, etc. -extern NSString *const OWSApplicationDidEnterBackgroundNotification; -extern NSString *const OWSApplicationWillEnterForegroundNotification; -extern NSString *const OWSApplicationWillResignActiveNotification; -extern NSString *const OWSApplicationDidBecomeActiveNotification; - -typedef void (^BackgroundTaskExpirationHandler)(void); -typedef void (^AppActiveBlock)(void); - -NSString *NSStringForUIApplicationState(UIApplicationState value); - -@class OWSAES256Key; - -@protocol SSKKeychainStorage; - -@protocol AppContext - -@property (nonatomic, readonly) BOOL isMainApp; -@property (nonatomic, readonly) BOOL isMainAppAndActive; -/// Whether the app was woken up by a silent push notification. This is important for -/// determining whether attachments should be downloaded or not. -@property (nonatomic) BOOL wasWokenUpByPushNotification; - -// Whether the user is using a right-to-left language like Arabic. -@property (nonatomic, readonly) BOOL isRTL; - -@property (nonatomic, readonly) BOOL isRunningTests; - -@property (atomic, nullable) UIWindow *mainWindow; - -// Unlike UIApplication.applicationState, this is thread-safe. -// It contains the "last known" application state. -// -// Because it is updated in response to "will/did-style" events, it is -// conservative and skews toward less-active and not-foreground: -// -// * It doesn't report "is active" until the app is active -// and reports "inactive" as soon as it _will become_ inactive. -// * It doesn't report "is foreground (but inactive)" until the app is -// foreground & inactive and reports "background" as soon as it _will -// enter_ background. -// -// This conservatism is useful, since we want to err on the side of -// caution when, for example, we do work that should only be done -// when the app is foreground and active. -@property (atomic, readonly) UIApplicationState reportedApplicationState; - -// A convenience accessor for reportedApplicationState. -// -// This method is thread-safe. -- (BOOL)isInBackground; - -// A convenience accessor for reportedApplicationState. -// -// This method is thread-safe. -- (BOOL)isAppForegroundAndActive; - -// Should start a background task if isMainApp is YES. -// Should just return UIBackgroundTaskInvalid if isMainApp is NO. -- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler: - (BackgroundTaskExpirationHandler)expirationHandler; - -// Should be a NOOP if isMainApp is NO. -- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)backgroundTaskIdentifier; - -// Should be a NOOP if isMainApp is NO. -- (void)ensureSleepBlocking:(BOOL)shouldBeBlocking blockingObjects:(NSArray *)blockingObjects; - -// Should only be called if isMainApp is YES. -- (void)setMainAppBadgeNumber:(NSInteger)value; - -- (void)setStatusBarHidden:(BOOL)isHidden animated:(BOOL)isAnimated; - -@property (nonatomic, readonly) CGFloat statusBarHeight; - -// Returns the VC that should be used to present alerts, modals, etc. -- (nullable UIViewController *)frontmostViewController; - -// Returns nil if isMainApp is NO -@property (nullable, nonatomic, readonly) UIAlertAction *openSystemSettingsAction; - -// Should be a NOOP if isMainApp is NO. -- (void)setNetworkActivityIndicatorVisible:(BOOL)value; - -- (void)runNowOrWhenMainAppIsActive:(AppActiveBlock)block; - -@property (atomic, readonly) NSDate *appLaunchTime; - -- (id)keychainStorage; - -- (NSString *)appDocumentDirectoryPath; - -- (NSString *)appSharedDataDirectoryPath; - -- (NSUserDefaults *)appUserDefaults; - -@end - -id CurrentAppContext(void); -void SetCurrentAppContext(id appContext); - -void ExitShareExtension(void); - -#ifdef DEBUG -void ClearCurrentAppContextForTests(void); -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/AppContext.m b/SignalServiceKit/src/Util/AppContext.m deleted file mode 100755 index 39423c3cc..000000000 --- a/SignalServiceKit/src/Util/AppContext.m +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "AppContext.h" - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSApplicationDidEnterBackgroundNotification = @"OWSApplicationDidEnterBackgroundNotification"; -NSString *const OWSApplicationWillEnterForegroundNotification = @"OWSApplicationWillEnterForegroundNotification"; -NSString *const OWSApplicationWillResignActiveNotification = @"OWSApplicationWillResignActiveNotification"; -NSString *const OWSApplicationDidBecomeActiveNotification = @"OWSApplicationDidBecomeActiveNotification"; - -NSString *NSStringForUIApplicationState(UIApplicationState value) -{ - switch (value) { - case UIApplicationStateActive: - return @"UIApplicationStateActive"; - case UIApplicationStateInactive: - return @"UIApplicationStateInactive"; - case UIApplicationStateBackground: - return @"UIApplicationStateBackground"; - } -} - -static id currentAppContext = nil; - -id CurrentAppContext(void) -{ - OWSCAssertDebug(currentAppContext); - - return currentAppContext; -} - -void SetCurrentAppContext(id appContext) -{ - // The main app context should only be set once. - // - // App extensions may be opened multiple times in the same process, - // so statics will persist. - OWSCAssertDebug(!currentAppContext || !currentAppContext.isMainApp); - - currentAppContext = appContext; -} - -#ifdef DEBUG -void ClearCurrentAppContextForTests() -{ - currentAppContext = nil; -} -#endif - -void ExitShareExtension(void) -{ - OWSLogInfo(@"ExitShareExtension"); - [DDLog flushLog]; - exit(0); -} - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/AppReadiness.h b/SignalServiceKit/src/Util/AppReadiness.h deleted file mode 100755 index f07ee3741..000000000 --- a/SignalServiceKit/src/Util/AppReadiness.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -typedef void (^AppReadyBlock)(void); - -@interface AppReadiness : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -// This method can be called on any thread. -+ (BOOL)isAppReady; - -// This method should only be called on the main thread. -+ (void)setAppIsReady; - -// If the app is ready, the block is called immediately; -// otherwise it is called when the app becomes ready. -// -// This method should only be called on the main thread. -// The block will always be called on the main thread. -// -// * The "will become ready" blocks are called before the "did become ready" blocks. -// * The "will become ready" blocks should be used for internal setup of components -// so that they are ready to interact with other components of the system. -// * The "did become ready" blocks should be used for any work that should be done -// on app launch, especially work that uses other components. -// * We should usually use "did become ready" blocks since they are safer. -+ (void)runNowOrWhenAppWillBecomeReady:(AppReadyBlock)block NS_SWIFT_NAME(runNowOrWhenAppWillBecomeReady(_:)); -+ (void)runNowOrWhenAppDidBecomeReady:(AppReadyBlock)block NS_SWIFT_NAME(runNowOrWhenAppDidBecomeReady(_:)); - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/AppReadiness.m b/SignalServiceKit/src/Util/AppReadiness.m deleted file mode 100755 index 8885e202b..000000000 --- a/SignalServiceKit/src/Util/AppReadiness.m +++ /dev/null @@ -1,142 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "AppReadiness.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface AppReadiness () - -@property (atomic) BOOL isAppReady; - -@property (nonatomic) NSMutableArray *appWillBecomeReadyBlocks; -@property (nonatomic) NSMutableArray *appDidBecomeReadyBlocks; - -@end - -#pragma mark - - -@implementation AppReadiness - -+ (instancetype)sharedManager -{ - static AppReadiness *sharedMyManager = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedMyManager = [[self alloc] initDefault]; - }); - return sharedMyManager; -} - -- (instancetype)initDefault -{ - self = [super init]; - - if (!self) { - return self; - } - - OWSSingletonAssert(); - - self.appWillBecomeReadyBlocks = [NSMutableArray new]; - self.appDidBecomeReadyBlocks = [NSMutableArray new]; - - return self; -} - -+ (BOOL)isAppReady -{ - return [self.sharedManager isAppReady]; -} - -+ (void)runNowOrWhenAppWillBecomeReady:(AppReadyBlock)block -{ - DispatchMainThreadSafe(^{ - [self.sharedManager runNowOrWhenAppWillBecomeReady:block]; - }); -} - -- (void)runNowOrWhenAppWillBecomeReady:(AppReadyBlock)block -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(block); - - if (CurrentAppContext().isRunningTests) { - // We don't need to do any "on app ready" work in the tests. - return; - } - - if (self.isAppReady) { - block(); - return; - } - - [self.appWillBecomeReadyBlocks addObject:block]; -} - -+ (void)runNowOrWhenAppDidBecomeReady:(AppReadyBlock)block -{ - DispatchMainThreadSafe(^{ - [self.sharedManager runNowOrWhenAppDidBecomeReady:block]; - }); -} - -- (void)runNowOrWhenAppDidBecomeReady:(AppReadyBlock)block -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(block); - - if (CurrentAppContext().isRunningTests) { - // We don't need to do any "on app ready" work in the tests. - return; - } - - if (self.isAppReady) { - block(); - return; - } - - [self.appDidBecomeReadyBlocks addObject:block]; -} - -+ (void)setAppIsReady -{ - [self.sharedManager setAppIsReady]; -} - -- (void)setAppIsReady -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(!self.isAppReady); - - OWSLogInfo(@""); - - self.isAppReady = YES; - - [self runAppReadyBlocks]; -} - -- (void)runAppReadyBlocks -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(self.isAppReady); - - NSArray *appWillBecomeReadyBlocks = [self.appWillBecomeReadyBlocks copy]; - [self.appWillBecomeReadyBlocks removeAllObjects]; - NSArray *appDidBecomeReadyBlocks = [self.appDidBecomeReadyBlocks copy]; - [self.appDidBecomeReadyBlocks removeAllObjects]; - - // We invoke the _will become_ blocks before the _did become_ blocks. - for (AppReadyBlock block in appWillBecomeReadyBlocks) { - block(); - } - for (AppReadyBlock block in appDidBecomeReadyBlocks) { - block(); - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/AppVersion.h b/SignalServiceKit/src/Util/AppVersion.h deleted file mode 100755 index 8c48c0f7e..000000000 --- a/SignalServiceKit/src/Util/AppVersion.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface AppVersion : NSObject - -// The properties are updated immediately after launch. -@property (atomic, readonly) NSString *firstAppVersion; -@property (atomic, nullable, readonly) NSString *lastAppVersion; -@property (atomic, readonly) NSString *currentAppVersion; - -// There properties aren't updated until appLaunchDidComplete is called. -@property (atomic, nullable, readonly) NSString *lastCompletedLaunchAppVersion; -@property (atomic, nullable, readonly) NSString *lastCompletedLaunchMainAppVersion; -@property (atomic, nullable, readonly) NSString *lastCompletedLaunchSAEAppVersion; - -- (instancetype)init NS_UNAVAILABLE; - -+ (instancetype)sharedInstance; - -- (void)mainAppLaunchDidComplete; -- (void)saeLaunchDidComplete; - -- (BOOL)isFirstLaunch; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/AppVersion.m b/SignalServiceKit/src/Util/AppVersion.m deleted file mode 100755 index 5362c1d3b..000000000 --- a/SignalServiceKit/src/Util/AppVersion.m +++ /dev/null @@ -1,132 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "AppVersion.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const kNSUserDefaults_FirstAppVersion = @"kNSUserDefaults_FirstAppVersion"; -NSString *const kNSUserDefaults_LastAppVersion = @"kNSUserDefaults_LastVersion"; -NSString *const kNSUserDefaults_LastCompletedLaunchAppVersion = @"kNSUserDefaults_LastCompletedLaunchAppVersion"; -NSString *const kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp - = @"kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp"; -NSString *const kNSUserDefaults_LastCompletedLaunchAppVersion_SAE - = @"kNSUserDefaults_LastCompletedLaunchAppVersion_SAE"; - -@interface AppVersion () - -@property (atomic) NSString *firstAppVersion; -@property (atomic, nullable) NSString *lastAppVersion; -@property (atomic) NSString *currentAppVersion; - -@property (atomic, nullable) NSString *lastCompletedLaunchAppVersion; -@property (atomic, nullable) NSString *lastCompletedLaunchMainAppVersion; -@property (atomic, nullable) NSString *lastCompletedLaunchSAEAppVersion; - -@end - -#pragma mark - - -@implementation AppVersion - -+ (instancetype)sharedInstance -{ - static AppVersion *instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [AppVersion new]; - [instance configure]; - }); - return instance; -} - -- (void)configure { - OWSAssertIsOnMainThread(); - - self.currentAppVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - - // The version of the app when it was first launched. - // nil if the app has never been launched before. - self.firstAppVersion = [[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_FirstAppVersion]; - // The version of the app the last time it was launched. - // nil if the app has never been launched before. - self.lastAppVersion = [[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_LastAppVersion]; - self.lastCompletedLaunchAppVersion = - [[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_LastCompletedLaunchAppVersion]; - self.lastCompletedLaunchMainAppVersion = - [[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp]; - self.lastCompletedLaunchSAEAppVersion = - [[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_LastCompletedLaunchAppVersion_SAE]; - - // Ensure the value for the "first launched version". - if (!self.firstAppVersion) { - self.firstAppVersion = self.currentAppVersion; - [[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion forKey:kNSUserDefaults_FirstAppVersion]; - } - - // Update the value for the "most recently launched version". - [[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion forKey:kNSUserDefaults_LastAppVersion]; - [[NSUserDefaults appUserDefaults] synchronize]; - - // The long version string looks like an IPv4 address. - // To prevent the log scrubber from scrubbing it, - // we replace . with _. - NSString *longVersionString = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] - stringByReplacingOccurrencesOfString:@"." - withString:@"_"]; - - OWSLogInfo(@"firstAppVersion: %@", self.firstAppVersion); - OWSLogInfo(@"lastAppVersion: %@", self.lastAppVersion); - OWSLogInfo(@"currentAppVersion: %@ (%@)", self.currentAppVersion, longVersionString); - - OWSLogInfo(@"lastCompletedLaunchAppVersion: %@", self.lastCompletedLaunchAppVersion); - OWSLogInfo(@"lastCompletedLaunchMainAppVersion: %@", self.lastCompletedLaunchMainAppVersion); - OWSLogInfo(@"lastCompletedLaunchSAEAppVersion: %@", self.lastCompletedLaunchSAEAppVersion); -} - -- (void)appLaunchDidComplete -{ - OWSAssertIsOnMainThread(); - - OWSLogInfo(@"appLaunchDidComplete"); - - self.lastCompletedLaunchAppVersion = self.currentAppVersion; - - // Update the value for the "most recently launch-completed version". - [[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion - forKey:kNSUserDefaults_LastCompletedLaunchAppVersion]; - [[NSUserDefaults appUserDefaults] synchronize]; -} - -- (void)mainAppLaunchDidComplete -{ - OWSAssertIsOnMainThread(); - - self.lastCompletedLaunchMainAppVersion = self.currentAppVersion; - [[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion - forKey:kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp]; - - [self appLaunchDidComplete]; -} - -- (void)saeLaunchDidComplete -{ - OWSAssertIsOnMainThread(); - - self.lastCompletedLaunchSAEAppVersion = self.currentAppVersion; - [[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion - forKey:kNSUserDefaults_LastCompletedLaunchAppVersion_SAE]; - - [self appLaunchDidComplete]; -} - -- (BOOL)isFirstLaunch -{ - return self.firstAppVersion != nil; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/ByteParser.h b/SignalServiceKit/src/Util/ByteParser.h deleted file mode 100644 index 0b4e9ca19..000000000 --- a/SignalServiceKit/src/Util/ByteParser.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface ByteParser : NSObject - -@property (nonatomic, readonly) BOOL hasError; - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithData:(NSData *)data littleEndian:(BOOL)littleEndian; - -#pragma mark - Short - -- (uint16_t)shortAtIndex:(NSUInteger)index; -- (uint16_t)nextShort; - -#pragma mark - Int - -- (uint32_t)intAtIndex:(NSUInteger)index; -- (uint32_t)nextInt; - -#pragma mark - Long - -- (uint64_t)longAtIndex:(NSUInteger)index; -- (uint64_t)nextLong; - -#pragma mark - - -- (BOOL)readZero:(NSUInteger)length; - -- (nullable NSData *)readBytes:(NSUInteger)length; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/ByteParser.m b/SignalServiceKit/src/Util/ByteParser.m deleted file mode 100644 index f8f1f6b10..000000000 --- a/SignalServiceKit/src/Util/ByteParser.m +++ /dev/null @@ -1,142 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "ByteParser.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface ByteParser () - -@property (nonatomic, readonly) BOOL littleEndian; -@property (nonatomic, readonly) NSData *data; -@property (nonatomic) NSUInteger cursor; -@property (nonatomic) BOOL hasError; - -@end - -#pragma mark - - -@implementation ByteParser - -- (instancetype)initWithData:(NSData *)data littleEndian:(BOOL)littleEndian -{ - if (self = [super init]) { - _littleEndian = littleEndian; - _data = data; - } - - return self; -} - -#pragma mark - Short - -- (uint16_t)shortAtIndex:(NSUInteger)index -{ - uint16_t value; - const size_t valueSize = sizeof(value); - OWSAssertDebug(valueSize == 2); - if (index + valueSize > self.data.length) { - self.hasError = YES; - return 0; - } - [self.data getBytes:&value range:NSMakeRange(index, valueSize)]; - if (self.littleEndian) { - return CFSwapInt16LittleToHost(value); - } else { - return CFSwapInt16BigToHost(value); - } -} - -- (uint16_t)nextShort -{ - uint16_t value = [self shortAtIndex:self.cursor]; - self.cursor += sizeof(value); - return value; -} - -#pragma mark - Int - -- (uint32_t)intAtIndex:(NSUInteger)index -{ - uint32_t value; - const size_t valueSize = sizeof(value); - OWSAssertDebug(valueSize == 4); - if (index + valueSize > self.data.length) { - self.hasError = YES; - return 0; - } - [self.data getBytes:&value range:NSMakeRange(index, valueSize)]; - if (self.littleEndian) { - return CFSwapInt32LittleToHost(value); - } else { - return CFSwapInt32BigToHost(value); - } -} - -- (uint32_t)nextInt -{ - uint32_t value = [self intAtIndex:self.cursor]; - self.cursor += sizeof(value); - return value; -} - -#pragma mark - Long - -- (uint64_t)longAtIndex:(NSUInteger)index -{ - uint64_t value; - const size_t valueSize = sizeof(value); - OWSAssertDebug(valueSize == 8); - if (index + valueSize > self.data.length) { - self.hasError = YES; - return 0; - } - [self.data getBytes:&value range:NSMakeRange(index, valueSize)]; - if (self.littleEndian) { - return CFSwapInt64LittleToHost(value); - } else { - return CFSwapInt64BigToHost(value); - } -} - -- (uint64_t)nextLong -{ - uint64_t value = [self longAtIndex:self.cursor]; - self.cursor += sizeof(value); - return value; -} - -#pragma mark - - -- (BOOL)readZero:(NSUInteger)length -{ - NSData *_Nullable subdata = [self readBytes:length]; - if (!subdata) { - return NO; - } - uint8_t bytes[length]; - [subdata getBytes:bytes range:NSMakeRange(0, length)]; - for (int i = 0; i < length; i++) { - if (bytes[i] != 0) { - return NO; - } - } - return YES; -} - -- (nullable NSData *)readBytes:(NSUInteger)length -{ - NSUInteger index = self.cursor; - if (index + length > self.data.length) { - self.hasError = YES; - return nil; - } - NSData *_Nullable subdata = [self.data subdataWithRange:NSMakeRange(index, length)]; - self.cursor += length; - return subdata; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/DataSource.h b/SignalServiceKit/src/Util/DataSource.h deleted file mode 100755 index ad8e2212a..000000000 --- a/SignalServiceKit/src/Util/DataSource.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -// A base class that abstracts away a source of NSData -// and allows us to: -// -// * Lazy-load if possible. -// * Avoid duplicate reads & writes. -@interface DataSource : NSObject - -@property (nonatomic, nullable) NSString *sourceFilename; - -// Should not be called unless necessary as it can involve an expensive read. -- (NSData *)data; - -// The URL for the data. Should always be a File URL. -// -// Should not be called unless necessary as it can involve an expensive write. -// -// Will only return nil in the error case. -- (nullable NSURL *)dataUrl; - -// Will return zero in the error case. -- (NSUInteger)dataLength; - -// Returns YES on success. -- (BOOL)writeToPath:(NSString *)dstFilePath; - -- (BOOL)isValidImage; - -- (BOOL)isValidVideo; - -@end - -#pragma mark - - -@interface DataSourceValue : DataSource - -+ (nullable DataSource *)dataSourceWithData:(NSData *)data fileExtension:(NSString *)fileExtension; - -+ (nullable DataSource *)dataSourceWithData:(NSData *)data utiType:(NSString *)utiType; - -+ (nullable DataSource *)dataSourceWithOversizeText:(NSString *_Nullable)text; - -+ (DataSource *)dataSourceWithSyncMessageData:(NSData *)data; - -+ (DataSource *)emptyDataSource; - -@end - -#pragma mark - - -@interface DataSourcePath : DataSource - -+ (nullable DataSource *)dataSourceWithURL:(NSURL *)fileUrl shouldDeleteOnDeallocation:(BOOL)shouldDeleteOnDeallocation; - -+ (nullable DataSource *)dataSourceWithFilePath:(NSString *)filePath - shouldDeleteOnDeallocation:(BOOL)shouldDeleteOnDeallocation; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/DataSource.m b/SignalServiceKit/src/Util/DataSource.m deleted file mode 100755 index 508313918..000000000 --- a/SignalServiceKit/src/Util/DataSource.m +++ /dev/null @@ -1,404 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "DataSource.h" -#import "MIMETypeUtil.h" -#import "NSData+Image.h" -#import "OWSFileSystem.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface DataSource () - -@property (nonatomic) BOOL shouldDeleteOnDeallocation; - -// The file path for the data, if it already exists on disk. -// -// This method is safe to call as it will not do any expensive reads or writes. -// -// May return nil if the data does not (yet) reside on disk. -// -// Use dataUrl instead if you need to access the data; it will -// ensure the data is on disk and return a URL, barring an error. -- (nullable NSString *)dataPathIfOnDisk; - -@end - -#pragma mark - - -@implementation DataSource - -- (NSData *)data -{ - OWSAbstractMethod(); - return nil; -} - -- (nullable NSURL *)dataUrl -{ - OWSAbstractMethod(); - return nil; -} - -- (nullable NSString *)dataPathIfOnDisk -{ - OWSAbstractMethod(); - return nil; -} - -- (NSUInteger)dataLength -{ - OWSAbstractMethod(); - return 0; -} - -- (BOOL)writeToPath:(NSString *)dstFilePath -{ - OWSAbstractMethod(); - return NO; -} - -- (BOOL)isValidImage -{ - NSString *_Nullable dataPath = [self dataPathIfOnDisk]; - if (dataPath) { - // if ows_isValidImage is given a file path, it will - // avoid loading most of the data into memory, which - // is considerably more performant, so try to do that. - return [NSData ows_isValidImageAtPath:dataPath mimeType:self.mimeType]; - } - NSData *data = [self data]; - return [data ows_isValidImage]; -} - -- (BOOL)isValidVideo -{ - return [OWSMediaUtils isValidVideoWithPath:self.dataUrl.path]; -} - -- (void)setSourceFilename:(nullable NSString *)sourceFilename -{ - _sourceFilename = sourceFilename.filterFilename; -} - -// Returns the MIME type, if known. -- (nullable NSString *)mimeType -{ - OWSAbstractMethod(); - - return nil; -} - -@end - -#pragma mark - - -@interface DataSourceValue () - -@property (nonatomic) NSData *dataValue; - -@property (nonatomic) NSString *fileExtension; - -// This property is lazy-populated. -@property (nonatomic, nullable) NSString *cachedFilePath; - -@end - -#pragma mark - - -@implementation DataSourceValue - -- (void)dealloc -{ - if (self.shouldDeleteOnDeallocation) { - NSString *_Nullable filePath = self.cachedFilePath; - if (filePath) { - dispatch_async(dispatch_get_main_queue(), ^{ - NSError *error; - BOOL success = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; - if (!success || error) { - OWSCFailDebug(@"DataSourceValue could not delete file: %@, %@", filePath, error); - } - }); - } - } -} - -+ (nullable DataSource *)dataSourceWithData:(NSData *)data - fileExtension:(NSString *)fileExtension -{ - OWSAssertDebug(data); - - if (!data) { - return nil; - } - - DataSourceValue *instance = [DataSourceValue new]; - instance.dataValue = data; - instance.fileExtension = fileExtension; - instance.shouldDeleteOnDeallocation = YES; - return instance; -} - -+ (nullable DataSource *)dataSourceWithData:(NSData *)data - utiType:(NSString *)utiType -{ - NSString *fileExtension = [MIMETypeUtil fileExtensionForUTIType:utiType]; - return [self dataSourceWithData:data fileExtension:fileExtension]; -} - -+ (nullable DataSource *)dataSourceWithOversizeText:(NSString *_Nullable)text -{ - if (!text) { - return nil; - } - - NSData *data = [text.filterStringForDisplay dataUsingEncoding:NSUTF8StringEncoding]; - return [self dataSourceWithData:data fileExtension:kOversizeTextAttachmentFileExtension]; -} - -+ (DataSource *)dataSourceWithSyncMessageData:(NSData *)data -{ - return [self dataSourceWithData:data fileExtension:kSyncMessageFileExtension]; -} - -+ (DataSource *)emptyDataSource -{ - return [self dataSourceWithData:[NSData new] fileExtension:@"bin"]; -} - -- (NSData *)data -{ - OWSAssertDebug(self.dataValue); - - return self.dataValue; -} - -- (nullable NSURL *)dataUrl -{ - NSString *_Nullable path = [self dataPath]; - return (path ? [NSURL fileURLWithPath:path] : nil); -} - -- (nullable NSString *)dataPath -{ - OWSAssertDebug(self.dataValue); - - @synchronized(self) - { - if (!self.cachedFilePath) { - NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:self.fileExtension]; - if ([self writeToPath:filePath]) { - self.cachedFilePath = filePath; - } else { - OWSLogDebug(@"Could not write data to disk: %@", self.fileExtension); - OWSFailDebug(@"Could not write data to disk."); - } - } - - return self.cachedFilePath; - } -} - -- (nullable NSString *)dataPathIfOnDisk -{ - return self.cachedFilePath; -} - -- (NSUInteger)dataLength -{ - OWSAssertDebug(self.dataValue); - - return self.dataValue.length; -} - -- (BOOL)writeToPath:(NSString *)dstFilePath -{ - OWSAssertDebug(self.dataValue); - - // There's an odd bug wherein instances of NSData/Data created in Swift - // code reliably crash on iOS 9 when calling [NSData writeToFile:...]. - // We can avoid these crashes by simply copying the Data. - NSData *dataCopy = (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(10, 0) ? self.dataValue : [self.dataValue copy]); - - BOOL success = [dataCopy writeToFile:dstFilePath atomically:YES]; - if (!success) { - OWSLogDebug(@"Could not write data to disk: %@", dstFilePath); - OWSFailDebug(@"Could not write data to disk."); - return NO; - } else { - return YES; - } -} - -- (nullable NSString *)mimeType -{ - return (self.fileExtension ? [MIMETypeUtil mimeTypeForFileExtension:self.fileExtension] : nil); -} - -@end - -#pragma mark - - -@interface DataSourcePath () - -@property (nonatomic) NSString *filePath; - -// These properties are lazy-populated. -@property (nonatomic) NSData *cachedData; -@property (nonatomic) NSNumber *cachedDataLength; - -@end - -#pragma mark - - -@implementation DataSourcePath - -- (void)dealloc -{ - if (self.shouldDeleteOnDeallocation) { - NSString *filePath = self.filePath; - if (filePath) { - dispatch_async(dispatch_get_main_queue(), ^{ - NSError *error; - BOOL success = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; - if (!success || error) { - OWSCFailDebug(@"DataSourcePath could not delete file: %@, %@", filePath, error); - } - }); - } - } -} - -+ (nullable DataSource *)dataSourceWithURL:(NSURL *)fileUrl shouldDeleteOnDeallocation:(BOOL)shouldDeleteOnDeallocation -{ - OWSAssertDebug(fileUrl); - - if (!fileUrl || ![fileUrl isFileURL]) { - return nil; - } - DataSourcePath *instance = [DataSourcePath new]; - instance.filePath = fileUrl.path; - instance.shouldDeleteOnDeallocation = shouldDeleteOnDeallocation; - return instance; -} - -+ (nullable DataSource *)dataSourceWithFilePath:(NSString *)filePath - shouldDeleteOnDeallocation:(BOOL)shouldDeleteOnDeallocation -{ - OWSAssertDebug(filePath); - - if (!filePath) { - return nil; - } - - DataSourcePath *instance = [DataSourcePath new]; - instance.filePath = filePath; - instance.shouldDeleteOnDeallocation = shouldDeleteOnDeallocation; - return instance; -} - -- (void)setFilePath:(NSString *)filePath -{ - OWSAssertDebug(filePath.length > 0); - -#ifdef DEBUG - BOOL isDirectory; - BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]; - OWSAssertDebug(exists); - OWSAssertDebug(!isDirectory); -#endif - - _filePath = filePath; -} - -- (NSData *)data -{ - OWSAssertDebug(self.filePath); - - @synchronized(self) - { - if (!self.cachedData) { - self.cachedData = [NSData dataWithContentsOfFile:self.filePath]; - } - if (!self.cachedData) { - OWSLogDebug(@"Could not read data from disk: %@", self.filePath); - OWSFailDebug(@"Could not read data from disk."); - self.cachedData = [NSData new]; - } - return self.cachedData; - } -} - -- (nullable NSURL *)dataUrl -{ - OWSAssertDebug(self.filePath); - - return [NSURL fileURLWithPath:self.filePath]; -} - -- (nullable NSString *)dataPath -{ - OWSAssertDebug(self.filePath); - - return self.filePath; -} - -- (nullable NSString *)dataPathIfOnDisk -{ - OWSAssertDebug(self.filePath); - - return self.filePath; -} - -- (NSUInteger)dataLength -{ - OWSAssertDebug(self.filePath); - - @synchronized(self) - { - if (!self.cachedDataLength) { - NSError *error; - NSDictionary *_Nullable attributes = - [[NSFileManager defaultManager] attributesOfItemAtPath:self.filePath error:&error]; - if (!attributes || error) { - OWSLogDebug(@"Could not read data length from disk: %@, %@", self.filePath, error); - OWSFailDebug(@"Could not read data length from disk with error: %@", error); - self.cachedDataLength = @(0); - } else { - uint64_t fileSize = [attributes fileSize]; - self.cachedDataLength = @(fileSize); - } - } - return [self.cachedDataLength unsignedIntegerValue]; - } -} - -- (BOOL)writeToPath:(NSString *)dstFilePath -{ - OWSAssertDebug(self.filePath); - - NSError *error; - BOOL success = [[NSFileManager defaultManager] copyItemAtPath:self.filePath toPath:dstFilePath error:&error]; - if (!success || error) { - OWSLogDebug(@"Could not write data from path: %@, to path: %@, %@", self.filePath, dstFilePath, error); - OWSFailDebug(@"Could not write data with error: %@", error); - return NO; - } else { - return YES; - } -} - -- (nullable NSString *)mimeType -{ - NSString *_Nullable fileExtension = self.filePath.pathExtension; - return (fileExtension ? [MIMETypeUtil mimeTypeForFileExtension:fileExtension] : nil); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/DeviceNames.swift b/SignalServiceKit/src/Util/DeviceNames.swift deleted file mode 100644 index e01db2cc8..000000000 --- a/SignalServiceKit/src/Util/DeviceNames.swift +++ /dev/null @@ -1,220 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import SessionCurve25519Kit -import SessionAxolotlKit - -@objc -public enum DeviceNameError: Int, Error { - case assertionFailure - case invalidInput -} - -@objc -public class DeviceNames: NSObject { - // Never instantiate this class. - private override init() {} - - private static let syntheticIVLength: UInt = 16 - - @objc - public class func encryptDeviceName(plaintext: String, - identityKeyPair: ECKeyPair) throws -> Data { - - guard let plaintextData = plaintext.data(using: .utf8) else { - owsFailDebug("Could not convert text to UTF-8.") - throw DeviceNameError.invalidInput - } - - let ephemeralKeyPair = Curve25519.generateKeyPair() - - // master_secret = ECDH(ephemeral_private, identity_public). - let masterSecret: Data - do { - masterSecret = try Curve25519.generateSharedSecret(fromPublicKey: identityKeyPair.publicKey, - privateKey: ephemeralKeyPair.privateKey) - } catch { - Logger.error("Could not generate shared secret: \(error)") - throw error - } - - // synthetic_iv = HmacSHA256(key=HmacSHA256(key=master_secret, input=“auth”), input=plaintext)[0:16] - let syntheticIV = try computeSyntheticIV(masterSecret: masterSecret, - plaintextData: plaintextData) - - // cipher_key = HmacSHA256(key=HmacSHA256(key=master_secret, “cipher”), input=synthetic_iv) - let cipherKey = try computeCipherKey(masterSecret: masterSecret, syntheticIV: syntheticIV) - - // cipher_text = AES-CTR(key=cipher_key, input=plaintext, counter=0) - // - // An all-zeros IV corresponds to an AES CTR counter of zero. - let ciphertextIV = Data(count: Int(kAES256CTR_IVLength)) - guard let ciphertextKey = OWSAES256Key(data: cipherKey) else { - owsFailDebug("Invalid cipher key.") - throw DeviceNameError.assertionFailure - } - guard let ciphertext: AES256CTREncryptionResult = Cryptography.encryptAESCTR(plaintextData: plaintextData, initializationVector: ciphertextIV, key: ciphertextKey) else { - owsFailDebug("Could not encrypt cipher text.") - throw DeviceNameError.assertionFailure - } - - guard let keyData = (ephemeralKeyPair.publicKey as NSData).prependKeyType() else { - owsFailDebug("Could not prepend key type.") - throw DeviceNameError.assertionFailure - } - let protoBuilder = SignalIOSProtoDeviceName.builder(ephemeralPublic: keyData as Data, - syntheticIv: syntheticIV, - ciphertext: ciphertext.ciphertext) - let protoData = try protoBuilder.buildSerializedData() - - // NOTE: This uses Data's foundation method rather than the NSData's SSK method. - let protoDataBase64 = protoData.base64EncodedData() - - return protoDataBase64 - } - - private class func computeSyntheticIV(masterSecret: Data, - plaintextData: Data) throws -> Data { - // synthetic_iv = HmacSHA256(key=HmacSHA256(key=master_secret, input=“auth”), input=plaintext)[0:16] - guard let syntheticIVInput = "auth".data(using: .utf8) else { - owsFailDebug("Could not convert text to UTF-8.") - throw DeviceNameError.assertionFailure - } - guard let syntheticIVKey = Cryptography.computeSHA256HMAC(syntheticIVInput, withHMACKey: masterSecret) else { - owsFailDebug("Could not compute synthetic IV key.") - throw DeviceNameError.assertionFailure - } - guard let syntheticIV = Cryptography.truncatedSHA256HMAC(plaintextData, withHMACKey: syntheticIVKey, truncation: syntheticIVLength) else { - owsFailDebug("Could not compute synthetic IV.") - throw DeviceNameError.assertionFailure - } - return syntheticIV - } - - private class func computeCipherKey(masterSecret: Data, - syntheticIV: Data) throws -> Data { - // cipher_key = HmacSHA256(key=HmacSHA256(key=master_secret, “cipher”), input=synthetic_iv) - guard let cipherKeyInput = "cipher".data(using: .utf8) else { - owsFailDebug("Could not convert text to UTF-8.") - throw DeviceNameError.assertionFailure - } - guard let cipherKeyKey = Cryptography.computeSHA256HMAC(cipherKeyInput, withHMACKey: masterSecret) else { - owsFailDebug("Could not compute cipher key key.") - throw DeviceNameError.assertionFailure - } - guard let cipherKey = Cryptography.computeSHA256HMAC(syntheticIV, withHMACKey: cipherKeyKey) else { - owsFailDebug("Could not compute cipher key.") - throw DeviceNameError.assertionFailure - } - return cipherKey - } - - @objc - public class func decryptDeviceName(base64String: String, - identityKeyPair: ECKeyPair) throws -> String { - - guard let protoData = Data(base64Encoded: base64String) else { - // Not necessarily an error; might be a legacy device name. - throw DeviceNameError.invalidInput - } - - return try decryptDeviceName(protoData: protoData, - identityKeyPair: identityKeyPair) - } - - @objc - public class func decryptDeviceName(base64Data: Data, - identityKeyPair: ECKeyPair) throws -> String { - - guard let protoData = Data(base64Encoded: base64Data) else { - // Not necessarily an error; might be a legacy device name. - throw DeviceNameError.invalidInput - } - - return try decryptDeviceName(protoData: protoData, - identityKeyPair: identityKeyPair) - } - - @objc - public class func decryptDeviceName(protoData: Data, - identityKeyPair: ECKeyPair) throws -> String { - - let proto: SignalIOSProtoDeviceName - do { - proto = try SignalIOSProtoDeviceName.parseData(protoData) - } catch { - // Not necessarily an error; might be a legacy device name. - Logger.error("failed to parse proto") - throw DeviceNameError.invalidInput - } - - let ephemeralPublicData = proto.ephemeralPublic - let receivedSyntheticIV = proto.syntheticIv - let ciphertext = proto.ciphertext - - let ephemeralPublic: Data - do { - ephemeralPublic = try (ephemeralPublicData as NSData).removeKeyType() as Data - } catch { - owsFailDebug("failed to remove key type") - throw DeviceNameError.invalidInput - } - - guard ephemeralPublic.count > 0 else { - owsFailDebug("Invalid ephemeral public.") - throw DeviceNameError.assertionFailure - } - guard receivedSyntheticIV.count == syntheticIVLength else { - owsFailDebug("Invalid synthetic IV.") - throw DeviceNameError.assertionFailure - } - guard ciphertext.count > 0 else { - owsFailDebug("Invalid cipher text.") - throw DeviceNameError.assertionFailure - } - - // master_secret = ECDH(identity_private, ephemeral_public) - let masterSecret: Data - do { - masterSecret = try Curve25519.generateSharedSecret(fromPublicKey: ephemeralPublic, - privateKey: identityKeyPair.privateKey) - } catch { - Logger.error("Could not generate shared secret: \(error)") - throw error - } - - // cipher_key = HmacSHA256(key=HmacSHA256(key=master_secret, input=“cipher”), input=synthetic_iv) - let cipherKey = try computeCipherKey(masterSecret: masterSecret, syntheticIV: receivedSyntheticIV) - - // plaintext = AES-CTR(key=cipher_key, input=ciphertext, counter=0) - // - // An all-zeros IV corresponds to an AES CTR counter of zero. - let ciphertextIV = Data(count: Int(kAES256CTR_IVLength)) - guard let ciphertextKey = OWSAES256Key(data: cipherKey) else { - owsFailDebug("Invalid cipher key.") - throw DeviceNameError.assertionFailure - } - guard let plaintextData = Cryptography.decryptAESCTR(cipherText: ciphertext, initializationVector: ciphertextIV, key: ciphertextKey) else { - owsFailDebug("Could not decrypt cipher text.") - throw DeviceNameError.assertionFailure - } - - // Verify the synthetic IV was correct. - // constant_time_compare(HmacSHA256(key=HmacSHA256(key=master_secret, input=”auth”), input=plaintext)[0:16], synthetic_iv) == true - let computedSyntheticIV = try computeSyntheticIV(masterSecret: masterSecret, - plaintextData: plaintextData) - guard receivedSyntheticIV.ows_constantTimeIsEqual(to: computedSyntheticIV) else { - owsFailDebug("Synthetic IV did not match.") - throw DeviceNameError.assertionFailure - } - - guard let plaintext = String(bytes: plaintextData, encoding: .utf8) else { - owsFailDebug("Invalid plaintext.") - throw DeviceNameError.invalidInput - } - - return plaintext - } -} diff --git a/SignalServiceKit/src/Util/FeatureFlags.swift b/SignalServiceKit/src/Util/FeatureFlags.swift deleted file mode 100644 index 0d78affd1..000000000 --- a/SignalServiceKit/src/Util/FeatureFlags.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation - -/// By centralizing feature flags here and documenting their rollout plan, it's easier to review -/// which feature flags are in play. -@objc(SSKFeatureFlags) -public class FeatureFlags: NSObject { - - @objc - public static var conversationSearch: Bool { - return false - } - - /// iOS has long supported sending oversized text as a sidecar attachment. The other clients - /// simply displayed it as a text attachment. As part of the new cross-client long-text feature, - /// we want to be able to display long text with attachments as well. Existing iOS clients - /// won't properly display this, so we'll need to wait a while for rollout. - /// The stakes aren't __too__ high, because legacy clients won't lose data - they just won't - /// see the media attached to a long text message until they update their client. - @objc - public static var sendingMediaWithOversizeText: Bool { - return false - } - - @objc - public static var useCustomPhotoCapture: Bool { - return true - } -} diff --git a/SignalServiceKit/src/Util/FunctionalUtil.h b/SignalServiceKit/src/Util/FunctionalUtil.h deleted file mode 100644 index 1bb032c4d..000000000 --- a/SignalServiceKit/src/Util/FunctionalUtil.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface NSArray (FunctionalUtil) - -/// Returns true when any of the items in this array match the given predicate. -- (bool)any:(int (^)(id item))predicate; - -/// Returns true when all of the items in this array match the given predicate. -- (bool)all:(int (^)(id item))predicate; - -/// Returns an array of all the results of passing items from this array through the given projection function. -- (NSArray *)map:(id (^)(id item))projection; - -/// Returns an array of all the results of passing items from this array through the given projection function. -- (NSArray *)filter:(int (^)(id item))predicate; - -- (NSDictionary *)groupBy:(id (^)(id value))keySelector; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/FunctionalUtil.m b/SignalServiceKit/src/Util/FunctionalUtil.m deleted file mode 100644 index 830b47812..000000000 --- a/SignalServiceKit/src/Util/FunctionalUtil.m +++ /dev/null @@ -1,97 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "FunctionalUtil.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface FUBadArgument : NSException - -+ (FUBadArgument *) new:(NSString *)reason; -+ (void)raise:(NSString *)message; - -@end - -@implementation FUBadArgument - -+ (FUBadArgument *) new:(NSString *)reason { - return [[FUBadArgument alloc] initWithName:@"Invalid Argument" reason:reason userInfo:nil]; -} -+ (void)raise:(NSString *)message { - [FUBadArgument raise:@"Invalid Argument" format:@"%@", message]; -} - -@end - -#define tskit_require(expr) \ - if (!(expr)) { \ - NSString *reason = \ - [NSString stringWithFormat:@"require %@ (in %s at line %d)", (@ #expr), __FILE__, __LINE__]; \ - OWSLogError(@"%@", reason); \ - [FUBadArgument raise:reason]; \ - }; - - -@implementation NSArray (FunctionalUtil) -- (bool)any:(int (^)(id item))predicate { - tskit_require(predicate != nil); - for (id e in self) { - if (predicate(e)) { - return true; - } - } - return false; -} -- (bool)all:(int (^)(id item))predicate { - tskit_require(predicate != nil); - for (id e in self) { - if (!predicate(e)) { - return false; - } - } - return true; -} -- (NSArray *)map:(id (^)(id item))projection { - tskit_require(projection != nil); - - NSMutableArray *r = [NSMutableArray arrayWithCapacity:self.count]; - for (id e in self) { - [r addObject:projection(e)]; - } - return r; -} -- (NSArray *)filter:(int (^)(id item))predicate { - tskit_require(predicate != nil); - - NSMutableArray *r = [NSMutableArray array]; - for (id e in self) { - if (predicate(e)) { - [r addObject:e]; - } - } - return r; -} - -- (NSDictionary *)groupBy:(id (^)(id value))keySelector { - tskit_require(keySelector != nil); - - NSMutableDictionary *result = [NSMutableDictionary dictionary]; - - for (id item in self) { - id key = keySelector(item); - - NSMutableArray *group = result[key]; - if (group == nil) { - group = [NSMutableArray array]; - result[key] = group; - } - [group addObject:item]; - } - - return result; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/JobQueue.swift b/SignalServiceKit/src/Util/JobQueue.swift deleted file mode 100644 index 366c9992c..000000000 --- a/SignalServiceKit/src/Util/JobQueue.swift +++ /dev/null @@ -1,411 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -/// JobQueue - A durable work queue -/// -/// When work needs to be done, add it to the JobQueue. -/// The JobQueue will persist a JobRecord to be sure that work can be restarted if the app is killed. -/// -/// The actual work, is carried out in a DurableOperation which the JobQueue spins off, based on the contents -/// of a JobRecord. -/// -/// For a concrete example, take message sending. -/// Add an outgoing message to the MessageSenderJobQueue, which first records a SSKMessageSenderJobRecord. -/// The MessageSenderJobQueue then uses that SSKMessageSenderJobRecord to create a MessageSenderOperation which -/// takes care of the actual business of communicating with the service. -/// -/// DurableOperations are retryable - via their `remainingRetries` logic. However, if the operation encounters -/// an error where `error.isRetryable == false`, the operation will fail, regardless of available retries. - -public extension Error { - var isRetryable: Bool { - return (self as NSError).isRetryable - } -} - -extension SSKJobRecordStatus: CustomStringConvertible { - public var description: String { - switch self { - case .ready: - return "ready" - case .unknown: - return "unknown" - case .running: - return "running" - case .permanentlyFailed: - return "permanentlyFailed" - case .obsolete: - return "obsolete" - } - } -} - -public enum JobError: Error { - case assertionFailure(description: String) - case obsolete(description: String) -} - -public protocol DurableOperation: class { - associatedtype JobRecordType: SSKJobRecord - associatedtype DurableOperationDelegateType: DurableOperationDelegate - - var jobRecord: JobRecordType { get } - var durableOperationDelegate: DurableOperationDelegateType? { get set } - var operation: OWSOperation { get } - var remainingRetries: UInt { get set } -} - -public protocol DurableOperationDelegate: class { - associatedtype DurableOperationType: DurableOperation - - func durableOperationDidSucceed(_ operation: DurableOperationType, transaction: YapDatabaseReadWriteTransaction) - func durableOperation(_ operation: DurableOperationType, didReportError: Error, transaction: YapDatabaseReadWriteTransaction) - func durableOperation(_ operation: DurableOperationType, didFailWithError error: Error, transaction: YapDatabaseReadWriteTransaction) -} - -public protocol JobQueue: DurableOperationDelegate { - typealias DurableOperationDelegateType = Self - typealias JobRecordType = DurableOperationType.JobRecordType - - // MARK: Dependencies - - var dbConnection: YapDatabaseConnection { get } - var finder: JobRecordFinder { get } - - // MARK: Default Implementations - - func add(jobRecord: JobRecordType, transaction: YapDatabaseReadWriteTransaction) - func restartOldJobs() - func workStep() - func defaultSetup() - - // MARK: Required - - var runningOperations: [DurableOperationType] { get set } - var jobRecordLabel: String { get } - - var isSetup: Bool { get set } - func setup() - func didMarkAsReady(oldJobRecord: JobRecordType, transaction: YapDatabaseReadWriteTransaction) - - func operationQueue(jobRecord: JobRecordType) -> OperationQueue - func buildOperation(jobRecord: JobRecordType, transaction: YapDatabaseReadTransaction) throws -> DurableOperationType - - /// When `requiresInternet` is true, we immediately run any jobs which are waiting for retry upon detecting Reachability. - /// - /// Because `Reachability` isn't 100% reliable, the jobs will be attempted regardless of what we think our current Reachability is. - /// However, because these jobs will likely fail many times in succession, their `retryInterval` could be quite long by the time we - /// are back online. - var requiresInternet: Bool { get } - static var maxRetries: UInt { get } -} - -public extension JobQueue { - - // MARK: Dependencies - - var dbConnection: YapDatabaseConnection { - return SSKEnvironment.shared.primaryStorage.dbReadWriteConnection - } - - var finder: JobRecordFinder { - return JobRecordFinder() - } - - var reachabilityManager: SSKReachabilityManager { - return SSKEnvironment.shared.reachabilityManager - } - - // MARK: - - func add(jobRecord: JobRecordType, transaction: YapDatabaseReadWriteTransaction) { - assert(jobRecord.status == .ready) - - jobRecord.save(with: transaction) - - transaction.addCompletionQueue(DispatchQueue.global()) { - self.startWorkWhenAppIsReady() - } - } - - func startWorkWhenAppIsReady() { - guard !CurrentAppContext().isRunningTests else { - DispatchQueue.global().async { - self.workStep() - } - return - } - - AppReadiness.runNowOrWhenAppDidBecomeReady { - DispatchQueue.global().async { - self.workStep() - } - } - } - - func workStep() { - Logger.debug("") - - guard isSetup else { - if !CurrentAppContext().isRunningTests { - owsFailDebug("not setup") - } - - return - } - - Storage.writeSync { transaction in - guard let nextJob: JobRecordType = self.finder.getNextReady(label: self.jobRecordLabel, transaction: transaction) as? JobRecordType else { - Logger.verbose("nothing left to enqueue") - return - } - - do { - try nextJob.saveAsStarted(transaction: transaction) - - let operationQueue = self.operationQueue(jobRecord: nextJob) - let durableOperation = try self.buildOperation(jobRecord: nextJob, transaction: transaction) - - durableOperation.durableOperationDelegate = self as? Self.DurableOperationType.DurableOperationDelegateType - assert(durableOperation.durableOperationDelegate != nil) - - let remainingRetries = self.remainingRetries(durableOperation: durableOperation) - durableOperation.remainingRetries = remainingRetries - - self.runningOperations.append(durableOperation) - - Logger.debug("adding operation: \(durableOperation) with remainingRetries: \(remainingRetries)") - operationQueue.addOperation(durableOperation.operation) - } catch JobError.assertionFailure(let description) { - owsFailDebug("assertion failure: \(description)") - nextJob.saveAsPermanentlyFailed(transaction: transaction) - } catch JobError.obsolete(let description) { - // TODO is this even worthwhile to have obsolete state? Should we just delete the task outright? - Logger.verbose("marking obsolete task as such. description:\(description)") - nextJob.saveAsObsolete(transaction: transaction) - } catch { - owsFailDebug("unexpected error") - } - - DispatchQueue.global().async { - self.workStep() - } - } - } - - public func restartOldJobs() { - Storage.writeSync { transaction in - let runningRecords = self.finder.allRecords(label: self.jobRecordLabel, status: .running, transaction: transaction) - Logger.info("marking old `running` JobRecords as ready: \(runningRecords.count)") - for record in runningRecords { - guard let jobRecord = record as? JobRecordType else { - owsFailDebug("unexpectred jobRecord: \(record)") - continue - } - do { - try jobRecord.saveRunningAsReady(transaction: transaction) - self.didMarkAsReady(oldJobRecord: jobRecord, transaction: transaction) - } catch { - owsFailDebug("failed to mark old running records as ready error: \(error)") - jobRecord.saveAsPermanentlyFailed(transaction: transaction) - } - } - } - } - - /// Unless you need special handling, your setup method can be as simple as - /// - /// func setup() { - /// defaultSetup() - /// } - /// - /// So you might ask, why not just rename this method to `setup`? Because - /// `setup` is called from objc, and default implementations from a protocol - /// cannot be marked as @objc. - func defaultSetup() { - guard !isSetup else { - owsFailDebug("already ready already") - return - } - self.restartOldJobs() - - if self.requiresInternet { - NotificationCenter.default.addObserver(forName: .reachabilityChanged, - object: self.reachabilityManager.observationContext, - queue: nil) { _ in - - if self.reachabilityManager.isReachable { - Logger.verbose("isReachable: true") - self.becameReachable() - } else { - Logger.verbose("isReachable: false") - } - } - } - - self.isSetup = true - - self.startWorkWhenAppIsReady() - } - - func remainingRetries(durableOperation: DurableOperationType) -> UInt { - let maxRetries = type(of: self).maxRetries - let failureCount = durableOperation.jobRecord.failureCount - - guard maxRetries > failureCount else { - return 0 - } - - return maxRetries - failureCount - } - - func becameReachable() { - guard requiresInternet else { - owsFailDebug("should only be called if `requiresInternet` is true") - return - } - - _ = self.runAnyQueuedRetry() - } - - func runAnyQueuedRetry() -> DurableOperationType? { - guard let runningDurableOperation = self.runningOperations.first else { - return nil - } - runningDurableOperation.operation.runAnyQueuedRetry() - - return runningDurableOperation - } - - // MARK: DurableOperationDelegate - - func durableOperationDidSucceed(_ operation: DurableOperationType, transaction: YapDatabaseReadWriteTransaction) { - self.runningOperations = self.runningOperations.filter { $0 !== operation } - operation.jobRecord.remove(with: transaction) - } - - func durableOperation(_ operation: DurableOperationType, didReportError: Error, transaction: YapDatabaseReadWriteTransaction) { - do { - try operation.jobRecord.addFailure(transaction: transaction) - } catch { - owsFailDebug("error while addingFailure: \(error)") - operation.jobRecord.saveAsPermanentlyFailed(transaction: transaction) - } - } - - func durableOperation(_ operation: DurableOperationType, didFailWithError error: Error, transaction: YapDatabaseReadWriteTransaction) { - self.runningOperations = self.runningOperations.filter { $0 !== operation } - operation.jobRecord.saveAsPermanentlyFailed(transaction: transaction) - } -} - -@objc(SSKJobRecordFinder) -public class JobRecordFinder: NSObject, Finder { - - typealias ExtensionType = YapDatabaseSecondaryIndex - typealias TransactionType = YapDatabaseSecondaryIndexTransaction - - enum JobRecordField: String { - case status, label, sortId - } - - func getNextReady(label: String, transaction: YapDatabaseReadTransaction) -> SSKJobRecord? { - var result: SSKJobRecord? - self.enumerateJobRecords(label: label, status: .ready, transaction: transaction) { jobRecord, stopPointer in - result = jobRecord - stopPointer.pointee = true - } - return result - } - - func allRecords(label: String, status: SSKJobRecordStatus, transaction: YapDatabaseReadTransaction) -> [SSKJobRecord] { - var result: [SSKJobRecord] = [] - self.enumerateJobRecords(label: label, status: status, transaction: transaction) { jobRecord, _ in - result.append(jobRecord) - } - return result - } - - func enumerateJobRecords(label: String, status: SSKJobRecordStatus, transaction: YapDatabaseReadTransaction, block: @escaping (SSKJobRecord, UnsafeMutablePointer) -> Void) { - let queryFormat = String(format: "WHERE %@ = ? AND %@ = ? ORDER BY %@", JobRecordField.status.rawValue, JobRecordField.label.rawValue, JobRecordField.sortId.rawValue) - let query = YapDatabaseQuery(string: queryFormat, parameters: [status.rawValue, label]) - - self.ext(transaction: transaction).enumerateKeysAndObjects(matching: query) { _, _, object, stopPointer in - guard let jobRecord = object as? SSKJobRecord else { - owsFailDebug("expecting jobRecord but found: \(object)") - return - } - block(jobRecord, stopPointer) - } - } - - static var dbExtensionName: String { - return "SecondaryIndexJobRecord" - } - - @objc - public class func asyncRegisterDatabaseExtensionObjC(storage: OWSStorage) { - asyncRegisterDatabaseExtension(storage: storage) - } - - static var dbExtensionConfig: YapDatabaseSecondaryIndex { - let setup = YapDatabaseSecondaryIndexSetup() - setup.addColumn(JobRecordField.sortId.rawValue, with: .integer) - setup.addColumn(JobRecordField.status.rawValue, with: .integer) - setup.addColumn(JobRecordField.label.rawValue, with: .text) - - let block: YapDatabaseSecondaryIndexWithObjectBlock = { transaction, dict, collection, key, object in - guard let jobRecord = object as? SSKJobRecord else { - return - } - - dict[JobRecordField.sortId.rawValue] = jobRecord.sortId - dict[JobRecordField.status.rawValue] = jobRecord.status.rawValue - dict[JobRecordField.label.rawValue] = jobRecord.label - } - - let handler = YapDatabaseSecondaryIndexHandler.withObjectBlock(block) - - let options = YapDatabaseSecondaryIndexOptions() - let whitelist = YapWhitelistBlacklist(whitelist: Set([SSKJobRecord.collection()])) - options.allowedCollections = whitelist - - return YapDatabaseSecondaryIndex.init(setup: setup, handler: handler, versionTag: "2", options: options) - } -} - -protocol Finder { - associatedtype ExtensionType: YapDatabaseExtension - associatedtype TransactionType: YapDatabaseExtensionTransaction - - static var dbExtensionName: String { get } - static var dbExtensionConfig: ExtensionType { get } - - func ext(transaction: YapDatabaseReadTransaction) -> TransactionType - - static func asyncRegisterDatabaseExtension(storage: OWSStorage) - static func testingOnly_ensureDatabaseExtensionRegistered(storage: OWSStorage) -} - -extension Finder { - - func ext(transaction: YapDatabaseReadTransaction) -> TransactionType { - return transaction.ext(type(of: self).dbExtensionName) as! TransactionType - } - - static func asyncRegisterDatabaseExtension(storage: OWSStorage) { - storage.asyncRegister(dbExtensionConfig, withName: dbExtensionName) - } - - // Only for testing. - static func testingOnly_ensureDatabaseExtensionRegistered(storage: OWSStorage) { - guard storage.registeredExtension(dbExtensionName) == nil else { - return - } - - storage.register(dbExtensionConfig, withName: dbExtensionName) - } -} diff --git a/SignalServiceKit/src/Util/LRUCache.swift b/SignalServiceKit/src/Util/LRUCache.swift deleted file mode 100644 index 436b4e365..000000000 --- a/SignalServiceKit/src/Util/LRUCache.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -@objc -public class AnyLRUCache: NSObject { - - private let backingCache: LRUCache - - @objc - public init(maxSize: Int) { - backingCache = LRUCache(maxSize: maxSize) - } - - @objc - public func get(key: NSObject) -> NSObject? { - return self.backingCache.get(key: key) - } - - @objc - public func set(key: NSObject, value: NSObject) { - self.backingCache.set(key: key, value: value) - } - - @objc - public func clear() { - self.backingCache.clear() - } -} - -// A simple LRU cache bounded by the number of entries. -public class LRUCache { - - private var cacheMap: [KeyType: ValueType] = [:] - private var cacheOrder: [KeyType] = [] - private let maxSize: Int - - @objc - public init(maxSize: Int) { - self.maxSize = maxSize - - NotificationCenter.default.addObserver(self, - selector: #selector(didReceiveMemoryWarning), - name: UIApplication.didReceiveMemoryWarningNotification, - object: nil) - NotificationCenter.default.addObserver(self, - selector: #selector(didEnterBackground), - name: NSNotification.Name.OWSApplicationDidEnterBackground, - object: nil) - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - @objc func didEnterBackground() { - AssertIsOnMainThread() - - clear() - } - - @objc func didReceiveMemoryWarning() { - AssertIsOnMainThread() - - clear() - } - - private func updateCacheOrder(key: KeyType) { - cacheOrder = cacheOrder.filter { $0 != key } - cacheOrder.append(key) - } - - public func get(key: KeyType) -> ValueType? { - guard let value = cacheMap[key] else { - // Miss - return nil - } - - // Hit - updateCacheOrder(key: key) - - return value - } - - public func set(key: KeyType, value: ValueType) { - cacheMap[key] = value - - updateCacheOrder(key: key) - - while cacheOrder.count > maxSize { - guard let staleKey = cacheOrder.first else { - owsFailDebug("Cache ordering unexpectedly empty") - return - } - cacheOrder.removeFirst() - cacheMap.removeValue(forKey: staleKey) - } - } - - @objc - public func clear() { - cacheMap.removeAll() - cacheOrder.removeAll() - } -} diff --git a/SignalServiceKit/src/Util/MIMETypeUtil.h b/SignalServiceKit/src/Util/MIMETypeUtil.h deleted file mode 100644 index 2c1063ad8..000000000 --- a/SignalServiceKit/src/Util/MIMETypeUtil.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const OWSMimeTypeApplicationOctetStream; -extern NSString *const OWSMimeTypeApplicationZip; -extern NSString *const OWSMimeTypeImagePng; -extern NSString *const OWSMimeTypeImageJpeg; -extern NSString *const OWSMimeTypeImageGif; -extern NSString *const OWSMimeTypeImageTiff1; -extern NSString *const OWSMimeTypeImageTiff2; -extern NSString *const OWSMimeTypeImageBmp1; -extern NSString *const OWSMimeTypeImageBmp2; -extern NSString *const OWSMimeTypeOversizeTextMessage; -extern NSString *const OWSMimeTypeUnknownForTests; - -extern NSString *const kOversizeTextAttachmentUTI; -extern NSString *const kOversizeTextAttachmentFileExtension; -extern NSString *const kUnknownTestAttachmentUTI; -extern NSString *const kSyncMessageFileExtension; - -@interface MIMETypeUtil : NSObject - -+ (BOOL)isSupportedVideoMIMEType:(NSString *)contentType; -+ (BOOL)isSupportedAudioMIMEType:(NSString *)contentType; -+ (BOOL)isSupportedImageMIMEType:(NSString *)contentType; -+ (BOOL)isSupportedAnimatedMIMEType:(NSString *)contentType; - -+ (BOOL)isSupportedVideoFile:(NSString *)filePath; -+ (BOOL)isSupportedAudioFile:(NSString *)filePath; -+ (BOOL)isSupportedImageFile:(NSString *)filePath; -+ (BOOL)isSupportedAnimatedFile:(NSString *)filePath; - -+ (nullable NSString *)getSupportedExtensionFromVideoMIMEType:(NSString *)supportedMIMEType; -+ (nullable NSString *)getSupportedExtensionFromAudioMIMEType:(NSString *)supportedMIMEType; -+ (nullable NSString *)getSupportedExtensionFromImageMIMEType:(NSString *)supportedMIMEType; -+ (nullable NSString *)getSupportedExtensionFromAnimatedMIMEType:(NSString *)supportedMIMEType; - -+ (BOOL)isAnimated:(NSString *)contentType; -+ (BOOL)isImage:(NSString *)contentType; -+ (BOOL)isVideo:(NSString *)contentType; -+ (BOOL)isAudio:(NSString *)contentType; -+ (BOOL)isVisualMedia:(NSString *)contentType; - -// filename is optional and should not be trusted. -+ (nullable NSString *)filePathForAttachment:(NSString *)uniqueId - ofMIMEType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - inFolder:(NSString *)folder; - -+ (NSSet *)supportedVideoUTITypes; -+ (NSSet *)supportedAudioUTITypes; -+ (NSSet *)supportedImageUTITypes; -+ (NSSet *)supportedAnimatedImageUTITypes; - -+ (nullable NSString *)utiTypeForMIMEType:(NSString *)mimeType; -+ (nullable NSString *)utiTypeForFileExtension:(NSString *)fileExtension; -+ (nullable NSString *)fileExtensionForUTIType:(NSString *)utiType; -+ (nullable NSString *)fileExtensionForMIMEType:(NSString *)mimeType; -+ (nullable NSString *)mimeTypeForFileExtension:(NSString *)fileExtension; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/MIMETypeUtil.m b/SignalServiceKit/src/Util/MIMETypeUtil.m deleted file mode 100644 index f55dd3bc3..000000000 --- a/SignalServiceKit/src/Util/MIMETypeUtil.m +++ /dev/null @@ -1,2625 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "MIMETypeUtil.h" -#import "OWSFileSystem.h" - -#if TARGET_OS_IPHONE -#import - -#else -#import - -#endif - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSMimeTypeApplicationOctetStream = @"application/octet-stream"; -NSString *const OWSMimeTypeImagePng = @"image/png"; -NSString *const OWSMimeTypeImageJpeg = @"image/jpeg"; -NSString *const OWSMimeTypeImageGif = @"image/gif"; -NSString *const OWSMimeTypeImageTiff1 = @"image/tiff"; -NSString *const OWSMimeTypeImageTiff2 = @"image/x-tiff"; -NSString *const OWSMimeTypeImageBmp1 = @"image/bmp"; -NSString *const OWSMimeTypeImageBmp2 = @"image/x-windows-bmp"; -NSString *const OWSMimeTypeOversizeTextMessage = @"text/x-signal-plain"; -NSString *const OWSMimeTypeUnknownForTests = @"unknown/mimetype"; -NSString *const OWSMimeTypeApplicationZip = @"application/zip"; - -NSString *const kOversizeTextAttachmentUTI = @"org.whispersystems.oversize-text-attachment"; -NSString *const kOversizeTextAttachmentFileExtension = @"txt"; -NSString *const kUnknownTestAttachmentUTI = @"org.whispersystems.unknown"; -NSString *const kSyncMessageFileExtension = @"bin"; - -@implementation MIMETypeUtil - -+ (NSDictionary *)supportedVideoMIMETypesToExtensionTypes { - static NSDictionary *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = @{ - @"video/3gpp" : @"3gp", - @"video/3gpp2" : @"3g2", - @"video/mp4" : @"mp4", - @"video/quicktime" : @"mov", - @"video/x-m4v" : @"m4v", - @"video/mpeg" : @"mpg", - }; - }); - return result; -} - -+ (NSDictionary *)supportedAudioMIMETypesToExtensionTypes { - static NSDictionary *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = @{ - @"audio/aac" : @"m4a", - @"audio/x-m4p" : @"m4p", - @"audio/x-m4b" : @"m4b", - @"audio/x-m4a" : @"m4a", - @"audio/wav" : @"wav", - @"audio/x-wav" : @"wav", - @"audio/x-mpeg" : @"mp3", - @"audio/mpeg" : @"mp3", - @"audio/mp4" : @"mp4", - @"audio/mp3" : @"mp3", - @"audio/mpeg3" : @"mp3", - @"audio/x-mp3" : @"mp3", - @"audio/x-mpeg3" : @"mp3", - @"audio/aiff" : @"aiff", - @"audio/x-aiff" : @"aiff", - @"audio/3gpp2" : @"3g2", - @"audio/3gpp" : @"3gp", - }; - }); - return result; -} - -+ (NSDictionary *)supportedImageMIMETypesToExtensionTypes { - static NSDictionary *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = @{ - OWSMimeTypeImageJpeg : @"jpeg", - @"image/pjpeg" : @"jpeg", - OWSMimeTypeImagePng : @"png", - @"image/tiff" : @"tif", - @"image/x-tiff" : @"tif", - @"image/bmp" : @"bmp", - @"image/x-windows-bmp" : @"bmp", - @"image/gif" : @"gif" - }; - }); - return result; -} - -+ (NSDictionary *)supportedAnimatedMIMETypesToExtensionTypes { - static NSDictionary *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = @{ - OWSMimeTypeImageGif : @"gif", - }; - }); - return result; -} - -+ (NSDictionary *)supportedBinaryDataMIMETypesToExtensionTypes -{ - static NSDictionary *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = @{ - OWSMimeTypeApplicationOctetStream : @"dat", - }; - }); - return result; -} - -+ (NSDictionary *)supportedVideoExtensionTypesToMIMETypes { - static NSDictionary *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = @{ - @"3gp" : @"video/3gpp", - @"3gpp" : @"video/3gpp", - @"3gp2" : @"video/3gpp2", - @"3gpp2" : @"video/3gpp2", - @"mp4" : @"video/mp4", - @"mov" : @"video/quicktime", - @"mqv" : @"video/quicktime", - @"m4v" : @"video/x-m4v", - @"mpg" : @"video/mpeg", - @"mpeg" : @"video/mpeg", - }; - }); - return result; -} - -+ (NSDictionary *)supportedAudioExtensionTypesToMIMETypes { - static NSDictionary *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = @{ - @"3gp" : @"audio/3gpp", - @"3gpp" : @"@audio/3gpp", - @"3g2" : @"audio/3gpp2", - @"3gp2" : @"audio/3gpp2", - @"aiff" : @"audio/aiff", - @"aif" : @"audio/aiff", - @"aifc" : @"audio/aiff", - @"cdda" : @"audio/aiff", - @"mp3" : @"audio/mp3", - @"swa" : @"audio/mp3", - @"mp4" : @"audio/mp4", - @"wav" : @"audio/wav", - @"bwf" : @"audio/wav", - @"m4a" : @"audio/x-m4a", - @"m4b" : @"audio/x-m4b", - @"m4p" : @"audio/x-m4p" - }; - }); - return result; -} - -+ (NSDictionary *)supportedImageExtensionTypesToMIMETypes { - static NSDictionary *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = @{ - @"png" : OWSMimeTypeImagePng, - @"x-png" : OWSMimeTypeImagePng, - @"jfif" : @"image/jpeg", - @"jfif" : @"image/pjpeg", - @"jfif-tbnl" : @"image/jpeg", - @"jpe" : @"image/jpeg", - @"jpe" : @"image/pjpeg", - @"jpeg" : @"image/jpeg", - @"jpg" : @"image/jpeg", - @"tif" : @"image/tiff", - @"tiff" : @"image/tiff" - }; - }); - return result; -} - -+ (NSDictionary *)supportedAnimatedExtensionTypesToMIMETypes { - static NSDictionary *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = @{ - @"gif" : OWSMimeTypeImageGif, - }; - }); - return result; -} - -+ (BOOL)isSupportedVideoMIMEType:(NSString *)contentType { - return [[self supportedVideoMIMETypesToExtensionTypes] objectForKey:contentType] != nil; -} - -+ (BOOL)isSupportedAudioMIMEType:(NSString *)contentType { - return [[self supportedAudioMIMETypesToExtensionTypes] objectForKey:contentType] != nil; -} - -+ (BOOL)isSupportedImageMIMEType:(NSString *)contentType { - return [[self supportedImageMIMETypesToExtensionTypes] objectForKey:contentType] != nil; -} - -+ (BOOL)isSupportedAnimatedMIMEType:(NSString *)contentType { - return [[self supportedAnimatedMIMETypesToExtensionTypes] objectForKey:contentType] != nil; -} - -+ (BOOL)isSupportedBinaryDataMIMEType:(NSString *)contentType -{ - return [[self supportedBinaryDataMIMETypesToExtensionTypes] objectForKey:contentType] != nil; -} - -+ (BOOL)isSupportedVideoFile:(NSString *)filePath { - return [[self supportedVideoExtensionTypesToMIMETypes] objectForKey:filePath.pathExtension.lowercaseString] != nil; -} - -+ (BOOL)isSupportedAudioFile:(NSString *)filePath { - return [[self supportedAudioExtensionTypesToMIMETypes] objectForKey:filePath.pathExtension.lowercaseString] != nil; -} - -+ (BOOL)isSupportedImageFile:(NSString *)filePath { - return [[self supportedImageExtensionTypesToMIMETypes] objectForKey:filePath.pathExtension.lowercaseString] != nil; -} - -+ (BOOL)isSupportedAnimatedFile:(NSString *)filePath { - return - [[self supportedAnimatedExtensionTypesToMIMETypes] objectForKey:filePath.pathExtension.lowercaseString] != nil; -} - -+ (nullable NSString *)getSupportedExtensionFromVideoMIMEType:(NSString *)supportedMIMEType -{ - return [[self supportedVideoMIMETypesToExtensionTypes] objectForKey:supportedMIMEType]; -} - -+ (nullable NSString *)getSupportedExtensionFromAudioMIMEType:(NSString *)supportedMIMEType -{ - return [[self supportedAudioMIMETypesToExtensionTypes] objectForKey:supportedMIMEType]; -} - -+ (nullable NSString *)getSupportedExtensionFromImageMIMEType:(NSString *)supportedMIMEType -{ - return [[self supportedImageMIMETypesToExtensionTypes] objectForKey:supportedMIMEType]; -} - -+ (nullable NSString *)getSupportedExtensionFromAnimatedMIMEType:(NSString *)supportedMIMEType -{ - return [[self supportedAnimatedMIMETypesToExtensionTypes] objectForKey:supportedMIMEType]; -} - -+ (nullable NSString *)getSupportedExtensionFromBinaryDataMIMEType:(NSString *)supportedMIMEType -{ - return [[self supportedBinaryDataMIMETypesToExtensionTypes] objectForKey:supportedMIMEType]; -} - -#pragma mark full attachment utilities -+ (BOOL)isAnimated:(NSString *)contentType { - return [MIMETypeUtil isSupportedAnimatedMIMEType:contentType]; -} - -+ (BOOL)isBinaryData:(NSString *)contentType -{ - return [MIMETypeUtil isSupportedBinaryDataMIMEType:contentType]; -} - -+ (BOOL)isImage:(NSString *)contentType { - return [MIMETypeUtil isSupportedImageMIMEType:contentType]; -} - -+ (BOOL)isVideo:(NSString *)contentType { - return [MIMETypeUtil isSupportedVideoMIMEType:contentType]; -} - -+ (BOOL)isAudio:(NSString *)contentType { - return [MIMETypeUtil isSupportedAudioMIMEType:contentType]; -} - -+ (BOOL)isVisualMedia:(NSString *)contentType -{ - if ([self isImage:contentType]) { - return YES; - } - - if ([self isVideo:contentType]) { - return YES; - } - - if ([self isAnimated:contentType]) { - return YES; - } - - return NO; -} - -+ (nullable NSString *)filePathForAttachment:(NSString *)uniqueId - ofMIMEType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - inFolder:(NSString *)folder -{ - NSString *kDefaultFileExtension = @"bin"; - - if (sourceFilename.length > 0) { - NSString *normalizedFilename = - [sourceFilename stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - // Ensure that the filename is a valid filesystem name, - // replacing invalid characters with an underscore. - for (NSCharacterSet *invalidCharacterSet in @[ - [NSCharacterSet whitespaceAndNewlineCharacterSet], - [NSCharacterSet illegalCharacterSet], - [NSCharacterSet controlCharacterSet], - [NSCharacterSet characterSetWithCharactersInString:@"<>|\\:()&;?*/~"], - ]) { - normalizedFilename = [[normalizedFilename componentsSeparatedByCharactersInSet:invalidCharacterSet] - componentsJoinedByString:@"_"]; - } - - // Remove leading periods to prevent hidden files, - // "." and ".." special file names. - while ([normalizedFilename hasPrefix:@"."]) { - normalizedFilename = [normalizedFilename substringFromIndex:1]; - } - - NSString *fileExtension = [[normalizedFilename pathExtension] - stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - NSString *filenameWithoutExtension = [[[normalizedFilename lastPathComponent] stringByDeletingPathExtension] - stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - - // If the filename has not file extension, deduce one - // from the MIME type. - if (fileExtension.length < 1) { - fileExtension = [self fileExtensionForMIMEType:contentType]; - if (fileExtension.length < 1) { - fileExtension = kDefaultFileExtension; - } - } - fileExtension = [fileExtension lowercaseString]; - - if (filenameWithoutExtension.length > 0) { - // Store the file in a subdirectory whose name is the uniqueId of this attachment, - // to avoid collisions between multiple attachments with the same name. - NSString *attachmentFolderPath = [folder stringByAppendingPathComponent:uniqueId]; - if (![OWSFileSystem ensureDirectoryExists:attachmentFolderPath]) { - return nil; - } - return [attachmentFolderPath - stringByAppendingPathComponent:[NSString - stringWithFormat:@"%@.%@", filenameWithoutExtension, fileExtension]]; - } - } - - if ([self isVideo:contentType]) { - return [MIMETypeUtil filePathForVideo:uniqueId ofMIMEType:contentType inFolder:folder]; - } else if ([self isAudio:contentType]) { - return [MIMETypeUtil filePathForAudio:uniqueId ofMIMEType:contentType inFolder:folder]; - } else if ([self isImage:contentType]) { - return [MIMETypeUtil filePathForImage:uniqueId ofMIMEType:contentType inFolder:folder]; - } else if ([self isAnimated:contentType]) { - return [MIMETypeUtil filePathForAnimated:uniqueId ofMIMEType:contentType inFolder:folder]; - } else if ([self isBinaryData:contentType]) { - return [MIMETypeUtil filePathForBinaryData:uniqueId ofMIMEType:contentType inFolder:folder]; - } else if ([contentType isEqualToString:OWSMimeTypeOversizeTextMessage]) { - // We need to use a ".txt" file extension since this file extension is used - // by UIActivityViewController to determine which kinds of sharing are - // appropriate for this text. - // be used outside the app. - return [self filePathForData:uniqueId withFileExtension:@"txt" inFolder:folder]; - } else if ([contentType isEqualToString:OWSMimeTypeUnknownForTests]) { - // This file extension is arbitrary - it should never be exposed to the user or - // be used outside the app. - return [self filePathForData:uniqueId withFileExtension:@"unknown" inFolder:folder]; - } - - NSString *fileExtension = [self fileExtensionForMIMEType:contentType]; - if (fileExtension) { - return [self filePathForData:uniqueId withFileExtension:fileExtension inFolder:folder]; - } - - OWSLogError(@"Got asked for path of file %@ which is unsupported", contentType); - // Use a fallback file extension. - return [self filePathForData:uniqueId withFileExtension:kDefaultFileExtension inFolder:folder]; -} - -+ (NSString *)filePathForImage:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder { - return [self filePathForData:uniqueId - withFileExtension:[self getSupportedExtensionFromImageMIMEType:contentType] - inFolder:folder]; -} - -+ (NSString *)filePathForVideo:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder { - return [self filePathForData:uniqueId - withFileExtension:[self getSupportedExtensionFromVideoMIMEType:contentType] - inFolder:folder]; -} - -+ (NSString *)filePathForAudio:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder { - return [self filePathForData:uniqueId - withFileExtension:[self getSupportedExtensionFromAudioMIMEType:contentType] - inFolder:folder]; -} - -+ (NSString *)filePathForAnimated:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder { - return [self filePathForData:uniqueId - withFileExtension:[self getSupportedExtensionFromAnimatedMIMEType:contentType] - inFolder:folder]; -} - -+ (NSString *)filePathForBinaryData:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder -{ - return [self filePathForData:uniqueId - withFileExtension:[self getSupportedExtensionFromBinaryDataMIMEType:contentType] - inFolder:folder]; -} - -+ (NSString *)filePathForData:(NSString *)uniqueId - withFileExtension:(NSString *)fileExtension - inFolder:(NSString *)folder -{ - return [folder stringByAppendingPathComponent:[uniqueId stringByAppendingPathExtension:fileExtension]]; -} - -+ (nullable NSString *)utiTypeForMIMEType:(NSString *)mimeType -{ - NSString *utiType = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag( - kUTTagClassMIMEType, (__bridge CFStringRef)mimeType, NULL); - - if (!utiType) { - if ([mimeType isEqualToString:@"audio/amr"]) { - utiType = @"org.3gpp.adaptive-multi-rate-audio"; - } else if ([mimeType isEqualToString:@"audio/mp3"] || [mimeType isEqualToString:@"audio/x-mpeg"] || - [mimeType isEqualToString:@"audio/mpeg"] || [mimeType isEqualToString:@"audio/mpeg3"] || - [mimeType isEqualToString:@"audio/x-mp3"] || [mimeType isEqualToString:@"audio/x-mpeg3"]) { - utiType = (NSString *)kUTTypeMP3; - } else if ([mimeType isEqualToString:@"audio/aac"] || [mimeType isEqualToString:@"audio/x-m4a"]) { - utiType = (NSString *)kUTTypeMPEG4Audio; - } else if ([mimeType isEqualToString:@"audio/aiff"] || [mimeType isEqualToString:@"audio/x-aiff"]) { - utiType = (NSString *)kUTTypeAudioInterchangeFileFormat; - } - } - - return utiType; -} - -+ (nullable NSString *)fileExtensionForUTIType:(NSString *)utiType -{ - // Special-case the "aac" filetype we use for voice messages (for legacy reasons) - // to use a .m4a file extension, not .aac, since AVAudioPlayer can't handle .aac - // properly. Doesn't affect file contents. - if ([utiType isEqualToString:@"public.aac-audio"]) { - return @"m4a"; - } - CFStringRef fileExtension - = UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)utiType, kUTTagClassFilenameExtension); - return (__bridge_transfer NSString *)fileExtension; -} - -+ (nullable NSString *)fileExtensionForMIMETypeViaUTIType:(NSString *)mimeType -{ - NSString *utiType = [self utiTypeForMIMEType:mimeType]; - if (!utiType) { - return nil; - } - NSString *fileExtension = [self fileExtensionForUTIType:utiType]; - return fileExtension; -} - -+ (NSSet *)utiTypesForMIMETypes:(NSArray *)mimeTypes -{ - NSMutableSet *result = [NSMutableSet new]; - for (NSString *mimeType in mimeTypes) { - NSString *_Nullable utiType = [self utiTypeForMIMEType:mimeType]; - if (!utiType) { - OWSFailDebug(@"unknown utiType for mimetype: %@", mimeType); - continue; - } - [result addObject:utiType]; - } - return result; -} - -+ (NSSet *)supportedVideoUTITypes -{ - static NSSet *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = [self utiTypesForMIMETypes:[self supportedVideoMIMETypesToExtensionTypes].allKeys]; - }); - return result; -} - -+ (NSSet *)supportedAudioUTITypes -{ - static NSSet *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = [self utiTypesForMIMETypes:[self supportedAudioMIMETypesToExtensionTypes].allKeys]; - }); - return result; -} - -+ (NSSet *)supportedImageUTITypes -{ - static NSSet *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = [self utiTypesForMIMETypes:[self supportedImageMIMETypesToExtensionTypes].allKeys]; - }); - return result; -} - -+ (NSSet *)supportedAnimatedImageUTITypes -{ - static NSSet *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = [self utiTypesForMIMETypes:[self supportedAnimatedMIMETypesToExtensionTypes].allKeys]; - }); - return result; -} - -+ (NSDictionary *)genericMIMETypesToExtensionTypes -{ - static NSDictionary *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = @{ - @"application/acad" : @"dwg", - @"application/andrew-inset" : @"ez", - @"application/applixware" : @"aw", - @"application/arj" : @"arj", - @"application/atom+xml" : @"atom", - @"application/atomcat+xml" : @"atomcat", - @"application/atomsvc+xml" : @"atomsvc", - @"application/binhex" : @"hqx", - @"application/binhex4" : @"hqx", - @"application/book" : @"book", - @"application/ccxml+xml" : @"ccxml", - @"application/cdf" : @"cdf", - @"application/cdmi-capability" : @"cdmia", - @"application/cdmi-container" : @"cdmic", - @"application/cdmi-domain" : @"cdmid", - @"application/cdmi-object" : @"cdmio", - @"application/cdmi-queue" : @"cdmiq", - @"application/clariscad" : @"ccad", - @"application/commonground" : @"dp", - @"application/cu-seeme" : @"cu", - @"application/davmount+xml" : @"davmount", - @"application/docbook+xml" : @"dbk", - @"application/drafting" : @"drw", - @"application/dsptype" : @"tsp", - @"application/dssc+der" : @"dssc", - @"application/dssc+xml" : @"xdssc", - @"application/dxf" : @"dxf", - @"application/ecmascript" : @"js", - @"application/emma+xml" : @"emma", - @"application/envoy" : @"evy", - @"application/epub+zip" : @"epub", - @"application/excel" : @"xls", - @"application/exi" : @"exi", - @"application/font-tdpfr" : @"pfr", - @"application/font-woff" : @"woff", - @"application/fractals" : @"fif", - @"application/freeloader" : @"frl", - @"application/futuresplash" : @"spl", - @"application/gml+xml" : @"gml", - @"application/gnutar" : @"tgz", - @"application/gpx+xml" : @"gpx", - @"application/groupwise" : @"vew", - @"application/gxf" : @"gxf", - @"application/hlp" : @"hlp", - @"application/hta" : @"hta", - @"application/hyperstudio" : @"stk", - @"application/i-deas" : @"unv", - @"application/iges" : @"iges", - @"application/inf" : @"inf", - @"application/inkml+xml" : @"ink", - @"application/internet-property-stream" : @"acx", - @"application/ipfix" : @"ipfix", - @"application/java" : @"class", - @"application/java-archive" : @"jar", - @"application/java-byte-code" : @"class", - @"application/java-serialized-object" : @"ser", - @"application/java-vm" : @"class", - @"application/javascript" : @"js", - @"application/json" : @"json", - @"application/jsonml+json" : @"jsonml", - @"application/lha" : @"lha", - @"application/lost+xml" : @"lostxml", - @"application/lzx" : @"lzx", - @"application/mac-binary" : @"bin", - @"application/mac-binhex" : @"hqx", - @"application/mac-binhex40" : @"hqx", - @"application/mac-compactpro" : @"cpt", - @"application/macbinary" : @"bin", - @"application/mads+xml" : @"mads", - @"application/marc" : @"mrc", - @"application/marcxml+xml" : @"mrcx", - @"application/mathematica" : @"ma", - @"application/mathml+xml" : @"mathml", - @"application/mbedlet" : @"mbd", - @"application/mbox" : @"mbox", - @"application/mcad" : @"mcd", - @"application/mediaservercontrol+xml" : @"mscml", - @"application/metalink+xml" : @"metalink", - @"application/metalink4+xml" : @"meta4", - @"application/mets+xml" : @"mets", - @"application/mime" : @"aps", - @"application/mods+xml" : @"mods", - @"application/mp21" : @"m21", - @"application/mp4" : @"mp4", - @"application/mspowerpoint" : @"ppt", - @"application/msword" : @"doc", - @"application/mswrite" : @"wri", - @"application/mxf" : @"mxf", - @"application/netmc" : @"mcp", - @"application/octet-stream" : @"bin", - @"application/oda" : @"oda", - @"application/oebps-package+xml" : @"opf", - @"application/ogg" : @"oga", - @"application/olescript" : @"axs", - @"application/omdoc+xml" : @"omdoc", - @"application/onenote" : @"onetoc", - @"application/oxps" : @"oxps", - @"application/patch-ops-error+xml" : @"xer", - @"application/pdf" : @"pdf", - @"application/pgp-encrypted" : @"pgp", - @"application/pgp-signature" : @"sig", - @"application/pics-rules" : @"prf", - @"application/pkcs-12" : @"p12", - @"application/pkcs-crl" : @"crl", - @"application/pkcs10" : @"p10", - @"application/pkcs7-mime" : @"p7m", - @"application/pkcs7-signature" : @"p7s", - @"application/pkcs8" : @"p8", - @"application/pkix-attr-cert" : @"ac", - @"application/pkix-cert" : @"cer", - @"application/pkix-crl" : @"crl", - @"application/pkix-pkipath" : @"pkipath", - @"application/pkixcmp" : @"pki", - @"application/plain" : @"text", - @"application/pls+xml" : @"pls", - @"application/postscript" : @"ps", - @"application/powerpoint" : @"ppt", - @"application/prs.cww" : @"cww", - @"application/pskc+xml" : @"pskcxml", - @"application/rdf+xml" : @"rdf", - @"application/reginfo+xml" : @"rif", - @"application/relax-ng-compact-syntax" : @"rnc", - @"application/resource-lists+xml" : @"rl", - @"application/resource-lists-diff+xml" : @"rld", - @"application/ringing-tones" : @"rng", - @"application/rls-services+xml" : @"rs", - @"application/rpki-ghostbusters" : @"gbr", - @"application/rpki-manifest" : @"mft", - @"application/rpki-roa" : @"roa", - @"application/rsd+xml" : @"rsd", - @"application/rss+xml" : @"rss", - @"application/rtf" : @"rtf", - @"application/sbml+xml" : @"sbml", - @"application/scvp-cv-request" : @"scq", - @"application/scvp-cv-response" : @"scs", - @"application/scvp-vp-request" : @"spq", - @"application/scvp-vp-response" : @"spp", - @"application/sdp" : @"sdp", - @"application/sea" : @"sea", - @"application/set" : @"set", - @"application/set-payment-initiation" : @"setpay", - @"application/set-registration-initiation" : @"setreg", - @"application/shf+xml" : @"shf", - @"application/sla" : @"stl", - @"application/smil" : @"smi", - @"application/smil+xml" : @"smi", - @"application/solids" : @"sol", - @"application/sounder" : @"sdr", - @"application/sparql-query" : @"rq", - @"application/sparql-results+xml" : @"srx", - @"application/srgs" : @"gram", - @"application/srgs+xml" : @"grxml", - @"application/sru+xml" : @"sru", - @"application/ssdl+xml" : @"ssdl", - @"application/ssml+xml" : @"ssml", - @"application/step" : @"step", - @"application/streamingmedia" : @"ssm", - @"application/tei+xml" : @"tei", - @"application/thraud+xml" : @"tfi", - @"application/timestamped-data" : @"tsd", - @"application/toolbook" : @"tbk", - @"application/vda" : @"vda", - @"application/vnd.3gpp.pic-bw-large" : @"plb", - @"application/vnd.3gpp.pic-bw-small" : @"psb", - @"application/vnd.3gpp.pic-bw-var" : @"pvb", - @"application/vnd.3gpp2.tcap" : @"tcap", - @"application/vnd.3m.post-it-notes" : @"pwn", - @"application/vnd.accpac.simply.aso" : @"aso", - @"application/vnd.accpac.simply.imp" : @"imp", - @"application/vnd.acucobol" : @"acu", - @"application/vnd.acucorp" : @"atc", - @"application/vnd.adobe.air-application-installer-package+zip" : @"air", - @"application/vnd.adobe.formscentral.fcdt" : @"fcdt", - @"application/vnd.adobe.fxp" : @"fxp", - @"application/vnd.adobe.xdp+xml" : @"xdp", - @"application/vnd.adobe.xfdf" : @"xfdf", - @"application/vnd.ahead.space" : @"ahead", - @"application/vnd.airzip.filesecure.azf" : @"azf", - @"application/vnd.airzip.filesecure.azs" : @"azs", - @"application/vnd.amazon.ebook" : @"azw", - @"application/vnd.americandynamics.acc" : @"acc", - @"application/vnd.amiga.ami" : @"ami", - @"application/vnd.android.package-archive" : @"apk", - @"application/vnd.anser-web-certificate-issue-initiation" : @"cii", - @"application/vnd.anser-web-funds-transfer-initiation" : @"fti", - @"application/vnd.antix.game-component" : @"atx", - @"application/vnd.apple.installer+xml" : @"mpkg", - @"application/vnd.apple.mpegurl" : @"m3u8", - @"application/vnd.aristanetworks.swi" : @"swi", - @"application/vnd.astraea-software.iota" : @"iota", - @"application/vnd.audiograph" : @"aep", - @"application/vnd.blueice.multipass" : @"mpm", - @"application/vnd.bmi" : @"bmi", - @"application/vnd.businessobjects" : @"rep", - @"application/vnd.chemdraw+xml" : @"cdxml", - @"application/vnd.chipnuts.karaoke-mmd" : @"mmd", - @"application/vnd.cinderella" : @"cdy", - @"application/vnd.claymore" : @"cla", - @"application/vnd.cloanto.rp9" : @"rp9", - @"application/vnd.clonk.c4group" : @"c4g", - @"application/vnd.cluetrust.cartomobile-config" : @"c11amc", - @"application/vnd.cluetrust.cartomobile-config-pkg" : @"c11amz", - @"application/vnd.commonspace" : @"csp", - @"application/vnd.contact.cmsg" : @"cdbcmsg", - @"application/vnd.cosmocaller" : @"cmc", - @"application/vnd.crick.clicker" : @"clkx", - @"application/vnd.crick.clicker.keyboard" : @"clkk", - @"application/vnd.crick.clicker.palette" : @"clkp", - @"application/vnd.crick.clicker.template" : @"clkt", - @"application/vnd.crick.clicker.wordbank" : @"clkw", - @"application/vnd.criticaltools.wbs+xml" : @"wbs", - @"application/vnd.ctc-posml" : @"pml", - @"application/vnd.cups-ppd" : @"ppd", - @"application/vnd.curl.car" : @"car", - @"application/vnd.curl.pcurl" : @"pcurl", - @"application/vnd.dart" : @"dart", - @"application/vnd.data-vision.rdz" : @"rdz", - @"application/vnd.dece.data" : @"uvf", - @"application/vnd.dece.ttml+xml" : @"uvt", - @"application/vnd.dece.unspecified" : @"uvx", - @"application/vnd.dece.zip" : @"uvz", - @"application/vnd.denovo.fcselayout-link" : @"fe_launch", - @"application/vnd.dna" : @"dna", - @"application/vnd.dolby.mlp" : @"mlp", - @"application/vnd.dpgraph" : @"dpg", - @"application/vnd.dreamfactory" : @"dfac", - @"application/vnd.ds-keypoint" : @"kpxx", - @"application/vnd.dvb.ait" : @"ait", - @"application/vnd.dvb.service" : @"svc", - @"application/vnd.dynageo" : @"geo", - @"application/vnd.ecowin.chart" : @"mag", - @"application/vnd.enliven" : @"nml", - @"application/vnd.epson.esf" : @"esf", - @"application/vnd.epson.msf" : @"msf", - @"application/vnd.epson.quickanime" : @"qam", - @"application/vnd.epson.salt" : @"slt", - @"application/vnd.epson.ssf" : @"ssf", - @"application/vnd.eszigno3+xml" : @"es3", - @"application/vnd.ezpix-album" : @"ez2", - @"application/vnd.ezpix-package" : @"ez3", - @"application/vnd.fdf" : @"fdf", - @"application/vnd.fdsn.mseed" : @"mseed", - @"application/vnd.fdsn.seed" : @"seed", - @"application/vnd.flographit" : @"gph", - @"application/vnd.fluxtime.clip" : @"ftc", - @"application/vnd.framemaker" : @"fm", - @"application/vnd.frogans.fnc" : @"fnc", - @"application/vnd.frogans.ltf" : @"ltf", - @"application/vnd.fsc.weblaunch" : @"fsc", - @"application/vnd.fujitsu.oasys" : @"oas", - @"application/vnd.fujitsu.oasys2" : @"oa2", - @"application/vnd.fujitsu.oasys3" : @"oa3", - @"application/vnd.fujitsu.oasysgp" : @"fg5", - @"application/vnd.fujitsu.oasysprs" : @"bh2", - @"application/vnd.fujixerox.ddd" : @"ddd", - @"application/vnd.fujixerox.docuworks" : @"xdw", - @"application/vnd.fujixerox.docuworks.binder" : @"xbd", - @"application/vnd.fuzzysheet" : @"fzs", - @"application/vnd.genomatix.tuxedo" : @"txd", - @"application/vnd.geogebra.file" : @"ggb", - @"application/vnd.geogebra.tool" : @"ggt", - @"application/vnd.geometry-explorer" : @"gex", - @"application/vnd.geonext" : @"gxt", - @"application/vnd.geoplan" : @"g2w", - @"application/vnd.geospace" : @"g3w", - @"application/vnd.gmx" : @"gmx", - @"application/vnd.google-earth.kml+xml" : @"kml", - @"application/vnd.google-earth.kmz" : @"kmz", - @"application/vnd.grafeq" : @"gqf", - @"application/vnd.groove-account" : @"gac", - @"application/vnd.groove-help" : @"ghf", - @"application/vnd.groove-identity-message" : @"gim", - @"application/vnd.groove-injector" : @"grv", - @"application/vnd.groove-tool-message" : @"gtm", - @"application/vnd.groove-tool-template" : @"tpl", - @"application/vnd.groove-vcard" : @"vcg", - @"application/vnd.hal+xml" : @"hal", - @"application/vnd.handheld-entertainment+xml" : @"zmm", - @"application/vnd.hbci" : @"hbci", - @"application/vnd.hhe.lesson-player" : @"les", - @"application/vnd.hp-hpgl" : @"hpgl", - @"application/vnd.hp-hpid" : @"hpid", - @"application/vnd.hp-hps" : @"hps", - @"application/vnd.hp-jlyt" : @"jlt", - @"application/vnd.hp-pcl" : @"pcl", - @"application/vnd.hp-pclxl" : @"pclxl", - @"application/vnd.hydrostatix.sof-data" : @"sfd-hdstx", - @"application/vnd.ibm.minipay" : @"mpy", - @"application/vnd.ibm.modcap" : @"afp", - @"application/vnd.ibm.rights-management" : @"irm", - @"application/vnd.ibm.secure-container" : @"sc", - @"application/vnd.iccprofile" : @"icc", - @"application/vnd.igloader" : @"igl", - @"application/vnd.immervision-ivp" : @"ivp", - @"application/vnd.immervision-ivu" : @"ivu", - @"application/vnd.insors.igm" : @"igm", - @"application/vnd.intercon.formnet" : @"xpw", - @"application/vnd.intergeo" : @"i2g", - @"application/vnd.intu.qbo" : @"qbo", - @"application/vnd.intu.qfx" : @"qfx", - @"application/vnd.ipunplugged.rcprofile" : @"rcprofile", - @"application/vnd.irepository.package+xml" : @"irp", - @"application/vnd.is-xpr" : @"xpr", - @"application/vnd.isac.fcs" : @"fcs", - @"application/vnd.jam" : @"jam", - @"application/vnd.jcp.javame.midlet-rms" : @"rms", - @"application/vnd.jisp" : @"jisp", - @"application/vnd.joost.joda-archive" : @"joda", - @"application/vnd.kahootz" : @"ktz", - @"application/vnd.kde.karbon" : @"karbon", - @"application/vnd.kde.kchart" : @"chrt", - @"application/vnd.kde.kformula" : @"kfo", - @"application/vnd.kde.kivio" : @"flw", - @"application/vnd.kde.kontour" : @"kon", - @"application/vnd.kde.kpresenter" : @"kpr", - @"application/vnd.kde.kspread" : @"ksp", - @"application/vnd.kde.kword" : @"kwd", - @"application/vnd.kenameaapp" : @"htke", - @"application/vnd.kidspiration" : @"kia", - @"application/vnd.kinar" : @"kne", - @"application/vnd.koan" : @"skp", - @"application/vnd.kodak-descriptor" : @"sse", - @"application/vnd.las.las+xml" : @"lasxml", - @"application/vnd.llamagraphics.life-balance.desktop" : @"lbd", - @"application/vnd.llamagraphics.life-balance.exchange+xml" : @"lbe", - @"application/vnd.lotus-1-2-3" : @"123", - @"application/vnd.lotus-approach" : @"apr", - @"application/vnd.lotus-freelance" : @"pre", - @"application/vnd.lotus-notes" : @"nsf", - @"application/vnd.lotus-organizer" : @"org", - @"application/vnd.lotus-screencam" : @"scm", - @"application/vnd.lotus-wordpro" : @"lwp", - @"application/vnd.macports.portpkg" : @"portpkg", - @"application/vnd.mcd" : @"mcd", - @"application/vnd.medcalcdata" : @"mc1", - @"application/vnd.mediastation.cdkey" : @"cdkey", - @"application/vnd.mfer" : @"mwf", - @"application/vnd.mfmp" : @"mfm", - @"application/vnd.micrografx.flo" : @"flo", - @"application/vnd.micrografx.igx" : @"igx", - @"application/vnd.mif" : @"mif", - @"application/vnd.mobius.daf" : @"daf", - @"application/vnd.mobius.dis" : @"dis", - @"application/vnd.mobius.mbk" : @"mbk", - @"application/vnd.mobius.mqy" : @"mqy", - @"application/vnd.mobius.msl" : @"msl", - @"application/vnd.mobius.plc" : @"plc", - @"application/vnd.mobius.txf" : @"txf", - @"application/vnd.mophun.application" : @"mpn", - @"application/vnd.mophun.certificate" : @"mpc", - @"application/vnd.mozilla.xul+xml" : @"xul", - @"application/vnd.ms-artgalry" : @"cil", - @"application/vnd.ms-cab-compressed" : @"cab", - @"application/vnd.ms-excel" : @"xls", - @"application/vnd.ms-excel.addin.macroenabled.12" : @"xlam", - @"application/vnd.ms-excel.sheet.binary.macroenabled.12" : @"xlsb", - @"application/vnd.ms-excel.sheet.macroenabled.12" : @"xlsm", - @"application/vnd.ms-excel.template.macroenabled.12" : @"xltm", - @"application/vnd.ms-fontobject" : @"eot", - @"application/vnd.ms-htmlhelp" : @"chm", - @"application/vnd.ms-ims" : @"ims", - @"application/vnd.ms-lrm" : @"lrm", - @"application/vnd.ms-officetheme" : @"thmx", - @"application/vnd.ms-outlook" : @"msg", - @"application/vnd.ms-pki.certstore" : @"sst", - @"application/vnd.ms-pki.pko" : @"pko", - @"application/vnd.ms-pki.seccat" : @"cat", - @"application/vnd.ms-pki.stl" : @"stl", - @"application/vnd.ms-pkicertstore" : @"sst", - @"application/vnd.ms-pkiseccat" : @"cat", - @"application/vnd.ms-pkistl" : @"stl", - @"application/vnd.ms-powerpoint" : @"ppt", - @"application/vnd.ms-powerpoint.addin.macroenabled.12" : @"ppam", - @"application/vnd.ms-powerpoint.presentation.macroenabled.12" : @"pptm", - @"application/vnd.ms-powerpoint.slide.macroenabled.12" : @"sldm", - @"application/vnd.ms-powerpoint.slideshow.macroenabled.12" : @"ppsm", - @"application/vnd.ms-powerpoint.template.macroenabled.12" : @"potm", - @"application/vnd.ms-project" : @"mpp", - @"application/vnd.ms-word.document.macroenabled.12" : @"docm", - @"application/vnd.ms-word.template.macroenabled.12" : @"dotm", - @"application/vnd.ms-works" : @"wps", - @"application/vnd.ms-wpl" : @"wpl", - @"application/vnd.ms-xpsdocument" : @"xps", - @"application/vnd.mseq" : @"mseq", - @"application/vnd.musician" : @"mus", - @"application/vnd.muvee.style" : @"msty", - @"application/vnd.mynfc" : @"taglet", - @"application/vnd.neurolanguage.nlu" : @"nlu", - @"application/vnd.nitf" : @"ntf", - @"application/vnd.noblenet-directory" : @"nnd", - @"application/vnd.noblenet-sealer" : @"nns", - @"application/vnd.noblenet-web" : @"nnw", - @"application/vnd.nokia.configuration-message" : @"ncm", - @"application/vnd.nokia.n-gage.data" : @"ngdat", - @"application/vnd.nokia.n-gage.symbian.install" : @"n-gage", - @"application/vnd.nokia.radio-preset" : @"rpst", - @"application/vnd.nokia.radio-presets" : @"rpss", - @"application/vnd.nokia.ringing-tone" : @"rng", - @"application/vnd.novadigm.edm" : @"edm", - @"application/vnd.novadigm.edx" : @"edx", - @"application/vnd.novadigm.ext" : @"ext", - @"application/vnd.oasis.opendocument.chart" : @"odc", - @"application/vnd.oasis.opendocument.chart-template" : @"otc", - @"application/vnd.oasis.opendocument.database" : @"odb", - @"application/vnd.oasis.opendocument.formula" : @"odf", - @"application/vnd.oasis.opendocument.formula-template" : @"odft", - @"application/vnd.oasis.opendocument.graphics" : @"odg", - @"application/vnd.oasis.opendocument.graphics-template" : @"otg", - @"application/vnd.oasis.opendocument.image" : @"odi", - @"application/vnd.oasis.opendocument.image-template" : @"oti", - @"application/vnd.oasis.opendocument.presentation" : @"odp", - @"application/vnd.oasis.opendocument.presentation-template" : @"otp", - @"application/vnd.oasis.opendocument.spreadsheet" : @"ods", - @"application/vnd.oasis.opendocument.spreadsheet-template" : @"ots", - @"application/vnd.oasis.opendocument.text" : @"odt", - @"application/vnd.oasis.opendocument.text-master" : @"odm", - @"application/vnd.oasis.opendocument.text-template" : @"ott", - @"application/vnd.oasis.opendocument.text-web" : @"oth", - @"application/vnd.olpc-sugar" : @"xo", - @"application/vnd.oma.dd2+xml" : @"dd2", - @"application/vnd.openofficeorg.extension" : @"oxt", - @"application/vnd.openxmlformats-officedocument.presentationml.presentation" : @"pptx", - @"application/vnd.openxmlformats-officedocument.presentationml.slide" : @"sldx", - @"application/vnd.openxmlformats-officedocument.presentationml.slideshow" : @"ppsx", - @"application/vnd.openxmlformats-officedocument.presentationml.template" : @"potx", - @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" : @"xlsx", - @"application/vnd.openxmlformats-officedocument.spreadsheetml.template" : @"xltx", - @"application/vnd.openxmlformats-officedocument.wordprocessingml.document" : @"docx", - @"application/vnd.openxmlformats-officedocument.wordprocessingml.template" : @"dotx", - @"application/vnd.osgeo.mapguide.package" : @"mgp", - @"application/vnd.osgi.dp" : @"dp", - @"application/vnd.osgi.subsystem" : @"esa", - @"application/vnd.palm" : @"pdb", - @"application/vnd.pawaafile" : @"paw", - @"application/vnd.pg.format" : @"str", - @"application/vnd.pg.osasli" : @"ei6", - @"application/vnd.picsel" : @"efif", - @"application/vnd.pmi.widget" : @"wg", - @"application/vnd.pocketlearn" : @"plf", - @"application/vnd.powerbuilder6" : @"pbd", - @"application/vnd.previewsystems.box" : @"box", - @"application/vnd.proteus.magazine" : @"mgz", - @"application/vnd.publishare-delta-tree" : @"qps", - @"application/vnd.pvi.ptid1" : @"ptid", - @"application/vnd.quark.quarkxpress" : @"qxd", - @"application/vnd.realvnc.bed" : @"bed", - @"application/vnd.recordare.musicxml" : @"mxl", - @"application/vnd.recordare.musicxml+xml" : @"musicxml", - @"application/vnd.rig.cryptonote" : @"cryptonote", - @"application/vnd.rim.cod" : @"cod", - @"application/vnd.rn-realmedia" : @"rm", - @"application/vnd.rn-realmedia-vbr" : @"rmvb", - @"application/vnd.rn-realplayer" : @"rnx", - @"application/vnd.route66.link66+xml" : @"link66", - @"application/vnd.sailingtracker.track" : @"st", - @"application/vnd.seemail" : @"see", - @"application/vnd.sema" : @"sema", - @"application/vnd.semd" : @"semd", - @"application/vnd.semf" : @"semf", - @"application/vnd.shana.informed.formdata" : @"ifm", - @"application/vnd.shana.informed.formtemplate" : @"itp", - @"application/vnd.shana.informed.interchange" : @"iif", - @"application/vnd.shana.informed.package" : @"ipk", - @"application/vnd.simtech-mindmapper" : @"twd", - @"application/vnd.smaf" : @"mmf", - @"application/vnd.smart.teacher" : @"teacher", - @"application/vnd.solent.sdkm+xml" : @"sdkm", - @"application/vnd.spotfire.dxp" : @"dxp", - @"application/vnd.spotfire.sfs" : @"sfs", - @"application/vnd.stardivision.calc" : @"sdc", - @"application/vnd.stardivision.draw" : @"sda", - @"application/vnd.stardivision.impress" : @"sdd", - @"application/vnd.stardivision.math" : @"smf", - @"application/vnd.stardivision.writer" : @"sdw", - @"application/vnd.stardivision.writer-global" : @"sgl", - @"application/vnd.stepmania.package" : @"smzip", - @"application/vnd.stepmania.stepchart" : @"sm", - @"application/vnd.sun.xml.calc" : @"sxc", - @"application/vnd.sun.xml.calc.template" : @"stc", - @"application/vnd.sun.xml.draw" : @"sxd", - @"application/vnd.sun.xml.draw.template" : @"std", - @"application/vnd.sun.xml.impress" : @"sxi", - @"application/vnd.sun.xml.impress.template" : @"sti", - @"application/vnd.sun.xml.math" : @"sxm", - @"application/vnd.sun.xml.writer" : @"sxw", - @"application/vnd.sun.xml.writer.global" : @"sxg", - @"application/vnd.sun.xml.writer.template" : @"stw", - @"application/vnd.sus-calendar" : @"sus", - @"application/vnd.svd" : @"svd", - @"application/vnd.symbian.install" : @"sis", - @"application/vnd.syncml+xml" : @"xsm", - @"application/vnd.syncml.dm+wbxml" : @"bdm", - @"application/vnd.syncml.dm+xml" : @"xdm", - @"application/vnd.tao.intent-module-archive" : @"tao", - @"application/vnd.tcpdump.pcap" : @"pcap", - @"application/vnd.tmobile-livetv" : @"tmo", - @"application/vnd.trid.tpt" : @"tpt", - @"application/vnd.triscape.mxs" : @"mxs", - @"application/vnd.trueapp" : @"tra", - @"application/vnd.ufdl" : @"ufd", - @"application/vnd.uiq.theme" : @"utz", - @"application/vnd.umajin" : @"umj", - @"application/vnd.unity" : @"unityweb", - @"application/vnd.uoml+xml" : @"uoml", - @"application/vnd.vcx" : @"vcx", - @"application/vnd.visio" : @"vsd", - @"application/vnd.visio2013" : @"vsdx", - @"application/vnd.visionary" : @"vis", - @"application/vnd.vsf" : @"vsf", - @"application/vnd.wap.wbxml" : @"wbxml", - @"application/vnd.wap.wmlc" : @"wmlc", - @"application/vnd.wap.wmlscriptc" : @"wmlsc", - @"application/vnd.webturbo" : @"wtb", - @"application/vnd.wolfram.player" : @"nbp", - @"application/vnd.wordperfect" : @"wpd", - @"application/vnd.wqd" : @"wqd", - @"application/vnd.wt.stf" : @"stf", - @"application/vnd.xara" : @"xar", - @"application/vnd.xfdl" : @"xfdl", - @"application/vnd.yamaha.hv-dic" : @"hvd", - @"application/vnd.yamaha.hv-script" : @"hvs", - @"application/vnd.yamaha.hv-voice" : @"hvp", - @"application/vnd.yamaha.openscoreformat" : @"osf", - @"application/vnd.yamaha.openscoreformat.osfpvg+xml" : @"osfpvg", - @"application/vnd.yamaha.smaf-audio" : @"saf", - @"application/vnd.yamaha.smaf-phrase" : @"spf", - @"application/vnd.yellowriver-custom-menu" : @"cmp", - @"application/vnd.zul" : @"zir", - @"application/vnd.zzazz.deck+xml" : @"zaz", - @"application/vocaltec-media-desc" : @"vmd", - @"application/vocaltec-media-file" : @"vmf", - @"application/voicexml+xml" : @"vxml", - @"application/widget" : @"wgt", - @"application/winhlp" : @"hlp", - @"application/wordperfect" : @"wp", - @"application/wordperfect6.0" : @"w60", - @"application/wordperfect6.1" : @"w61", - @"application/wsdl+xml" : @"wsdl", - @"application/wspolicy+xml" : @"wspolicy", - @"application/x-123" : @"wk1", - @"application/x-7z-compressed" : @"7z", - @"application/x-abiword" : @"abw", - @"application/x-ace-compressed" : @"ace", - @"application/x-aim" : @"aim", - @"application/x-apple-diskimage" : @"dmg", - @"application/x-authorware-bin" : @"aab", - @"application/x-authorware-map" : @"aam", - @"application/x-authorware-seg" : @"aas", - @"application/x-bcpio" : @"bcpio", - @"application/x-binary" : @"bin", - @"application/x-binhex40" : @"hqx", - @"application/x-bittorrent" : @"torrent", - @"application/x-blorb" : @"blb", - @"application/x-bsh" : @"sh", - @"application/x-bytecode.elisp" : @"elc", - @"application/x-bytecode.python" : @"pyc", - @"application/x-bzip" : @"bz", - @"application/x-bzip2" : @"bz2", - @"application/x-cbr" : @"cbr", - @"application/x-cdf" : @"cdf", - @"application/x-cdlink" : @"vcd", - @"application/x-cfs-compressed" : @"cfs", - @"application/x-chat" : @"chat", - @"application/x-chess-pgn" : @"pgn", - @"application/x-cmu-raster" : @"ras", - @"application/x-cocoa" : @"cco", - @"application/x-compactpro" : @"cpt", - @"application/x-compress" : @"z", - @"application/x-conference" : @"nsc", - @"application/x-cpio" : @"cpio", - @"application/x-cpt" : @"cpt", - @"application/x-csh" : @"csh", - @"application/x-debian-package" : @"deb", - @"application/x-deepv" : @"deepv", - @"application/x-dgc-compressed" : @"dgc", - @"application/x-director" : @"dir", - @"application/x-doom" : @"wad", - @"application/x-dtbncx+xml" : @"ncx", - @"application/x-dtbook+xml" : @"dtb", - @"application/x-dtbresource+xml" : @"res", - @"application/x-dvi" : @"dvi", - @"application/x-elc" : @"elc", - @"application/x-envoy" : @"evy", - @"application/x-esrehber" : @"es", - @"application/x-eva" : @"eva", - @"application/x-excel" : @"xls", - @"application/x-font-bdf" : @"bdf", - @"application/x-font-ghostscript" : @"gsf", - @"application/x-font-linux-psf" : @"psf", - @"application/x-font-otf" : @"otf", - @"application/x-font-pcf" : @"pcf", - @"application/x-font-snf" : @"snf", - @"application/x-font-ttf" : @"ttf", - @"application/x-font-type1" : @"pfa", - @"application/x-font-woff" : @"woff", - @"application/x-frame" : @"mif", - @"application/x-freearc" : @"arc", - @"application/x-freelance" : @"pre", - @"application/x-futuresplash" : @"spl", - @"application/x-gca-compressed" : @"gca", - @"application/x-glulx" : @"ulx", - @"application/x-gnumeric" : @"gnumeric", - @"application/x-gramps-xml" : @"gramps", - @"application/x-gsp" : @"gsp", - @"application/x-gss" : @"gss", - @"application/x-gtar" : @"gtar", - @"application/x-gzip" : @"gz", - @"application/x-hdf" : @"hdf", - @"application/x-httpd-imap" : @"imap", - @"application/x-ima" : @"ima", - @"application/x-install-instructions" : @"install", - @"application/x-internett-signup" : @"ins", - @"application/x-inventor" : @"iv", - @"application/x-ip2" : @"ip", - @"application/x-iphone" : @"iii", - @"application/x-iso9660-image" : @"iso", - @"application/x-java-class" : @"class", - @"application/x-java-commerce" : @"jcm", - @"application/x-java-jnlp-file" : @"jnlp", - @"application/x-javascript" : @"js", - @"application/x-ksh" : @"ksh", - @"application/x-latex" : @"ltx", - @"application/x-lha" : @"lha", - @"application/x-lisp" : @"lsp", - @"application/x-livescreen" : @"ivy", - @"application/x-lotus" : @"wq1", - @"application/x-lotusscreencam" : @"scm", - @"application/x-lzh" : @"lzh", - @"application/x-lzh-compressed" : @"lzh", - @"application/x-lzx" : @"lzx", - @"application/x-mac-binhex40" : @"hqx", - @"application/x-macbinary" : @"bin", - @"application/x-magic-cap-package-1.0" : @"mc$", - @"application/x-mathcad" : @"mcd", - @"application/x-meme" : @"mm", - @"application/x-midi" : @"midi", - @"application/x-mie" : @"mie", - @"application/x-mif" : @"mif", - @"application/x-mix-transfer" : @"nix", - @"application/x-mobipocket-ebook" : @"prc", - @"application/x-mplayer2" : @"asx", - @"application/x-ms-application" : @"application", - @"application/x-ms-shortcut" : @"lnk", - @"application/x-ms-wmd" : @"wmd", - @"application/x-ms-wmz" : @"wmz", - @"application/x-ms-xbap" : @"xbap", - @"application/x-msaccess" : @"mdb", - @"application/x-msbinder" : @"obd", - @"application/x-mscardfile" : @"crd", - @"application/x-msclip" : @"clp", - @"application/x-msdownload" : @"exe", - @"application/x-msexcel" : @"xls", - @"application/x-msmediaview" : @"mvb", - @"application/x-msmetafile" : @"wmf", - @"application/x-msmoney" : @"mny", - @"application/x-mspowerpoint" : @"ppt", - @"application/x-mspublisher" : @"pub", - @"application/x-msschedule" : @"scd", - @"application/x-msterminal" : @"trm", - @"application/x-mswrite" : @"wri", - @"application/x-navi-animation" : @"ani", - @"application/x-navidoc" : @"nvd", - @"application/x-navimap" : @"map", - @"application/x-navistyle" : @"stl", - @"application/x-netcdf" : @"nc", - @"application/x-newton-compatible-pkg" : @"pkg", - @"application/x-nokia-9000-communicator-add-on-software" : @"aos", - @"application/x-nzb" : @"nzb", - @"application/x-omc" : @"omc", - @"application/x-omcdatamaker" : @"omcd", - @"application/x-omcregerator" : @"omcr", - @"application/x-pcl" : @"pcl", - @"application/x-pixclscript" : @"plx", - @"application/x-pkcs10" : @"p10", - @"application/x-pkcs12" : @"p12", - @"application/x-pkcs7-certificates" : @"p7b", - @"application/x-pkcs7-certreqresp" : @"p7r", - @"application/x-pkcs7-mime" : @"p7m", - @"application/x-pkcs7-signature" : @"p7s", - @"application/x-pointplus" : @"css", - @"application/x-portable-anymap" : @"pnm", - @"application/x-qpro" : @"wb1", - @"application/x-rar-compressed" : @"rar", - @"application/x-research-info-systems" : @"ris", - @"application/x-rtf" : @"rtf", - @"application/x-sdp" : @"sdp", - @"application/x-sea" : @"sea", - @"application/x-seelogo" : @"sl", - @"application/x-sh" : @"sh", - @"application/x-shar" : @"shar", - @"application/x-shockwave-flash" : @"swf", - @"application/x-silverlight-app" : @"xap", - @"application/x-sit" : @"sit", - @"application/x-sprite" : @"spr", - @"application/x-sql" : @"sql", - @"application/x-stuffit" : @"sit", - @"application/x-stuffitx" : @"sitx", - @"application/x-subrip" : @"srt", - @"application/x-sv4cpio" : @"sv4cpio", - @"application/x-sv4crc" : @"sv4crc", - @"application/x-t3vm-image" : @"t3", - @"application/x-tads" : @"gam", - @"application/x-tar" : @"tar", - @"application/x-tbook" : @"tbk", - @"application/x-tcl" : @"tcl", - @"application/x-tex" : @"tex", - @"application/x-tex-tfm" : @"tfm", - @"application/x-texinfo" : @"texinfo", - @"application/x-tgif" : @"obj", - @"application/x-troff-man" : @"man", - @"application/x-troff-me" : @"me", - @"application/x-troff-ms" : @"ms", - @"application/x-troff-msvideo" : @"avi", - @"application/x-ustar" : @"ustar", - @"application/x-visio" : @"vsd", - @"application/x-vnd.audioexplosion.mzz" : @"mzz", - @"application/x-vnd.ls-xpix" : @"xpix", - @"application/x-vrml" : @"vrml", - @"application/x-wais-source" : @"src", - @"application/x-winhelp" : @"hlp", - @"application/x-wintalk" : @"wtk", - @"application/x-wpwin" : @"wpd", - @"application/x-wri" : @"wri", - @"application/x-x509-ca-cert" : @"crt", - @"application/x-x509-user-cert" : @"crt", - @"application/x-xfig" : @"fig", - @"application/x-xliff+xml" : @"xlf", - @"application/x-xpinstall" : @"xpi", - @"application/x-xz" : @"xz", - @"application/x-zip-compressed" : @"zip", - @"application/x-zmachine" : @"z1", - @"application/xaml+xml" : @"xaml", - @"application/xcap-diff+xml" : @"xdf", - @"application/xenc+xml" : @"xenc", - @"application/xhtml+xml" : @"xhtml", - @"application/xml" : @"xml", - @"application/xml-dtd" : @"dtd", - @"application/xop+xml" : @"xop", - @"application/xproc+xml" : @"xpl", - @"application/xslt+xml" : @"xslt", - @"application/xspf+xml" : @"xspf", - @"application/xv+xml" : @"mxml", - @"application/yang" : @"yang", - @"application/yin+xml" : @"yin", - @"application/ynd.ms-pkipko" : @"pko", - OWSMimeTypeApplicationZip : @"zip", - @"audio/aac" : @"aac", - @"audio/adpcm" : @"adp", - @"audio/aiff" : @"aiff", - @"audio/basic" : @"au", - @"audio/it" : @"it", - @"audio/mid" : @"rmi", - @"audio/midi" : @"midi", - @"audio/mod" : @"mod", - @"audio/mp4" : @"m4a", - @"audio/mpeg" : @"mpg", - @"audio/mpeg3" : @"mp3", - @"audio/ogg" : @"oga", - @"audio/s3m" : @"s3m", - @"audio/silk" : @"sil", - @"audio/tsp-audio" : @"tsi", - @"audio/tsplayer" : @"tsp", - @"audio/vnd.dece.audio" : @"uva", - @"audio/vnd.digital-winds" : @"eol", - @"audio/vnd.dra" : @"dra", - @"audio/vnd.dts" : @"dts", - @"audio/vnd.dts.hd" : @"dtshd", - @"audio/vnd.lucent.voice" : @"lvp", - @"audio/vnd.ms-playready.media.pya" : @"pya", - @"audio/vnd.nuera.ecelp4800" : @"ecelp4800", - @"audio/vnd.nuera.ecelp7470" : @"ecelp7470", - @"audio/vnd.nuera.ecelp9600" : @"ecelp9600", - @"audio/vnd.qcelp" : @"qcp", - @"audio/vnd.rip" : @"rip", - @"audio/voc" : @"voc", - @"audio/voxware" : @"vox", - @"audio/wav" : @"wav", - @"audio/webm" : @"weba", - @"audio/x-aac" : @"aac", - @"audio/x-adpcm" : @"snd", - @"audio/x-aiff" : @"aiff", - @"audio/x-au" : @"au", - @"audio/x-caf" : @"caf", - @"audio/x-flac" : @"flac", - @"audio/x-gsm" : @"gsm", - @"audio/x-jam" : @"jam", - @"audio/x-liveaudio" : @"lam", - @"audio/x-matroska" : @"mka", - @"audio/x-mid" : @"midi", - @"audio/x-midi" : @"midi", - @"audio/x-mod" : @"mod", - @"audio/x-mpeg" : @"mp2", - @"audio/x-mpeg-3" : @"mp3", - @"audio/x-mpegurl" : @"m3u", - @"audio/x-mpequrl" : @"m3u", - @"audio/x-ms-wax" : @"wax", - @"audio/x-ms-wma" : @"wma", - @"audio/x-pn-realaudio" : @"ram", - @"audio/x-pn-realaudio-plugin" : @"rmp", - @"audio/x-psid" : @"sid", - @"audio/x-realaudio" : @"ra", - @"audio/x-twinvq" : @"vqf", - @"audio/x-vnd.audioexplosion.mjuicemediafile" : @"mjf", - @"audio/x-voc" : @"voc", - @"audio/x-wav" : @"wav", - @"audio/xm" : @"xm", - @"chemical/x-cdx" : @"cdx", - @"chemical/x-cif" : @"cif", - @"chemical/x-cmdf" : @"cmdf", - @"chemical/x-cml" : @"cml", - @"chemical/x-csml" : @"csml", - @"chemical/x-pdb" : @"pdb", - @"chemical/x-xyz" : @"xyz", - @"drawing/x-dwf" : @"dwf", - @"font/ttf" : @"ttf", - @"font/woff" : @"woff", - @"font/woff2" : @"woff2", - @"i-world/i-vrml" : @"ivr", - @"image/bmp" : @"bmp", - @"image/cgm" : @"cgm", - @"image/cis-cod" : @"cod", - @"image/fif" : @"fif", - @"image/g3fax" : @"g3", - @"image/gif" : @"gif", - @"image/ief" : @"ief", - @"image/jpeg" : @"jpg", - @"image/jutvision" : @"jut", - @"image/ktx" : @"ktx", - @"image/pict" : @"pict", - @"image/pjpeg" : @"jpg", - @"image/png" : @"png", - @"image/prs.btif" : @"btif", - @"image/sgi" : @"sgi", - @"image/svg+xml" : @"svg", - @"image/tiff" : @"tiff", - @"image/vasa" : @"mcf", - @"image/vnd.adobe.photoshop" : @"psd", - @"image/vnd.dece.graphic" : @"uvi", - @"image/vnd.djvu" : @"djvu", - @"image/vnd.dvb.subtitle" : @"sub", - @"image/vnd.dwg" : @"dwg", - @"image/vnd.dxf" : @"dxf", - @"image/vnd.fastbidsheet" : @"fbs", - @"image/vnd.fpx" : @"fpx", - @"image/vnd.fst" : @"fst", - @"image/vnd.fujixerox.edmics-mmr" : @"mmr", - @"image/vnd.fujixerox.edmics-rlc" : @"rlc", - @"image/vnd.ms-modi" : @"mdi", - @"image/vnd.ms-photo" : @"wdp", - @"image/vnd.net-fpx" : @"fpx", - @"image/vnd.rn-realflash" : @"rf", - @"image/vnd.rn-realpix" : @"rp", - @"image/vnd.wap.wbmp" : @"wbmp", - @"image/vnd.xiff" : @"xif", - @"image/webp" : @"webp", - @"image/x-3ds" : @"3ds", - @"image/x-citrix-jpeg" : @"jpg", - @"image/x-citrix-png" : @"png", - @"image/x-cmu-raster" : @"ras", - @"image/x-cmx" : @"cmx", - @"image/x-dwg" : @"dwg", - @"image/x-freehand" : @"fh", - @"image/x-icon" : @"ico", - @"image/x-jg" : @"art", - @"image/x-jps" : @"jps", - @"image/x-mrsid-image" : @"sid", - @"image/x-niff" : @"niff", - @"image/x-pcx" : @"pcx", - @"image/x-pict" : @"pic", - @"image/x-png" : @"png", - @"image/x-portable-anymap" : @"pnm", - @"image/x-portable-bitmap" : @"pbm", - @"image/x-portable-graymap" : @"pgm", - @"image/x-portable-greymap" : @"pgm", - @"image/x-portable-pixmap" : @"ppm", - @"image/x-rgb" : @"rgb", - @"image/x-tga" : @"tga", - @"image/x-tiff" : @"tiff", - @"image/x-windows-bmp" : @"bmp", - @"image/x-xbitmap" : @"xbm", - @"image/x-xbm" : @"xbm", - @"image/x-xpixmap" : @"xpm", - @"image/x-xwd" : @"xwd", - @"image/x-xwindowdump" : @"xwd", - @"image/xbm" : @"xbm", - @"image/xpm" : @"xpm", - @"message/rfc822" : @"eml", - @"model/iges" : @"iges", - @"model/mesh" : @"msh", - @"model/vnd.collada+xml" : @"dae", - @"model/vnd.dwf" : @"dwf", - @"model/vnd.gdl" : @"gdl", - @"model/vnd.gtw" : @"gtw", - @"model/vnd.mts" : @"mts", - @"model/vnd.vtu" : @"vtu", - @"model/vrml" : @"vrml", - @"model/x-pov" : @"pov", - @"model/x3d+binary" : @"x3db", - @"model/x3d+vrml" : @"x3dv", - @"model/x3d+xml" : @"x3d", - @"multipart/x-gzip" : @"gzip", - @"multipart/x-ustar" : @"ustar", - @"multipart/x-zip" : @"zip", - @"music/x-karaoke" : @"kar", - @"paleovu/x-pv" : @"pvu", - @"text/asp" : @"asp", - @"text/cache-manifest" : @"appcache", - @"text/calendar" : @"ics", - @"text/css" : @"css", - @"text/csv" : @"csv", - @"text/ecmascript" : @"js", - @"text/h323" : @"323", - @"text/html" : @"html", - @"text/iuls" : @"uls", - @"text/java" : @"java", - @"text/javascript" : @"js", - @"text/mcf" : @"mcf", - @"text/n3" : @"n3", - @"text/pascal" : @"pas", - @"text/plain" : @"txt", - @"text/plain-bas" : @"par", - @"text/prs.lines.logTag" : @"dsc", - @"text/richtext" : @"rtf", - @"text/scriplet" : @"wsc", - @"text/scriptlet" : @"sct", - @"text/sgml" : @"sgml", - @"text/tab-separated-values" : @"tsv", - @"text/troff" : @"t", - @"text/turtle" : @"ttl", - @"text/uri-list" : @"uri", - @"text/vcard" : @"vcard", - @"text/vnd.abc" : @"abc", - @"text/vnd.curl" : @"curl", - @"text/vnd.curl.dcurl" : @"dcurl", - @"text/vnd.curl.mcurl" : @"mcurl", - @"text/vnd.curl.scurl" : @"scurl", - @"text/vnd.dvb.subtitle" : @"sub", - @"text/vnd.fly" : @"fly", - @"text/vnd.fmi.flexstor" : @"flx", - @"text/vnd.graphviz" : @"gv", - @"text/vnd.in3d.3dml" : @"3dml", - @"text/vnd.in3d.spot" : @"spot", - @"text/vnd.rn-realtext" : @"rt", - @"text/vnd.sun.j2me.app-descriptor" : @"jad", - @"text/vnd.wap.wml" : @"wml", - @"text/vnd.wap.wmlscript" : @"wmls", - @"text/webviewhtml" : @"htt", - @"text/x-asm" : @"asm", - @"text/x-audiosoft-intra" : @"aip", - @"text/x-c" : @"c", - @"text/x-component" : @"htc", - @"text/x-fortran" : @"f", - @"text/x-h" : @"h", - @"text/x-java-source" : @"java", - @"text/x-la-asf" : @"lsx", - @"text/x-m" : @"m", - @"text/x-nfo" : @"nfo", - @"text/x-opml" : @"opml", - @"text/x-pascal" : @"p", - @"text/x-script" : @"hlb", - @"text/x-script.csh" : @"csh", - @"text/x-script.elisp" : @"el", - @"text/x-script.guile" : @"scm", - @"text/x-script.ksh" : @"ksh", - @"text/x-script.lisp" : @"lsp", - @"text/x-script.perl" : @"pl", - @"text/x-script.perl-module" : @"pm", - @"text/x-script.phyton" : @"py", - @"text/x-script.rexx" : @"rexx", - @"text/x-script.scheme" : @"scm", - @"text/x-script.sh" : @"sh", - @"text/x-script.tcl" : @"tcl", - @"text/x-script.tcsh" : @"tcsh", - @"text/x-script.zsh" : @"zsh", - @"text/x-setext" : @"etx", - @"text/x-sfv" : @"sfv", - @"text/x-sgml" : @"sgml", - @"text/x-uil" : @"uil", - @"text/x-uuencode" : @"uu", - @"text/x-vcalendar" : @"vcs", - @"text/x-vcard" : @"vcf", - @"text/xml" : @"xml", - @"text/yaml" : @"yaml", - @"video/3gpp" : @"3gp", - @"video/3gpp2" : @"3g2", - @"video/animaflex" : @"afl", - @"video/avi" : @"avi", - @"video/avs-video" : @"avs", - @"video/dl" : @"dl", - @"video/fli" : @"fli", - @"video/gl" : @"gl", - @"video/h261" : @"h261", - @"video/h263" : @"h263", - @"video/h264" : @"h264", - @"video/jpeg" : @"jpgv", - @"video/jpm" : @"jpm", - @"video/mj2" : @"mj2", - @"video/mp4" : @"mp4", - @"video/mpeg" : @"mpg", - @"video/msvideo" : @"avi", - @"video/ogg" : @"ogv", - @"video/quicktime" : @"mov", - @"video/vdo" : @"vdo", - @"video/vnd.dece.hd" : @"uvh", - @"video/vnd.dece.mobile" : @"uvm", - @"video/vnd.dece.pd" : @"uvp", - @"video/vnd.dece.sd" : @"uvs", - @"video/vnd.dece.video" : @"uvv", - @"video/vnd.dvb.file" : @"dvb", - @"video/vnd.fvt" : @"fvt", - @"video/vnd.mpegurl" : @"mxu", - @"video/vnd.ms-playready.media.pyv" : @"pyv", - @"video/vnd.rn-realvideo" : @"rv", - @"video/vnd.uvvu.mp4" : @"uvu", - @"video/vnd.vivo" : @"viv", - @"video/vosaic" : @"vos", - @"video/webm" : @"webm", - @"video/x-amt-demorun" : @"xdr", - @"video/x-amt-showrun" : @"xsr", - @"video/x-atomic3d-feature" : @"fmf", - @"video/x-dl" : @"dl", - @"video/x-dv" : @"dv", - @"video/x-f4v" : @"f4v", - @"video/x-fli" : @"fli", - @"video/x-flv" : @"flv", - @"video/x-gl" : @"gl", - @"video/x-isvideo" : @"isu", - @"video/x-la-asf" : @"lsf", - @"video/x-m4v" : @"m4v", - @"video/x-matroska" : @"mkv", - @"video/x-mng" : @"mng", - @"video/x-motion-jpeg" : @"mjpg", - @"video/x-mpeg" : @"mpg", - @"video/x-mpeq2a" : @"mp2", - @"video/x-ms-asf" : @"asf", - @"video/x-ms-asf-plugin" : @"asx", - @"video/x-ms-vob" : @"vob", - @"video/x-ms-wm" : @"wm", - @"video/x-ms-wmv" : @"wmv", - @"video/x-ms-wmx" : @"wmx", - @"video/x-ms-wvx" : @"wvx", - @"video/x-msvideo" : @"avi", - @"video/x-qtc" : @"qtc", - @"video/x-scm" : @"scm", - @"video/x-sgi-movie" : @"movie", - @"video/x-smv" : @"smv", - @"windows/metafile" : @"wmf", - @"www/mime" : @"mime", - @"x-conference/x-cooltalk" : @"ice", - @"x-music/x-midi" : @"midi", - @"x-world/x-3dmf" : @"3dmf", - @"x-world/x-svr" : @"svr", - @"x-world/x-vrml" : @"vrml", - @"x-world/x-vrt" : @"vrt", - @"xgl/drawing" : @"xgz", - @"xgl/movie" : @"xmz", - }; - }); - return result; -} - -+ (nullable NSString *)mimeTypeForFileExtension:(NSString *)fileExtension -{ - OWSAssertDebug(fileExtension.length > 0); - - return [self genericExtensionTypesToMIMETypes][fileExtension]; -} - -+ (NSDictionary *)genericExtensionTypesToMIMETypes -{ - static NSDictionary *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - result = @{ - @"123" : @"application/vnd.lotus-1-2-3", - @"3dml" : @"text/vnd.in3d.3dml", - @"3ds" : @"image/x-3ds", - @"3g2" : @"video/3gpp2", - @"3gp" : @"video/3gpp", - @"7z" : @"application/x-7z-compressed", - @"aab" : @"application/x-authorware-bin", - @"aac" : @"audio/x-aac", - @"aam" : @"application/x-authorware-map", - @"aas" : @"application/x-authorware-seg", - @"abw" : @"application/x-abiword", - @"ac" : @"application/pkix-attr-cert", - @"acc" : @"application/vnd.americandynamics.acc", - @"ace" : @"application/x-ace-compressed", - @"acu" : @"application/vnd.acucobol", - @"acutc" : @"application/vnd.acucorp", - @"adp" : @"audio/adpcm", - @"aep" : @"application/vnd.audiograph", - @"afm" : @"application/x-font-type1", - @"afp" : @"application/vnd.ibm.modcap", - @"ahead" : @"application/vnd.ahead.space", - @"ai" : @"application/postscript", - @"aif" : @"audio/x-aiff", - @"aifc" : @"audio/x-aiff", - @"aiff" : @"audio/x-aiff", - @"air" : @"application/vnd.adobe.air-application-installer-package+zip", - @"ait" : @"application/vnd.dvb.ait", - @"ami" : @"application/vnd.amiga.ami", - @"apk" : @"application/vnd.android.package-archive", - @"appcache" : @"text/cache-manifest", - @"application" : @"application/x-ms-application", - @"apr" : @"application/vnd.lotus-approach", - @"arc" : @"application/x-freearc", - @"asc" : @"application/pgp-signature", - @"asf" : @"video/x-ms-asf", - @"asm" : @"text/x-asm", - @"aso" : @"application/vnd.accpac.simply.aso", - @"asx" : @"video/x-ms-asf", - @"atc" : @"application/vnd.acucorp", - @"atom" : @"application/atom+xml", - @"atomcat" : @"application/atomcat+xml", - @"atomsvc" : @"application/atomsvc+xml", - @"atx" : @"application/vnd.antix.game-component", - @"au" : @"audio/basic", - @"avi" : @"video/x-msvideo", - @"aw" : @"application/applixware", - @"azf" : @"application/vnd.airzip.filesecure.azf", - @"azs" : @"application/vnd.airzip.filesecure.azs", - @"azw" : @"application/vnd.amazon.ebook", - @"bat" : @"application/x-msdownload", - @"bcpio" : @"application/x-bcpio", - @"bdf" : @"application/x-font-bdf", - @"bdm" : @"application/vnd.syncml.dm+wbxml", - @"bed" : @"application/vnd.realvnc.bed", - @"bh2" : @"application/vnd.fujitsu.oasysprs", - @"bin" : @"application/octet-stream", - @"blb" : @"application/x-blorb", - @"blorb" : @"application/x-blorb", - @"bmi" : @"application/vnd.bmi", - @"bmp" : @"image/bmp", - @"book" : @"application/vnd.framemaker", - @"box" : @"application/vnd.previewsystems.box", - @"boz" : @"application/x-bzip2", - @"bpk" : @"application/octet-stream", - @"btif" : @"image/prs.btif", - @"bz" : @"application/x-bzip", - @"bz2" : @"application/x-bzip2", - @"c" : @"text/x-c", - @"c11amc" : @"application/vnd.cluetrust.cartomobile-config", - @"c11amz" : @"application/vnd.cluetrust.cartomobile-config-pkg", - @"c4d" : @"application/vnd.clonk.c4group", - @"c4f" : @"application/vnd.clonk.c4group", - @"c4g" : @"application/vnd.clonk.c4group", - @"c4p" : @"application/vnd.clonk.c4group", - @"c4u" : @"application/vnd.clonk.c4group", - @"cab" : @"application/vnd.ms-cab-compressed", - @"caf" : @"audio/x-caf", - @"cap" : @"application/vnd.tcpdump.pcap", - @"car" : @"application/vnd.curl.car", - @"cat" : @"application/vnd.ms-pki.seccat", - @"cb7" : @"application/x-cbr", - @"cba" : @"application/x-cbr", - @"cbr" : @"application/x-cbr", - @"cbt" : @"application/x-cbr", - @"cbz" : @"application/x-cbr", - @"cc" : @"text/x-c", - @"cct" : @"application/x-director", - @"ccxml" : @"application/ccxml+xml", - @"cdbcmsg" : @"application/vnd.contact.cmsg", - @"cdf" : @"application/x-netcdf", - @"cdkey" : @"application/vnd.mediastation.cdkey", - @"cdmia" : @"application/cdmi-capability", - @"cdmic" : @"application/cdmi-container", - @"cdmid" : @"application/cdmi-domain", - @"cdmio" : @"application/cdmi-object", - @"cdmiq" : @"application/cdmi-queue", - @"cdx" : @"chemical/x-cdx", - @"cdxml" : @"application/vnd.chemdraw+xml", - @"cdy" : @"application/vnd.cinderella", - @"cer" : @"application/pkix-cert", - @"cfs" : @"application/x-cfs-compressed", - @"cgm" : @"image/cgm", - @"chat" : @"application/x-chat", - @"chm" : @"application/vnd.ms-htmlhelp", - @"chrt" : @"application/vnd.kde.kchart", - @"cif" : @"chemical/x-cif", - @"cii" : @"application/vnd.anser-web-certificate-issue-initiation", - @"cil" : @"application/vnd.ms-artgalry", - @"cla" : @"application/vnd.claymore", - @"class" : @"application/java-vm", - @"clkk" : @"application/vnd.crick.clicker.keyboard", - @"clkp" : @"application/vnd.crick.clicker.palette", - @"clkt" : @"application/vnd.crick.clicker.template", - @"clkw" : @"application/vnd.crick.clicker.wordbank", - @"clkx" : @"application/vnd.crick.clicker", - @"clp" : @"application/x-msclip", - @"cmc" : @"application/vnd.cosmocaller", - @"cmdf" : @"chemical/x-cmdf", - @"cml" : @"chemical/x-cml", - @"cmp" : @"application/vnd.yellowriver-custom-menu", - @"cmx" : @"image/x-cmx", - @"cod" : @"application/vnd.rim.cod", - @"com" : @"application/x-msdownload", - @"conf" : @"text/plain", - @"cpio" : @"application/x-cpio", - @"cpp" : @"text/x-c", - @"cpt" : @"application/mac-compactpro", - @"crd" : @"application/x-mscardfile", - @"crl" : @"application/pkix-crl", - @"crt" : @"application/x-x509-ca-cert", - @"cryptonote" : @"application/vnd.rig.cryptonote", - @"csh" : @"application/x-csh", - @"csml" : @"chemical/x-csml", - @"csp" : @"application/vnd.commonspace", - @"css" : @"text/css", - @"cst" : @"application/x-director", - @"csv" : @"text/csv", - @"cu" : @"application/cu-seeme", - @"curl" : @"text/vnd.curl", - @"cww" : @"application/prs.cww", - @"cxt" : @"application/x-director", - @"cxx" : @"text/x-c", - @"dae" : @"model/vnd.collada+xml", - @"daf" : @"application/vnd.mobius.daf", - @"dart" : @"application/vnd.dart", - @"dataless" : @"application/vnd.fdsn.seed", - @"davmount" : @"application/davmount+xml", - @"dbk" : @"application/docbook+xml", - @"dcr" : @"application/x-director", - @"dcurl" : @"text/vnd.curl.dcurl", - @"dd2" : @"application/vnd.oma.dd2+xml", - @"ddd" : @"application/vnd.fujixerox.ddd", - @"deb" : @"application/x-debian-package", - @"def" : @"text/plain", - @"deploy" : @"application/octet-stream", - @"der" : @"application/x-x509-ca-cert", - @"dfac" : @"application/vnd.dreamfactory", - @"dgc" : @"application/x-dgc-compressed", - @"dic" : @"text/x-c", - @"dir" : @"application/x-director", - @"dis" : @"application/vnd.mobius.dis", - @"dist" : @"application/octet-stream", - @"distz" : @"application/octet-stream", - @"djv" : @"image/vnd.djvu", - @"djvu" : @"image/vnd.djvu", - @"dll" : @"application/x-msdownload", - @"dmg" : @"application/x-apple-diskimage", - @"dmp" : @"application/vnd.tcpdump.pcap", - @"dms" : @"application/octet-stream", - @"dna" : @"application/vnd.dna", - @"doc" : @"application/msword", - @"docm" : @"application/vnd.ms-word.document.macroenabled.12", - @"docx" : @"application/vnd.openxmlformats-officedocument.wordprocessingml.document", - @"dot" : @"application/msword", - @"dotm" : @"application/vnd.ms-word.template.macroenabled.12", - @"dotx" : @"application/vnd.openxmlformats-officedocument.wordprocessingml.template", - @"dp" : @"application/vnd.osgi.dp", - @"dpg" : @"application/vnd.dpgraph", - @"dra" : @"audio/vnd.dra", - @"dsc" : @"text/prs.lines.logTag", - @"dssc" : @"application/dssc+der", - @"dtb" : @"application/x-dtbook+xml", - @"dtd" : @"application/xml-dtd", - @"dts" : @"audio/vnd.dts", - @"dtshd" : @"audio/vnd.dts.hd", - @"dump" : @"application/octet-stream", - @"dvb" : @"video/vnd.dvb.file", - @"dvi" : @"application/x-dvi", - @"dwf" : @"model/vnd.dwf", - @"dwg" : @"image/vnd.dwg", - @"dxf" : @"image/vnd.dxf", - @"dxp" : @"application/vnd.spotfire.dxp", - @"dxr" : @"application/x-director", - @"ecelp4800" : @"audio/vnd.nuera.ecelp4800", - @"ecelp7470" : @"audio/vnd.nuera.ecelp7470", - @"ecelp9600" : @"audio/vnd.nuera.ecelp9600", - @"ecma" : @"application/ecmascript", - @"edm" : @"application/vnd.novadigm.edm", - @"edx" : @"application/vnd.novadigm.edx", - @"efif" : @"application/vnd.picsel", - @"ei6" : @"application/vnd.pg.osasli", - @"elc" : @"application/octet-stream", - @"emf" : @"application/x-msmetafile", - @"eml" : @"message/rfc822", - @"emma" : @"application/emma+xml", - @"emz" : @"application/x-msmetafile", - @"eol" : @"audio/vnd.digital-winds", - @"eot" : @"application/vnd.ms-fontobject", - @"eps" : @"application/postscript", - @"epub" : @"application/epub+zip", - @"es3" : @"application/vnd.eszigno3+xml", - @"esa" : @"application/vnd.osgi.subsystem", - @"esf" : @"application/vnd.epson.esf", - @"et3" : @"application/vnd.eszigno3+xml", - @"etx" : @"text/x-setext", - @"eva" : @"application/x-eva", - @"evy" : @"application/x-envoy", - @"exe" : @"application/x-msdownload", - @"exi" : @"application/exi", - @"ext" : @"application/vnd.novadigm.ext", - @"ez" : @"application/andrew-inset", - @"ez2" : @"application/vnd.ezpix-album", - @"ez3" : @"application/vnd.ezpix-package", - @"f" : @"text/x-fortran", - @"f4v" : @"video/x-f4v", - @"f77" : @"text/x-fortran", - @"f90" : @"text/x-fortran", - @"fbs" : @"image/vnd.fastbidsheet", - @"fcdt" : @"application/vnd.adobe.formscentral.fcdt", - @"fcs" : @"application/vnd.isac.fcs", - @"fdf" : @"application/vnd.fdf", - @"fe_launch" : @"application/vnd.denovo.fcselayout-link", - @"fg5" : @"application/vnd.fujitsu.oasysgp", - @"fgd" : @"application/x-director", - @"fh" : @"image/x-freehand", - @"fh4" : @"image/x-freehand", - @"fh5" : @"image/x-freehand", - @"fh7" : @"image/x-freehand", - @"fhc" : @"image/x-freehand", - @"fig" : @"application/x-xfig", - @"flac" : @"audio/x-flac", - @"fli" : @"video/x-fli", - @"flo" : @"application/vnd.micrografx.flo", - @"flv" : @"video/x-flv", - @"flw" : @"application/vnd.kde.kivio", - @"flx" : @"text/vnd.fmi.flexstor", - @"fly" : @"text/vnd.fly", - @"fm" : @"application/vnd.framemaker", - @"fnc" : @"application/vnd.frogans.fnc", - @"for" : @"text/x-fortran", - @"fpx" : @"image/vnd.fpx", - @"frame" : @"application/vnd.framemaker", - @"fsc" : @"application/vnd.fsc.weblaunch", - @"fst" : @"image/vnd.fst", - @"ftc" : @"application/vnd.fluxtime.clip", - @"fti" : @"application/vnd.anser-web-funds-transfer-initiation", - @"fvt" : @"video/vnd.fvt", - @"fxp" : @"application/vnd.adobe.fxp", - @"fxpl" : @"application/vnd.adobe.fxp", - @"fzs" : @"application/vnd.fuzzysheet", - @"g2w" : @"application/vnd.geoplan", - @"g3" : @"image/g3fax", - @"g3w" : @"application/vnd.geospace", - @"gac" : @"application/vnd.groove-account", - @"gam" : @"application/x-tads", - @"gbr" : @"application/rpki-ghostbusters", - @"gca" : @"application/x-gca-compressed", - @"gdl" : @"model/vnd.gdl", - @"geo" : @"application/vnd.dynageo", - @"gex" : @"application/vnd.geometry-explorer", - @"ggb" : @"application/vnd.geogebra.file", - @"ggt" : @"application/vnd.geogebra.tool", - @"ghf" : @"application/vnd.groove-help", - @"gif" : @"image/gif", - @"gim" : @"application/vnd.groove-identity-message", - @"gml" : @"application/gml+xml", - @"gmx" : @"application/vnd.gmx", - @"gnumeric" : @"application/x-gnumeric", - @"gph" : @"application/vnd.flographit", - @"gpx" : @"application/gpx+xml", - @"gqf" : @"application/vnd.grafeq", - @"gqs" : @"application/vnd.grafeq", - @"gram" : @"application/srgs", - @"gramps" : @"application/x-gramps-xml", - @"gre" : @"application/vnd.geometry-explorer", - @"grv" : @"application/vnd.groove-injector", - @"grxml" : @"application/srgs+xml", - @"gsf" : @"application/x-font-ghostscript", - @"gtar" : @"application/x-gtar", - @"gtm" : @"application/vnd.groove-tool-message", - @"gtw" : @"model/vnd.gtw", - @"gv" : @"text/vnd.graphviz", - @"gxf" : @"application/gxf", - @"gxt" : @"application/vnd.geonext", - @"h" : @"text/x-c", - @"h261" : @"video/h261", - @"h263" : @"video/h263", - @"h264" : @"video/h264", - @"hal" : @"application/vnd.hal+xml", - @"hbci" : @"application/vnd.hbci", - @"hdf" : @"application/x-hdf", - @"hh" : @"text/x-c", - @"hlp" : @"application/winhlp", - @"hpgl" : @"application/vnd.hp-hpgl", - @"hpid" : @"application/vnd.hp-hpid", - @"hps" : @"application/vnd.hp-hps", - @"hqx" : @"application/mac-binhex40", - @"htke" : @"application/vnd.kenameaapp", - @"htm" : @"text/html", - @"html" : @"text/html", - @"hvd" : @"application/vnd.yamaha.hv-dic", - @"hvp" : @"application/vnd.yamaha.hv-voice", - @"hvs" : @"application/vnd.yamaha.hv-script", - @"i2g" : @"application/vnd.intergeo", - @"icc" : @"application/vnd.iccprofile", - @"ice" : @"x-conference/x-cooltalk", - @"icm" : @"application/vnd.iccprofile", - @"ico" : @"image/x-icon", - @"ics" : @"text/calendar", - @"ief" : @"image/ief", - @"ifb" : @"text/calendar", - @"ifm" : @"application/vnd.shana.informed.formdata", - @"iges" : @"model/iges", - @"igl" : @"application/vnd.igloader", - @"igm" : @"application/vnd.insors.igm", - @"igs" : @"model/iges", - @"igx" : @"application/vnd.micrografx.igx", - @"iif" : @"application/vnd.shana.informed.interchange", - @"imp" : @"application/vnd.accpac.simply.imp", - @"ims" : @"application/vnd.ms-ims", - @"in" : @"text/plain", - @"ink" : @"application/inkml+xml", - @"inkml" : @"application/inkml+xml", - @"install" : @"application/x-install-instructions", - @"iota" : @"application/vnd.astraea-software.iota", - @"ipfix" : @"application/ipfix", - @"ipk" : @"application/vnd.shana.informed.package", - @"irm" : @"application/vnd.ibm.rights-management", - @"irp" : @"application/vnd.irepository.package+xml", - @"iso" : @"application/x-iso9660-image", - @"itp" : @"application/vnd.shana.informed.formtemplate", - @"ivp" : @"application/vnd.immervision-ivp", - @"ivu" : @"application/vnd.immervision-ivu", - @"jad" : @"text/vnd.sun.j2me.app-descriptor", - @"jam" : @"application/vnd.jam", - @"jar" : @"application/java-archive", - @"java" : @"text/x-java-source", - @"jisp" : @"application/vnd.jisp", - @"jlt" : @"application/vnd.hp-jlyt", - @"jnlp" : @"application/x-java-jnlp-file", - @"joda" : @"application/vnd.joost.joda-archive", - @"jpe" : @"image/jpeg", - @"jpeg" : @"image/jpeg", - @"jpg" : @"image/jpeg", - @"jpgm" : @"video/jpm", - @"jpgv" : @"video/jpeg", - @"jpm" : @"video/jpm", - @"js" : @"application/javascript", - @"json" : @"application/json", - @"jsonml" : @"application/jsonml+json", - @"kar" : @"audio/midi", - @"karbon" : @"application/vnd.kde.karbon", - @"kfo" : @"application/vnd.kde.kformula", - @"kia" : @"application/vnd.kidspiration", - @"kml" : @"application/vnd.google-earth.kml+xml", - @"kmz" : @"application/vnd.google-earth.kmz", - @"kne" : @"application/vnd.kinar", - @"knp" : @"application/vnd.kinar", - @"kon" : @"application/vnd.kde.kontour", - @"kpr" : @"application/vnd.kde.kpresenter", - @"kpt" : @"application/vnd.kde.kpresenter", - @"kpxx" : @"application/vnd.ds-keypoint", - @"ksp" : @"application/vnd.kde.kspread", - @"ktr" : @"application/vnd.kahootz", - @"ktx" : @"image/ktx", - @"ktz" : @"application/vnd.kahootz", - @"kwd" : @"application/vnd.kde.kword", - @"kwt" : @"application/vnd.kde.kword", - @"lasxml" : @"application/vnd.las.las+xml", - @"latex" : @"application/x-latex", - @"lbd" : @"application/vnd.llamagraphics.life-balance.desktop", - @"lbe" : @"application/vnd.llamagraphics.life-balance.exchange+xml", - @"les" : @"application/vnd.hhe.lesson-player", - @"lha" : @"application/x-lzh-compressed", - @"link66" : @"application/vnd.route66.link66+xml", - @"list" : @"text/plain", - @"list3820" : @"application/vnd.ibm.modcap", - @"listafp" : @"application/vnd.ibm.modcap", - @"lnk" : @"application/x-ms-shortcut", - @"log" : @"text/plain", - @"lostxml" : @"application/lost+xml", - @"lrf" : @"application/octet-stream", - @"lrm" : @"application/vnd.ms-lrm", - @"ltf" : @"application/vnd.frogans.ltf", - @"lvp" : @"audio/vnd.lucent.voice", - @"lwp" : @"application/vnd.lotus-wordpro", - @"lzh" : @"application/x-lzh-compressed", - @"m13" : @"application/x-msmediaview", - @"m14" : @"application/x-msmediaview", - @"m1v" : @"video/mpeg", - @"m21" : @"application/mp21", - @"m2a" : @"audio/mpeg", - @"m2v" : @"video/mpeg", - @"m3a" : @"audio/mpeg", - @"m3u" : @"audio/x-mpegurl", - @"m3u8" : @"application/vnd.apple.mpegurl", - @"m4a" : @"audio/mp4", - @"m4u" : @"video/vnd.mpegurl", - @"m4v" : @"video/x-m4v", - @"ma" : @"application/mathematica", - @"mads" : @"application/mads+xml", - @"mag" : @"application/vnd.ecowin.chart", - @"maker" : @"application/vnd.framemaker", - @"man" : @"text/troff", - @"mar" : @"application/octet-stream", - @"mathml" : @"application/mathml+xml", - @"mb" : @"application/mathematica", - @"mbk" : @"application/vnd.mobius.mbk", - @"mbox" : @"application/mbox", - @"mc1" : @"application/vnd.medcalcdata", - @"mcd" : @"application/vnd.mcd", - @"mcurl" : @"text/vnd.curl.mcurl", - @"mdb" : @"application/x-msaccess", - @"mdi" : @"image/vnd.ms-modi", - @"me" : @"text/troff", - @"mesh" : @"model/mesh", - @"meta4" : @"application/metalink4+xml", - @"metalink" : @"application/metalink+xml", - @"mets" : @"application/mets+xml", - @"mfm" : @"application/vnd.mfmp", - @"mft" : @"application/rpki-manifest", - @"mgp" : @"application/vnd.osgeo.mapguide.package", - @"mgz" : @"application/vnd.proteus.magazine", - @"mid" : @"audio/midi", - @"midi" : @"audio/midi", - @"mie" : @"application/x-mie", - @"mif" : @"application/vnd.mif", - @"mime" : @"message/rfc822", - @"mj2" : @"video/mj2", - @"mjp2" : @"video/mj2", - @"mk3d" : @"video/x-matroska", - @"mka" : @"audio/x-matroska", - @"mks" : @"video/x-matroska", - @"mkv" : @"video/x-matroska", - @"mlp" : @"application/vnd.dolby.mlp", - @"mmd" : @"application/vnd.chipnuts.karaoke-mmd", - @"mmf" : @"application/vnd.smaf", - @"mmr" : @"image/vnd.fujixerox.edmics-mmr", - @"mng" : @"video/x-mng", - @"mny" : @"application/x-msmoney", - @"mobi" : @"application/x-mobipocket-ebook", - @"mods" : @"application/mods+xml", - @"mov" : @"video/quicktime", - @"movie" : @"video/x-sgi-movie", - @"mp2" : @"audio/mpeg", - @"mp21" : @"application/mp21", - @"mp2a" : @"audio/mpeg", - @"mp3" : @"audio/mpeg", - @"mp4" : @"video/mp4", - @"mp4a" : @"audio/mp4", - @"mp4s" : @"application/mp4", - @"mp4v" : @"video/mp4", - @"mpc" : @"application/vnd.mophun.certificate", - @"mpe" : @"video/mpeg", - @"mpeg" : @"video/mpeg", - @"mpg" : @"video/mpeg", - @"mpg4" : @"video/mp4", - @"mpga" : @"audio/mpeg", - @"mpkg" : @"application/vnd.apple.installer+xml", - @"mpm" : @"application/vnd.blueice.multipass", - @"mpn" : @"application/vnd.mophun.application", - @"mpp" : @"application/vnd.ms-project", - @"mpt" : @"application/vnd.ms-project", - @"mpy" : @"application/vnd.ibm.minipay", - @"mqy" : @"application/vnd.mobius.mqy", - @"mrc" : @"application/marc", - @"mrcx" : @"application/marcxml+xml", - @"ms" : @"text/troff", - @"mscml" : @"application/mediaservercontrol+xml", - @"mseed" : @"application/vnd.fdsn.mseed", - @"mseq" : @"application/vnd.mseq", - @"msf" : @"application/vnd.epson.msf", - @"msh" : @"model/mesh", - @"msi" : @"application/x-msdownload", - @"msl" : @"application/vnd.mobius.msl", - @"msty" : @"application/vnd.muvee.style", - @"mts" : @"model/vnd.mts", - @"mus" : @"application/vnd.musician", - @"musicxml" : @"application/vnd.recordare.musicxml+xml", - @"mvb" : @"application/x-msmediaview", - @"mwf" : @"application/vnd.mfer", - @"mxf" : @"application/mxf", - @"mxl" : @"application/vnd.recordare.musicxml", - @"mxml" : @"application/xv+xml", - @"mxs" : @"application/vnd.triscape.mxs", - @"mxu" : @"video/vnd.mpegurl", - @"n-gage" : @"application/vnd.nokia.n-gage.symbian.install", - @"n3" : @"text/n3", - @"nb" : @"application/mathematica", - @"nbp" : @"application/vnd.wolfram.player", - @"nc" : @"application/x-netcdf", - @"ncx" : @"application/x-dtbncx+xml", - @"nfo" : @"text/x-nfo", - @"ngdat" : @"application/vnd.nokia.n-gage.data", - @"nitf" : @"application/vnd.nitf", - @"nlu" : @"application/vnd.neurolanguage.nlu", - @"nml" : @"application/vnd.enliven", - @"nnd" : @"application/vnd.noblenet-directory", - @"nns" : @"application/vnd.noblenet-sealer", - @"nnw" : @"application/vnd.noblenet-web", - @"npx" : @"image/vnd.net-fpx", - @"nsc" : @"application/x-conference", - @"nsf" : @"application/vnd.lotus-notes", - @"ntf" : @"application/vnd.nitf", - @"nzb" : @"application/x-nzb", - @"oa2" : @"application/vnd.fujitsu.oasys2", - @"oa3" : @"application/vnd.fujitsu.oasys3", - @"oas" : @"application/vnd.fujitsu.oasys", - @"obd" : @"application/x-msbinder", - @"obj" : @"application/x-tgif", - @"oda" : @"application/oda", - @"odb" : @"application/vnd.oasis.opendocument.database", - @"odc" : @"application/vnd.oasis.opendocument.chart", - @"odf" : @"application/vnd.oasis.opendocument.formula", - @"odft" : @"application/vnd.oasis.opendocument.formula-template", - @"odg" : @"application/vnd.oasis.opendocument.graphics", - @"odi" : @"application/vnd.oasis.opendocument.image", - @"odm" : @"application/vnd.oasis.opendocument.text-master", - @"odp" : @"application/vnd.oasis.opendocument.presentation", - @"ods" : @"application/vnd.oasis.opendocument.spreadsheet", - @"odt" : @"application/vnd.oasis.opendocument.text", - @"oga" : @"audio/ogg", - @"ogg" : @"audio/ogg", - @"ogv" : @"video/ogg", - @"ogx" : @"application/ogg", - @"omdoc" : @"application/omdoc+xml", - @"onepkg" : @"application/onenote", - @"onetmp" : @"application/onenote", - @"onetoc" : @"application/onenote", - @"onetoc2" : @"application/onenote", - @"opf" : @"application/oebps-package+xml", - @"opml" : @"text/x-opml", - @"oprc" : @"application/vnd.palm", - @"org" : @"application/vnd.lotus-organizer", - @"osf" : @"application/vnd.yamaha.openscoreformat", - @"osfpvg" : @"application/vnd.yamaha.openscoreformat.osfpvg+xml", - @"otc" : @"application/vnd.oasis.opendocument.chart-template", - @"otf" : @"application/x-font-otf", - @"otg" : @"application/vnd.oasis.opendocument.graphics-template", - @"oth" : @"application/vnd.oasis.opendocument.text-web", - @"oti" : @"application/vnd.oasis.opendocument.image-template", - @"otp" : @"application/vnd.oasis.opendocument.presentation-template", - @"ots" : @"application/vnd.oasis.opendocument.spreadsheet-template", - @"ott" : @"application/vnd.oasis.opendocument.text-template", - @"oxps" : @"application/oxps", - @"oxt" : @"application/vnd.openofficeorg.extension", - @"p" : @"text/x-pascal", - @"p10" : @"application/pkcs10", - @"p12" : @"application/x-pkcs12", - @"p7b" : @"application/x-pkcs7-certificates", - @"p7c" : @"application/pkcs7-mime", - @"p7m" : @"application/pkcs7-mime", - @"p7r" : @"application/x-pkcs7-certreqresp", - @"p7s" : @"application/pkcs7-signature", - @"p8" : @"application/pkcs8", - @"pas" : @"text/x-pascal", - @"paw" : @"application/vnd.pawaafile", - @"pbd" : @"application/vnd.powerbuilder6", - @"pbm" : @"image/x-portable-bitmap", - @"pcap" : @"application/vnd.tcpdump.pcap", - @"pcf" : @"application/x-font-pcf", - @"pcl" : @"application/vnd.hp-pcl", - @"pclxl" : @"application/vnd.hp-pclxl", - @"pct" : @"image/x-pict", - @"pcurl" : @"application/vnd.curl.pcurl", - @"pcx" : @"image/x-pcx", - @"pdb" : @"application/vnd.palm", - @"pdf" : @"application/pdf", - @"pfa" : @"application/x-font-type1", - @"pfb" : @"application/x-font-type1", - @"pfm" : @"application/x-font-type1", - @"pfr" : @"application/font-tdpfr", - @"pfx" : @"application/x-pkcs12", - @"pgm" : @"image/x-portable-graymap", - @"pgn" : @"application/x-chess-pgn", - @"pgp" : @"application/pgp-encrypted", - @"pic" : @"image/x-pict", - @"pkg" : @"application/octet-stream", - @"pki" : @"application/pkixcmp", - @"pkipath" : @"application/pkix-pkipath", - @"plb" : @"application/vnd.3gpp.pic-bw-large", - @"plc" : @"application/vnd.mobius.plc", - @"plf" : @"application/vnd.pocketlearn", - @"pls" : @"application/pls+xml", - @"pml" : @"application/vnd.ctc-posml", - @"png" : @"image/png", - @"pnm" : @"image/x-portable-anymap", - @"portpkg" : @"application/vnd.macports.portpkg", - @"pot" : @"application/vnd.ms-powerpoint", - @"potm" : @"application/vnd.ms-powerpoint.template.macroenabled.12", - @"potx" : @"application/vnd.openxmlformats-officedocument.presentationml.template", - @"ppam" : @"application/vnd.ms-powerpoint.addin.macroenabled.12", - @"ppd" : @"application/vnd.cups-ppd", - @"ppm" : @"image/x-portable-pixmap", - @"pps" : @"application/vnd.ms-powerpoint", - @"ppsm" : @"application/vnd.ms-powerpoint.slideshow.macroenabled.12", - @"ppsx" : @"application/vnd.openxmlformats-officedocument.presentationml.slideshow", - @"ppt" : @"application/vnd.ms-powerpoint", - @"pptm" : @"application/vnd.ms-powerpoint.presentation.macroenabled.12", - @"pptx" : @"application/vnd.openxmlformats-officedocument.presentationml.presentation", - @"pqa" : @"application/vnd.palm", - @"prc" : @"application/x-mobipocket-ebook", - @"pre" : @"application/vnd.lotus-freelance", - @"prf" : @"application/pics-rules", - @"ps" : @"application/postscript", - @"psb" : @"application/vnd.3gpp.pic-bw-small", - @"psd" : @"image/vnd.adobe.photoshop", - @"psf" : @"application/x-font-linux-psf", - @"pskcxml" : @"application/pskc+xml", - @"ptid" : @"application/vnd.pvi.ptid1", - @"pub" : @"application/x-mspublisher", - @"pvb" : @"application/vnd.3gpp.pic-bw-var", - @"pwn" : @"application/vnd.3m.post-it-notes", - @"pya" : @"audio/vnd.ms-playready.media.pya", - @"pyv" : @"video/vnd.ms-playready.media.pyv", - @"qam" : @"application/vnd.epson.quickanime", - @"qbo" : @"application/vnd.intu.qbo", - @"qfx" : @"application/vnd.intu.qfx", - @"qps" : @"application/vnd.publishare-delta-tree", - @"qt" : @"video/quicktime", - @"qwd" : @"application/vnd.quark.quarkxpress", - @"qwt" : @"application/vnd.quark.quarkxpress", - @"qxb" : @"application/vnd.quark.quarkxpress", - @"qxd" : @"application/vnd.quark.quarkxpress", - @"qxl" : @"application/vnd.quark.quarkxpress", - @"qxt" : @"application/vnd.quark.quarkxpress", - @"ra" : @"audio/x-pn-realaudio", - @"ram" : @"audio/x-pn-realaudio", - @"rar" : @"application/x-rar-compressed", - @"ras" : @"image/x-cmu-raster", - @"rcprofile" : @"application/vnd.ipunplugged.rcprofile", - @"rdf" : @"application/rdf+xml", - @"rdz" : @"application/vnd.data-vision.rdz", - @"rep" : @"application/vnd.businessobjects", - @"res" : @"application/x-dtbresource+xml", - @"rgb" : @"image/x-rgb", - @"rif" : @"application/reginfo+xml", - @"rip" : @"audio/vnd.rip", - @"ris" : @"application/x-research-info-systems", - @"rl" : @"application/resource-lists+xml", - @"rlc" : @"image/vnd.fujixerox.edmics-rlc", - @"rld" : @"application/resource-lists-diff+xml", - @"rm" : @"application/vnd.rn-realmedia", - @"rmi" : @"audio/midi", - @"rmp" : @"audio/x-pn-realaudio-plugin", - @"rms" : @"application/vnd.jcp.javame.midlet-rms", - @"rmvb" : @"application/vnd.rn-realmedia-vbr", - @"rnc" : @"application/relax-ng-compact-syntax", - @"roa" : @"application/rpki-roa", - @"roff" : @"text/troff", - @"rp9" : @"application/vnd.cloanto.rp9", - @"rpss" : @"application/vnd.nokia.radio-presets", - @"rpst" : @"application/vnd.nokia.radio-preset", - @"rq" : @"application/sparql-query", - @"rs" : @"application/rls-services+xml", - @"rsd" : @"application/rsd+xml", - @"rss" : @"application/rss+xml", - @"rtf" : @"application/rtf", - @"rtx" : @"text/richtext", - @"s" : @"text/x-asm", - @"s3m" : @"audio/s3m", - @"saf" : @"application/vnd.yamaha.smaf-audio", - @"sbml" : @"application/sbml+xml", - @"sc" : @"application/vnd.ibm.secure-container", - @"scd" : @"application/x-msschedule", - @"scm" : @"application/vnd.lotus-screencam", - @"scq" : @"application/scvp-cv-request", - @"scs" : @"application/scvp-cv-response", - @"scurl" : @"text/vnd.curl.scurl", - @"sda" : @"application/vnd.stardivision.draw", - @"sdc" : @"application/vnd.stardivision.calc", - @"sdd" : @"application/vnd.stardivision.impress", - @"sdkd" : @"application/vnd.solent.sdkm+xml", - @"sdkm" : @"application/vnd.solent.sdkm+xml", - @"sdp" : @"application/sdp", - @"sdw" : @"application/vnd.stardivision.writer", - @"see" : @"application/vnd.seemail", - @"seed" : @"application/vnd.fdsn.seed", - @"sema" : @"application/vnd.sema", - @"semd" : @"application/vnd.semd", - @"semf" : @"application/vnd.semf", - @"ser" : @"application/java-serialized-object", - @"setpay" : @"application/set-payment-initiation", - @"setreg" : @"application/set-registration-initiation", - @"sfd-hdstx" : @"application/vnd.hydrostatix.sof-data", - @"sfs" : @"application/vnd.spotfire.sfs", - @"sfv" : @"text/x-sfv", - @"sgi" : @"image/sgi", - @"sgl" : @"application/vnd.stardivision.writer-global", - @"sgm" : @"text/sgml", - @"sgml" : @"text/sgml", - @"sh" : @"application/x-sh", - @"shar" : @"application/x-shar", - @"shf" : @"application/shf+xml", - @"sid" : @"image/x-mrsid-image", - @"sig" : @"application/pgp-signature", - @"sil" : @"audio/silk", - @"silo" : @"model/mesh", - @"sis" : @"application/vnd.symbian.install", - @"sisx" : @"application/vnd.symbian.install", - @"sit" : @"application/x-stuffit", - @"sitx" : @"application/x-stuffitx", - @"skd" : @"application/vnd.koan", - @"skm" : @"application/vnd.koan", - @"skp" : @"application/vnd.koan", - @"skt" : @"application/vnd.koan", - @"sldm" : @"application/vnd.ms-powerpoint.slide.macroenabled.12", - @"sldx" : @"application/vnd.openxmlformats-officedocument.presentationml.slide", - @"slt" : @"application/vnd.epson.salt", - @"sm" : @"application/vnd.stepmania.stepchart", - @"smf" : @"application/vnd.stardivision.math", - @"smi" : @"application/smil+xml", - @"smil" : @"application/smil+xml", - @"smv" : @"video/x-smv", - @"smzip" : @"application/vnd.stepmania.package", - @"snd" : @"audio/basic", - @"snf" : @"application/x-font-snf", - @"so" : @"application/octet-stream", - @"spc" : @"application/x-pkcs7-certificates", - @"spf" : @"application/vnd.yamaha.smaf-phrase", - @"spl" : @"application/x-futuresplash", - @"spot" : @"text/vnd.in3d.spot", - @"spp" : @"application/scvp-vp-response", - @"spq" : @"application/scvp-vp-request", - @"spx" : @"audio/ogg", - @"sql" : @"application/x-sql", - @"src" : @"application/x-wais-source", - @"srt" : @"application/x-subrip", - @"sru" : @"application/sru+xml", - @"srx" : @"application/sparql-results+xml", - @"ssdl" : @"application/ssdl+xml", - @"sse" : @"application/vnd.kodak-descriptor", - @"ssf" : @"application/vnd.epson.ssf", - @"ssml" : @"application/ssml+xml", - @"st" : @"application/vnd.sailingtracker.track", - @"stc" : @"application/vnd.sun.xml.calc.template", - @"std" : @"application/vnd.sun.xml.draw.template", - @"stf" : @"application/vnd.wt.stf", - @"sti" : @"application/vnd.sun.xml.impress.template", - @"stk" : @"application/hyperstudio", - @"stl" : @"application/vnd.ms-pki.stl", - @"str" : @"application/vnd.pg.format", - @"stw" : @"application/vnd.sun.xml.writer.template", - @"sub" : @"text/vnd.dvb.subtitle", - @"sus" : @"application/vnd.sus-calendar", - @"susp" : @"application/vnd.sus-calendar", - @"sv4cpio" : @"application/x-sv4cpio", - @"sv4crc" : @"application/x-sv4crc", - @"svc" : @"application/vnd.dvb.service", - @"svd" : @"application/vnd.svd", - @"svg" : @"image/svg+xml", - @"svgz" : @"image/svg+xml", - @"swa" : @"application/x-director", - @"swf" : @"application/x-shockwave-flash", - @"swi" : @"application/vnd.aristanetworks.swi", - @"sxc" : @"application/vnd.sun.xml.calc", - @"sxd" : @"application/vnd.sun.xml.draw", - @"sxg" : @"application/vnd.sun.xml.writer.global", - @"sxi" : @"application/vnd.sun.xml.impress", - @"sxm" : @"application/vnd.sun.xml.math", - @"sxw" : @"application/vnd.sun.xml.writer", - @"t" : @"text/troff", - @"t3" : @"application/x-t3vm-image", - @"taglet" : @"application/vnd.mynfc", - @"tao" : @"application/vnd.tao.intent-module-archive", - @"tar" : @"application/x-tar", - @"tcap" : @"application/vnd.3gpp2.tcap", - @"tcl" : @"application/x-tcl", - @"teacher" : @"application/vnd.smart.teacher", - @"tei" : @"application/tei+xml", - @"teicorpus" : @"application/tei+xml", - @"tex" : @"application/x-tex", - @"texi" : @"application/x-texinfo", - @"texinfo" : @"application/x-texinfo", - @"text" : @"text/plain", - @"tfi" : @"application/thraud+xml", - @"tfm" : @"application/x-tex-tfm", - @"tga" : @"image/x-tga", - @"thmx" : @"application/vnd.ms-officetheme", - @"tif" : @"image/tiff", - @"tiff" : @"image/tiff", - @"tmo" : @"application/vnd.tmobile-livetv", - @"torrent" : @"application/x-bittorrent", - @"tpl" : @"application/vnd.groove-tool-template", - @"tpt" : @"application/vnd.trid.tpt", - @"tr" : @"text/troff", - @"tra" : @"application/vnd.trueapp", - @"trm" : @"application/x-msterminal", - @"tsd" : @"application/timestamped-data", - @"tsv" : @"text/tab-separated-values", - @"ttc" : @"application/x-font-ttf", - @"ttf" : @"application/x-font-ttf", - @"ttl" : @"text/turtle", - @"twd" : @"application/vnd.simtech-mindmapper", - @"twds" : @"application/vnd.simtech-mindmapper", - @"txd" : @"application/vnd.genomatix.tuxedo", - @"txf" : @"application/vnd.mobius.txf", - @"txt" : @"text/plain", - @"u32" : @"application/x-authorware-bin", - @"udeb" : @"application/x-debian-package", - @"ufd" : @"application/vnd.ufdl", - @"ufdl" : @"application/vnd.ufdl", - @"ulx" : @"application/x-glulx", - @"umj" : @"application/vnd.umajin", - @"unityweb" : @"application/vnd.unity", - @"uoml" : @"application/vnd.uoml+xml", - @"uri" : @"text/uri-list", - @"uris" : @"text/uri-list", - @"urls" : @"text/uri-list", - @"ustar" : @"application/x-ustar", - @"utz" : @"application/vnd.uiq.theme", - @"uu" : @"text/x-uuencode", - @"uva" : @"audio/vnd.dece.audio", - @"uvd" : @"application/vnd.dece.data", - @"uvf" : @"application/vnd.dece.data", - @"uvg" : @"image/vnd.dece.graphic", - @"uvh" : @"video/vnd.dece.hd", - @"uvi" : @"image/vnd.dece.graphic", - @"uvm" : @"video/vnd.dece.mobile", - @"uvp" : @"video/vnd.dece.pd", - @"uvs" : @"video/vnd.dece.sd", - @"uvt" : @"application/vnd.dece.ttml+xml", - @"uvu" : @"video/vnd.uvvu.mp4", - @"uvv" : @"video/vnd.dece.video", - @"uvva" : @"audio/vnd.dece.audio", - @"uvvd" : @"application/vnd.dece.data", - @"uvvf" : @"application/vnd.dece.data", - @"uvvg" : @"image/vnd.dece.graphic", - @"uvvh" : @"video/vnd.dece.hd", - @"uvvi" : @"image/vnd.dece.graphic", - @"uvvm" : @"video/vnd.dece.mobile", - @"uvvp" : @"video/vnd.dece.pd", - @"uvvs" : @"video/vnd.dece.sd", - @"uvvt" : @"application/vnd.dece.ttml+xml", - @"uvvu" : @"video/vnd.uvvu.mp4", - @"uvvv" : @"video/vnd.dece.video", - @"uvvx" : @"application/vnd.dece.unspecified", - @"uvvz" : @"application/vnd.dece.zip", - @"uvx" : @"application/vnd.dece.unspecified", - @"uvz" : @"application/vnd.dece.zip", - @"vcard" : @"text/vcard", - @"vcd" : @"application/x-cdlink", - @"vcf" : @"text/x-vcard", - @"vcg" : @"application/vnd.groove-vcard", - @"vcs" : @"text/x-vcalendar", - @"vcx" : @"application/vnd.vcx", - @"vis" : @"application/vnd.visionary", - @"viv" : @"video/vnd.vivo", - @"vob" : @"video/x-ms-vob", - @"vor" : @"application/vnd.stardivision.writer", - @"vox" : @"application/x-authorware-bin", - @"vrml" : @"model/vrml", - @"vsd" : @"application/vnd.visio", - @"vsf" : @"application/vnd.vsf", - @"vss" : @"application/vnd.visio", - @"vst" : @"application/vnd.visio", - @"vsw" : @"application/vnd.visio", - @"vtu" : @"model/vnd.vtu", - @"vxml" : @"application/voicexml+xml", - @"w3d" : @"application/x-director", - @"wad" : @"application/x-doom", - @"wav" : @"audio/x-wav", - @"wax" : @"audio/x-ms-wax", - @"wbmp" : @"image/vnd.wap.wbmp", - @"wbs" : @"application/vnd.criticaltools.wbs+xml", - @"wbxml" : @"application/vnd.wap.wbxml", - @"wcm" : @"application/vnd.ms-works", - @"wdb" : @"application/vnd.ms-works", - @"wdp" : @"image/vnd.ms-photo", - @"weba" : @"audio/webm", - @"webm" : @"video/webm", - @"webp" : @"image/webp", - @"wg" : @"application/vnd.pmi.widget", - @"wgt" : @"application/widget", - @"wks" : @"application/vnd.ms-works", - @"wm" : @"video/x-ms-wm", - @"wma" : @"audio/x-ms-wma", - @"wmd" : @"application/x-ms-wmd", - @"wmf" : @"application/x-msmetafile", - @"wml" : @"text/vnd.wap.wml", - @"wmlc" : @"application/vnd.wap.wmlc", - @"wmls" : @"text/vnd.wap.wmlscript", - @"wmlsc" : @"application/vnd.wap.wmlscriptc", - @"wmv" : @"video/x-ms-wmv", - @"wmx" : @"video/x-ms-wmx", - @"wmz" : @"application/x-msmetafile", - @"woff" : @"application/font-woff", - @"wpd" : @"application/vnd.wordperfect", - @"wpl" : @"application/vnd.ms-wpl", - @"wps" : @"application/vnd.ms-works", - @"wqd" : @"application/vnd.wqd", - @"wri" : @"application/x-mswrite", - @"wrl" : @"model/vrml", - @"wsdl" : @"application/wsdl+xml", - @"wspolicy" : @"application/wspolicy+xml", - @"wtb" : @"application/vnd.webturbo", - @"wvx" : @"video/x-ms-wvx", - @"x32" : @"application/x-authorware-bin", - @"x3d" : @"model/x3d+xml", - @"x3db" : @"model/x3d+binary", - @"x3dbz" : @"model/x3d+binary", - @"x3dv" : @"model/x3d+vrml", - @"x3dvz" : @"model/x3d+vrml", - @"x3dz" : @"model/x3d+xml", - @"xaml" : @"application/xaml+xml", - @"xap" : @"application/x-silverlight-app", - @"xar" : @"application/vnd.xara", - @"xbap" : @"application/x-ms-xbap", - @"xbd" : @"application/vnd.fujixerox.docuworks.binder", - @"xbm" : @"image/x-xbitmap", - @"xdf" : @"application/xcap-diff+xml", - @"xdm" : @"application/vnd.syncml.dm+xml", - @"xdp" : @"application/vnd.adobe.xdp+xml", - @"xdssc" : @"application/dssc+xml", - @"xdw" : @"application/vnd.fujixerox.docuworks", - @"xenc" : @"application/xenc+xml", - @"xer" : @"application/patch-ops-error+xml", - @"xfdf" : @"application/vnd.adobe.xfdf", - @"xfdl" : @"application/vnd.xfdl", - @"xht" : @"application/xhtml+xml", - @"xhtml" : @"application/xhtml+xml", - @"xhvml" : @"application/xv+xml", - @"xif" : @"image/vnd.xiff", - @"xla" : @"application/vnd.ms-excel", - @"xlam" : @"application/vnd.ms-excel.addin.macroenabled.12", - @"xlc" : @"application/vnd.ms-excel", - @"xlf" : @"application/x-xliff+xml", - @"xlm" : @"application/vnd.ms-excel", - @"xls" : @"application/vnd.ms-excel", - @"xlsb" : @"application/vnd.ms-excel.sheet.binary.macroenabled.12", - @"xlsm" : @"application/vnd.ms-excel.sheet.macroenabled.12", - @"xlsx" : @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - @"xlt" : @"application/vnd.ms-excel", - @"xltm" : @"application/vnd.ms-excel.template.macroenabled.12", - @"xltx" : @"application/vnd.openxmlformats-officedocument.spreadsheetml.template", - @"xlw" : @"application/vnd.ms-excel", - @"xm" : @"audio/xm", - @"xml" : @"application/xml", - @"xo" : @"application/vnd.olpc-sugar", - @"xop" : @"application/xop+xml", - @"xpi" : @"application/x-xpinstall", - @"xpl" : @"application/xproc+xml", - @"xpm" : @"image/x-xpixmap", - @"xpr" : @"application/vnd.is-xpr", - @"xps" : @"application/vnd.ms-xpsdocument", - @"xpw" : @"application/vnd.intercon.formnet", - @"xpx" : @"application/vnd.intercon.formnet", - @"xsl" : @"application/xml", - @"xslt" : @"application/xslt+xml", - @"xsm" : @"application/vnd.syncml+xml", - @"xspf" : @"application/xspf+xml", - @"xul" : @"application/vnd.mozilla.xul+xml", - @"xvm" : @"application/xv+xml", - @"xvml" : @"application/xv+xml", - @"xwd" : @"image/x-xwindowdump", - @"xyz" : @"chemical/x-xyz", - @"xz" : @"application/x-xz", - @"yang" : @"application/yang", - @"yin" : @"application/yin+xml", - @"z1" : @"application/x-zmachine", - @"z2" : @"application/x-zmachine", - @"z3" : @"application/x-zmachine", - @"z4" : @"application/x-zmachine", - @"z5" : @"application/x-zmachine", - @"z6" : @"application/x-zmachine", - @"z7" : @"application/x-zmachine", - @"z8" : @"application/x-zmachine", - @"zaz" : @"application/vnd.zzazz.deck+xml", - @"zip" : OWSMimeTypeApplicationZip, - @"zir" : @"application/vnd.zul", - @"zirz" : @"application/vnd.zul", - @"zmm" : @"application/vnd.handheld-entertainment+xml", - }; - }); - return result; -} - -+ (nullable NSString *)fileExtensionForMIMETypeViaLookup:(NSString *)mimeType -{ - return [[self genericMIMETypesToExtensionTypes] objectForKey:mimeType]; -} - -+ (nullable NSString *)fileExtensionForMIMEType:(NSString *)mimeType -{ - // Try to deduce the file extension by using a lookup table. - // - // This should be more accurate than deducing the file extension by - // converting to a UTI type. For example, .m4a files will have a - // UTI type of kUTTypeMPEG4Audio which incorrectly yields the file - // extension .mp4 instead of .m4a. - NSString *_Nullable fileExtension = [self fileExtensionForMIMETypeViaLookup:mimeType]; - if (!fileExtension) { - // Try to deduce the file extension by converting to a UTI type. - fileExtension = [self fileExtensionForMIMETypeViaUTIType:mimeType]; - } - return fileExtension; -} - -+ (nullable NSString *)utiTypeForFileExtension:(NSString *)fileExtension -{ - OWSAssertDebug(fileExtension.length > 0); - - NSString *_Nullable utiType = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag( - kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL); - return utiType; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/MessageSender+Promise.swift b/SignalServiceKit/src/Util/MessageSender+Promise.swift deleted file mode 100644 index ac426fbb8..000000000 --- a/SignalServiceKit/src/Util/MessageSender+Promise.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit - -public extension MessageSender { - - /** - * Wrap message sending in a Promise for easier callback chaining. - */ - func sendPromise(message: TSOutgoingMessage) -> Promise { - let promise: Promise = Promise { resolver in - self.send(message, success: { resolver.fulfill(()) }, failure: resolver.reject) - } - - // Ensure sends complete before they're GC'd. - // This *should* be redundant, since we should be calling retainUntilComplete - // at all call sites where the promise isn't otherwise retained. - promise.retainUntilComplete() - - return promise - } -} diff --git a/SignalServiceKit/src/Util/NSArray+OWS.h b/SignalServiceKit/src/Util/NSArray+OWS.h deleted file mode 100644 index 15c3143f2..000000000 --- a/SignalServiceKit/src/Util/NSArray+OWS.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface NSArray (OWS) - -- (NSArray *)uniqueIds; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSArray+OWS.m b/SignalServiceKit/src/Util/NSArray+OWS.m deleted file mode 100644 index cb6c06376..000000000 --- a/SignalServiceKit/src/Util/NSArray+OWS.m +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import "NSArray+OWS.h" -#import "TSYapDatabaseObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation NSArray (OWS) - -- (NSArray *)uniqueIds -{ - NSMutableArray *result = [NSMutableArray new]; - for (id object in self) { - OWSAssertDebug([object isKindOfClass:[TSYapDatabaseObject class]]); - TSYapDatabaseObject *dbObject = object; - [result addObject:dbObject.uniqueId]; - } - return result; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSData+Image.h b/SignalServiceKit/src/Util/NSData+Image.h deleted file mode 100644 index 7c588fa03..000000000 --- a/SignalServiceKit/src/Util/NSData+Image.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface NSData (Image) - -// If mimeType is non-nil, we ensure that the magic numbers agree with the -// mimeType. -+ (BOOL)ows_isValidImageAtPath:(NSString *)filePath; -+ (BOOL)ows_isValidImageAtPath:(NSString *)filePath mimeType:(nullable NSString *)mimeType; -- (BOOL)ows_isValidImage; -- (BOOL)ows_isValidImageWithMimeType:(nullable NSString *)mimeType; -- (NSString *_Nullable)ows_guessMimeType; - -// Returns the image size in pixels. -// -// Returns CGSizeZero on error. -+ (CGSize)imageSizeForFilePath:(NSString *)filePath mimeType:(NSString *)mimeType; - -+ (BOOL)hasAlphaForValidImageFilePath:(NSString *)filePath; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSData+Image.m b/SignalServiceKit/src/Util/NSData+Image.m deleted file mode 100644 index 644c6c645..000000000 --- a/SignalServiceKit/src/Util/NSData+Image.m +++ /dev/null @@ -1,420 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "NSData+Image.h" -#import "MIMETypeUtil.h" -#import "OWSFileSystem.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSInteger, ImageFormat) { - ImageFormat_Unknown, - ImageFormat_Png, - ImageFormat_Gif, - ImageFormat_Tiff, - ImageFormat_Jpeg, - ImageFormat_Bmp, -}; - -@implementation NSData (Image) - -+ (BOOL)ows_isValidImageAtPath:(NSString *)filePath -{ - return [self ows_isValidImageAtPath:filePath mimeType:nil]; -} - -- (BOOL)ows_isValidImage -{ - ImageFormat imageFormat = [self ows_guessImageFormat]; - - BOOL isAnimated = imageFormat == ImageFormat_Gif; - - const NSUInteger kMaxFileSize - = (isAnimated ? OWSMediaUtils.kMaxFileSizeAnimatedImage : OWSMediaUtils.kMaxFileSizeImage); - NSUInteger fileSize = self.length; - if (fileSize > kMaxFileSize) { - OWSLogWarn(@"Oversize image."); - return NO; - } - - if (![self ows_isValidImageWithMimeType:nil imageFormat:imageFormat]) { - return NO; - } - - if (![self ows_hasValidImageDimensionsWithIsAnimated:isAnimated]) { - return NO; - } - - return YES; -} - -+ (BOOL)ows_isValidImageAtPath:(NSString *)filePath mimeType:(nullable NSString *)mimeType -{ - if (mimeType.length < 1) { - NSString *fileExtension = [filePath pathExtension].lowercaseString; - mimeType = [MIMETypeUtil mimeTypeForFileExtension:fileExtension]; - } - if (mimeType.length < 1) { - OWSLogError(@"Image has unknown MIME type."); - return NO; - } - NSNumber *_Nullable fileSize = [OWSFileSystem fileSizeOfPath:filePath]; - if (!fileSize) { - OWSLogError(@"Could not determine file size."); - return NO; - } - - BOOL isAnimated = [MIMETypeUtil isSupportedAnimatedMIMEType:mimeType]; - if (isAnimated) { - if (fileSize.unsignedIntegerValue > OWSMediaUtils.kMaxFileSizeAnimatedImage) { - OWSLogWarn(@"Oversize animated image."); - return NO; - } - } else if ([MIMETypeUtil isSupportedImageMIMEType:mimeType]) { - if (fileSize.unsignedIntegerValue > OWSMediaUtils.kMaxFileSizeImage) { - OWSLogWarn(@"Oversize still image."); - return NO; - } - } else { - OWSLogError(@"Image has unsupported MIME type."); - return NO; - } - - NSError *error = nil; - NSData *_Nullable data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&error]; - if (!data || error) { - OWSLogError(@"Could not read image data: %@", error); - return NO; - } - - if (![data ows_isValidImageWithMimeType:mimeType]) { - return NO; - } - - if (![self ows_hasValidImageDimensionsAtPath:filePath isAnimated:isAnimated]) { - OWSLogError(@"%@ image had invalid dimensions.", self.logTag); - return NO; - } - - return YES; -} - -- (BOOL)ows_hasValidImageDimensionsWithIsAnimated:(BOOL)isAnimated -{ - CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self, NULL); - if (imageSource == NULL) { - return NO; - } - BOOL result = [NSData ows_hasValidImageDimensionWithImageSource:imageSource isAnimated:isAnimated]; - CFRelease(imageSource); - return result; -} - -+ (BOOL)ows_hasValidImageDimensionsAtPath:(NSString *)path isAnimated:(BOOL)isAnimated -{ - NSURL *url = [NSURL fileURLWithPath:path]; - if (!url) { - return NO; - } - - CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL); - if (imageSource == NULL) { - return NO; - } - BOOL result = [self ows_hasValidImageDimensionWithImageSource:imageSource isAnimated:isAnimated]; - CFRelease(imageSource); - return result; -} - -+ (BOOL)ows_hasValidImageDimensionWithImageSource:(CGImageSourceRef)imageSource isAnimated:(BOOL)isAnimated -{ - OWSAssertDebug(imageSource); - - NSDictionary *imageProperties - = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL); - - if (!imageProperties) { - return NO; - } - - NSNumber *widthNumber = imageProperties[(__bridge NSString *)kCGImagePropertyPixelWidth]; - if (!widthNumber) { - OWSLogError(@"widthNumber was unexpectedly nil"); - return NO; - } - CGFloat width = widthNumber.floatValue; - - NSNumber *heightNumber = imageProperties[(__bridge NSString *)kCGImagePropertyPixelHeight]; - if (!heightNumber) { - OWSLogError(@"heightNumber was unexpectedly nil"); - return NO; - } - CGFloat height = heightNumber.floatValue; - - /* The number of bits in each color sample of each pixel. The value of this - * key is a CFNumberRef. */ - NSNumber *depthNumber = imageProperties[(__bridge NSString *)kCGImagePropertyDepth]; - if (!depthNumber) { - OWSLogError(@"depthNumber was unexpectedly nil"); - return NO; - } - NSUInteger depthBits = depthNumber.unsignedIntegerValue; - // This should usually be 1. - CGFloat depthBytes = (CGFloat)ceil(depthBits / 8.f); - - /* The color model of the image such as "RGB", "CMYK", "Gray", or "Lab". - * The value of this key is CFStringRef. */ - NSString *colorModel = imageProperties[(__bridge NSString *)kCGImagePropertyColorModel]; - if (!colorModel) { - OWSLogError(@"colorModel was unexpectedly nil"); - return NO; - } - if (![colorModel isEqualToString:(__bridge NSString *)kCGImagePropertyColorModelRGB] - && ![colorModel isEqualToString:(__bridge NSString *)kCGImagePropertyColorModelGray]) { - OWSLogError(@"Invalid colorModel: %@", colorModel); - return NO; - } - - // We only support (A)RGB and (A)Grayscale, so worst case is 4. - const CGFloat kWorseCastComponentsPerPixel = 4; - CGFloat bytesPerPixel = kWorseCastComponentsPerPixel * depthBytes; - - const CGFloat kExpectedBytePerPixel = 4; - CGFloat kMaxValidImageDimension - = (isAnimated ? OWSMediaUtils.kMaxAnimatedImageDimensions : OWSMediaUtils.kMaxStillImageDimensions); - CGFloat kMaxBytes = kMaxValidImageDimension * kMaxValidImageDimension * kExpectedBytePerPixel; - CGFloat actualBytes = width * height * bytesPerPixel; - if (actualBytes > kMaxBytes) { - OWSLogWarn(@"invalid dimensions width: %f, height %f, bytesPerPixel: %f", width, height, bytesPerPixel); - return NO; - } - - return YES; -} - -- (BOOL)ows_isValidImageWithMimeType:(nullable NSString *)mimeType -{ - ImageFormat imageFormat = [self ows_guessImageFormat]; - return [self ows_isValidImageWithMimeType:mimeType imageFormat:imageFormat]; -} - -- (BOOL)ows_isValidImageWithMimeType:(nullable NSString *)mimeType imageFormat:(ImageFormat)imageFormat -{ - // Don't trust the file extension; iOS (e.g. UIKit, Core Graphics) will happily - // load a .gif with a .png file extension. - // - // Instead, use the "magic numbers" in the file data to determine the image format. - // - // If the image has a declared MIME type, ensure that agrees with the - // deduced image format. - switch (imageFormat) { - case ImageFormat_Unknown: - return NO; - case ImageFormat_Png: - return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImagePng]); - case ImageFormat_Gif: - if (![self ows_hasValidGifSize]) { - return NO; - } - return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImageGif]); - case ImageFormat_Tiff: - return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImageTiff1] || - [mimeType isEqualToString:OWSMimeTypeImageTiff2]); - case ImageFormat_Jpeg: - return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImageJpeg]); - case ImageFormat_Bmp: - return (mimeType == nil || [mimeType isEqualToString:OWSMimeTypeImageBmp1] || - [mimeType isEqualToString:OWSMimeTypeImageBmp2]); - } -} - -- (ImageFormat)ows_guessImageFormat -{ - const NSUInteger kTwoBytesLength = 2; - if (self.length < kTwoBytesLength) { - return ImageFormat_Unknown; - } - - unsigned char bytes[kTwoBytesLength]; - [self getBytes:&bytes range:NSMakeRange(0, kTwoBytesLength)]; - - unsigned char byte0 = bytes[0]; - unsigned char byte1 = bytes[1]; - - if (byte0 == 0x47 && byte1 == 0x49) { - return ImageFormat_Gif; - } else if (byte0 == 0x89 && byte1 == 0x50) { - return ImageFormat_Png; - } else if (byte0 == 0xff && byte1 == 0xd8) { - return ImageFormat_Jpeg; - } else if (byte0 == 0x42 && byte1 == 0x4d) { - return ImageFormat_Bmp; - } else if (byte0 == 0x4D && byte1 == 0x4D) { - // Motorola byte order TIFF - return ImageFormat_Tiff; - } else if (byte0 == 0x49 && byte1 == 0x49) { - // Intel byte order TIFF - return ImageFormat_Tiff; - } - - return ImageFormat_Unknown; -} - -- (NSString *_Nullable)ows_guessMimeType -{ - ImageFormat format = [self ows_guessImageFormat]; - switch (format) { - case ImageFormat_Gif: return OWSMimeTypeImageGif; - case ImageFormat_Png: return OWSMimeTypeImagePng; - case ImageFormat_Jpeg: return OWSMimeTypeImageJpeg; - default: return nil; - } -} - -+ (BOOL)ows_areByteArraysEqual:(NSUInteger)length left:(unsigned char *)left right:(unsigned char *)right -{ - for (NSUInteger i = 0; i < length; i++) { - if (left[i] != right[i]) { - return NO; - } - } - return YES; -} - -// Parse the GIF header to prevent the "GIF of death" issue. -// -// See: https://blog.flanker017.me/cve-2017-2416-gif-remote-exec/ -// See: https://www.w3.org/Graphics/GIF/spec-gif89a.txt -- (BOOL)ows_hasValidGifSize -{ - const NSUInteger kSignatureLength = 3; - const NSUInteger kVersionLength = 3; - const NSUInteger kWidthLength = 2; - const NSUInteger kHeightLength = 2; - const NSUInteger kPrefixLength = kSignatureLength + kVersionLength; - const NSUInteger kBufferLength = kSignatureLength + kVersionLength + kWidthLength + kHeightLength; - - if (self.length < kBufferLength) { - return NO; - } - - unsigned char bytes[kBufferLength]; - [self getBytes:&bytes range:NSMakeRange(0, kBufferLength)]; - - unsigned char kGif87APrefix[kPrefixLength] = { - 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, - }; - unsigned char kGif89APrefix[kPrefixLength] = { - 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, - }; - if (![NSData ows_areByteArraysEqual:kPrefixLength left:bytes right:kGif87APrefix] - && ![NSData ows_areByteArraysEqual:kPrefixLength left:bytes right:kGif89APrefix]) { - return NO; - } - NSUInteger width = ((NSUInteger)bytes[kPrefixLength + 0]) | (((NSUInteger)bytes[kPrefixLength + 1] << 8)); - NSUInteger height = ((NSUInteger)bytes[kPrefixLength + 2]) | (((NSUInteger)bytes[kPrefixLength + 3] << 8)); - - // We need to ensure that the image size is "reasonable". - // We impose an arbitrary "very large" limit on image size - // to eliminate harmful values. - const NSUInteger kMaxValidSize = 1 << 18; - - return (width > 0 && width < kMaxValidSize && height > 0 && height < kMaxValidSize); -} - -+ (CGSize)imageSizeForFilePath:(NSString *)filePath mimeType:(NSString *)mimeType -{ - if (![NSData ows_isValidImageAtPath:filePath mimeType:mimeType]) { - OWSLogError(@"Invalid image."); - return CGSizeZero; - } - NSURL *url = [NSURL fileURLWithPath:filePath]; - - // With CGImageSource we avoid loading the whole image into memory. - CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, NULL); - if (!source) { - OWSFailDebug(@"Could not load image: %@", url); - return CGSizeZero; - } - - NSDictionary *options = @{ - (NSString *)kCGImageSourceShouldCache : @(NO), - }; - NSDictionary *properties - = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, 0, (CFDictionaryRef)options); - CGSize imageSize = CGSizeZero; - if (properties) { - NSNumber *orientation = properties[(NSString *)kCGImagePropertyOrientation]; - NSNumber *width = properties[(NSString *)kCGImagePropertyPixelWidth]; - NSNumber *height = properties[(NSString *)kCGImagePropertyPixelHeight]; - - if (width && height) { - imageSize = CGSizeMake(width.floatValue, height.floatValue); - - if (orientation) { - imageSize = [self applyImageOrientation:(UIImageOrientation)orientation.intValue toImageSize:imageSize]; - } - } else { - OWSFailDebug(@"Could not determine size of image: %@", url); - } - } - CFRelease(source); - return imageSize; -} - -+ (CGSize)applyImageOrientation:(UIImageOrientation)orientation toImageSize:(CGSize)imageSize -{ - switch (orientation) { - case UIImageOrientationUp: // EXIF = 1 - case UIImageOrientationUpMirrored: // EXIF = 2 - case UIImageOrientationDown: // EXIF = 3 - case UIImageOrientationDownMirrored: // EXIF = 4 - return imageSize; - case UIImageOrientationLeftMirrored: // EXIF = 5 - case UIImageOrientationLeft: // EXIF = 6 - case UIImageOrientationRightMirrored: // EXIF = 7 - case UIImageOrientationRight: // EXIF = 8 - return CGSizeMake(imageSize.height, imageSize.width); - default: - return imageSize; - } -} - -+ (BOOL)hasAlphaForValidImageFilePath:(NSString *)filePath -{ - NSURL *url = [NSURL fileURLWithPath:filePath]; - - // With CGImageSource we avoid loading the whole image into memory. - CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, NULL); - if (!source) { - OWSFailDebug(@"Could not load image: %@", url); - return NO; - } - - NSDictionary *options = @{ - (NSString *)kCGImageSourceShouldCache : @(NO), - }; - NSDictionary *properties - = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, 0, (CFDictionaryRef)options); - BOOL result = NO; - if (properties) { - NSNumber *_Nullable hasAlpha = properties[(NSString *)kCGImagePropertyHasAlpha]; - if (hasAlpha) { - result = hasAlpha.boolValue; - } else { - // This is not an error; kCGImagePropertyHasAlpha is an optional - // property. - OWSLogWarn(@"Could not determine transparency of image: %@", url); - result = NO; - } - } - CFRelease(source); - return result; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSError+MessageSending.h b/SignalServiceKit/src/Util/NSError+MessageSending.h deleted file mode 100644 index 18c738917..000000000 --- a/SignalServiceKit/src/Util/NSError+MessageSending.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface NSError (MessageSending) - -@property (nonatomic) BOOL isRetryable; -@property (nonatomic) BOOL isFatal; -@property (nonatomic) BOOL shouldBeIgnoredForGroups; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSError+MessageSending.m b/SignalServiceKit/src/Util/NSError+MessageSending.m deleted file mode 100644 index 04f84e03a..000000000 --- a/SignalServiceKit/src/Util/NSError+MessageSending.m +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "NSError+MessageSending.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -static void *kNSError_MessageSender_IsRetryable = &kNSError_MessageSender_IsRetryable; -static void *kNSError_MessageSender_ShouldBeIgnoredForGroups = &kNSError_MessageSender_ShouldBeIgnoredForGroups; -static void *kNSError_MessageSender_IsFatal = &kNSError_MessageSender_IsFatal; - -// isRetryable and isFatal are opposites but not redundant. -// -// If a group message send fails, the send will be retried if any of the errors were retryable UNLESS -// any of the errors were fatal. Fatal errors trump retryable errors. -@implementation NSError (MessageSending) - -- (BOOL)isRetryable -{ - NSNumber *value = objc_getAssociatedObject(self, kNSError_MessageSender_IsRetryable); - // This value should always be set for all errors by the time OWSSendMessageOperation - // queries it's value. If not, default to retrying in production. - return value ? [value boolValue] : YES; -} - -- (void)setIsRetryable:(BOOL)value -{ - objc_setAssociatedObject(self, kNSError_MessageSender_IsRetryable, @(value), OBJC_ASSOCIATION_COPY); -} - -- (BOOL)shouldBeIgnoredForGroups -{ - NSNumber *value = objc_getAssociatedObject(self, kNSError_MessageSender_ShouldBeIgnoredForGroups); - // This value will NOT always be set for all errors by the time we query it's value. - // Default to NOT ignoring. - return value ? [value boolValue] : NO; -} - -- (void)setShouldBeIgnoredForGroups:(BOOL)value -{ - objc_setAssociatedObject(self, kNSError_MessageSender_ShouldBeIgnoredForGroups, @(value), OBJC_ASSOCIATION_COPY); -} - -- (BOOL)isFatal -{ - NSNumber *value = objc_getAssociatedObject(self, kNSError_MessageSender_IsFatal); - // This value will NOT always be set for all errors by the time we query it's value. - // Default to NOT fatal. - return value ? [value boolValue] : NO; -} - -- (void)setIsFatal:(BOOL)value -{ - objc_setAssociatedObject(self, kNSError_MessageSender_IsFatal, @(value), OBJC_ASSOCIATION_COPY); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSNotificationCenter+OWS.h b/SignalServiceKit/src/Util/NSNotificationCenter+OWS.h deleted file mode 100644 index b58a7c202..000000000 --- a/SignalServiceKit/src/Util/NSNotificationCenter+OWS.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -// We often use notifications as way to publish events. -// -// We never need these events to be received synchronously, -// so we should always send them asynchronously to avoid any -// possible risk of deadlock. These methods also ensure that -// the notifications are always fired on the main thread. -@interface NSNotificationCenter (OWS) - -- (void)postNotificationNameAsync:(NSNotificationName)name object:(nullable id)object; -- (void)postNotificationNameAsync:(NSNotificationName)name - object:(nullable id)object - userInfo:(nullable NSDictionary *)userInfo; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSNotificationCenter+OWS.m b/SignalServiceKit/src/Util/NSNotificationCenter+OWS.m deleted file mode 100644 index c406ff6b7..000000000 --- a/SignalServiceKit/src/Util/NSNotificationCenter+OWS.m +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import "NSNotificationCenter+OWS.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation NSNotificationCenter (OWS) - -- (void)postNotificationNameAsync:(NSNotificationName)name object:(nullable id)object -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [self postNotificationName:name object:object]; - }); -} - -- (void)postNotificationNameAsync:(NSNotificationName)name - object:(nullable id)object - userInfo:(nullable NSDictionary *)userInfo -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [self postNotificationName:name object:object userInfo:userInfo]; - }); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSRegularExpression+SSK.swift b/SignalServiceKit/src/Util/NSRegularExpression+SSK.swift deleted file mode 100644 index e4574467d..000000000 --- a/SignalServiceKit/src/Util/NSRegularExpression+SSK.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation - -@objc -public extension NSRegularExpression { - - @objc - public func hasMatch(input: String) -> Bool { - return self.firstMatch(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count)) != nil - } - - @objc - public class func parseFirstMatch(pattern: String, - text: String, - options: NSRegularExpression.Options = []) -> String? { - do { - let regex = try NSRegularExpression(pattern: pattern, options: options) - guard let match = regex.firstMatch(in: text, - options: [], - range: NSRange(location: 0, length: text.utf16.count)) else { - return nil - } - let matchRange = match.range(at: 1) - guard let textRange = Range(matchRange, in: text) else { - owsFailDebug("Invalid match.") - return nil - } - let substring = String(text[textRange]) - return substring - } catch { - Logger.error("Error: \(error)") - return nil - } - } - - @objc - public func parseFirstMatch(inText text: String, - options: NSRegularExpression.Options = []) -> String? { - guard let match = self.firstMatch(in: text, - options: [], - range: NSRange(location: 0, length: text.utf16.count)) else { - return nil - } - let matchRange = match.range(at: 1) - guard let textRange = Range(matchRange, in: text) else { - owsFailDebug("Invalid match.") - return nil - } - let substring = String(text[textRange]) - return substring - } -} diff --git a/SignalServiceKit/src/Util/NSString+SSK.h b/SignalServiceKit/src/Util/NSString+SSK.h deleted file mode 100644 index 65affca06..000000000 --- a/SignalServiceKit/src/Util/NSString+SSK.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface NSString (SSK) - -- (NSString *)rtlSafeAppend:(NSString *)string; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSString+SSK.m b/SignalServiceKit/src/Util/NSString+SSK.m deleted file mode 100644 index cbe139ee8..000000000 --- a/SignalServiceKit/src/Util/NSString+SSK.m +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "NSString+SSK.h" -#import "AppContext.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation NSString (SSK) - -- (NSString *)rtlSafeAppend:(NSString *)string -{ - OWSAssertDebug(string); - - if (CurrentAppContext().isRTL) { - return [string stringByAppendingString:self]; - } else { - return [self stringByAppendingString:string]; - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSTimer+OWS.h b/SignalServiceKit/src/Util/NSTimer+OWS.h deleted file mode 100644 index c530f209e..000000000 --- a/SignalServiceKit/src/Util/NSTimer+OWS.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface NSTimer (OWS) - -// This method avoids the classic NSTimer retain cycle bug -// by using a weak reference to the target. -+ (NSTimer *)weakScheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval - target:(id)target - selector:(SEL)selector - userInfo:(nullable id)userInfo - repeats:(BOOL)repeats; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSTimer+OWS.m b/SignalServiceKit/src/Util/NSTimer+OWS.m deleted file mode 100644 index 12eb39175..000000000 --- a/SignalServiceKit/src/Util/NSTimer+OWS.m +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "NSTimer+OWS.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface NSTimerProxy : NSObject - -@property (nonatomic, weak) id target; -@property (nonatomic) SEL selector; - -@end - -#pragma mark - - -@implementation NSTimerProxy - -- (void)timerFired:(NSDictionary *)userInfo -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - [self.target performSelector:self.selector withObject:userInfo]; -#pragma clang diagnostic pop -} - -@end - -#pragma mark - - -static void *kNSTimer_OWS_Proxy = &kNSTimer_OWS_Proxy; - -@implementation NSTimer (OWS) - -- (NSTimerProxy *)ows_proxy -{ - return objc_getAssociatedObject(self, kNSTimer_OWS_Proxy); -} - -- (void)ows_setProxy:(NSTimerProxy *)proxy -{ - OWSAssertDebug(proxy); - - objc_setAssociatedObject(self, kNSTimer_OWS_Proxy, proxy, OBJC_ASSOCIATION_RETAIN); -} - -+ (NSTimer *)weakScheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval - target:(id)target - selector:(SEL)selector - userInfo:(nullable id)userInfo - repeats:(BOOL)repeats -{ - NSTimerProxy *proxy = [NSTimerProxy new]; - proxy.target = target; - proxy.selector = selector; - NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:timeInterval - target:proxy - selector:@selector(timerFired:) - userInfo:userInfo - repeats:repeats]; - [timer ows_setProxy:proxy]; - return timer; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSURLSessionDataTask+StatusCode.h b/SignalServiceKit/src/Util/NSURLSessionDataTask+StatusCode.h deleted file mode 100644 index 58067be8b..000000000 --- a/SignalServiceKit/src/Util/NSURLSessionDataTask+StatusCode.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface NSURLSessionTask (StatusCode) - -- (long)statusCode; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSURLSessionDataTask+StatusCode.m b/SignalServiceKit/src/Util/NSURLSessionDataTask+StatusCode.m deleted file mode 100644 index 212eeac55..000000000 --- a/SignalServiceKit/src/Util/NSURLSessionDataTask+StatusCode.m +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "NSURLSessionDataTask+StatusCode.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation NSURLSessionTask (StatusCode) - -- (long)statusCode { - NSHTTPURLResponse *response = (NSHTTPURLResponse *)self.response; - return response.statusCode; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSUserDefaults+OWS.h b/SignalServiceKit/src/Util/NSUserDefaults+OWS.h deleted file mode 100644 index 24bbbcbc2..000000000 --- a/SignalServiceKit/src/Util/NSUserDefaults+OWS.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface NSUserDefaults (OWS) - -+ (NSUserDefaults *)appUserDefaults; - -+ (void)migrateToSharedUserDefaults; - -+ (void)removeAll; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSUserDefaults+OWS.m b/SignalServiceKit/src/Util/NSUserDefaults+OWS.m deleted file mode 100644 index 79ab359db..000000000 --- a/SignalServiceKit/src/Util/NSUserDefaults+OWS.m +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "NSUserDefaults+OWS.h" -#import "AppContext.h" -#import "TSConstants.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation NSUserDefaults (OWS) - -+ (NSUserDefaults *)appUserDefaults -{ - return CurrentAppContext().appUserDefaults; -} - -+ (void)migrateToSharedUserDefaults -{ - OWSLogInfo(@""); - - NSUserDefaults *appUserDefaults = self.appUserDefaults; - - NSDictionary *dictionary = [NSUserDefaults standardUserDefaults].dictionaryRepresentation; - for (NSString *key in dictionary) { - id value = dictionary[key]; - OWSAssertDebug(value); - [appUserDefaults setObject:value forKey:key]; - } -} - -+ (void)removeAll -{ - [NSUserDefaults.standardUserDefaults removeAll]; - [self.appUserDefaults removeAll]; -} - -- (void)removeAll -{ - OWSAssertDebug(CurrentAppContext().isMainApp); - - NSDictionary *dictionary = self.dictionaryRepresentation; - for (NSString *key in dictionary) { - [self removeObjectForKey:key]; - } - [self synchronize]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWS2FAManager.h b/SignalServiceKit/src/Util/OWS2FAManager.h deleted file mode 100644 index e1a334ad9..000000000 --- a/SignalServiceKit/src/Util/OWS2FAManager.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const NSNotificationName_2FAStateDidChange; - -typedef void (^OWS2FASuccess)(void); -typedef void (^OWS2FAFailure)(NSError *error); - -@class OWSPrimaryStorage; - -// This class can be safely accessed and used from any thread. -@interface OWS2FAManager : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -+ (instancetype)sharedManager; - -@property (nullable, nonatomic, readonly) NSString *pinCode; - -- (BOOL)is2FAEnabled; -- (BOOL)isDueForReminder; - -// Request with service -- (void)requestEnable2FAWithPin:(NSString *)pin - success:(nullable OWS2FASuccess)success - failure:(nullable OWS2FAFailure)failure; - -// Sore local settings if, used during registration -- (void)mark2FAAsEnabledWithPin:(NSString *)pin; - -- (void)disable2FAWithSuccess:(nullable OWS2FASuccess)success failure:(nullable OWS2FAFailure)failure; - -- (void)updateRepetitionIntervalWithWasSuccessful:(BOOL)wasSuccessful; - -// used for testing -- (void)setDefaultRepetitionInterval; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWS2FAManager.m b/SignalServiceKit/src/Util/OWS2FAManager.m deleted file mode 100644 index 09c534fcd..000000000 --- a/SignalServiceKit/src/Util/OWS2FAManager.m +++ /dev/null @@ -1,270 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWS2FAManager.h" -#import "NSNotificationCenter+OWS.h" -#import "OWSPrimaryStorage.h" -#import "OWSRequestFactory.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSNetworkManager.h" -#import "YapDatabaseConnection+OWS.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -NSString *const NSNotificationName_2FAStateDidChange = @"NSNotificationName_2FAStateDidChange"; - -NSString *const kOWS2FAManager_Collection = @"kOWS2FAManager_Collection"; -NSString *const kOWS2FAManager_LastSuccessfulReminderDateKey = @"kOWS2FAManager_LastSuccessfulReminderDateKey"; -NSString *const kOWS2FAManager_PinCode = @"kOWS2FAManager_PinCode"; -NSString *const kOWS2FAManager_RepetitionInterval = @"kOWS2FAManager_RepetitionInterval"; - -const NSUInteger kHourSecs = 60 * 60; -const NSUInteger kDaySecs = kHourSecs * 24; - -@interface OWS2FAManager () - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@end - -#pragma mark - - -@implementation OWS2FAManager - -+ (instancetype)sharedManager -{ - OWSAssertDebug(SSKEnvironment.shared.ows2FAManager); - - return SSKEnvironment.shared.ows2FAManager; -} - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - - if (!self) { - return self; - } - - OWSAssertDebug(primaryStorage); - - _dbConnection = primaryStorage.newDatabaseConnection; - - OWSSingletonAssert(); - - return self; -} - -#pragma mark - Dependencies - -- (TSNetworkManager *)networkManager { - OWSAssertDebug(SSKEnvironment.shared.networkManager); - - return SSKEnvironment.shared.networkManager; -} - -- (TSAccountManager *)tsAccountManager { - return TSAccountManager.sharedInstance; -} - -#pragma mark - - -- (nullable NSString *)pinCode -{ - return [self.dbConnection objectForKey:kOWS2FAManager_PinCode inCollection:kOWS2FAManager_Collection]; -} - -- (BOOL)is2FAEnabled -{ - return self.pinCode != nil; -} - -- (void)set2FANotEnabled -{ - [self.dbConnection removeObjectForKey:kOWS2FAManager_PinCode inCollection:kOWS2FAManager_Collection]; - - [[NSNotificationCenter defaultCenter] postNotificationNameAsync:NSNotificationName_2FAStateDidChange - object:nil - userInfo:nil]; - - [[self.tsAccountManager updateAccountAttributes] retainUntilComplete]; -} - -- (void)mark2FAAsEnabledWithPin:(NSString *)pin -{ - OWSAssertDebug(pin.length > 0); - - [self.dbConnection setObject:pin forKey:kOWS2FAManager_PinCode inCollection:kOWS2FAManager_Collection]; - - // Schedule next reminder relative to now - self.lastSuccessfulReminderDate = [NSDate new]; - - [[NSNotificationCenter defaultCenter] postNotificationNameAsync:NSNotificationName_2FAStateDidChange - object:nil - userInfo:nil]; - - [[self.tsAccountManager updateAccountAttributes] retainUntilComplete]; -} - -- (void)requestEnable2FAWithPin:(NSString *)pin - success:(nullable OWS2FASuccess)success - failure:(nullable OWS2FAFailure)failure -{ - OWSAssertDebug(pin.length > 0); - OWSAssertDebug(success); - OWSAssertDebug(failure); - - TSRequest *request = [OWSRequestFactory enable2FARequestWithPin:pin]; - [self.networkManager makeRequest:request - success:^(NSURLSessionDataTask *task, id responseObject) { - OWSAssertIsOnMainThread(); - - [self mark2FAAsEnabledWithPin:pin]; - - if (success) { - success(); - } - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - OWSAssertIsOnMainThread(); - - if (failure) { - failure(error); - } - }]; -} - -- (void)disable2FAWithSuccess:(nullable OWS2FASuccess)success failure:(nullable OWS2FAFailure)failure -{ - TSRequest *request = [OWSRequestFactory disable2FARequest]; - [self.networkManager makeRequest:request - success:^(NSURLSessionDataTask *task, id responseObject) { - OWSAssertIsOnMainThread(); - - [self set2FANotEnabled]; - - if (success) { - success(); - } - } - failure:^(NSURLSessionDataTask *task, NSError *error) { - OWSAssertIsOnMainThread(); - - if (failure) { - failure(error); - } - }]; -} - - -#pragma mark - Reminders - -- (nullable NSDate *)lastSuccessfulReminderDate -{ - return [self.dbConnection dateForKey:kOWS2FAManager_LastSuccessfulReminderDateKey - inCollection:kOWS2FAManager_Collection]; -} - -- (void)setLastSuccessfulReminderDate:(nullable NSDate *)date -{ - OWSLogDebug(@"Seting setLastSuccessfulReminderDate:%@", date); - [self.dbConnection setDate:date - forKey:kOWS2FAManager_LastSuccessfulReminderDateKey - inCollection:kOWS2FAManager_Collection]; -} - -- (BOOL)isDueForReminder -{ - if (!self.is2FAEnabled) { - return NO; - } - - return self.nextReminderDate.timeIntervalSinceNow < 0; -} - -- (NSDate *)nextReminderDate -{ - NSDate *lastSuccessfulReminderDate = self.lastSuccessfulReminderDate ?: [NSDate distantPast]; - - return [lastSuccessfulReminderDate dateByAddingTimeInterval:self.repetitionInterval]; -} - -- (NSArray *)allRepetitionIntervals -{ - // Keep sorted monotonically increasing. - return @[ - @(6 * kHourSecs), - @(12 * kHourSecs), - @(1 * kDaySecs), - @(3 * kDaySecs), - @(7 * kDaySecs), - ]; -} - -- (double)defaultRepetitionInterval -{ - return self.allRepetitionIntervals.firstObject.doubleValue; -} - -- (NSTimeInterval)repetitionInterval -{ - return [self.dbConnection doubleForKey:kOWS2FAManager_RepetitionInterval - inCollection:kOWS2FAManager_Collection - defaultValue:self.defaultRepetitionInterval]; -} - -- (void)updateRepetitionIntervalWithWasSuccessful:(BOOL)wasSuccessful -{ - if (wasSuccessful) { - self.lastSuccessfulReminderDate = [NSDate new]; - } - - NSTimeInterval oldInterval = self.repetitionInterval; - NSTimeInterval newInterval = [self adjustRepetitionInterval:oldInterval wasSuccessful:wasSuccessful]; - - OWSLogInfo(@"%@ guess. Updating repetition interval: %f -> %f", - (wasSuccessful ? @"successful" : @"failed"), - oldInterval, - newInterval); - [self.dbConnection setDouble:newInterval - forKey:kOWS2FAManager_RepetitionInterval - inCollection:kOWS2FAManager_Collection]; -} - -- (NSTimeInterval)adjustRepetitionInterval:(NSTimeInterval)oldInterval wasSuccessful:(BOOL)wasSuccessful -{ - NSArray *allIntervals = self.allRepetitionIntervals; - - NSUInteger oldIndex = - [allIntervals indexOfObjectPassingTest:^BOOL(NSNumber *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { - return oldInterval <= (NSTimeInterval)obj.doubleValue; - }]; - - NSUInteger newIndex; - if (wasSuccessful) { - newIndex = oldIndex + 1; - } else { - // prevent overflow - newIndex = oldIndex <= 0 ? 0 : oldIndex - 1; - } - - // clamp to be valid - newIndex = MAX(0, MIN(allIntervals.count - 1, newIndex)); - - NSTimeInterval newInterval = allIntervals[newIndex].doubleValue; - return newInterval; -} - -- (void)setDefaultRepetitionInterval -{ - [self.dbConnection setDouble:self.defaultRepetitionInterval - forKey:kOWS2FAManager_RepetitionInterval - inCollection:kOWS2FAManager_Collection]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSAnalytics.h b/SignalServiceKit/src/Util/OWSAnalytics.h deleted file mode 100755 index 270423f30..000000000 --- a/SignalServiceKit/src/Util/OWSAnalytics.h +++ /dev/null @@ -1,165 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSAnalyticsEvents.h" - -NS_ASSUME_NONNULL_BEGIN - -// TODO: We probably don't need all of these levels. -typedef NS_ENUM(NSUInteger, OWSAnalyticsSeverity) { - // Info events are routine. - // - // It's safe to discard a large fraction of these events. - OWSAnalyticsSeverityInfo = 1, - // Error events should never be discarded. - OWSAnalyticsSeverityError = 3, - // Critical events are special. They are submitted immediately - // and not persisted, since the database may not be working. - OWSAnalyticsSeverityCritical = 4 -}; - -// This is a placeholder. We don't yet serialize or transmit analytics events. -// -// If/when we take this on, we'll want to develop a solution that can be used -// report user activity - especially serious bugs - without compromising user -// privacy in any way. We must _never_ include any identifying information. -@interface OWSAnalytics : NSObject - -// description: A non-empty string without any leading whitespace. -// This should conform to our analytics event naming conventions. -// "category_event_name", e.g. "database_error_no_database_file_found". -// parameters: Optional. -// If non-nil, the keys should all be non-empty NSStrings. -// Values should be NSStrings or NSNumbers. -+ (void)logEvent:(NSString *)eventName - severity:(OWSAnalyticsSeverity)severity - parameters:(nullable NSDictionary *)parameters - location:(const char *)location - line:(int)line; - -+ (void)appLaunchDidBegin; - -+ (long)orderOfMagnitudeOf:(long)value; - -@end - -typedef NSDictionary *_Nonnull (^OWSProdAssertParametersBlock)(void); - -// These methods should be used to assert errors for which we want to fire analytics events. -// -// In production, returns __Value, the assert value, so that we can handle this case. -// In debug builds, asserts. -// -// parametersBlock is of type OWSProdAssertParametersBlock. -// The "C" variants (e.g. OWSProdAssert() vs. OWSProdCAssert() should be used in free functions, -// where there is no self. They can also be used in blocks to avoid capturing a reference to self. -#define OWSProdAssertWParamsTemplate(__value, __eventName, __parametersBlock, __assertMacro) \ - { \ - if (!(BOOL)(__value)) { \ - NSDictionary *__eventParameters = (__parametersBlock ? __parametersBlock() : nil); \ - [DDLog flushLog]; \ - [OWSAnalytics logEvent:__eventName \ - severity:OWSAnalyticsSeverityError \ - parameters:__eventParameters \ - location:__PRETTY_FUNCTION__ \ - line:__LINE__]; \ - } \ - __assertMacro(__value); \ - return (BOOL)(__value); \ - } - -#define OWSProdAssertWParams(__value, __eventName, __parametersBlock) \ - OWSProdAssertWParamsTemplate(__value, __eventName, __parametersBlock, OWSAssert) - -#define OWSProdCAssertWParams(__value, __eventName, __parametersBlock) \ - OWSProdAssertWParamsTemplate(__value, __eventName, __parametersBlock, OWSCAssert) - -#define OWSProdAssert(__value, __eventName) OWSProdAssertWParams(__value, __eventName, nil) - -#define OWSProdCAssert(__value, __eventName) OWSProdCAssertWParams(__value, __eventName, nil) - -#define OWSProdFailWParamsTemplate(__eventName, __parametersBlock, __failMacro) \ - { \ - NSDictionary *__eventParameters \ - = (__parametersBlock ? ((OWSProdAssertParametersBlock)__parametersBlock)() : nil); \ - [OWSAnalytics logEvent:__eventName \ - severity:OWSAnalyticsSeverityCritical \ - parameters:__eventParameters \ - location:__PRETTY_FUNCTION__ \ - line:__LINE__]; \ - __failMacro(__eventName); \ - } - -#define OWSProdFailWParams(__eventName, __parametersBlock) \ - OWSProdFailWParamsTemplate(__eventName, __parametersBlock, OWSFailNoFormat) -#define OWSProdCFailWParams(__eventName, __parametersBlock) \ - OWSProdFailWParamsTemplate(__eventName, __parametersBlock, OWSCFailNoFormat) - -#define OWSProdFail(__eventName) OWSProdFailWParams(__eventName, nil) - -#define OWSProdCFail(__eventName) OWSProdCFailWParams(__eventName, nil) - -#define OWSProdCFail(__eventName) OWSProdCFailWParams(__eventName, nil) - -#define OWSProdEventWParams(__severityLevel, __eventName, __parametersBlock) \ - { \ - NSDictionary *__eventParameters \ - = (__parametersBlock ? ((OWSProdAssertParametersBlock)__parametersBlock)() : nil); \ - [OWSAnalytics logEvent:__eventName \ - severity:__severityLevel \ - parameters:__eventParameters \ - location:__PRETTY_FUNCTION__ \ - line:__LINE__]; \ - } - -#pragma mark - Info Events - -#define OWSProdInfoWParams(__eventName, __parametersBlock) \ - OWSProdEventWParams(OWSAnalyticsSeverityInfo, __eventName, __parametersBlock) - -#define OWSProdInfo(__eventName) OWSProdEventWParams(OWSAnalyticsSeverityInfo, __eventName, nil) - -#pragma mark - Error Events - -#define OWSProdErrorWParams(__eventName, __parametersBlock) \ - OWSProdEventWParams(OWSAnalyticsSeverityError, __eventName, __parametersBlock) - -#define OWSProdError(__eventName) OWSProdEventWParams(OWSAnalyticsSeverityError, __eventName, nil) - -#pragma mark - Critical Events - -#define OWSProdCriticalWParams(__eventName, __parametersBlock) \ - OWSProdEventWParams(OWSAnalyticsSeverityCritical, __eventName, __parametersBlock) - -#define OWSProdCritical(__eventName) OWSProdEventWParams(OWSAnalyticsSeverityCritical, __eventName, nil) - -#pragma mark - OWSMessageManager macros -// Defined here rather than in OWSMessageManager so that our analytic event extraction script -// can properly detect the event names. -// -// The debug logs can be more verbose than the analytics events. -// -// In this case `descriptionForEnvelope` is valuable enough to -// log but too dangerous to include in the analytics event. -#define OWSProdErrorWEnvelope(__analyticsEventName, __envelope) \ - { \ - OWSLogError(@"%s:%d %@: %@", \ - __PRETTY_FUNCTION__, \ - __LINE__, \ - __analyticsEventName, \ - [self descriptionForEnvelope:__envelope]); \ - OWSProdError(__analyticsEventName) \ - } - -#define OWSProdInfoWEnvelope(__analyticsEventName, __envelope) \ - { \ - OWSLogInfo(@"%s:%d %@: %@", \ - __PRETTY_FUNCTION__, \ - __LINE__, \ - __analyticsEventName, \ - [self descriptionForEnvelope:__envelope]); \ - OWSProdInfo(__analyticsEventName) \ - } - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSAnalytics.m b/SignalServiceKit/src/Util/OWSAnalytics.m deleted file mode 100755 index 311419207..000000000 --- a/SignalServiceKit/src/Util/OWSAnalytics.m +++ /dev/null @@ -1,425 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSAnalytics.h" -#import "AppContext.h" -#import "OWSBackgroundTask.h" -#import "OWSPrimaryStorage.h" -#import "OWSQueues.h" -#import "SSKEnvironment.h" -#import "YapDatabaseConnection+OWS.h" -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -#define NO_SIGNAL_ANALYTICS - -#endif - -NSString *const kOWSAnalytics_EventsCollection = @"kOWSAnalytics_EventsCollection"; - -// Percentage of analytics events to discard. 0 <= x <= 100. -const int kOWSAnalytics_DiscardFrequency = 0; - -NSString *NSStringForOWSAnalyticsSeverity(OWSAnalyticsSeverity severity) -{ - switch (severity) { - case OWSAnalyticsSeverityInfo: - return @"Info"; - case OWSAnalyticsSeverityError: - return @"Error"; - case OWSAnalyticsSeverityCritical: - return @"Critical"; - } -} - -@interface OWSAnalytics () - -@property (nonatomic, readonly) Reachability *reachability; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@property (atomic) BOOL hasRequestInFlight; - -@end - -#pragma mark - - -@implementation OWSAnalytics - -+ (instancetype)sharedInstance -{ - static OWSAnalytics *instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[self alloc] initDefault]; - }); - return instance; -} - -// We lazy-create the analytics DB connection, so that we can handle -// errors that occur while initializing OWSPrimaryStorage. -+ (YapDatabaseConnection *)dbConnection -{ - return SSKEnvironment.shared.analyticsDBConnection; -} - -- (instancetype)initDefault -{ - self = [super init]; - - if (!self) { - return self; - } - - _reachability = [Reachability reachabilityForInternetConnection]; - - [self observeNotifications]; - - OWSSingletonAssert(); - - return self; -} - -- (void)observeNotifications -{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(reachabilityChanged) - name:kReachabilityChangedNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidBecomeActive) - name:OWSApplicationDidBecomeActiveNotification - object:nil]; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)reachabilityChanged -{ - OWSAssertIsOnMainThread(); - - [self tryToSyncEvents]; -} - -- (void)applicationDidBecomeActive -{ - OWSAssertIsOnMainThread(); - - [self tryToSyncEvents]; -} - -- (void)tryToSyncEvents -{ - return; // Loki: Do nothing - dispatch_async(self.serialQueue, ^{ - // Don't try to sync if: - // - // * There's no network available. - // * There's already a sync request in flight. - if (!self.reachability.isReachable) { - OWSLogVerbose(@"Not reachable"); - return; - } - if (self.hasRequestInFlight) { - return; - } - - __block NSString *firstEventKey = nil; - __block NSDictionary *firstEventDictionary = nil; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - // Take any event. We don't need to deliver them in any particular order. - [transaction enumerateKeysInCollection:kOWSAnalytics_EventsCollection - usingBlock:^(NSString *key, BOOL *_Nonnull stop) { - firstEventKey = key; - *stop = YES; - }]; - if (!firstEventKey) { - return; - } - - firstEventDictionary = [transaction objectForKey:firstEventKey inCollection:kOWSAnalytics_EventsCollection]; - OWSAssertDebug(firstEventDictionary); - OWSAssertDebug([firstEventDictionary isKindOfClass:[NSDictionary class]]); - }]; - - if (firstEventDictionary) { - [self sendEvent:firstEventDictionary eventKey:firstEventKey isCritical:NO]; - } - }); -} - -- (void)sendEvent:(NSDictionary *)eventDictionary eventKey:(NSString *)eventKey isCritical:(BOOL)isCritical -{ - return; // Loki: Do nothing - OWSAssertDebug(eventDictionary); - OWSAssertDebug(eventKey); - AssertOnDispatchQueue(self.serialQueue); - - if (isCritical) { - [self submitEvent:eventDictionary - eventKey:eventKey - success:^{ - OWSLogDebug(@"sendEvent[critical] succeeded: %@", eventKey); - } - failure:^{ - OWSLogError(@"sendEvent[critical] failed: %@", eventKey); - }]; - } else { - self.hasRequestInFlight = YES; - __block BOOL isComplete = NO; - [self submitEvent:eventDictionary - eventKey:eventKey - success:^{ - if (isComplete) { - return; - } - isComplete = YES; - OWSLogDebug(@"sendEvent succeeded: %@", eventKey); - dispatch_async(self.serialQueue, ^{ - self.hasRequestInFlight = NO; - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - // Remove from queue. - [transaction removeObjectForKey:eventKey inCollection:kOWSAnalytics_EventsCollection]; - }]; - - // Wait a second between network requests / retries. - dispatch_after( - dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self tryToSyncEvents]; - }); - }); - } - failure:^{ - if (isComplete) { - return; - } - isComplete = YES; - OWSLogError(@"sendEvent failed: %@", eventKey); - dispatch_async(self.serialQueue, ^{ - self.hasRequestInFlight = NO; - - // Wait a second between network requests / retries. - dispatch_after( - dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self tryToSyncEvents]; - }); - }); - }]; - } -} - -- (void)submitEvent:(NSDictionary *)eventDictionary - eventKey:(NSString *)eventKey - success:(void (^_Nonnull)(void))successBlock - failure:(void (^_Nonnull)(void))failureBlock -{ - return; // Loki: Do nothing - OWSAssertDebug(eventDictionary); - OWSAssertDebug(eventKey); - AssertOnDispatchQueue(self.serialQueue); - - OWSLogDebug(@"submitting: %@", eventKey); - - __block OWSBackgroundTask *backgroundTask = - [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__ - completionBlock:^(BackgroundTaskState backgroundTaskState) { - if (backgroundTaskState == BackgroundTaskState_Success) { - successBlock(); - } else { - failureBlock(); - } - }]; - - // Until we integrate with an analytics platform, behave as though all event delivery succeeds. - dispatch_async(self.serialQueue, ^{ - backgroundTask = nil; - }); -} - -- (dispatch_queue_t)serialQueue -{ - static dispatch_queue_t queue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - queue = dispatch_queue_create("org.whispersystems.analytics.serial", DISPATCH_QUEUE_SERIAL); - }); - return queue; -} - -- (NSString *)operatingSystemVersionString -{ - static NSString *result = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSOperatingSystemVersion operatingSystemVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; - result = [NSString stringWithFormat:@"%lu.%lu.%lu", - (unsigned long)operatingSystemVersion.majorVersion, - (unsigned long)operatingSystemVersion.minorVersion, - (unsigned long)operatingSystemVersion.patchVersion]; - }); - return result; -} - -- (NSDictionary *)eventSuperProperties -{ - NSMutableDictionary *result = [NSMutableDictionary new]; - result[@"app_version"] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - result[@"platform"] = @"ios"; - result[@"ios_version"] = self.operatingSystemVersionString; - return result; -} - -- (long)orderOfMagnitudeOf:(long)value -{ - return [OWSAnalytics orderOfMagnitudeOf:value]; -} - -+ (long)orderOfMagnitudeOf:(long)value -{ - if (value <= 0) { - return 0; - } - return (long)round(pow(10, floor(log10(value)))); -} - -- (void)addEvent:(NSString *)eventName severity:(OWSAnalyticsSeverity)severity properties:(NSDictionary *)properties -{ - return; // Loki: Do nothing - OWSAssertDebug(eventName.length > 0); - OWSAssertDebug(properties); - -#ifndef NO_SIGNAL_ANALYTICS - BOOL isError = severity == OWSAnalyticsSeverityError; - BOOL isCritical = severity == OWSAnalyticsSeverityCritical; - - uint32_t discardValue = arc4random_uniform(101); - if (!isError && !isCritical && discardValue < kOWSAnalytics_DiscardFrequency) { - OWSLogVerbose(@"Discarding event: %@", eventName); - return; - } - - void (^addEvent)(void) = ^{ - // Add super properties. - NSMutableDictionary *eventProperties = (properties ? [properties mutableCopy] : [NSMutableDictionary new]); - [eventProperties addEntriesFromDictionary:self.eventSuperProperties]; - - NSDictionary *eventDictionary = [eventProperties copy]; - OWSAssertDebug(eventDictionary); - NSString *eventKey = [NSUUID UUID].UUIDString; - OWSLogDebug(@"enqueuing event: %@", eventKey); - - if (isCritical) { - // Critical events should not be serialized or enqueued - they should be submitted immediately. - [self sendEvent:eventDictionary eventKey:eventKey isCritical:YES]; - } else { - // Add to queue. - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - const int kMaxQueuedEvents = 5000; - if ([transaction numberOfKeysInCollection:kOWSAnalytics_EventsCollection] > kMaxQueuedEvents) { - OWSLogError(@"Event queue overflow."); - return; - } - - [transaction setObject:eventDictionary forKey:eventKey inCollection:kOWSAnalytics_EventsCollection]; - }]; - - [self tryToSyncEvents]; - } - }; - - if ([self shouldReportAsync:severity]) { - dispatch_async(self.serialQueue, addEvent); - } else { - dispatch_sync(self.serialQueue, addEvent); - } -#endif -} - -+ (void)logEvent:(NSString *)eventName - severity:(OWSAnalyticsSeverity)severity - parameters:(nullable NSDictionary *)parameters - location:(const char *)location - line:(int)line -{ - [[self sharedInstance] logEvent:eventName severity:severity parameters:parameters location:location line:line]; -} - -- (void)logEvent:(NSString *)eventName - severity:(OWSAnalyticsSeverity)severity - parameters:(nullable NSDictionary *)parameters - location:(const char *)location - line:(int)line -{ - return; // Loki: Do nothing - DDLogFlag logFlag; - switch (severity) { - case OWSAnalyticsSeverityInfo: - logFlag = DDLogFlagInfo; - break; - case OWSAnalyticsSeverityError: - logFlag = DDLogFlagError; - break; - case OWSAnalyticsSeverityCritical: - logFlag = DDLogFlagError; - break; - default: - OWSFailDebug(@"Unknown severity."); - logFlag = DDLogFlagDebug; - break; - } - - // Log the event. - NSString *logString = [NSString stringWithFormat:@"%s:%d %@", location, line, eventName]; - if (!parameters) { - LOG_MAYBE([self shouldReportAsync:severity], LOG_LEVEL_DEF, logFlag, 0, nil, location, @"%@", logString); - } else { - LOG_MAYBE([self shouldReportAsync:severity], - LOG_LEVEL_DEF, - logFlag, - 0, - nil, - location, - @"%@ %@", - logString, - parameters); - } - if (![self shouldReportAsync:severity]) { - [DDLog flushLog]; - } - - NSMutableDictionary *eventProperties = (parameters ? [parameters mutableCopy] : [NSMutableDictionary new]); - eventProperties[@"event_location"] = [NSString stringWithFormat:@"%s:%d", location, line]; - [self addEvent:eventName severity:severity properties:eventProperties]; -} - -- (BOOL)shouldReportAsync:(OWSAnalyticsSeverity)severity -{ - return severity != OWSAnalyticsSeverityCritical; -} - -#pragma mark - Logging - -+ (void)appLaunchDidBegin -{ - [self.sharedInstance appLaunchDidBegin]; -} - -- (void)appLaunchDidBegin -{ - OWSProdInfo([OWSAnalyticsEvents appLaunch]); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSAnalyticsEvents.h b/SignalServiceKit/src/Util/OWSAnalyticsEvents.h deleted file mode 100755 index 8b05ac835..000000000 --- a/SignalServiceKit/src/Util/OWSAnalyticsEvents.h +++ /dev/null @@ -1,237 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSAnalyticsEvents : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -// The code between these markers is code-generated by: -// SignalServiceKit/Utilities/extract_analytics_event_names.py -// To add an event, insert your logging event as a string e.g.: -// -// OWSProdFail(@"messageSenderErrorMissingNewPreKeyBundle"); -// -// Then run SignalServiceKit/Utilities/extract_analytics_event_names.py, which -// will extract the string into a named method in this class. -#pragma mark - Code Generation Marker - -+ (NSString *)accountsErrorRegisterPushTokensFailed; - -+ (NSString *)accountsErrorUnregisterAccountRequestFailed; - -+ (NSString *)accountsErrorVerificationCodeRequestFailed; - -+ (NSString *)accountsErrorVerifyAccountRequestFailed; - -+ (NSString *)appDelegateErrorFailedToRegisterForRemoteNotifications; - -+ (NSString *)appLaunch; - -+ (NSString *)appLaunchComplete; - -+ (NSString *)callServiceCallAlreadySet; - -+ (NSString *)callServiceCallIdMismatch; - -+ (NSString *)callServiceCallMismatch; - -+ (NSString *)callServiceCallMissing; - -+ (NSString *)callServiceCallUnexpectedlyIdle; - -+ (NSString *)callServiceCallViewCouldNotPresent; - -+ (NSString *)callServiceCouldNotCreatePeerConnectionClientPromise; - -+ (NSString *)callServiceCouldNotCreateReadyToSendIceUpdatesPromise; - -+ (NSString *)callServiceErrorHandleLocalAddedIceCandidate; - -+ (NSString *)callServiceErrorHandleLocalHungupCall; - -+ (NSString *)callServiceErrorHandleReceivedErrorExternal; - -+ (NSString *)callServiceErrorHandleReceivedErrorInternal; - -+ (NSString *)callServiceErrorHandleRemoteAddedIceCandidate; - -+ (NSString *)callServiceErrorIncomingConnectionFailedExternal; - -+ (NSString *)callServiceErrorIncomingConnectionFailedInternal; - -+ (NSString *)callServiceErrorOutgoingConnectionFailedExternal; - -+ (NSString *)callServiceErrorOutgoingConnectionFailedInternal; - -+ (NSString *)callServiceErrorTimeoutWhileConnectingIncoming; - -+ (NSString *)callServiceErrorTimeoutWhileConnectingOutgoing; - -+ (NSString *)callServiceMissingFulfillReadyToSendIceUpdatesPromise; - -+ (NSString *)callServicePeerConnectionAlreadySet; - -+ (NSString *)callServicePeerConnectionMissing; - -+ (NSString *)callServiceCallDataMissing; - -+ (NSString *)contactsErrorContactsIntersectionFailed; - -+ (NSString *)errorAttachmentRequestFailed; - -+ (NSString *)errorCouldNotPresentViewDueToCall; - -+ (NSString *)errorEnableVideoCallingRequestFailed; - -+ (NSString *)errorGetDevicesFailed; - -+ (NSString *)errorPrekeysAvailablePrekeysRequestFailed; - -+ (NSString *)errorPrekeysCurrentSignedPrekeyRequestFailed; - -+ (NSString *)errorPrekeysUpdateFailedJustSigned; - -+ (NSString *)errorPrekeysUpdateFailedSignedAndOnetime; - -+ (NSString *)errorProvisioningCodeRequestFailed; - -+ (NSString *)errorProvisioningRequestFailed; - -+ (NSString *)errorUnlinkDeviceFailed; - -+ (NSString *)errorUpdateAttributesRequestFailed; - -+ (NSString *)messageSenderErrorMissingNewPreKeyBundle; - -+ (NSString *)messageManagerErrorCallMessageNoActionablePayload; - -+ (NSString *)messageManagerErrorCorruptMessage; - -+ (NSString *)messageManagerErrorCouldNotHandlePrekeyBundle; - -+ (NSString *)messageManagerErrorCouldNotHandleUnidentifiedSenderMessage; - -+ (NSString *)messageManagerErrorCouldNotHandleSecureMessage; - -+ (NSString *)messageManagerErrorEnvelopeNoActionablePayload; - -+ (NSString *)messageManagerErrorEnvelopeTypeKeyExchange; - -+ (NSString *)messageManagerErrorEnvelopeTypeOther; - -+ (NSString *)messageManagerErrorEnvelopeTypeUnknown; - -+ (NSString *)messageManagerErrorInvalidKey; - -+ (NSString *)messageManagerErrorInvalidKeyId; - -+ (NSString *)messageManagerErrorInvalidMessageVersion; - -+ (NSString *)messageManagerErrorInvalidProtocolMessage; - -+ (NSString *)messageManagerErrorMessageEnvelopeHasNoContent; - -+ (NSString *)messageManagerErrorNoSession; - -+ (NSString *)messageManagerErrorOversizeMessage; - -+ (NSString *)messageManagerErrorSyncMessageFromUnknownSource; - -+ (NSString *)messageManagerErrorUntrustedIdentityKeyException; - -+ (NSString *)messageReceiverErrorLargeMessage; - -+ (NSString *)messageReceiverErrorOversizeMessage; - -+ (NSString *)messageSendErrorCouldNotSerializeMessageJson; - -+ (NSString *)messageSendErrorFailedDueToPrekeyUpdateFailures; - -+ (NSString *)messageSendErrorFailedDueToUntrustedKey; - -+ (NSString *)messageSenderErrorCouldNotFindContacts1; - -+ (NSString *)messageSenderErrorCouldNotFindContacts2; - -+ (NSString *)messageSenderErrorCouldNotFindContacts3; - -+ (NSString *)messageSenderErrorCouldNotLoadAttachment; - -+ (NSString *)messageSenderErrorCouldNotParseMismatchedDevicesJson; - -+ (NSString *)messageSenderErrorCouldNotWriteAttachment; - -+ (NSString *)messageSenderErrorGenericSendFailure; - -+ (NSString *)messageSenderErrorInvalidIdentityKeyLength; - -+ (NSString *)messageSenderErrorInvalidIdentityKeyType; - -+ (NSString *)messageSenderErrorNoMissingOrExtraDevices; - -+ (NSString *)messageSenderErrorRecipientPrekeyRequestFailed; - -+ (NSString *)messageSenderErrorSendOperationDidNotComplete; - -+ (NSString *)messageSenderErrorUnexpectedKeyBundle; - -+ (NSString *)peerConnectionClientErrorSendDataChannelMessageFailed; - -+ (NSString *)prekeysDeletedOldAcceptedSignedPrekey; - -+ (NSString *)prekeysDeletedOldSignedPrekey; - -+ (NSString *)prekeysDeletedOldUnacceptedSignedPrekey; - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidAcl; - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidAlgorithm; - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidCredential; - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidDate; - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidKey; - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidPolicy; - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidResponse; - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidSignature; - -+ (NSString *)registrationBegan; - -+ (NSString *)registrationRegisteredPhoneNumber; - -+ (NSString *)registrationRegisteringCode; - -+ (NSString *)registrationRegisteringRequestedNewCodeBySms; - -+ (NSString *)registrationRegisteringRequestedNewCodeByVoice; - -+ (NSString *)registrationRegisteringSubmittedCode; - -+ (NSString *)registrationRegistrationFailed; - -+ (NSString *)registrationVerificationBack; - -+ (NSString *)storageErrorCouldNotDecodeClass; - -+ (NSString *)storageErrorCouldNotLoadDatabase; - -+ (NSString *)storageErrorCouldNotLoadDatabaseSecondAttempt; - -+ (NSString *)storageErrorCouldNotStoreKeychainValue; - -+ (NSString *)storageErrorDeserialization; - -+ (NSString *)storageErrorFileProtection; - -#pragma mark - Code Generation Marker - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSAnalyticsEvents.m b/SignalServiceKit/src/Util/OWSAnalyticsEvents.m deleted file mode 100755 index 1a4b0cb0c..000000000 --- a/SignalServiceKit/src/Util/OWSAnalyticsEvents.m +++ /dev/null @@ -1,549 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSAnalyticsEvents.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSAnalyticsEvents - -// The code between these markers is code-generated by: -// SignalServiceKit/Utilities/extract_analytics_event_names.py -#pragma mark - Code Generation Marker - -+ (NSString *)accountsErrorRegisterPushTokensFailed -{ - return @"accounts_error_register_push_tokens_failed"; -} - -+ (NSString *)accountsErrorUnregisterAccountRequestFailed -{ - return @"accounts_error_unregister_account_request_failed"; -} - -+ (NSString *)accountsErrorVerificationCodeRequestFailed -{ - return @"accounts_error_verification_code_request_failed"; -} - -+ (NSString *)accountsErrorVerifyAccountRequestFailed -{ - return @"accounts_error_verify_account_request_failed"; -} - -+ (NSString *)appDelegateErrorFailedToRegisterForRemoteNotifications -{ - return @"app_delegate_error_failed_to_register_for_remote_notifications"; -} - -+ (NSString *)appLaunch -{ - return @"app_launch"; -} - -+ (NSString *)appLaunchComplete -{ - return @"app_launch_complete"; -} - -+ (NSString *)callServiceCallAlreadySet -{ - return @"call_service_call_already_set"; -} - -+ (NSString *)callServiceCallIdMismatch -{ - return @"call_service_call_id_mismatch"; -} - -+ (NSString *)callServiceCallMismatch -{ - return @"call_service_call_mismatch"; -} - -+ (NSString *)callServiceCallMissing -{ - return @"call_service_call_missing"; -} - -+ (NSString *)callServiceCallUnexpectedlyIdle -{ - return @"call_service_call_unexpectedly_idle"; -} - -+ (NSString *)callServiceCallViewCouldNotPresent -{ - return @"call_service_call_view_could_not_present"; -} - -+ (NSString *)callServiceCouldNotCreatePeerConnectionClientPromise -{ - return @"call_service_could_not_create_peer_connection_client_promise"; -} - -+ (NSString *)callServiceCouldNotCreateReadyToSendIceUpdatesPromise -{ - return @"call_service_could_not_create_ready_to_send_ice_updates_promise"; -} - -+ (NSString *)callServiceErrorHandleLocalAddedIceCandidate -{ - return @"call_service_error_handle_local_added_ice_candidate"; -} - -+ (NSString *)callServiceErrorHandleLocalHungupCall -{ - return @"call_service_error_handle_local_hungup_call"; -} - -+ (NSString *)callServiceErrorHandleReceivedErrorExternal -{ - return @"call_service_error_handle_received_error_external"; -} - -+ (NSString *)callServiceErrorHandleReceivedErrorInternal -{ - return @"call_service_error_handle_received_error_internal"; -} - -+ (NSString *)callServiceErrorHandleRemoteAddedIceCandidate -{ - return @"call_service_error_handle_remote_added_ice_candidate"; -} - -+ (NSString *)callServiceErrorIncomingConnectionFailedExternal -{ - return @"call_service_error_incoming_connection_failed_external"; -} - -+ (NSString *)callServiceErrorIncomingConnectionFailedInternal -{ - return @"call_service_error_incoming_connection_failed_internal"; -} - -+ (NSString *)callServiceErrorOutgoingConnectionFailedExternal -{ - return @"call_service_error_outgoing_connection_failed_external"; -} - -+ (NSString *)callServiceErrorOutgoingConnectionFailedInternal -{ - return @"call_service_error_outgoing_connection_failed_internal"; -} - -+ (NSString *)callServiceErrorTimeoutWhileConnectingIncoming -{ - return @"call_service_error_timeout_while_connecting_incoming"; -} - -+ (NSString *)callServiceErrorTimeoutWhileConnectingOutgoing -{ - return @"call_service_error_timeout_while_connecting_outgoing"; -} - -+ (NSString *)callServiceMissingFulfillReadyToSendIceUpdatesPromise -{ - return @"call_service_missing_fulfill_ready_to_send_ice_updates_promise"; -} - -+ (NSString *)callServicePeerConnectionAlreadySet -{ - return @"call_service_peer_connection_already_set"; -} - -+ (NSString *)callServicePeerConnectionMissing -{ - return @"call_service_peer_connection_missing"; -} - -+ (NSString *)callServiceCallDataMissing -{ - return @"call_service_call_data_missing"; -} - -+ (NSString *)contactsErrorContactsIntersectionFailed -{ - return @"contacts_error_contacts_intersection_failed"; -} - -+ (NSString *)errorAttachmentRequestFailed -{ - return @"error_attachment_request_failed"; -} - -+ (NSString *)errorCouldNotPresentViewDueToCall -{ - return @"error_could_not_present_view_due_to_call"; -} - -+ (NSString *)errorEnableVideoCallingRequestFailed -{ - return @"error_enable_video_calling_request_failed"; -} - -+ (NSString *)errorGetDevicesFailed -{ - return @"error_get_devices_failed"; -} - -+ (NSString *)errorPrekeysAvailablePrekeysRequestFailed -{ - return @"error_prekeys_available_prekeys_request_failed"; -} - -+ (NSString *)errorPrekeysCurrentSignedPrekeyRequestFailed -{ - return @"error_prekeys_current_signed_prekey_request_failed"; -} - -+ (NSString *)errorPrekeysUpdateFailedJustSigned -{ - return @"error_prekeys_update_failed_just_signed"; -} - -+ (NSString *)errorPrekeysUpdateFailedSignedAndOnetime -{ - return @"error_prekeys_update_failed_signed_and_onetime"; -} - -+ (NSString *)errorProvisioningCodeRequestFailed -{ - return @"error_provisioning_code_request_failed"; -} - -+ (NSString *)errorProvisioningRequestFailed -{ - return @"error_provisioning_request_failed"; -} - -+ (NSString *)errorUnlinkDeviceFailed -{ - return @"error_unlink_device_failed"; -} - -+ (NSString *)errorUpdateAttributesRequestFailed -{ - return @"error_update_attributes_request_failed"; -} - -+ (NSString *)messageSenderErrorMissingNewPreKeyBundle -{ - return @"messageSenderErrorMissingNewPreKeyBundle"; -} - -+ (NSString *)messageManagerErrorCallMessageNoActionablePayload -{ - return @"message_manager_error_call_message_no_actionable_payload"; -} - -+ (NSString *)messageManagerErrorCorruptMessage -{ - return @"message_manager_error_corrupt_message"; -} - -+ (NSString *)messageManagerErrorCouldNotHandlePrekeyBundle -{ - return @"message_manager_error_could_not_handle_prekey_bundle"; -} - -+ (NSString *)messageManagerErrorCouldNotHandleUnidentifiedSenderMessage -{ - return @"message_manager_error_could_not_handle_unidentified_sender_message"; -} - -+ (NSString *)messageManagerErrorCouldNotHandleSecureMessage -{ - return @"message_manager_error_could_not_handle_secure_message"; -} - -+ (NSString *)messageManagerErrorEnvelopeNoActionablePayload -{ - return @"message_manager_error_envelope_no_actionable_payload"; -} - -+ (NSString *)messageManagerErrorEnvelopeTypeKeyExchange -{ - return @"message_manager_error_envelope_type_key_exchange"; -} - -+ (NSString *)messageManagerErrorEnvelopeTypeOther -{ - return @"message_manager_error_envelope_type_other"; -} - -+ (NSString *)messageManagerErrorEnvelopeTypeUnknown -{ - return @"message_manager_error_envelope_type_unknown"; -} - -+ (NSString *)messageManagerErrorInvalidKey -{ - return @"message_manager_error_invalid_key"; -} - -+ (NSString *)messageManagerErrorInvalidKeyId -{ - return @"message_manager_error_invalid_key_id"; -} - -+ (NSString *)messageManagerErrorInvalidMessageVersion -{ - return @"message_manager_error_invalid_message_version"; -} - -+ (NSString *)messageManagerErrorInvalidProtocolMessage -{ - return @"message_manager_error_invalid_protocol_message"; -} - -+ (NSString *)messageManagerErrorMessageEnvelopeHasNoContent -{ - return @"message_manager_error_message_envelope_has_no_content"; -} - -+ (NSString *)messageManagerErrorNoSession -{ - return @"message_manager_error_no_session"; -} - -+ (NSString *)messageManagerErrorOversizeMessage -{ - return @"message_manager_error_oversize_message"; -} - -+ (NSString *)messageManagerErrorSyncMessageFromUnknownSource -{ - return @"message_manager_error_sync_message_from_unknown_source"; -} - -+ (NSString *)messageManagerErrorUntrustedIdentityKeyException -{ - return @"message_manager_error_untrusted_identity_key_exception"; -} - -+ (NSString *)messageReceiverErrorLargeMessage -{ - return @"message_receiver_error_large_message"; -} - -+ (NSString *)messageReceiverErrorOversizeMessage -{ - return @"message_receiver_error_oversize_message"; -} - -+ (NSString *)messageSendErrorCouldNotSerializeMessageJson -{ - return @"message_send_error_could_not_serialize_message_json"; -} - -+ (NSString *)messageSendErrorFailedDueToPrekeyUpdateFailures -{ - return @"message_send_error_failed_due_to_prekey_update_failures"; -} - -+ (NSString *)messageSendErrorFailedDueToUntrustedKey -{ - return @"message_send_error_failed_due_to_untrusted_key"; -} - -+ (NSString *)messageSenderErrorCouldNotFindContacts1 -{ - return @"message_sender_error_could_not_find_contacts_1"; -} - -+ (NSString *)messageSenderErrorCouldNotFindContacts2 -{ - return @"message_sender_error_could_not_find_contacts_2"; -} - -+ (NSString *)messageSenderErrorCouldNotFindContacts3 -{ - return @"message_sender_error_could_not_find_contacts_3"; -} - -+ (NSString *)messageSenderErrorCouldNotLoadAttachment -{ - return @"message_sender_error_could_not_load_attachment"; -} - -+ (NSString *)messageSenderErrorCouldNotParseMismatchedDevicesJson -{ - return @"message_sender_error_could_not_parse_mismatched_devices_json"; -} - -+ (NSString *)messageSenderErrorCouldNotWriteAttachment -{ - return @"message_sender_error_could_not_write_attachment"; -} - -+ (NSString *)messageSenderErrorGenericSendFailure -{ - return @"message_sender_error_generic_send_failure"; -} - -+ (NSString *)messageSenderErrorInvalidIdentityKeyLength -{ - return @"message_sender_error_invalid_identity_key_length"; -} - -+ (NSString *)messageSenderErrorInvalidIdentityKeyType -{ - return @"message_sender_error_invalid_identity_key_type"; -} - -+ (NSString *)messageSenderErrorNoMissingOrExtraDevices -{ - return @"message_sender_error_no_missing_or_extra_devices"; -} - -+ (NSString *)messageSenderErrorRecipientPrekeyRequestFailed -{ - return @"message_sender_error_recipient_prekey_request_failed"; -} - -+ (NSString *)messageSenderErrorSendOperationDidNotComplete -{ - return @"message_sender_error_send_operation_did_not_complete"; -} - -+ (NSString *)messageSenderErrorUnexpectedKeyBundle -{ - return @"message_sender_error_unexpected_key_bundle"; -} - -+ (NSString *)peerConnectionClientErrorSendDataChannelMessageFailed -{ - return @"peer_connection_client_error_send_data_channel_message_failed"; -} - -+ (NSString *)prekeysDeletedOldAcceptedSignedPrekey -{ - return @"prekeys_deleted_old_accepted_signed_prekey"; -} - -+ (NSString *)prekeysDeletedOldSignedPrekey -{ - return @"prekeys_deleted_old_signed_prekey"; -} - -+ (NSString *)prekeysDeletedOldUnacceptedSignedPrekey -{ - return @"prekeys_deleted_old_unaccepted_signed_prekey"; -} - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidAcl -{ - return @"profile_manager_error_avatar_upload_form_invalid_acl"; -} - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidAlgorithm -{ - return @"profile_manager_error_avatar_upload_form_invalid_algorithm"; -} - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidCredential -{ - return @"profile_manager_error_avatar_upload_form_invalid_credential"; -} - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidDate -{ - return @"profile_manager_error_avatar_upload_form_invalid_date"; -} - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidKey -{ - return @"profile_manager_error_avatar_upload_form_invalid_key"; -} - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidPolicy -{ - return @"profile_manager_error_avatar_upload_form_invalid_policy"; -} - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidResponse -{ - return @"profile_manager_error_avatar_upload_form_invalid_response"; -} - -+ (NSString *)profileManagerErrorAvatarUploadFormInvalidSignature -{ - return @"profile_manager_error_avatar_upload_form_invalid_signature"; -} - -+ (NSString *)registrationBegan -{ - return @"registration_began"; -} - -+ (NSString *)registrationRegisteredPhoneNumber -{ - return @"registration_registered_phone_number"; -} - -+ (NSString *)registrationRegisteringCode -{ - return @"registration_registering_code"; -} - -+ (NSString *)registrationRegisteringRequestedNewCodeBySms -{ - return @"registration_registering_requested_new_code_by_sms"; -} - -+ (NSString *)registrationRegisteringRequestedNewCodeByVoice -{ - return @"registration_registering_requested_new_code_by_voice"; -} - -+ (NSString *)registrationRegisteringSubmittedCode -{ - return @"registration_registering_submitted_code"; -} - -+ (NSString *)registrationRegistrationFailed -{ - return @"registration_registration_failed"; -} - -+ (NSString *)registrationVerificationBack -{ - return @"registration_verification_back"; -} - -+ (NSString *)storageErrorCouldNotDecodeClass -{ - return @"storage_error_could_not_decode_class"; -} - -+ (NSString *)storageErrorCouldNotLoadDatabase -{ - return @"storage_error_could_not_load_database"; -} - -+ (NSString *)storageErrorCouldNotLoadDatabaseSecondAttempt -{ - return @"storage_error_could_not_load_database_second_attempt"; -} - -+ (NSString *)storageErrorCouldNotStoreKeychainValue -{ - return @"storage_error_could_not_store_keychain_value"; -} - -+ (NSString *)storageErrorDeserialization -{ - return @"storage_error_deserialization"; -} - -+ (NSString *)storageErrorFileProtection -{ - return @"storage_error_file_protection"; -} - -#pragma mark - Code Generation Marker - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSBackgroundTask.h b/SignalServiceKit/src/Util/OWSBackgroundTask.h deleted file mode 100644 index 2f9bb0be9..000000000 --- a/SignalServiceKit/src/Util/OWSBackgroundTask.h +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSUInteger, BackgroundTaskState) { - BackgroundTaskState_Success, - BackgroundTaskState_CouldNotStart, - BackgroundTaskState_Expired, -}; - -typedef void (^BackgroundTaskCompletionBlock)(BackgroundTaskState backgroundTaskState); - -// This class can be safely accessed and used from any thread. -@interface OWSBackgroundTaskManager : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -+ (instancetype)sharedManager; - -- (void)observeNotifications; - -@end - -#pragma mark - - -// This class makes it easier and safer to use background tasks. -// -// * Uses RAII (Resource Acquisition Is Initialization) pattern. -// * Ensures completion block is called exactly once and on main thread, -// to facilitate handling "background task timed out" case, for example. -// * Ensures we properly handle the "background task could not be created" -// case. -// -// Usage: -// -// * Use factory method to start a background task. -// * Retain a strong reference to the OWSBackgroundTask during the "work". -// * Clear all references to the OWSBackgroundTask when the work is done, -// if possible. -@interface OWSBackgroundTask : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -+ (OWSBackgroundTask *)backgroundTaskWithLabelStr:(const char *)labelStr; - -// completionBlock will be called exactly once on the main thread. -+ (OWSBackgroundTask *)backgroundTaskWithLabelStr:(const char *)labelStr - completionBlock:(BackgroundTaskCompletionBlock)completionBlock; - -+ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label; - -// completionBlock will be called exactly once on the main thread. -+ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label - completionBlock:(BackgroundTaskCompletionBlock)completionBlock; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSBackgroundTask.m b/SignalServiceKit/src/Util/OWSBackgroundTask.m deleted file mode 100644 index ec007a04b..000000000 --- a/SignalServiceKit/src/Util/OWSBackgroundTask.m +++ /dev/null @@ -1,437 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSBackgroundTask.h" -#import "AppContext.h" -#import "NSTimer+OWS.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef void (^BackgroundTaskExpirationBlock)(void); -typedef NSNumber *OWSTaskId; - -// This class can be safely accessed and used from any thread. -@interface OWSBackgroundTaskManager () - -// This property should only be accessed while synchronized on this instance. -@property (nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; - -// This property should only be accessed while synchronized on this instance. -@property (nonatomic) NSMutableDictionary *expirationMap; - -// This property should only be accessed while synchronized on this instance. -@property (nonatomic) unsigned long long idCounter; - -// Note that this flag is set a little early in "will resign active". -// -// This property should only be accessed while synchronized on this instance. -@property (nonatomic) BOOL isAppActive; - -// We use this timer to provide continuity and reduce churn, -// so that if one OWSBackgroundTask ends right before another -// begins, we use a single uninterrupted background that -// spans their lifetimes. -// -// This property should only be accessed while synchronized on this instance. -@property (nonatomic, nullable) NSTimer *continuityTimer; - -@end - -#pragma mark - - -@implementation OWSBackgroundTaskManager - -+ (instancetype)sharedManager -{ - static OWSBackgroundTaskManager *sharedMyManager = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedMyManager = [[self alloc] initDefault]; - }); - return sharedMyManager; -} - -- (instancetype)initDefault -{ - OWSAssertIsOnMainThread(); - - self = [super init]; - - if (!self) { - return self; - } - - self.backgroundTaskId = UIBackgroundTaskInvalid; - self.expirationMap = [NSMutableDictionary new]; - self.idCounter = 0; - self.isAppActive = CurrentAppContext().isMainAppAndActive; - - OWSSingletonAssert(); - - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)observeNotifications -{ - if (!CurrentAppContext().isMainApp) { - return; - } - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidBecomeActive:) - name:OWSApplicationDidBecomeActiveNotification - object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillResignActive:) - name:OWSApplicationWillResignActiveNotification - object:nil]; -} - -- (void)applicationDidBecomeActive:(UIApplication *)application -{ - OWSAssertIsOnMainThread(); - - @synchronized(self) - { - self.isAppActive = YES; - - [self ensureBackgroundTaskState]; - } -} - -- (void)applicationWillResignActive:(UIApplication *)application -{ - OWSAssertIsOnMainThread(); - - @synchronized(self) - { - self.isAppActive = NO; - - [self ensureBackgroundTaskState]; - } -} - -// This method registers a new task with this manager. We only bother -// requesting a background task from iOS if the app is inactive (or about -// to become inactive), so this will often not start a background task. -// -// Returns nil if adding this task _should have_ started a -// background task, but the background task couldn't be begun. -// In that case expirationBlock will not be called. -- (nullable OWSTaskId)addTaskWithExpirationBlock:(BackgroundTaskExpirationBlock)expirationBlock -{ - OWSAssertDebug(expirationBlock); - - OWSTaskId _Nullable taskId; - - @synchronized(self) - { - self.idCounter = self.idCounter + 1; - taskId = @(self.idCounter); - self.expirationMap[taskId] = expirationBlock; - - if (![self ensureBackgroundTaskState]) { - [self.expirationMap removeObjectForKey:taskId]; - return nil; - } - - [self.continuityTimer invalidate]; - self.continuityTimer = nil; - - return taskId; - } -} - -- (void)removeTask:(OWSTaskId)taskId -{ - OWSAssertDebug(taskId); - - @synchronized(self) - { - OWSAssertDebug(self.expirationMap[taskId] != nil); - - [self.expirationMap removeObjectForKey:taskId]; - - // This timer will ensure that we keep the background task active (if necessary) - // for an extra fraction of a second to provide continuity between tasks. - // This makes it easier and safer to use background tasks, since most code - // should be able to ensure background tasks by "narrowly" wrapping - // their core logic with a OWSBackgroundTask and not worrying about "hand off" - // between OWSBackgroundTasks. - [self.continuityTimer invalidate]; - self.continuityTimer = [NSTimer weakScheduledTimerWithTimeInterval:0.25f - target:self - selector:@selector(timerDidFire) - userInfo:nil - repeats:NO]; - - [self ensureBackgroundTaskState]; - } -} - -// Begins or end a background task if necessary. -- (BOOL)ensureBackgroundTaskState -{ - if (!CurrentAppContext().isMainApp) { - // We can't create background tasks in the SAE, but pretend that we succeeded. - return YES; - } - - @synchronized(self) - { - // We only want to have a background task if we are: - // a) "not active" AND - // b1) there is one or more active instance of OWSBackgroundTask OR... - // b2) ...there _was_ an active instance recently. - BOOL shouldHaveBackgroundTask = (!self.isAppActive && (self.expirationMap.count > 0 || self.continuityTimer)); - BOOL hasBackgroundTask = self.backgroundTaskId != UIBackgroundTaskInvalid; - - if (shouldHaveBackgroundTask == hasBackgroundTask) { - // Current state is correct. - return YES; - } else if (shouldHaveBackgroundTask) { - OWSLogInfo(@"Starting background task."); - return [self startBackgroundTask]; - } else { - // Need to end background task. - OWSLogInfo(@"Ending background task."); - UIBackgroundTaskIdentifier backgroundTaskId = self.backgroundTaskId; - self.backgroundTaskId = UIBackgroundTaskInvalid; - [CurrentAppContext() endBackgroundTask:backgroundTaskId]; - return YES; - } - } -} - -// Returns NO if the background task cannot be begun. -- (BOOL)startBackgroundTask -{ - OWSAssertDebug(CurrentAppContext().isMainApp); - - @synchronized(self) - { - OWSAssertDebug(self.backgroundTaskId == UIBackgroundTaskInvalid); - - self.backgroundTaskId = [CurrentAppContext() beginBackgroundTaskWithExpirationHandler:^{ - // Supposedly [UIApplication beginBackgroundTaskWithExpirationHandler]'s handler - // will always be called on the main thread, but in practice we've observed - // otherwise. - // - // See: - // https://developer.apple.com/documentation/uikit/uiapplication/1623031-beginbackgroundtaskwithexpiratio) - OWSAssertDebug([NSThread isMainThread]); - - [self backgroundTaskExpired]; - }]; - - // If the background task could not begin, return NO to indicate that. - if (self.backgroundTaskId == UIBackgroundTaskInvalid) { - OWSLogError(@"background task could not be started."); - - return NO; - } - return YES; - } -} - -- (void)backgroundTaskExpired -{ - UIBackgroundTaskIdentifier backgroundTaskId; - NSDictionary *expirationMap; - - @synchronized(self) - { - backgroundTaskId = self.backgroundTaskId; - self.backgroundTaskId = UIBackgroundTaskInvalid; - - expirationMap = [self.expirationMap copy]; - [self.expirationMap removeAllObjects]; - } - - // Supposedly [UIApplication beginBackgroundTaskWithExpirationHandler]'s handler - // will always be called on the main thread, but in practice we've observed - // otherwise. OWSBackgroundTask's API guarantees that completionBlock will - // always be called on the main thread, so we use DispatchSyncMainThreadSafe() - // to ensure that. We thereby ensure that we don't end the background task - // until all of the completion blocks have completed. - DispatchSyncMainThreadSafe(^{ - for (BackgroundTaskExpirationBlock expirationBlock in expirationMap.allValues) { - expirationBlock(); - } - if (backgroundTaskId != UIBackgroundTaskInvalid) { - // Apparently we need to "end" even expired background tasks. - [CurrentAppContext() endBackgroundTask:backgroundTaskId]; - } - }); -} - -- (void)timerDidFire -{ - @synchronized(self) - { - [self.continuityTimer invalidate]; - self.continuityTimer = nil; - - [self ensureBackgroundTaskState]; - } -} - -@end - -#pragma mark - - -@interface OWSBackgroundTask () - -@property (nonatomic, readonly) NSString *label; - -// This property should only be accessed while synchronized on this instance. -@property (nonatomic, nullable) OWSTaskId taskId; - -// This property should only be accessed while synchronized on this instance. -@property (nonatomic, nullable) BackgroundTaskCompletionBlock completionBlock; - -@end - -#pragma mark - - -@implementation OWSBackgroundTask - -+ (OWSBackgroundTask *)backgroundTaskWithLabelStr:(const char *)labelStr -{ - OWSAssertDebug(labelStr); - - NSString *label = [NSString stringWithFormat:@"%s", labelStr]; - return [[OWSBackgroundTask alloc] initWithLabel:label completionBlock:nil]; -} - -+ (OWSBackgroundTask *)backgroundTaskWithLabelStr:(const char *)labelStr - completionBlock:(BackgroundTaskCompletionBlock)completionBlock -{ - - OWSAssertDebug(labelStr); - - NSString *label = [NSString stringWithFormat:@"%s", labelStr]; - return [[OWSBackgroundTask alloc] initWithLabel:label completionBlock:completionBlock]; -} - -+ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label -{ - return [[OWSBackgroundTask alloc] initWithLabel:label completionBlock:nil]; -} - -+ (OWSBackgroundTask *)backgroundTaskWithLabel:(NSString *)label - completionBlock:(BackgroundTaskCompletionBlock)completionBlock -{ - return [[OWSBackgroundTask alloc] initWithLabel:label completionBlock:completionBlock]; -} - -- (instancetype)initWithLabel:(NSString *)label completionBlock:(BackgroundTaskCompletionBlock _Nullable)completionBlock -{ - self = [super init]; - - if (!self) { - return self; - } - - OWSAssertDebug(label.length > 0); - - _label = label; - self.completionBlock = completionBlock; - - [self startBackgroundTask]; - - return self; -} - -- (void)dealloc -{ - [self endBackgroundTask]; -} - -- (void)startBackgroundTask -{ - __weak typeof(self) weakSelf = self; - self.taskId = [OWSBackgroundTaskManager.sharedManager addTaskWithExpirationBlock:^{ - DispatchMainThreadSafe(^{ - OWSBackgroundTask *strongSelf = weakSelf; - if (!strongSelf) { - return; - } - OWSLogVerbose(@"task expired"); - - // Make a local copy of completionBlock to ensure that it is called - // exactly once. - BackgroundTaskCompletionBlock _Nullable completionBlock = nil; - - @synchronized(strongSelf) - { - if (!strongSelf.taskId) { - return; - } - OWSLogInfo(@"%@ background task expired.", strongSelf.label); - strongSelf.taskId = nil; - - completionBlock = strongSelf.completionBlock; - strongSelf.completionBlock = nil; - } - - if (completionBlock) { - completionBlock(BackgroundTaskState_Expired); - } - }); - }]; - - // If a background task could not be begun, call the completion block. - if (!self.taskId) { - OWSLogError(@"%@ background task could not be started.", self.label); - - // Make a local copy of completionBlock to ensure that it is called - // exactly once. - BackgroundTaskCompletionBlock _Nullable completionBlock; - @synchronized(self) - { - completionBlock = self.completionBlock; - self.completionBlock = nil; - } - if (completionBlock) { - DispatchMainThreadSafe(^{ - completionBlock(BackgroundTaskState_CouldNotStart); - }); - } - } -} - -- (void)endBackgroundTask -{ - // Make a local copy of this state, since this method is called by `dealloc`. - BackgroundTaskCompletionBlock _Nullable completionBlock; - - @synchronized(self) - { - if (!self.taskId) { - return; - } - [OWSBackgroundTaskManager.sharedManager removeTask:self.taskId]; - self.taskId = nil; - - completionBlock = self.completionBlock; - self.completionBlock = nil; - } - - // endBackgroundTask must be called on the main thread. - DispatchMainThreadSafe(^{ - if (completionBlock) { - completionBlock(BackgroundTaskState_Success); - } - }); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSBackupFragment.h b/SignalServiceKit/src/Util/OWSBackupFragment.h deleted file mode 100644 index 3acddd799..000000000 --- a/SignalServiceKit/src/Util/OWSBackupFragment.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSYapDatabaseObject.h" - -NS_ASSUME_NONNULL_BEGIN - -// We store metadata for known backup fragments (i.e. CloudKit record) in -// the database. We might learn about them from: -// -// * Past backup exports. -// * An import downloading and parsing the manifest of the last complete backup. -// -// Storing this data in the database provides continuity. -// -// * Backup exports can reuse fragments from previous Backup exports even if they -// don't complete (i.e. backup export resume). -// * Backup exports can reuse fragments from the backup import, if any. -@interface OWSBackupFragment : TSYapDatabaseObject - -@property (nonatomic) NSString *recordName; - -@property (nonatomic) NSData *encryptionKey; - -// This property is only set for certain types of manifest item, -// namely attachments where we need to know where the attachment's -// file should reside relative to the attachments folder. -@property (nonatomic, nullable) NSString *relativeFilePath; - -// This property is only set for attachments. -@property (nonatomic, nullable) NSString *attachmentId; - -// This property is only set if the manifest item is downloaded. -@property (nonatomic, nullable) NSString *downloadFilePath; - -// This property is only set if the manifest item is compressed. -@property (nonatomic, nullable) NSNumber *uncompressedDataLength; - -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSBackupFragment.m b/SignalServiceKit/src/Util/OWSBackupFragment.m deleted file mode 100644 index 87627f26f..000000000 --- a/SignalServiceKit/src/Util/OWSBackupFragment.m +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSBackupFragment.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSBackupFragment - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSDispatch.h b/SignalServiceKit/src/Util/OWSDispatch.h deleted file mode 100644 index 3d7b51b37..000000000 --- a/SignalServiceKit/src/Util/OWSDispatch.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSDispatch : NSObject - -/** - * Attachment downloading - */ -+ (dispatch_queue_t)attachmentsQueue; - -/** - * Serial message sending queue - */ -+ (dispatch_queue_t)sendingQueue; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSDispatch.m b/SignalServiceKit/src/Util/OWSDispatch.m deleted file mode 100644 index 5f300a2f9..000000000 --- a/SignalServiceKit/src/Util/OWSDispatch.m +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDispatch.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSDispatch - -+ (dispatch_queue_t)attachmentsQueue -{ - static dispatch_once_t onceToken; - static dispatch_queue_t queue; - dispatch_once(&onceToken, ^{ - queue = dispatch_queue_create("org.whispersystems.signal.attachments", NULL); - }); - return queue; -} - -+ (dispatch_queue_t)sendingQueue -{ - static dispatch_once_t onceToken; - static dispatch_queue_t queue; - dispatch_once(&onceToken, ^{ - dispatch_queue_attr_t attributes = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0); - queue = dispatch_queue_create("org.whispersystems.signal.sendQueue", attributes); - }); - return queue; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSError.h b/SignalServiceKit/src/Util/OWSError.h deleted file mode 100644 index 5600d449c..000000000 --- a/SignalServiceKit/src/Util/OWSError.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const OWSSignalServiceKitErrorDomain; - -typedef NS_ENUM(NSInteger, OWSErrorCode) { - OWSErrorCodeInvalidMethodParameters = 11, - OWSErrorCodeUnableToProcessServerResponse = 12, - OWSErrorCodeFailedToDecodeJson = 13, - OWSErrorCodeFailedToEncodeJson = 14, - OWSErrorCodeFailedToDecodeQR = 15, - OWSErrorCodePrivacyVerificationFailure = 20, - OWSErrorCodeUntrustedIdentity = 25, - OWSErrorCodeFailedToSendOutgoingMessage = 30, - OWSErrorCodeAssertionFailure = 31, - OWSErrorCodeFailedToDecryptMessage = 100, - OWSErrorCodeFailedToDecryptUDMessage = 101, - OWSErrorCodeFailedToEncryptMessage = 110, - OWSErrorCodeFailedToEncryptUDMessage = 111, - OWSErrorCodeSignalServiceFailure = 1001, - OWSErrorCodeSignalServiceRateLimited = 1010, - OWSErrorCodeUserError = 2001, - OWSErrorCodeNoSuchSignalRecipient = 777404, - OWSErrorCodeMessageSendDisabledDueToPreKeyUpdateFailures = 777405, - OWSErrorCodeMessageSendFailedToBlockList = 777406, - OWSErrorCodeMessageSendNoValidRecipients = 777407, - OWSErrorCodeContactsUpdaterRateLimit = 777408, - OWSErrorCodeCouldNotWriteAttachmentData = 777409, - OWSErrorCodeMessageDeletedBeforeSent = 777410, - OWSErrorCodeDatabaseConversionFatalError = 777411, - OWSErrorCodeMoveFileToSharedDataContainerError = 777412, - OWSErrorCodeRegistrationMissing2FAPIN = 777413, - OWSErrorCodeDebugLogUploadFailed = 777414, - // A non-recoverable error occured while exporting a backup. - OWSErrorCodeExportBackupFailed = 777415, - // A possibly recoverable error occured while exporting a backup. - OWSErrorCodeExportBackupError = 777416, - // A non-recoverable error occured while importing a backup. - OWSErrorCodeImportBackupFailed = 777417, - // A possibly recoverable error occured while importing a backup. - OWSErrorCodeImportBackupError = 777418, - // A non-recoverable while importing or exporting a backup. - OWSErrorCodeBackupFailure = 777419, - OWSErrorCodeLocalAuthenticationError = 777420, - OWSErrorCodeMessageRequestFailed = 777421, - OWSErrorCodeMessageResponseFailed = 777422, - OWSErrorCodeInvalidMessage = 777423, - OWSErrorCodeProfileUpdateFailed = 777424, - OWSErrorCodeAvatarWriteFailed = 777425, - OWSErrorCodeAvatarUploadFailed = 777426, - OWSErrorCodeNoSessionForTransientMessage, -}; - -extern NSString *const OWSErrorRecipientIdentifierKey; - -extern NSError *OWSErrorWithCodeDescription(OWSErrorCode code, NSString *description); -extern NSError *OWSErrorMakeUntrustedIdentityError(NSString *description, NSString *recipientId); -extern NSError *OWSErrorMakeUnableToProcessServerResponseError(void); -extern NSError *OWSErrorMakeFailedToSendOutgoingMessageError(void); -extern NSError *OWSErrorMakeNoSuchSignalRecipientError(void); -extern NSError *OWSErrorMakeAssertionError(NSString *description); -extern NSError *OWSErrorMakeMessageSendDisabledDueToPreKeyUpdateFailuresError(void); -extern NSError *OWSErrorMakeMessageSendFailedDueToBlockListError(void); -extern NSError *OWSErrorMakeWriteAttachmentDataError(void); - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSError.m b/SignalServiceKit/src/Util/OWSError.m deleted file mode 100644 index b089f235d..000000000 --- a/SignalServiceKit/src/Util/OWSError.m +++ /dev/null @@ -1,74 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSError.h" - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSSignalServiceKitErrorDomain = @"OWSSignalServiceKitErrorDomain"; -NSString *const OWSErrorRecipientIdentifierKey = @"OWSErrorKeyRecipientIdentifier"; - -NSError *OWSErrorWithCodeDescription(OWSErrorCode code, NSString *description) -{ - return [NSError errorWithDomain:OWSSignalServiceKitErrorDomain - code:code - userInfo:@{ NSLocalizedDescriptionKey: description }]; -} - -NSError *OWSErrorMakeUnableToProcessServerResponseError() -{ - return OWSErrorWithCodeDescription(OWSErrorCodeUnableToProcessServerResponse, - NSLocalizedString(@"ERROR_DESCRIPTION_SERVER_FAILURE", @"Generic server error")); -} - -NSError *OWSErrorMakeFailedToSendOutgoingMessageError() -{ - return OWSErrorWithCodeDescription(OWSErrorCodeFailedToSendOutgoingMessage, - NSLocalizedString(@"ERROR_DESCRIPTION_CLIENT_SENDING_FAILURE", @"Generic notice when message failed to send.")); -} - -NSError *OWSErrorMakeNoSuchSignalRecipientError() -{ - return OWSErrorWithCodeDescription(OWSErrorCodeNoSuchSignalRecipient, - NSLocalizedString( - @"ERROR_DESCRIPTION_UNREGISTERED_RECIPIENT", @"Error message when attempting to send message")); -} - -NSError *OWSErrorMakeAssertionError(NSString *description) -{ - OWSCFailDebug(@"Assertion failed: %@", description); - return OWSErrorWithCodeDescription(OWSErrorCodeAssertionFailure, - NSLocalizedString(@"ERROR_DESCRIPTION_UNKNOWN_ERROR", @"Worst case generic error message")); -} - -NSError *OWSErrorMakeUntrustedIdentityError(NSString *description, NSString *recipientId) -{ - return [NSError - errorWithDomain:OWSSignalServiceKitErrorDomain - code:OWSErrorCodeUntrustedIdentity - userInfo:@{ NSLocalizedDescriptionKey : description, OWSErrorRecipientIdentifierKey : recipientId }]; -} - -NSError *OWSErrorMakeMessageSendDisabledDueToPreKeyUpdateFailuresError() -{ - return OWSErrorWithCodeDescription(OWSErrorCodeMessageSendDisabledDueToPreKeyUpdateFailures, - NSLocalizedString(@"ERROR_DESCRIPTION_MESSAGE_SEND_DISABLED_PREKEY_UPDATE_FAILURES", - @"Error message indicating that message send is disabled due to prekey update failures")); -} - -NSError *OWSErrorMakeMessageSendFailedDueToBlockListError() -{ - return OWSErrorWithCodeDescription(OWSErrorCodeMessageSendFailedToBlockList, - NSLocalizedString(@"ERROR_DESCRIPTION_MESSAGE_SEND_FAILED_DUE_TO_BLOCK_LIST", - @"Error message indicating that message send failed due to block list")); -} - -NSError *OWSErrorMakeWriteAttachmentDataError() -{ - return OWSErrorWithCodeDescription(OWSErrorCodeCouldNotWriteAttachmentData, - NSLocalizedString(@"ERROR_DESCRIPTION_MESSAGE_SEND_FAILED_DUE_TO_FAILED_ATTACHMENT_WRITE", - @"Error message indicating that message send failed due to failed attachment write")); -} - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSFileSystem.h b/SignalServiceKit/src/Util/OWSFileSystem.h deleted file mode 100644 index aa4dcc90d..000000000 --- a/SignalServiceKit/src/Util/OWSFileSystem.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -// Use instead of NSTemporaryDirectory() -// prefer the more restrictice OWSTemporaryDirectory, -// unless the temp data may need to be accessed while the device is locked. -NSString *OWSTemporaryDirectory(void); -NSString *OWSTemporaryDirectoryAccessibleAfterFirstAuth(void); -void ClearOldTemporaryDirectories(void); - -@interface OWSFileSystem : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -+ (BOOL)protectFileOrFolderAtPath:(NSString *)path; -+ (BOOL)protectFileOrFolderAtPath:(NSString *)path fileProtectionType:(NSFileProtectionType)fileProtectionType; - -+ (BOOL)protectRecursiveContentsAtPath:(NSString *)path; - -+ (NSString *)appDocumentDirectoryPath; - -+ (NSString *)appLibraryDirectoryPath; - -+ (NSString *)appSharedDataDirectoryPath; - -+ (NSString *)cachesDirectoryPath; - -+ (nullable NSError *)renameFilePathUsingRandomExtension:(NSString *)oldFilePath; - -+ (nullable NSError *)moveAppFilePath:(NSString *)oldFilePath sharedDataFilePath:(NSString *)newFilePath; - -// Returns NO IFF the directory does not exist and could not be created. -+ (BOOL)ensureDirectoryExists:(NSString *)dirPath; - -+ (BOOL)ensureFileExists:(NSString *)filePath; - -+ (BOOL)deleteFile:(NSString *)filePath; - -+ (BOOL)deleteFileIfExists:(NSString *)filePath; - -+ (NSArray *_Nullable)allFilesInDirectoryRecursive:(NSString *)dirPath error:(NSError **)error; - -+ (NSString *)temporaryFilePath; -+ (NSString *)temporaryFilePathWithFileExtension:(NSString *_Nullable)fileExtension; - -// Returns nil on failure. -+ (nullable NSString *)writeDataToTemporaryFile:(NSData *)data fileExtension:(NSString *_Nullable)fileExtension; - -+ (nullable NSNumber *)fileSizeOfPath:(NSString *)filePath; -+ (void)logAttributesOfItemAtPathRecursively:(NSString *)path; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSFileSystem.m b/SignalServiceKit/src/Util/OWSFileSystem.m deleted file mode 100644 index 7938639c1..000000000 --- a/SignalServiceKit/src/Util/OWSFileSystem.m +++ /dev/null @@ -1,430 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSFileSystem.h" -#import "OWSError.h" -#import "TSConstants.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation OWSFileSystem - -+ (BOOL)protectRecursiveContentsAtPath:(NSString *)path -{ - BOOL isDirectory; - if (![NSFileManager.defaultManager fileExistsAtPath:path isDirectory:&isDirectory]) { - return NO; - } - - if (!isDirectory) { - return [self protectFileOrFolderAtPath:path]; - } - NSString *dirPath = path; - - BOOL success = YES; - NSDirectoryEnumerator *directoryEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:dirPath]; - - for (NSString *relativePath in directoryEnumerator) { - NSString *filePath = [dirPath stringByAppendingPathComponent:relativePath]; - OWSLogDebug(@"path: %@ had attributes: %@", filePath, directoryEnumerator.fileAttributes); - - success = success && [self protectFileOrFolderAtPath:filePath]; - } - - OWSLogInfo(@"protected contents at path: %@", path); - return success; -} - -+ (BOOL)protectFileOrFolderAtPath:(NSString *)path -{ - return - [self protectFileOrFolderAtPath:path fileProtectionType:NSFileProtectionCompleteUntilFirstUserAuthentication]; -} - -+ (BOOL)protectFileOrFolderAtPath:(NSString *)path fileProtectionType:(NSFileProtectionType)fileProtectionType -{ - OWSLogVerbose(@"protecting file at path: %@", path); - if (![NSFileManager.defaultManager fileExistsAtPath:path]) { - return NO; - } - - NSError *error; - NSDictionary *fileProtection = @{ NSFileProtectionKey : fileProtectionType }; - [[NSFileManager defaultManager] setAttributes:fileProtection ofItemAtPath:path error:&error]; - - NSDictionary *resourcesAttrs = @{ NSURLIsExcludedFromBackupKey : @YES }; - - NSURL *ressourceURL = [NSURL fileURLWithPath:path]; - BOOL success = [ressourceURL setResourceValues:resourcesAttrs error:&error]; - - if (error || !success) { - OWSFailDebug(@"Could not protect file or folder: %@", error); - OWSProdCritical([OWSAnalyticsEvents storageErrorFileProtection]); - return NO; - } - return YES; -} - -+ (void)logAttributesOfItemAtPathRecursively:(NSString *)path -{ - BOOL isDirectory; - BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]; - if (!exists) { - OWSFailDebug(@"error retrieving file attributes for missing file"); - return; - } - - if (isDirectory) { - NSDirectoryEnumerator *directoryEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:(NSString *)path]; - for (NSString *path in directoryEnumerator) { - OWSLogDebug(@"path: %@ has attributes: %@", path, directoryEnumerator.fileAttributes); - } - } else { - NSError *error; - NSDictionary *_Nullable attributes = - [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; - if (error) { - OWSFailDebug(@"error retrieving file attributes: %@", error); - } else { - OWSLogDebug(@"path: %@ has attributes: %@", path, attributes); - } - } -} - -+ (NSString *)appLibraryDirectoryPath -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSURL *documentDirectoryURL = - [[fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject]; - return [documentDirectoryURL path]; -} - -+ (NSString *)appDocumentDirectoryPath -{ - return CurrentAppContext().appDocumentDirectoryPath; -} - -+ (NSString *)appSharedDataDirectoryPath -{ - return CurrentAppContext().appSharedDataDirectoryPath; -} - -+ (NSString *)cachesDirectoryPath -{ - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - OWSAssertDebug(paths.count >= 1); - return paths[0]; -} - -+ (nullable NSError *)renameFilePathUsingRandomExtension:(NSString *)oldFilePath -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; - if (![fileManager fileExistsAtPath:oldFilePath]) { - return nil; - } - - NSString *newFilePath = - [[oldFilePath stringByAppendingString:@"."] stringByAppendingString:[NSUUID UUID].UUIDString]; - - OWSLogInfo(@"Moving file or directory from: %@ to: %@", oldFilePath, newFilePath); - - NSError *_Nullable error; - BOOL success = [fileManager moveItemAtPath:oldFilePath toPath:newFilePath error:&error]; - if (!success || error) { - OWSLogDebug(@"Could not move file or directory from: %@ to: %@, error: %@", oldFilePath, newFilePath, error); - OWSFailDebug(@"Could not move file or directory with error: %@", error); - return error; - } - return nil; -} - -+ (nullable NSError *)moveAppFilePath:(NSString *)oldFilePath sharedDataFilePath:(NSString *)newFilePath -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; - if (![fileManager fileExistsAtPath:oldFilePath]) { - return nil; - } - - OWSLogInfo(@"Moving file or directory from: %@ to: %@", oldFilePath, newFilePath); - - if ([fileManager fileExistsAtPath:newFilePath]) { - // If a file/directory already exists at the destination, - // try to move it "aside" by renaming it with an extension. - NSError *_Nullable error = [self renameFilePathUsingRandomExtension:newFilePath]; - if (error) { - return error; - } - } - - if ([fileManager fileExistsAtPath:newFilePath]) { - OWSLogDebug( - @"Can't move file or directory from: %@ to: %@; destination already exists.", oldFilePath, newFilePath); - OWSFailDebug(@"Can't move file or directory; destination already exists."); - return OWSErrorWithCodeDescription( - OWSErrorCodeMoveFileToSharedDataContainerError, @"Can't move file; destination already exists."); - } - - NSDate *startDate = [NSDate new]; - - NSError *_Nullable error; - BOOL success = [fileManager moveItemAtPath:oldFilePath toPath:newFilePath error:&error]; - if (!success || error) { - OWSLogDebug(@"Could not move file or directory from: %@ to: %@, error: %@", oldFilePath, newFilePath, error); - OWSFailDebug(@"Could not move file or directory with error: %@", error); - return error; - } - - OWSLogInfo(@"Moved file or directory in: %f", fabs([startDate timeIntervalSinceNow])); - OWSLogDebug(@"Moved file or directory from: %@ to: %@ in: %f", - oldFilePath, - newFilePath, - fabs([startDate timeIntervalSinceNow])); - - // Ensure all files moved have the proper data protection class. - // On large directories this can take a while, so we dispatch async - // since we're in the launch path. - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self protectRecursiveContentsAtPath:newFilePath]; - }); - - return nil; -} - -+ (BOOL)ensureDirectoryExists:(NSString *)dirPath -{ - return [self ensureDirectoryExists:dirPath fileProtectionType:NSFileProtectionCompleteUntilFirstUserAuthentication]; -} - -+ (BOOL)ensureDirectoryExists:(NSString *)dirPath fileProtectionType:(NSFileProtectionType)fileProtectionType -{ - BOOL isDirectory; - BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:dirPath isDirectory:&isDirectory]; - if (exists) { - OWSAssertDebug(isDirectory); - - return [self protectFileOrFolderAtPath:dirPath fileProtectionType:fileProtectionType]; - } else { - OWSLogInfo(@"Creating directory at: %@", dirPath); - - NSError *error = nil; - [[NSFileManager defaultManager] createDirectoryAtPath:dirPath - withIntermediateDirectories:YES - attributes:nil - error:&error]; - if (error) { - OWSFailDebug(@"Failed to create directory: %@, error: %@", dirPath, error); - return NO; - } - return [self protectFileOrFolderAtPath:dirPath fileProtectionType:fileProtectionType]; - } -} - -+ (BOOL)ensureFileExists:(NSString *)filePath -{ - BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filePath]; - if (exists) { - return [self protectFileOrFolderAtPath:filePath]; - } else { - BOOL success = [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]; - if (!success) { - OWSFailDebug(@"Failed to create file."); - return NO; - } - return [self protectFileOrFolderAtPath:filePath]; - } -} - -+ (BOOL)deleteFile:(NSString *)filePath -{ - NSError *error; - BOOL success = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; - if (!success || error) { - OWSLogError(@"Failed to delete file: %@", error.description); - return NO; - } - return YES; -} - -+ (BOOL)deleteFileIfExists:(NSString *)filePath -{ - if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { - return YES; - } - return [self deleteFile:filePath]; -} - -+ (NSArray *_Nullable)allFilesInDirectoryRecursive:(NSString *)dirPath error:(NSError **)error -{ - OWSAssertDebug(dirPath.length > 0); - - *error = nil; - - NSArray *filenames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:error]; - if (*error) { - OWSFailDebug(@"could not find files in directory: %@", *error); - return nil; - } - - NSMutableArray *filePaths = [NSMutableArray new]; - - for (NSString *filename in filenames) { - NSString *filePath = [dirPath stringByAppendingPathComponent:filename]; - - BOOL isDirectory; - [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]; - if (isDirectory) { - [filePaths addObjectsFromArray:[self allFilesInDirectoryRecursive:filePath error:error]]; - if (*error) { - return nil; - } - } else { - [filePaths addObject:filePath]; - } - } - - return filePaths; -} - -+ (NSString *)temporaryFilePath -{ - return [self temporaryFilePathWithFileExtension:nil]; -} - -+ (NSString *)temporaryFilePathWithFileExtension:(NSString *_Nullable)fileExtension -{ - NSString *temporaryDirectory = OWSTemporaryDirectory(); - NSString *tempFileName = NSUUID.UUID.UUIDString; - if (fileExtension.length > 0) { - tempFileName = [[tempFileName stringByAppendingString:@"."] stringByAppendingString:fileExtension]; - } - NSString *tempFilePath = [temporaryDirectory stringByAppendingPathComponent:tempFileName]; - - return tempFilePath; -} - -+ (nullable NSString *)writeDataToTemporaryFile:(NSData *)data fileExtension:(NSString *_Nullable)fileExtension -{ - OWSAssertDebug(data); - - NSString *tempFilePath = [self temporaryFilePathWithFileExtension:fileExtension]; - NSError *error; - BOOL success = [data writeToFile:tempFilePath options:NSDataWritingAtomic error:&error]; - if (!success || error) { - OWSFailDebug(@"could not write to temporary file: %@", error); - return nil; - } - - [self protectFileOrFolderAtPath:tempFilePath]; - - return tempFilePath; -} - -+ (nullable NSNumber *)fileSizeOfPath:(NSString *)filePath -{ - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSError *_Nullable error; - unsigned long long fileSize = - [[fileManager attributesOfItemAtPath:filePath error:&error][NSFileSize] unsignedLongLongValue]; - if (error) { - OWSLogError(@"Couldn't fetch file size[%@]: %@", filePath, error); - return nil; - } else { - return @(fileSize); - } -} - -@end - -#pragma mark - - -NSString *OWSTemporaryDirectory(void) -{ - static NSString *dirPath; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSString *dirName = [NSString stringWithFormat:@"ows_temp_%@", NSUUID.UUID.UUIDString]; - dirPath = [NSTemporaryDirectory() stringByAppendingPathComponent:dirName]; - BOOL success = [OWSFileSystem ensureDirectoryExists:dirPath fileProtectionType:NSFileProtectionComplete]; - OWSCAssert(success); - }); - return dirPath; -} - -NSString *OWSTemporaryDirectoryAccessibleAfterFirstAuth(void) -{ - NSString *dirPath = NSTemporaryDirectory(); - BOOL success = [OWSFileSystem ensureDirectoryExists:dirPath - fileProtectionType:NSFileProtectionCompleteUntilFirstUserAuthentication]; - OWSCAssert(success); - return dirPath; -} - -void ClearOldTemporaryDirectoriesSync(void) -{ - // Ignore the "current" temp directory. - NSString *currentTempDirName = OWSTemporaryDirectory().lastPathComponent; - - NSDate *thresholdDate = CurrentAppContext().appLaunchTime; - NSString *dirPath = NSTemporaryDirectory(); - NSError *error; - NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:&error]; - if (error) { - OWSCFailDebug(@"contentsOfDirectoryAtPath error: %@", error); - return; - } - for (NSString *fileName in fileNames) { - if (!CurrentAppContext().isAppForegroundAndActive) { - // Abort if app not active. - return; - } - if ([fileName isEqualToString:currentTempDirName]) { - continue; - } - - NSString *filePath = [dirPath stringByAppendingPathComponent:fileName]; - - // Delete files with either: - // - // a) "ows_temp" name prefix. - // b) modified time before app launch time. - if (![fileName hasPrefix:@"ows_temp"]) { - NSError *error; - NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error]; - if (!attributes || error) { - // This is fine; the file may have been deleted since we found it. - OWSLogError(@"Could not get attributes of file or directory at: %@", filePath); - continue; - } - // Don't delete files which were created in the last N minutes. - NSDate *creationDate = attributes.fileModificationDate; - if ([creationDate isAfterDate:thresholdDate]) { - OWSLogInfo(@"Skipping file due to age: %f", fabs([creationDate timeIntervalSinceNow])); - continue; - } - } - - OWSLogVerbose(@"Removing temp file or directory: %@", filePath); - if (![OWSFileSystem deleteFile:filePath]) { - // This can happen if the app launches before the phone is unlocked. - // Clean up will occur when app becomes active. - OWSLogWarn(@"Could not delete old temp directory: %@", filePath); - } - } -} - -// NOTE: We need to call this method on launch _and_ every time the app becomes active, -// since file protection may prevent it from succeeding in the background. -void ClearOldTemporaryDirectories(void) -{ - // We use the lowest priority queue for this, and wait N seconds - // to avoid interfering with app startup. - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.f * NSEC_PER_SEC)), - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), - ^{ - ClearOldTemporaryDirectoriesSync(); - }); -} - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSMath.h b/SignalServiceKit/src/Util/OWSMath.h deleted file mode 100644 index d09f00763..000000000 --- a/SignalServiceKit/src/Util/OWSMath.h +++ /dev/null @@ -1,112 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -CG_INLINE CGFloat CGFloatClamp(CGFloat value, CGFloat minValue, CGFloat maxValue) -{ - return MAX(minValue, MIN(maxValue, value)); -} - -CG_INLINE CGFloat CGFloatClamp01(CGFloat value) -{ - return CGFloatClamp(value, 0.f, 1.f); -} - -CG_INLINE CGFloat CGFloatLerp(CGFloat left, CGFloat right, CGFloat alpha) -{ - return (left * (1.f - alpha)) + (right * alpha); -} - -CG_INLINE CGFloat CGFloatInverseLerp(CGFloat value, CGFloat minValue, CGFloat maxValue) -{ - return (value - minValue) / (maxValue - minValue); -} - -// Ceil to an even number -CG_INLINE CGFloat CeilEven(CGFloat value) -{ - return 2.f * (CGFloat)ceil(value * 0.5f); -} - -CG_INLINE CGSize CGSizeCeil(CGSize size) -{ - return CGSizeMake((CGFloat)ceil(size.width), (CGFloat)ceil(size.height)); -} - -CG_INLINE CGSize CGSizeFloor(CGSize size) -{ - return CGSizeMake((CGFloat)floor(size.width), (CGFloat)floor(size.height)); -} - -CG_INLINE CGSize CGSizeRound(CGSize size) -{ - return CGSizeMake((CGFloat)round(size.width), (CGFloat)round(size.height)); -} - -CG_INLINE CGSize CGSizeMax(CGSize size1, CGSize size2) -{ - return CGSizeMake(MAX(size1.width, size2.width), MAX(size1.height, size2.height)); -} - -CG_INLINE CGPoint CGPointAdd(CGPoint left, CGPoint right) -{ - return CGPointMake(left.x + right.x, left.y + right.y); -} - -CG_INLINE CGPoint CGPointSubtract(CGPoint left, CGPoint right) -{ - return CGPointMake(left.x - right.x, left.y - right.y); -} - -CG_INLINE CGPoint CGPointScale(CGPoint point, CGFloat factor) -{ - return CGPointMake(point.x * factor, point.y * factor); -} - -CG_INLINE CGFloat CGPointDistance(CGPoint left, CGPoint right) -{ - CGPoint delta = CGPointSubtract(left, right); - return sqrt(delta.x * delta.x + delta.y * delta.y); -} - -CG_INLINE CGPoint CGPointMin(CGPoint left, CGPoint right) -{ - return CGPointMake(MIN(left.x, right.x), MIN(left.y, right.y)); -} - -CG_INLINE CGPoint CGPointMax(CGPoint left, CGPoint right) -{ - return CGPointMake(MAX(left.x, right.x), MAX(left.y, right.y)); -} - -CG_INLINE CGPoint CGPointClamp01(CGPoint point) -{ - return CGPointMake(CGFloatClamp01(point.x), CGFloatClamp01(point.y)); -} - -CG_INLINE CGPoint CGPointInvert(CGPoint point) -{ - return CGPointMake(-point.x, -point.y); -} - -CG_INLINE CGSize CGSizeScale(CGSize size, CGFloat factor) -{ - return CGSizeMake(size.width * factor, size.height * factor); -} - -CG_INLINE CGSize CGSizeAdd(CGSize left, CGSize right) -{ - return CGSizeMake(left.width + right.width, left.height + right.height); -} - -CG_INLINE CGRect CGRectScale(CGRect rect, CGFloat factor) -{ - CGRect result; - result.origin = CGPointScale(rect.origin, factor); - result.size = CGSizeScale(rect.size, factor); - return result; -} - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSOperation.h b/SignalServiceKit/src/Util/OWSOperation.h deleted file mode 100644 index ebeeaa53f..000000000 --- a/SignalServiceKit/src/Util/OWSOperation.h +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSInteger, OWSOperationState) { - OWSOperationStateNew, - OWSOperationStateExecuting, - OWSOperationStateFinished -}; - -// A base class for implementing retryable operations. -// To utilize the retryable behavior: -// Set remainingRetries to something greater than 0, and when you're reporting an error, -// set `error.isRetryable = YES`. -// If the failure is one that will not succeed upon retry, set `error.isFatal = YES`. -// -// isRetryable and isFatal are opposites but not redundant. -// -// If a group message send fails, the send will be retried if any of the errors were retryable UNLESS -// any of the errors were fatal. Fatal errors trump retryable errors. -@interface OWSOperation : NSOperation - -@property (readonly, nullable) NSError *failingError; - -// Defaults to 0, set to greater than 0 in init if you'd like the operation to be retryable. -@property NSUInteger remainingRetries; - -#pragma mark - Mandatory Subclass Overrides - -// Called every retry, this is where the bulk of the operation's work should go. -- (void)run; - -#pragma mark - Optional Subclass Overrides - -// Called one time only -- (nullable NSError *)checkForPreconditionError; - -// Called at most one time. -- (void)didSucceed; - -// Called at most one time. -- (void)didCancel; - -// Called zero or more times, retry may be possible -- (void)didReportError:(NSError *)error; - -// Called at most one time, once retry is no longer possible. -- (void)didFailWithError:(NSError *)error NS_SWIFT_NAME(didFail(error:)); - -// How long to wait before retry, if possible -- (NSTimeInterval)retryInterval; - -#pragma mark - Success/Error - Do Not Override - -// Runs now if a retry timer has been set by a previous failure, -// otherwise assumes we're currently running and does nothing. -- (void)runAnyQueuedRetry; - -// Report that the operation completed successfully. -// -// Each invocation of `run` must make exactly one call to one of: `reportSuccess`, `reportCancelled`, or `reportError:` -- (void)reportSuccess; - -// Call this when you abort before completion due to being cancelled. -// -// Each invocation of `run` must make exactly one call to one of: `reportSuccess`, `reportCancelled`, or `reportError:` -- (void)reportCancelled; - -// Report that the operation failed to complete due to an error. -// -// Each invocation of `run` must make exactly one call to one of: `reportSuccess`, `reportCancelled`, or `reportError:` -// You must ensure that `run` cannot succeed after calling `reportError`, e.g. generally you'll write something like -// this: -// -// [self reportError:someError]; -// return; -// -// If the error is terminal, and you want to avoid retry, report an error with `error.isFatal = YES` otherwise the -// operation will retry if possible. -- (void)reportError:(NSError *)error; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSOperation.m b/SignalServiceKit/src/Util/OWSOperation.m deleted file mode 100644 index 444d9c886..000000000 --- a/SignalServiceKit/src/Util/OWSOperation.m +++ /dev/null @@ -1,268 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSOperation.h" -#import "NSError+MessageSending.h" -#import "NSTimer+OWS.h" -#import "OWSBackgroundTask.h" -#import "OWSError.h" - -NS_ASSUME_NONNULL_BEGIN - -NSString *const OWSOperationKeyIsExecuting = @"isExecuting"; -NSString *const OWSOperationKeyIsFinished = @"isFinished"; - -@interface OWSOperation () - -@property (nullable) NSError *failingError; -@property (atomic) OWSOperationState operationState; -@property (nonatomic) OWSBackgroundTask *backgroundTask; - -// This property should only be accessed on the main queue. -@property (nonatomic) NSTimer *_Nullable retryTimer; - -@end - -@implementation OWSOperation - -- (instancetype)init -{ - self = [super init]; - if (!self) { - return self; - } - - _operationState = OWSOperationStateNew; - _backgroundTask = [OWSBackgroundTask backgroundTaskWithLabel:self.logTag]; - - // Operations are not retryable by default. - _remainingRetries = 0; - - return self; -} - -- (void)dealloc -{ - OWSLogDebug(@"in dealloc"); -} - -#pragma mark - Subclass Overrides - -// Called one time only -- (nullable NSError *)checkForPreconditionError -{ - // OWSOperation have a notion of failure, which is inferred by the presence of a `failingError`. - // - // By default, any failing dependency cascades that failure to it's dependent. - // If you'd like different behavior, override this method (`checkForPreconditionError`) without calling `super`. - for (NSOperation *dependency in self.dependencies) { - if (![dependency isKindOfClass:[OWSOperation class]]) { - // Native operations, like NSOperation and NSBlockOperation have no notion of "failure". - // So there's no `failingError` to cascade. - continue; - } - - OWSOperation *dependentOperation = (OWSOperation *)dependency; - - // Don't proceed if dependency failed - surface the dependency's error. - NSError *_Nullable dependencyError = dependentOperation.failingError; - if (dependencyError != nil) { - return dependencyError; - } - } - - return nil; -} - -// Called every retry, this is where the bulk of the operation's work should go. -- (void)run -{ - OWSAbstractMethod(); -} - -// Called at most one time. -- (void)didSucceed -{ - // no-op - // Override in subclass if necessary -} - -// Called at most one time. -- (void)didCancel -{ - // no-op - // Override in subclass if necessary -} - -// Called zero or more times, retry may be possible -- (void)didReportError:(NSError *)error -{ - // no-op - // Override in subclass if necessary -} - -// Called at most one time, once retry is no longer possible. -- (void)didFailWithError:(NSError *)error -{ - // no-op - // Override in subclass if necessary -} - -#pragma mark - NSOperation overrides - -// Do not override this method in a subclass instead, override `run` -- (void)main -{ - OWSLogDebug(@"started."); - NSError *_Nullable preconditionError = [self checkForPreconditionError]; - if (preconditionError) { - [self failOperationWithError:preconditionError]; - return; - } - - if (self.isCancelled) { - [self reportCancelled]; - return; - } - - [self run]; -} - -- (void)runAnyQueuedRetry -{ - dispatch_async(dispatch_get_main_queue(), ^{ - NSTimer *_Nullable retryTimer = self.retryTimer; - self.retryTimer = nil; - [retryTimer invalidate]; - - if (retryTimer != nil) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self run]; - }); - } else { - OWSLogVerbose(@"not re-running since operation is already running."); - } - }); -} - -#pragma mark - Public Methods - -// These methods are not intended to be subclassed -- (void)reportSuccess -{ - OWSLogDebug(@"succeeded."); - [self didSucceed]; - [self markAsComplete]; -} - -// These methods are not intended to be subclassed -- (void)reportCancelled -{ - OWSLogDebug(@"cancelled."); - [self didCancel]; - [self markAsComplete]; -} - -- (void)reportError:(NSError *)error -{ - OWSLogDebug(@"reportError: %@, fatal?: %d, retryable?: %d, remainingRetries: %lu", - error, - error.isFatal, - error.isRetryable, - (unsigned long)self.remainingRetries); - - [self didReportError:error]; - - if (error.isFatal) { - [self failOperationWithError:error]; - return; - } - - if (!error.isRetryable) { - [self failOperationWithError:error]; - return; - } - - if (self.remainingRetries == 0) { - [self failOperationWithError:error]; - return; - } - - self.remainingRetries--; - - dispatch_async(dispatch_get_main_queue(), ^{ - OWSAssertDebug(self.retryTimer == nil); - [self.retryTimer invalidate]; - - // The `scheduledTimerWith*` methods add the timer to the current thread's RunLoop. - // Since Operations typically run on a background thread, that would mean the background - // thread's RunLoop. However, the OS can spin down background threads if there's no work - // being done, so we run the risk of the timer's RunLoop being deallocated before it's - // fired. - // - // To ensure the timer's thread sticks around, we schedule it while on the main RunLoop. - self.retryTimer = [NSTimer weakScheduledTimerWithTimeInterval:self.retryInterval - target:self - selector:@selector(runAnyQueuedRetry) - userInfo:nil - repeats:NO]; - }); -} - -// Override in subclass if you want something more sophisticated, e.g. exponential backoff -- (NSTimeInterval)retryInterval -{ - return 0.1; -} - -#pragma mark - Life Cycle - -- (void)failOperationWithError:(NSError *)error -{ - OWSLogDebug(@"failed terminally."); - self.failingError = error; - - [self didFailWithError:error]; - [self markAsComplete]; -} - -- (BOOL)isExecuting -{ - return self.operationState == OWSOperationStateExecuting; -} - -- (BOOL)isFinished -{ - return self.operationState == OWSOperationStateFinished; -} - -- (void)start -{ - [self willChangeValueForKey:OWSOperationKeyIsExecuting]; - self.operationState = OWSOperationStateExecuting; - [self didChangeValueForKey:OWSOperationKeyIsExecuting]; - - [self main]; -} - -- (void)markAsComplete -{ - [self willChangeValueForKey:OWSOperationKeyIsExecuting]; - [self willChangeValueForKey:OWSOperationKeyIsFinished]; - - // Ensure we call the success or failure handler exactly once. - @synchronized(self) - { - OWSAssertDebug(self.operationState != OWSOperationStateFinished); - - self.operationState = OWSOperationStateFinished; - } - - [self didChangeValueForKey:OWSOperationKeyIsExecuting]; - [self didChangeValueForKey:OWSOperationKeyIsFinished]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSQueues.h b/SignalServiceKit/src/Util/OWSQueues.h deleted file mode 100644 index eace0d71b..000000000 --- a/SignalServiceKit/src/Util/OWSQueues.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -#define AssertOnDispatchQueue(queue) \ - { \ - if (@available(iOS 10.0, *)) { \ - dispatch_assert_queue(queue); \ - } else { \ - _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \ - OWSAssertDebug(dispatch_get_current_queue() == queue); \ - _Pragma("clang diagnostic pop") \ - } \ - } - -#else - -#define AssertOnDispatchQueue(queue) - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSSyncManagerProtocol.h b/SignalServiceKit/src/Util/OWSSyncManagerProtocol.h deleted file mode 100644 index ae6d68a6a..000000000 --- a/SignalServiceKit/src/Util/OWSSyncManagerProtocol.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@class AnyPromise; -@class SignalAccount; -@class YapDatabaseReadTransaction; -@class TSGroupThread; - -@protocol OWSSyncManagerProtocol - -- (void)sendConfigurationSyncMessage; - -- (AnyPromise *)syncLocalContact __attribute__((warn_unused_result)); - -- (AnyPromise *)syncContact:(NSString *)hexEncodedPubKey transaction:(YapDatabaseReadTransaction *)transaction; - -- (AnyPromise *)syncAllContacts __attribute__((warn_unused_result)); - -- (AnyPromise *)syncContactsForSignalAccounts:(NSArray *)signalAccounts __attribute__((warn_unused_result)); - -- (AnyPromise *)syncAllGroups __attribute__((warn_unused_result)); - -- (AnyPromise *)syncGroupForThread:(TSGroupThread *)thread; - -- (AnyPromise *)syncAllOpenGroups __attribute__((warn_unused_result)); - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/ParamParser.swift b/SignalServiceKit/src/Util/ParamParser.swift deleted file mode 100644 index 9a470ebb6..000000000 --- a/SignalServiceKit/src/Util/ParamParser.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -// A DSL for parsing expected and optional values from a Dictionary, appropriate for -// validating a service response. -// -// Additionally it includes some helpers to DRY up common conversions. -// -// Rather than exhaustively enumerate accessors for types like `requireUInt32`, `requireInt64`, etc. -// We instead leverage generics at the call site. -// -// do { -// // Required -// let name: String = try paramParser.required(key: "name") -// let count: UInt32 = try paramParser.required(key: "count") -// -// // Optional -// let last_seen: Date? = try paramParser.optional(key: "last_seen") -// -// return Foo(name: name, count: count, isNew: lastSeen == nil) -// } catch { -// handleInvalidResponse(error: error) -// } -// -public class ParamParser { - public typealias Key = AnyHashable - - let dictionary: Dictionary - - public init(dictionary: Dictionary) { - self.dictionary = dictionary - } - - public convenience init?(responseObject: Any?) { - guard let responseDict = responseObject as? [String: AnyObject] else { - return nil - } - - self.init(dictionary: responseDict) - } - - // MARK: Errors - - public enum ParseError: Error { - case missingField(Key) - case invalidFormat(Key) - } - - private func invalid(key: Key) -> ParseError { - return ParseError.invalidFormat(key) - } - - private func missing(key: Key) -> ParseError { - return ParseError.missingField(key) - } - - // MARK: - Public API - - public func required(key: Key) throws -> T { - guard let value: T = try optional(key: key) else { - throw missing(key: key) - } - - return value - } - - public func optional(key: Key) throws -> T? { - guard let someValue = dictionary[key] else { - return nil - } - - guard !(someValue is NSNull) else { - return nil - } - - guard let typedValue = someValue as? T else { - throw invalid(key: key) - } - - return typedValue - } - - // MARK: FixedWidthIntegers (e.g. Int, Int32, UInt, UInt32, etc.) - - // You can't blindly cast accross Integer types, so we need to specify and validate which Int type we want. - // In general, you'll find numeric types parsed into a Dictionary as `Int`. - - public func required(key: Key) throws -> T where T: FixedWidthInteger { - guard let value: T = try optional(key: key) else { - throw missing(key: key) - } - - return value - } - - public func optional(key: Key) throws -> T? where T: FixedWidthInteger { - guard let someValue: Any = try optional(key: key) else { - return nil - } - - switch someValue { - case let typedValue as T: - return typedValue - case let int as Int: - guard int >= T.min, int <= T.max else { - throw invalid(key: key) - } - return T(int) - default: - throw invalid(key: key) - } - } - - // MARK: Base64 Data - - public func requiredBase64EncodedData(key: Key) throws -> Data { - guard let data: Data = try optionalBase64EncodedData(key: key) else { - throw ParseError.missingField(key) - } - - return data - } - - public func optionalBase64EncodedData(key: Key) throws -> Data? { - guard let encodedData: String = try self.optional(key: key) else { - return nil - } - - guard let data = Data(base64Encoded: encodedData) else { - throw ParseError.invalidFormat(key) - } - - guard data.count > 0 else { - return nil - } - - return data - } -} diff --git a/SignalServiceKit/src/Util/Promise+retainUntilComplete.swift b/SignalServiceKit/src/Util/Promise+retainUntilComplete.swift deleted file mode 100644 index 34a9f60c0..000000000 --- a/SignalServiceKit/src/Util/Promise+retainUntilComplete.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import PromiseKit - -@objc -public extension AnyPromise { - /** - * Sometimes there isn't a straight forward candidate to retain a promise, in that case we tell the - * promise to self retain, until it completes to avoid the risk it's GC'd before completion. - */ - @objc - func retainUntilComplete() { - var retainCycle: AnyPromise? = self - _ = self.ensure { - assert(retainCycle != nil) - retainCycle = nil - } - } -} - -public extension PMKFinalizer { - /** - * Sometimes there isn't a straight forward candidate to retain a promise, in that case we tell the - * promise to self retain, until it completes to avoid the risk it's GC'd before completion. - */ - func retainUntilComplete() { - var retainCycle: PMKFinalizer? = self - _ = self.finally { - assert(retainCycle != nil) - retainCycle = nil - } - } -} - -public extension Promise { - /** - * Sometimes there isn't a straight forward candidate to retain a promise, in that case we tell the - * promise to self retain, until it completes to avoid the risk it's GC'd before completion. - */ - func retainUntilComplete() { - var retainCycle: Promise? = self - _ = self.ensure { - assert(retainCycle != nil) - retainCycle = nil - } - } -} - -public extension Guarantee { - /** - * Sometimes there isn't a straight forward candidate to retain a promise, in that case we tell the - * promise to self retain, until it completes to avoid the risk it's GC'd before completion. - */ - func retainUntilComplete() { - var retainCycle: Guarantee? = self - _ = self.done { _ in - assert(retainCycle != nil) - retainCycle = nil - } - } -} diff --git a/SignalServiceKit/src/Util/ReverseDispatchQueue.swift b/SignalServiceKit/src/Util/ReverseDispatchQueue.swift deleted file mode 100644 index 1133f4384..000000000 --- a/SignalServiceKit/src/Util/ReverseDispatchQueue.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -// This is intended to be a drop-in replacement for DispatchQueue -// that processes its queue in reverse order. -@objc -public class ReverseDispatchQueue: NSObject { - - private static let isVerbose: Bool = false - - private let label: String - private let serialQueue: DispatchQueue - - // TODO: We could allow creation with various QOS. - @objc - public required init(label: String) { - self.label = label - serialQueue = DispatchQueue(label: label) - - super.init() - } - - public typealias WorkBlock = () -> Void - - private class Item { - let workBlock: WorkBlock - let index: UInt64 - - required init(workBlock : @escaping WorkBlock, index: UInt64) { - self.workBlock = workBlock - self.index = index - } - } - - // These properties should only be accessed on serialQueue. - private var items = [Item]() - private var indexCounter: UInt64 = 0 - - @objc - public func async(workBlock : @escaping WorkBlock) { - serialQueue.async { - self.indexCounter = self.indexCounter + 1 - let index = self.indexCounter - let item = Item(workBlock: workBlock, index: index ) - self.items.append(item) - - if ReverseDispatchQueue.isVerbose { - Logger.verbose("Enqueued[\(self.label)]: \(item.index)") - } - - self.process() - } - } - - private func process() { - serialQueue.async { - // Note that we popLast() so that we process - // the queue in the _reverse_ order from - // which it was enqueued. - guard let item = self.items.popLast() else { - // No enqueued work to do. - return - } - if ReverseDispatchQueue.isVerbose { - Logger.verbose("Processing[\(self.label)]: \(item.index)") - } - item.workBlock() - - self.process() - } - } -} diff --git a/SignalServiceKit/src/Util/SSKAsserts.h b/SignalServiceKit/src/Util/SSKAsserts.h deleted file mode 100755 index f63bcdb7d..000000000 --- a/SignalServiceKit/src/Util/SSKAsserts.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "AppContext.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#pragma mark - Singleton Asserts - -// The "singleton asserts" can be used to ensure -// that we only create a singleton once. -// -// The simplest way to use them is the OWSSingletonAssert() macro. -// It is intended to be used inside the singleton's initializer. -// -// If, however, a singleton has multiple possible initializers, -// you need to: -// -// 1. Use OWSSingletonAssertFlag() outside the class definition. -// 2. Use OWSSingletonAssertInit() in each initializer. - -#ifdef DEBUG - -#define ENFORCE_SINGLETONS - -#endif - -#ifdef ENFORCE_SINGLETONS - -#define OWSSingletonAssertFlag() static BOOL _isSingletonCreated = NO; - -#define OWSSingletonAssertInit() \ - @synchronized([self class]) { \ - if (!CurrentAppContext().isRunningTests) { \ - OWSAssertDebug(!_isSingletonCreated); \ - _isSingletonCreated = YES; \ - } \ - } - -#define OWSSingletonAssert() OWSSingletonAssertFlag() OWSSingletonAssertInit() - -#else - -#define OWSSingletonAssertFlag() -#define OWSSingletonAssertInit() -#define OWSSingletonAssert() - -#endif - -#define OWSFailDebugUnlessRunningTests(_messageFormat, ...) \ - do { \ - if (!CurrentAppContext().isRunningTests) { \ - OWSFailDebug(_messageFormat, ##__VA_ARGS__); \ - } \ - } while (0) - -#define OWSCFailDebugUnlessRunningTests(_messageFormat, ...) \ - do { \ - if (!CurrentAppContext().isRunningTests) { \ - OWSCFailDebug(_messageFormat, ##__VA_ARGS__); \ - } \ - } while (NO) - - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/SSKPreferences.swift b/SignalServiceKit/src/Util/SSKPreferences.swift deleted file mode 100644 index 655903fd2..000000000 --- a/SignalServiceKit/src/Util/SSKPreferences.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation - -@objc -public class SSKPreferences: NSObject { - // Never instantiate this class. - private override init() {} - - private static let collection = "SSKPreferences" - - // MARK: - - - private static let areLinkPreviewsEnabledKey = "areLinkPreviewsEnabled" - - @objc - public static var areLinkPreviewsEnabled: Bool { - get { - return getBool(key: areLinkPreviewsEnabledKey, defaultValue: false) - } - set { - setBool(newValue, key: areLinkPreviewsEnabledKey) - - SSKEnvironment.shared.syncManager.sendConfigurationSyncMessage() - } - } - - // MARK: - - - private static let hasSavedThreadKey = "hasSavedThread" - - @objc - public static var hasSavedThread: Bool { - get { - return getBool(key: hasSavedThreadKey) - } - set { - setBool(newValue, key: hasSavedThreadKey) - } - } - - @objc - public class func setHasSavedThread(value: Bool, transaction: YapDatabaseReadWriteTransaction) { - transaction.setBool(value, - forKey: hasSavedThreadKey, - inCollection: collection) - } - - // MARK: - - - private class func getBool(key: String, defaultValue: Bool = false) -> Bool { - return OWSPrimaryStorage.dbReadConnection().bool(forKey: key, inCollection: collection, defaultValue: defaultValue) - } - - private class func setBool(_ value: Bool, key: String) { - OWSPrimaryStorage.dbReadWriteConnection().setBool(value, forKey: key, inCollection: collection) - } -} diff --git a/SignalServiceKit/src/Util/String+SSK.swift b/SignalServiceKit/src/Util/String+SSK.swift deleted file mode 100644 index cdfdaee57..000000000 --- a/SignalServiceKit/src/Util/String+SSK.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation - -public extension String { - public var digitsOnly: String { - return (self as NSString).digitsOnly() - } - - func rtlSafeAppend(_ string: String) -> String { - return (self as NSString).rtlSafeAppend(string) - } - - public func substring(from index: Int) -> String { - return String(self[self.index(self.startIndex, offsetBy: index)...]) - } - - public func substring(to index: Int) -> String { - return String(prefix(index)) - } - - enum StringError: Error { - case invalidCharacterShift - } -} - -// MARK: - Selector Encoding - -private let selectorOffset: UInt32 = 17 - -public extension String { - - public func caesar(shift: UInt32) throws -> String { - let shiftedScalars: [UnicodeScalar] = try unicodeScalars.map { c in - guard let shiftedScalar = UnicodeScalar((c.value + shift) % 127) else { - owsFailDebug("invalidCharacterShift") - throw StringError.invalidCharacterShift - } - return shiftedScalar - } - return String(String.UnicodeScalarView(shiftedScalars)) - } - - public var encodedForSelector: String? { - guard let shifted = try? self.caesar(shift: selectorOffset) else { - owsFailDebug("shifted was unexpectedly nil") - return nil - } - - guard let data = shifted.data(using: .utf8) else { - owsFailDebug("data was unexpectedly nil") - return nil - } - - return data.base64EncodedString() - } - - public var decodedForSelector: String? { - guard let data = Data(base64Encoded: self) else { - owsFailDebug("data was unexpectedly nil") - return nil - } - - guard let shifted = String(data: data, encoding: .utf8) else { - owsFailDebug("shifted was unexpectedly nil") - return nil - } - - return try? shifted.caesar(shift: 127 - selectorOffset) - } -} - -public extension NSString { - - @objc - public var encodedForSelector: String? { - return (self as String).encodedForSelector - } - - @objc - public var decodedForSelector: String? { - return (self as String).decodedForSelector - } -} diff --git a/SignalServiceKit/src/Util/SwiftSingletons.swift b/SignalServiceKit/src/Util/SwiftSingletons.swift deleted file mode 100644 index 346069bf1..000000000 --- a/SignalServiceKit/src/Util/SwiftSingletons.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -public class SwiftSingletons: NSObject { - public static let shared = SwiftSingletons() - - private var classSet = Set() - - private override init() { - super.init() - } - - public func register(_ singleton: AnyObject) { - guard !CurrentAppContext().isRunningTests else { - return - } - guard _isDebugAssertConfiguration() else { - return - } - let singletonClassName = String(describing: type(of: singleton)) - guard !classSet.contains(singletonClassName) else { - owsFailDebug("Duplicate singleton: \(singletonClassName).") - return - } - Logger.verbose("Registering singleton: \(singletonClassName).") - classSet.insert(singletonClassName) - } - - public static func register(_ singleton: AnyObject) { - shared.register(singleton) - } -} diff --git a/SignalServiceKit/src/Util/TypingIndicators.swift b/SignalServiceKit/src/Util/TypingIndicators.swift deleted file mode 100644 index d0e7ebd73..000000000 --- a/SignalServiceKit/src/Util/TypingIndicators.swift +++ /dev/null @@ -1,459 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - -@objc(OWSTypingIndicators) -public protocol TypingIndicators: class { - @objc - func didStartTypingOutgoingInput(inThread thread: TSThread) - - @objc - func didStopTypingOutgoingInput(inThread thread: TSThread) - - @objc - func didSendOutgoingMessage(inThread thread: TSThread) - - @objc - func didReceiveTypingStartedMessage(inThread thread: TSThread, recipientId: String, deviceId: UInt) - - @objc - func didReceiveTypingStoppedMessage(inThread thread: TSThread, recipientId: String, deviceId: UInt) - - @objc - func didReceiveIncomingMessage(inThread thread: TSThread, recipientId: String, deviceId: UInt) - - // Returns the recipient id of the user who should currently be shown typing for a given thread. - // - // If no one is typing in that thread, returns nil. - // If multiple users are typing in that thread, returns the user to show. - // - // TODO: Use this method. - @objc - func typingRecipientId(forThread thread: TSThread) -> String? - - @objc - func setTypingIndicatorsEnabled(value: Bool) - - @objc - func areTypingIndicatorsEnabled() -> Bool -} - -// MARK: - - -@objc(OWSTypingIndicatorsImpl) -public class TypingIndicatorsImpl: NSObject, TypingIndicators { - - @objc - public static let typingIndicatorStateDidChange = Notification.Name("typingIndicatorStateDidChange") - - private let kDatabaseCollection = "TypingIndicators" - private let kDatabaseKey_TypingIndicatorsEnabled = "kDatabaseKey_TypingIndicatorsEnabled" - - private var _areTypingIndicatorsEnabled = false - - public override init() { - super.init() - - AppReadiness.runNowOrWhenAppWillBecomeReady { - self.setup() - } - } - - private func setup() { - AssertIsOnMainThread() - - _areTypingIndicatorsEnabled = primaryStorage.dbReadConnection.bool(forKey: kDatabaseKey_TypingIndicatorsEnabled, inCollection: kDatabaseCollection, defaultValue: true) - } - - // MARK: - Dependencies - - private var primaryStorage: OWSPrimaryStorage { - return SSKEnvironment.shared.primaryStorage - } - - private var syncManager: OWSSyncManagerProtocol { - return SSKEnvironment.shared.syncManager - } - - // MARK: - - - @objc - public func setTypingIndicatorsEnabled(value: Bool) { - AssertIsOnMainThread() - Logger.info("\(_areTypingIndicatorsEnabled) -> \(value)") - _areTypingIndicatorsEnabled = value - - primaryStorage.dbReadWriteConnection.setBool(value, forKey: kDatabaseKey_TypingIndicatorsEnabled, inCollection: kDatabaseCollection) - - syncManager.sendConfigurationSyncMessage() - - NotificationCenter.default.postNotificationNameAsync(TypingIndicatorsImpl.typingIndicatorStateDidChange, object: nil) - } - - @objc - public func areTypingIndicatorsEnabled() -> Bool { - AssertIsOnMainThread() - - return _areTypingIndicatorsEnabled - } - - // MARK: - - - @objc - public func didStartTypingOutgoingInput(inThread thread: TSThread) { - AssertIsOnMainThread() - guard let outgoingIndicators = ensureOutgoingIndicators(forThread: thread) else { - owsFailDebug("Could not locate outgoing indicators state") - return - } - outgoingIndicators.didStartTypingOutgoingInput() - } - - @objc - public func didStopTypingOutgoingInput(inThread thread: TSThread) { - AssertIsOnMainThread() - guard let outgoingIndicators = ensureOutgoingIndicators(forThread: thread) else { - owsFailDebug("Could not locate outgoing indicators state") - return - } - outgoingIndicators.didStopTypingOutgoingInput() - } - - @objc - public func didSendOutgoingMessage(inThread thread: TSThread) { - AssertIsOnMainThread() - guard let outgoingIndicators = ensureOutgoingIndicators(forThread: thread) else { - owsFailDebug("Could not locate outgoing indicators state") - return - } - outgoingIndicators.didSendOutgoingMessage() - } - - @objc - public func didReceiveTypingStartedMessage(inThread thread: TSThread, recipientId: String, deviceId: UInt) { - AssertIsOnMainThread() - Logger.info("") - let incomingIndicators = ensureIncomingIndicators(forThread: thread, recipientId: recipientId, deviceId: deviceId) - incomingIndicators.didReceiveTypingStartedMessage() - } - - @objc - public func didReceiveTypingStoppedMessage(inThread thread: TSThread, recipientId: String, deviceId: UInt) { - AssertIsOnMainThread() - Logger.info("") - let incomingIndicators = ensureIncomingIndicators(forThread: thread, recipientId: recipientId, deviceId: deviceId) - incomingIndicators.didReceiveTypingStoppedMessage() - } - - @objc - public func didReceiveIncomingMessage(inThread thread: TSThread, recipientId: String, deviceId: UInt) { - AssertIsOnMainThread() - Logger.info("") - let incomingIndicators = ensureIncomingIndicators(forThread: thread, recipientId: recipientId, deviceId: deviceId) - incomingIndicators.didReceiveIncomingMessage() - } - - @objc - public func typingRecipientId(forThread thread: TSThread) -> String? { - AssertIsOnMainThread() - - guard areTypingIndicatorsEnabled() else { - return nil - } - - var firstRecipientId: String? - var firstTimestamp: UInt64? - - let threadKey = incomingIndicatorsKey(forThread: thread) - guard let deviceMap = incomingIndicatorsMap[threadKey] else { - // No devices are typing in this thread. - return nil - } - for incomingIndicators in deviceMap.values { - guard incomingIndicators.isTyping else { - continue - } - guard let startedTypingTimestamp = incomingIndicators.startedTypingTimestamp else { - owsFailDebug("Typing device is missing start timestamp.") - continue - } - if let firstTimestamp = firstTimestamp, - firstTimestamp < startedTypingTimestamp { - // More than one recipient/device is typing in this conversation; - // prefer the one that started typing first. - continue - } - firstRecipientId = incomingIndicators.recipientId - firstTimestamp = startedTypingTimestamp - } - return firstRecipientId - } - - // MARK: - - - // Map of thread id-to-OutgoingIndicators. - private var outgoingIndicatorsMap = [String: OutgoingIndicators]() - - private func ensureOutgoingIndicators(forThread thread: TSThread) -> OutgoingIndicators? { - AssertIsOnMainThread() - - guard let threadId = thread.uniqueId else { - owsFailDebug("Thread missing id") - return nil - } - if let outgoingIndicators = outgoingIndicatorsMap[threadId] { - return outgoingIndicators - } - let outgoingIndicators = OutgoingIndicators(delegate: self, thread: thread) - outgoingIndicatorsMap[threadId] = outgoingIndicators - return outgoingIndicators - } - - // The sender maintains two timers per chat: - // - // A sendPause timer - // A sendRefresh timer - private class OutgoingIndicators { - private weak var delegate: TypingIndicators? - private let thread: TSThread - private var sendPauseTimer: Timer? - private var sendRefreshTimer: Timer? - - init(delegate: TypingIndicators, thread: TSThread) { - self.delegate = delegate - self.thread = thread - } - - // MARK: - Dependencies - - private var messageSender: MessageSender { - return SSKEnvironment.shared.messageSender - } - - // MARK: - - - func didStartTypingOutgoingInput() { - AssertIsOnMainThread() - - if sendRefreshTimer == nil { - // If the user types a character into the compose box, and the sendRefresh timer isn’t running: - - sendTypingMessageIfNecessary(forThread: thread, action: .started) - - sendRefreshTimer?.invalidate() - sendRefreshTimer = Timer.weakScheduledTimer(withTimeInterval: 10, - target: self, - selector: #selector(OutgoingIndicators.sendRefreshTimerDidFire), - userInfo: nil, - repeats: false) - } else { - // If the user types a character into the compose box, and the sendRefresh timer is running: - } - - sendPauseTimer?.invalidate() - sendPauseTimer = Timer.weakScheduledTimer(withTimeInterval: 3, - target: self, - selector: #selector(OutgoingIndicators.sendPauseTimerDidFire), - userInfo: nil, - repeats: false) - } - - func didStopTypingOutgoingInput() { - AssertIsOnMainThread() - - sendTypingMessageIfNecessary(forThread: thread, action: .stopped) - - sendRefreshTimer?.invalidate() - sendRefreshTimer = nil - - sendPauseTimer?.invalidate() - sendPauseTimer = nil - } - - @objc - func sendPauseTimerDidFire() { - AssertIsOnMainThread() - - sendTypingMessageIfNecessary(forThread: thread, action: .stopped) - - sendRefreshTimer?.invalidate() - sendRefreshTimer = nil - - sendPauseTimer?.invalidate() - sendPauseTimer = nil - } - - @objc - func sendRefreshTimerDidFire() { - AssertIsOnMainThread() - - sendTypingMessageIfNecessary(forThread: thread, action: .started) - - sendRefreshTimer?.invalidate() - sendRefreshTimer = Timer.weakScheduledTimer(withTimeInterval: 10, - target: self, - selector: #selector(sendRefreshTimerDidFire), - userInfo: nil, - repeats: false) - } - - func didSendOutgoingMessage() { - AssertIsOnMainThread() - - sendRefreshTimer?.invalidate() - sendRefreshTimer = nil - - sendPauseTimer?.invalidate() - sendPauseTimer = nil - } - - private func sendTypingMessageIfNecessary(forThread thread: TSThread, action: TypingIndicatorAction) { - Logger.verbose("\(TypingIndicatorMessage.string(forTypingIndicatorAction: action))") - - guard let delegate = delegate else { - owsFailDebug("Missing delegate.") - return - } - // `areTypingIndicatorsEnabled` reflects the user-facing setting in the app preferences. - // If it's disabled we don't want to emit "typing indicator" messages - // or show typing indicators for other users. - guard delegate.areTypingIndicatorsEnabled() else { - return - } - - if !SessionMetaProtocol.shouldSendTypingIndicator(in: thread) { return } - - let message = TypingIndicatorMessage(thread: thread, action: action) - messageSender.sendPromise(message: message).retainUntilComplete() - } - } - - // MARK: - - - // Map of (thread id)-to-(recipient id and device id)-to-IncomingIndicators. - private var incomingIndicatorsMap = [String: [String: IncomingIndicators]]() - - private func incomingIndicatorsKey(forThread thread: TSThread) -> String { - return String(describing: thread.uniqueId) - } - - private func incomingIndicatorsKey(recipientId: String, deviceId: UInt) -> String { - return "\(recipientId) \(deviceId)" - } - - private func ensureIncomingIndicators(forThread thread: TSThread, recipientId: String, deviceId: UInt) -> IncomingIndicators { - AssertIsOnMainThread() - - let threadKey = incomingIndicatorsKey(forThread: thread) - let deviceKey = incomingIndicatorsKey(recipientId: recipientId, deviceId: deviceId) - guard let deviceMap = incomingIndicatorsMap[threadKey] else { - let incomingIndicators = IncomingIndicators(delegate: self, thread: thread, recipientId: recipientId, deviceId: deviceId) - incomingIndicatorsMap[threadKey] = [deviceKey: incomingIndicators] - return incomingIndicators - } - guard let incomingIndicators = deviceMap[deviceKey] else { - let incomingIndicators = IncomingIndicators(delegate: self, thread: thread, recipientId: recipientId, deviceId: deviceId) - var deviceMapCopy = deviceMap - deviceMapCopy[deviceKey] = incomingIndicators - incomingIndicatorsMap[threadKey] = deviceMapCopy - return incomingIndicators - } - return incomingIndicators - } - - // The receiver maintains one timer for each (sender, device) in a chat: - private class IncomingIndicators { - private weak var delegate: TypingIndicators? - private let thread: TSThread - fileprivate let recipientId: String - private let deviceId: UInt - private var displayTypingTimer: Timer? - fileprivate var startedTypingTimestamp: UInt64? - - var isTyping = false { - didSet { - AssertIsOnMainThread() - - let didChange = oldValue != isTyping - if didChange { - Logger.debug("isTyping changed: \(oldValue) -> \(self.isTyping)") - - notifyIfNecessary() - } - } - } - - init(delegate: TypingIndicators, thread: TSThread, - recipientId: String, deviceId: UInt) { - self.delegate = delegate - self.thread = thread - self.recipientId = recipientId - self.deviceId = deviceId - } - - func didReceiveTypingStartedMessage() { - AssertIsOnMainThread() - - displayTypingTimer?.invalidate() - displayTypingTimer = Timer.weakScheduledTimer(withTimeInterval: 5, - target: self, - selector: #selector(IncomingIndicators.displayTypingTimerDidFire), - userInfo: nil, - repeats: false) - if !isTyping { - startedTypingTimestamp = NSDate.ows_millisecondTimeStamp() - } - isTyping = true - } - - func didReceiveTypingStoppedMessage() { - AssertIsOnMainThread() - - clearTyping() - } - - @objc - func displayTypingTimerDidFire() { - AssertIsOnMainThread() - - clearTyping() - } - - func didReceiveIncomingMessage() { - AssertIsOnMainThread() - - clearTyping() - } - - private func clearTyping() { - AssertIsOnMainThread() - - displayTypingTimer?.invalidate() - displayTypingTimer = nil - startedTypingTimestamp = nil - isTyping = false - } - - private func notifyIfNecessary() { - Logger.verbose("") - - guard let delegate = delegate else { - owsFailDebug("Missing delegate.") - return - } - // `areTypingIndicatorsEnabled` reflects the user-facing setting in the app preferences. - // If it's disabled we don't want to emit "typing indicator" messages - // or show typing indicators for other users. - guard delegate.areTypingIndicatorsEnabled() else { - return - } - guard let threadId = thread.uniqueId else { - owsFailDebug("Thread is missing id.") - return - } - NotificationCenter.default.postNotificationNameAsync(TypingIndicatorsImpl.typingIndicatorStateDidChange, object: threadId) - } - } -} diff --git a/SignalServiceKit/src/Util/UIImage+OWS.h b/SignalServiceKit/src/Util/UIImage+OWS.h deleted file mode 100644 index 770bb3eb7..000000000 --- a/SignalServiceKit/src/Util/UIImage+OWS.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -NS_ASSUME_NONNULL_BEGIN - -@interface UIImage (normalizeImage) - -- (UIImage *)normalizedImage; -- (UIImage *)resizedWithQuality:(CGInterpolationQuality)quality rate:(CGFloat)rate; - -- (nullable UIImage *)resizedWithMaxDimensionPoints:(CGFloat)maxDimensionPoints; -- (nullable UIImage *)resizedImageToSize:(CGSize)dstSize; -- (UIImage *)resizedImageToFillPixelSize:(CGSize)boundingSize; - -+ (UIImage *)imageWithColor:(UIColor *)color; -+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/UIImage+OWS.m b/SignalServiceKit/src/Util/UIImage+OWS.m deleted file mode 100644 index dd303a7aa..000000000 --- a/SignalServiceKit/src/Util/UIImage+OWS.m +++ /dev/null @@ -1,242 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "UIImage+OWS.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation UIImage (normalizeImage) - -- (UIImage *)normalizedImage -{ - if (self.imageOrientation == UIImageOrientationUp) { - return self; - } - - UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale); - [self drawInRect:(CGRect){ { 0, 0 }, self.size }]; - UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return normalizedImage; -} - -- (UIImage *)resizedWithQuality:(CGInterpolationQuality)quality rate:(CGFloat)rate -{ - UIImage *resized = nil; - CGFloat width = self.size.width * rate; - CGFloat height = self.size.height * rate; - - UIGraphicsBeginImageContext(CGSizeMake(width, height)); - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetInterpolationQuality(context, quality); - [self drawInRect:CGRectMake(0, 0, width, height)]; - resized = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return resized; -} - -- (nullable UIImage *)resizedWithMaxDimensionPoints:(CGFloat)maxDimensionPoints -{ - CGSize originalSize = self.size; - if (originalSize.width < 1 || originalSize.height < 1) { - OWSLogError(@"Invalid original size: %@", NSStringFromCGSize(originalSize)); - return nil; - } - - CGFloat maxOriginalDimensionPoints = MAX(originalSize.width, originalSize.height); - if (maxOriginalDimensionPoints < maxDimensionPoints) { - // Don't bother scaling an image that is already smaller than the max dimension. - return self; - } - - CGSize thumbnailSize = CGSizeZero; - if (originalSize.width > originalSize.height) { - thumbnailSize.width = maxDimensionPoints; - thumbnailSize.height = round(maxDimensionPoints * originalSize.height / originalSize.width); - } else { - thumbnailSize.width = round(maxDimensionPoints * originalSize.width / originalSize.height); - thumbnailSize.height = maxDimensionPoints; - } - if (thumbnailSize.width < 1 || thumbnailSize.height < 1) { - OWSLogError(@"Invalid thumbnail size: %@", NSStringFromCGSize(thumbnailSize)); - return nil; - } - - UIGraphicsBeginImageContext(CGSizeMake(thumbnailSize.width, thumbnailSize.height)); - CGContextRef _Nullable context = UIGraphicsGetCurrentContext(); - if (context == NULL) { - OWSLogError(@"Couldn't create context."); - return nil; - } - CGContextSetInterpolationQuality(context, kCGInterpolationHigh); - [self drawInRect:CGRectMake(0, 0, thumbnailSize.width, thumbnailSize.height)]; - UIImage *_Nullable resized = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return resized; -} - -// Source: https://github.com/AliSoftware/UIImage-Resize - -- (nullable UIImage *)resizedImageToSize:(CGSize)dstSize -{ - CGImageRef imgRef = self.CGImage; - // the below values are regardless of orientation : for UIImages from Camera, width>height (landscape) - CGSize srcSize = CGSizeMake(CGImageGetWidth(imgRef), - CGImageGetHeight(imgRef)); // not equivalent to self.size (which is dependant on the imageOrientation)! - - /* Don't resize if we already meet the required destination size. */ - if (CGSizeEqualToSize(srcSize, dstSize)) { - return self; - } - - CGFloat scaleRatio = dstSize.width / srcSize.width; - UIImageOrientation orient = self.imageOrientation; - CGAffineTransform transform = CGAffineTransformIdentity; - switch (orient) { - case UIImageOrientationUp: // EXIF = 1 - transform = CGAffineTransformIdentity; - break; - - case UIImageOrientationUpMirrored: // EXIF = 2 - transform = CGAffineTransformMakeTranslation(srcSize.width, 0.0); - transform = CGAffineTransformScale(transform, -1.0, 1.0); - break; - - case UIImageOrientationDown: // EXIF = 3 - transform = CGAffineTransformMakeTranslation(srcSize.width, srcSize.height); - transform = CGAffineTransformRotate(transform, (CGFloat)M_PI); - break; - - case UIImageOrientationDownMirrored: // EXIF = 4 - transform = CGAffineTransformMakeTranslation(0.0, srcSize.height); - transform = CGAffineTransformScale(transform, 1.0, -1.0); - break; - - case UIImageOrientationLeftMirrored: // EXIF = 5 - dstSize = CGSizeMake(dstSize.height, dstSize.width); - transform = CGAffineTransformMakeTranslation(srcSize.height, srcSize.width); - transform = CGAffineTransformScale(transform, -1.0, 1.0); - transform = CGAffineTransformRotate(transform, (CGFloat)(3.0f * M_PI_2)); - break; - - case UIImageOrientationLeft: // EXIF = 6 - dstSize = CGSizeMake(dstSize.height, dstSize.width); - transform = CGAffineTransformMakeTranslation(0.0, srcSize.width); - transform = CGAffineTransformRotate(transform, (CGFloat)(3.0 * M_PI_2)); - break; - - case UIImageOrientationRightMirrored: // EXIF = 7 - dstSize = CGSizeMake(dstSize.height, dstSize.width); - transform = CGAffineTransformMakeScale(-1.0, 1.0); - transform = CGAffineTransformRotate(transform, (CGFloat)M_PI_2); - break; - - case UIImageOrientationRight: // EXIF = 8 - dstSize = CGSizeMake(dstSize.height, dstSize.width); - transform = CGAffineTransformMakeTranslation(srcSize.height, 0.0); - transform = CGAffineTransformRotate(transform, (CGFloat)M_PI_2); - break; - - default: - OWSFailDebug(@"Invalid image orientation"); - return nil; - } - - ///////////////////////////////////////////////////////////////////////////// - // The actual resize: draw the image on a new context, applying a transform matrix - UIGraphicsBeginImageContextWithOptions(dstSize, NO, self.scale); - - CGContextRef context = UIGraphicsGetCurrentContext(); - if (!context) { - return nil; - } - CGContextSetInterpolationQuality(context, kCGInterpolationHigh); - - if (orient == UIImageOrientationRight || orient == UIImageOrientationLeft) { - CGContextScaleCTM(context, -scaleRatio, scaleRatio); - CGContextTranslateCTM(context, -srcSize.height, 0); - } else { - CGContextScaleCTM(context, scaleRatio, -scaleRatio); - CGContextTranslateCTM(context, 0, -srcSize.height); - } - - CGContextConcatCTM(context, transform); - - // we use srcSize (and not dstSize) as the size to specify is in user space (and we use the CTM to apply a - // scaleRatio) - CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, srcSize.width, srcSize.height), imgRef); - UIImage *_Nullable resizedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return resizedImage; -} - -- (UIImage *)resizedImageToFillPixelSize:(CGSize)dstSize -{ - OWSAssertDebug(dstSize.width > 0); - OWSAssertDebug(dstSize.height > 0); - - UIImage *normalized = [self normalizedImage]; - - // Get the size in pixels, not points. - CGSize srcSize = CGSizeMake(CGImageGetWidth(normalized.CGImage), CGImageGetHeight(normalized.CGImage)); - OWSAssertDebug(srcSize.width > 0); - OWSAssertDebug(srcSize.height > 0); - - CGFloat widthRatio = srcSize.width / dstSize.width; - CGFloat heightRatio = srcSize.height / dstSize.height; - CGRect drawRect = CGRectZero; - if (widthRatio > heightRatio) { - drawRect.origin.y = 0; - drawRect.size.height = dstSize.height; - drawRect.size.width = dstSize.height * srcSize.width / srcSize.height; - OWSAssertDebug(drawRect.size.width > dstSize.width); - drawRect.origin.x = (drawRect.size.width - dstSize.width) * -0.5f; - } else { - drawRect.origin.x = 0; - drawRect.size.width = dstSize.width; - drawRect.size.height = dstSize.width * srcSize.height / srcSize.width; - OWSAssertDebug(drawRect.size.height >= dstSize.height); - drawRect.origin.y = (drawRect.size.height - dstSize.height) * -0.5f; - } - - UIGraphicsBeginImageContextWithOptions(dstSize, NO, 1.f); - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetInterpolationQuality(context, kCGInterpolationHigh); - [self drawInRect:drawRect]; - UIImage *dstImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return dstImage; -} - -+ (UIImage *)imageWithColor:(UIColor *)color -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(color); - - return [self imageWithColor:color size:CGSizeMake(1.f, 1.f)]; -} - -+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size -{ - OWSAssertIsOnMainThread(); - OWSAssertDebug(color); - - CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height); - UIGraphicsBeginImageContextWithOptions(rect.size, NO, 1.f); - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextClearRect(context, rect); - CGContextSetFillColorWithColor(context, [color CGColor]); - CGContextFillRect(context, rect); - - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return image; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/WeakTimer.swift b/SignalServiceKit/src/Util/WeakTimer.swift deleted file mode 100644 index f3553b6f4..000000000 --- a/SignalServiceKit/src/Util/WeakTimer.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -/** - * As of iOS10, the timer API's take a block, which makes it easy to reference weak self in Swift. This class offers a - * similar API that works pre iOS10. - * - * Solution modified from - * http://stackoverflow.com/questions/16821736/weak-reference-to-nstimer-target-to-prevent-retain-cycle/41003985#41003985 - */ -public final class WeakTimer { - - fileprivate weak var timer: Timer? - fileprivate weak var target: AnyObject? - fileprivate let action: (Timer) -> Void - - fileprivate init(timeInterval: TimeInterval, target: AnyObject, userInfo: Any?, repeats: Bool, action: @escaping (Timer) -> Void) { - self.target = target - self.action = action - self.timer = Timer.scheduledTimer(timeInterval: timeInterval, - target: self, - selector: #selector(fire), - userInfo: userInfo, - repeats: repeats) - } - - public class func scheduledTimer(timeInterval: TimeInterval, target: AnyObject, userInfo: Any?, repeats: Bool, action: @escaping (Timer) -> Void) -> Timer { - return WeakTimer(timeInterval: timeInterval, - target: target, - userInfo: userInfo, - repeats: repeats, - action: action).timer! - } - - @objc public func fire(timer: Timer) { - if target != nil { - action(timer) - } else { - timer.invalidate() - } - } -} diff --git a/SignalServiceKit/src/Util/YapDatabase+Promise.swift b/SignalServiceKit/src/Util/YapDatabase+Promise.swift deleted file mode 100644 index 49ad9fcc0..000000000 --- a/SignalServiceKit/src/Util/YapDatabase+Promise.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit - -public extension YapDatabaseConnection { - - @objc - func readWritePromise(_ block: @escaping (YapDatabaseReadWriteTransaction) -> Void) -> AnyPromise { - return AnyPromise(readWritePromise(block) as Promise) - } - - func readWritePromise(_ block: @escaping (YapDatabaseReadWriteTransaction) -> Void) -> Promise { - return Promise { resolver in - self.asyncReadWrite(block, completionBlock: { resolver.fulfill(()) }) - } - } - - func read(_ block: @escaping (YapDatabaseReadTransaction) throws -> Void) throws { - var errorToRaise: Error? - - read { transaction in - do { - try block(transaction) - } catch { - errorToRaise = error - } - } - - if let errorToRaise = errorToRaise { - throw errorToRaise - } - } - - func readWrite(_ block: @escaping (YapDatabaseReadWriteTransaction) throws -> Void) throws { - var errorToRaise: Error? - - readWrite { transaction in - do { - try block(transaction) - } catch { - errorToRaise = error - } - } - - if let errorToRaise = errorToRaise { - throw errorToRaise - } - } -} diff --git a/SignalServiceKit/tests/Account/SignedPreKeyDeletionTests.m b/SignalServiceKit/tests/Account/SignedPreKeyDeletionTests.m deleted file mode 100644 index efaf491b6..000000000 --- a/SignalServiceKit/tests/Account/SignedPreKeyDeletionTests.m +++ /dev/null @@ -1,133 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSPrimaryStorage+SignedPreKeyStore.h" -#import "SSKBaseTestObjC.h" -#import "TSPreKeyManager.h" -#import - -@interface TSPreKeyManager (Testing) - -+ (void)clearSignedPreKeyRecordsWithKeyId:(NSNumber *)keyId; - -@end - -@interface SignedPreKeyDeletionTests : SSKBaseTestObjC - -@end - -@implementation SignedPreKeyDeletionTests - -#ifdef BROKEN_TESTS - -- (void)setUp { - [super setUp]; -} - -- (void)tearDown { - [super tearDown]; -} - -- (void)testSignedPreKeyDeletion { - [self readWithBlock:^(YapDatabaseReadTransaction *transaction) { - XCTAssertEqual(0, [transaction numberOfKeysInCollection:OWSPrimaryStorageSignedPreKeyStoreCollection]); - }]; - - int days = 20; - int lastPreKeyId = days; - - for (int i = 0; i <= days; i++) { // 21 signed keys are generated, one per day from now until 20 days ago. - int secondsAgo = (i - days) * 24 * 60 * 60; - NSAssert(secondsAgo <= 0, @"Time in past must be negative"); - NSDate *generatedAt = [NSDate dateWithTimeIntervalSinceNow:secondsAgo]; - SignedPreKeyRecord *record = [[SignedPreKeyRecord alloc] initWithId:i - keyPair:[Curve25519 generateKeyPair] - signature:[NSData new] - generatedAt:generatedAt]; - [[OWSPrimaryStorage sharedManager] storeSignedPreKey:i signedPreKeyRecord:record]; - } - - NSArray *signedPreKeys = [[OWSPrimaryStorage sharedManager] loadSignedPreKeys]; - // Sanity check - XCTAssert(signedPreKeys.count == 21); - - [TSPreKeyManager clearSignedPreKeyRecordsWithKeyId:@(lastPreKeyId)]; - XCTAssert([[OWSPrimaryStorage sharedManager] loadSignedPrekey:lastPreKeyId] != nil); - - // We'll delete every key created 7 or more days ago. - signedPreKeys = [[OWSPrimaryStorage sharedManager] loadSignedPreKeys]; - XCTAssert(signedPreKeys.count == 7); -} - -- (void)testSignedPreKeyDeletionKeepsSomeOldKeys -{ - [self readWithBlock:^(YapDatabaseReadTransaction *transaction) { - XCTAssertEqual(0, [transaction numberOfKeysInCollection:OWSPrimaryStorageSignedPreKeyStoreCollection]); - }]; - - int lastPreKeyId = 10; - for (int i = 0; i <= 10; i++) { - // All these keys will be considered "old", since they were created more than 7 days ago. - int secondsAgo = (i - 20) * 24 * 60 * 60; - NSAssert(secondsAgo <= 0, @"Time in past must be negative"); - NSDate *generatedAt = [NSDate dateWithTimeIntervalSinceNow:secondsAgo]; - SignedPreKeyRecord *record = [[SignedPreKeyRecord alloc] initWithId:i - keyPair:[Curve25519 generateKeyPair] - signature:[NSData new] - generatedAt:generatedAt]; - // we only retain accepted keys - [record markAsAcceptedByService]; - [[OWSPrimaryStorage sharedManager] storeSignedPreKey:i signedPreKeyRecord:record]; - } - - - NSArray *signedPreKeys = [[OWSPrimaryStorage sharedManager] loadSignedPreKeys]; - // Sanity check - XCTAssert(signedPreKeys.count == 11); - - [TSPreKeyManager clearSignedPreKeyRecordsWithKeyId:@(lastPreKeyId)]; - - XCTAssert([[OWSPrimaryStorage sharedManager] loadSignedPrekey:lastPreKeyId] != nil); - - signedPreKeys = [[OWSPrimaryStorage sharedManager] loadSignedPreKeys]; - - // We need to keep 3 "old" keys, plus the "current" key - XCTAssert(signedPreKeys.count == 4); -} - -- (void)testOlderRecordsNotDeletedIfNoReplacement { - - [self readWithBlock:^(YapDatabaseReadTransaction *transaction) { - XCTAssertEqual(0, [transaction numberOfKeysInCollection:OWSPrimaryStorageSignedPreKeyStoreCollection]); - }]; - - int days = 3; - int lastPreKeyId = days; - - for (int i = 0; i <= days; i++) { // 4 signed keys are generated, one per day from now until 3 days ago. - int secondsAgo = (i - days) * 24 * 60 * 60; - NSAssert(secondsAgo <= 0, @"Time in past must be negative"); - NSDate *generatedAt = [NSDate dateWithTimeIntervalSinceNow:secondsAgo]; - SignedPreKeyRecord *record = [[SignedPreKeyRecord alloc] initWithId:i - keyPair:[Curve25519 generateKeyPair] - signature:[NSData new] - generatedAt:generatedAt]; - [[OWSPrimaryStorage sharedManager] storeSignedPreKey:i signedPreKeyRecord:record]; - } - - NSArray *signedPreKeys = [[OWSPrimaryStorage sharedManager] loadSignedPreKeys]; - // Sanity check - XCTAssert(signedPreKeys.count == 4); - - [TSPreKeyManager clearSignedPreKeyRecordsWithKeyId:@(lastPreKeyId)]; - XCTAssert([[OWSPrimaryStorage sharedManager] loadSignedPrekey:lastPreKeyId] != nil); - - // All three records should still be stored. - signedPreKeys = [[OWSPrimaryStorage sharedManager] loadSignedPreKeys]; - XCTAssert(signedPreKeys.count == 4); -} - -#endif - -@end diff --git a/SignalServiceKit/tests/Contacts/OWSDisappearingMessagesConfigurationTest.m b/SignalServiceKit/tests/Contacts/OWSDisappearingMessagesConfigurationTest.m deleted file mode 100644 index 6c621eb35..000000000 --- a/SignalServiceKit/tests/Contacts/OWSDisappearingMessagesConfigurationTest.m +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDisappearingMessagesConfiguration.h" -#import "SSKBaseTestObjC.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSDisappearingMessagesConfigurationTest : SSKBaseTestObjC - -@end - -@implementation OWSDisappearingMessagesConfigurationTest - -- (void)testDictionaryValueDidChange -{ - OWSDisappearingMessagesConfiguration *configuration = - [[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:@"fake-thread-id" - enabled:YES - durationSeconds:10]; - XCTAssertFalse(configuration.dictionaryValueDidChange); - - [configuration save]; - XCTAssertFalse(configuration.dictionaryValueDidChange); - - configuration.enabled = NO; - XCTAssertTrue(configuration.dictionaryValueDidChange); - - configuration.enabled = YES; - XCTAssertFalse(configuration.dictionaryValueDidChange); - - OWSDisappearingMessagesConfiguration *reloadedConfiguration = - [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:@"fake-thread-id"]; - XCTAssertNotNil(reloadedConfiguration); // Sanity Check. - XCTAssertFalse(reloadedConfiguration.dictionaryValueDidChange); - - reloadedConfiguration.durationSeconds = 30; - XCTAssertTrue(reloadedConfiguration.dictionaryValueDidChange); - - reloadedConfiguration.durationSeconds = 10; - XCTAssertFalse(reloadedConfiguration.dictionaryValueDidChange); -} - -- (void)testDontStoreEphemeralProperties -{ - OWSDisappearingMessagesConfiguration *configuration = - [[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:@"fake-thread-id" - enabled:YES - durationSeconds:10]; - - - // Unfortunately this test will break every time you add, remove, or rename a property, but on the - // plus side it has a chance of catching when you indadvertently remove our ephemeral properties - // from our Mantle storage blacklist. - NSArray *expected = @[ @"enabled", @"durationSeconds", @"uniqueId" ]; - - XCTAssertEqualObjects(expected, [configuration.dictionaryValue allKeys]); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/tests/Contacts/SignalRecipientTest.m b/SignalServiceKit/tests/Contacts/SignalRecipientTest.m deleted file mode 100644 index 8bc9d42d5..000000000 --- a/SignalServiceKit/tests/Contacts/SignalRecipientTest.m +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "MockSSKEnvironment.h" -#import "OWSPrimaryStorage.h" -#import "SSKBaseTestObjC.h" -#import "SignalRecipient.h" -#import "TSAccountManager.h" -#import "TestAppContext.h" -#import - -@interface TSAccountManager (Testing) - -- (void)storeLocalNumber:(NSString *)localNumber; - -@end - -@interface SignalRecipientTest : SSKBaseTestObjC - -@property (nonatomic) NSString *localNumber; - -@end - -@implementation SignalRecipientTest - -- (void)setUp -{ - [super setUp]; - - self.localNumber = @"+13231231234"; - [[TSAccountManager sharedInstance] storeLocalNumber:self.localNumber]; -} - -- (void)tearDown -{ - [super tearDown]; -} - -- (void)testSelfRecipientWithExistingRecord -{ - // Sanity Check - XCTAssertNotNil(self.localNumber); - - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [SignalRecipient markRecipientAsRegisteredAndGet:self.localNumber transaction:transaction]; - - XCTAssertTrue([SignalRecipient isRegisteredRecipient:self.localNumber transaction:transaction]); - }]; -} - -- (void)testRecipientWithExistingRecord -{ - // Sanity Check - XCTAssertNotNil(self.localNumber); - NSString *recipientId = @"+15551231234"; - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [SignalRecipient markRecipientAsRegisteredAndGet:recipientId transaction:transaction]; - - XCTAssertTrue([SignalRecipient isRegisteredRecipient:recipientId transaction:transaction]); - }]; -} - -@end diff --git a/SignalServiceKit/tests/Contacts/TSContactThreadTest.m b/SignalServiceKit/tests/Contacts/TSContactThreadTest.m deleted file mode 100644 index f56b0e3d9..000000000 --- a/SignalServiceKit/tests/Contacts/TSContactThreadTest.m +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "MockSSKEnvironment.h" -#import "OWSIdentityManager.h" -#import "SSKBaseTestObjC.h" -#import "TSContactThread.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface TSContactThreadTest : SSKBaseTestObjC - -@property (nonatomic) TSContactThread *contactThread; - -@end - -@implementation TSContactThreadTest - -- (void)setUp -{ - [super setUp]; - - self.contactThread = [TSContactThread getOrCreateThreadWithContactId:@"fake-contact-id"]; - [OWSRecipientIdentity removeAllObjectsInCollection]; -} - -- (void)testHasSafetyNumbersWithoutRemoteIdentity -{ - XCTAssertFalse(self.contactThread.hasSafetyNumbers); -} - -- (void)testHasSafetyNumbersWithRemoteIdentity -{ - [[OWSIdentityManager sharedManager] saveRemoteIdentity:[[NSMutableData alloc] initWithLength:kStoredIdentityKeyLength] - recipientId:self.contactThread.contactIdentifier]; - XCTAssert(self.contactThread.hasSafetyNumbers); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/tests/Contacts/TSGroupThreadTest.m b/SignalServiceKit/tests/Contacts/TSGroupThreadTest.m deleted file mode 100644 index cacf21018..000000000 --- a/SignalServiceKit/tests/Contacts/TSGroupThreadTest.m +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "TSGroupThread.h" -#import "SSKBaseTestObjC.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface TSGroupThreadTest : SSKBaseTestObjC - -@end - -@implementation TSGroupThreadTest - -- (void)testHasSafetyNumbers -{ - TSGroupThread *groupThread = [TSGroupThread new]; - XCTAssertFalse(groupThread.hasSafetyNumbers); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/tests/Contacts/TSThreadTest.m b/SignalServiceKit/tests/Contacts/TSThreadTest.m deleted file mode 100644 index a4ab8b0bf..000000000 --- a/SignalServiceKit/tests/Contacts/TSThreadTest.m +++ /dev/null @@ -1,148 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSDevice.h" -#import "OWSPrimaryStorage.h" -#import "SSKBaseTestObjC.h" -#import "TSAttachmentStream.h" -#import "TSContactThread.h" -#import "TSIncomingMessage.h" -#import "TSOutgoingMessage.h" -#import "TestAppContext.h" -#import - -@interface TSThreadTest : SSKBaseTestObjC - -@end - -#pragma mark - - -@implementation TSThreadTest - -- (void)setUp -{ - [super setUp]; -} - -- (void)tearDown -{ - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - -- (void)testDeletingThreadDeletesInteractions -{ - TSContactThread *thread = - [[TSContactThread alloc] initWithUniqueId:[TSContactThread threadIdFromContactId:@"+13334445555"]]; - [thread save]; - - XCTAssertEqual(0, [thread numberOfInteractions]); - - TSIncomingMessage *incomingMessage = - [[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:10000 - inThread:thread - authorId:@"+12223334444" - sourceDeviceId:OWSDevicePrimaryDeviceId - messageBody:@"Incoming message body" - attachmentIds:@[] - expiresInSeconds:0 - quotedMessage:nil - contactShare:nil - linkPreview:nil - serverTimestamp:nil - wasReceivedByUD:NO]; - [incomingMessage save]; - - TSOutgoingMessage *outgoingMessage = - [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:20000 - inThread:thread - messageBody:@"outgoing message body" - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - [outgoingMessage save]; - - XCTAssertEqual(2, [thread numberOfInteractions]); - - [thread remove]; - XCTAssertEqual(0, [thread numberOfInteractions]); - XCTAssertEqual(0, [TSInteraction numberOfKeysInCollection]); -} - -- (void)testDeletingThreadDeletesAttachmentFiles -{ - TSContactThread *thread = - [[TSContactThread alloc] initWithUniqueId:[TSContactThread threadIdFromContactId:@"+13334445555"]]; - [thread save]; - - // Sanity check - XCTAssertEqual(0, [thread numberOfInteractions]); - - TSAttachmentStream *incomingAttachment = - [AttachmentStreamFactory createWithContentType:@"image/jpeg" dataSource:DataSourceValue.emptyDataSource]; - - // Sanity check - BOOL incomingFileWasCreated = - [[NSFileManager defaultManager] fileExistsAtPath:[incomingAttachment originalFilePath]]; - XCTAssert(incomingFileWasCreated); - - TSIncomingMessage *incomingMessage = - [[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:10000 - inThread:thread - authorId:@"+12223334444" - sourceDeviceId:OWSDevicePrimaryDeviceId - messageBody:@"incoming message body" - attachmentIds:@[ incomingAttachment.uniqueId ] - expiresInSeconds:0 - quotedMessage:nil - contactShare:nil - linkPreview:nil - serverTimestamp:nil - wasReceivedByUD:NO]; - [incomingMessage save]; - - TSAttachmentStream *outgoingAttachment = - [AttachmentStreamFactory createWithContentType:@"image/jpeg" dataSource:DataSourceValue.emptyDataSource]; - - // Sanity check - BOOL outgoingFileWasCreated = - [[NSFileManager defaultManager] fileExistsAtPath:[outgoingAttachment originalFilePath]]; - XCTAssert(outgoingFileWasCreated); - - TSOutgoingMessage *outgoingMessage = - [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:10000 - inThread:thread - messageBody:@"outgoing message body" - attachmentIds:[@[ outgoingAttachment.uniqueId ] mutableCopy] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - [outgoingMessage save]; - - // Sanity check - XCTAssertEqual(2, [thread numberOfInteractions]); - - // Actual Test Follows - [thread remove]; - XCTAssertEqual(0, [thread numberOfInteractions]); - - BOOL incomingFileStillExists = - [[NSFileManager defaultManager] fileExistsAtPath:[incomingAttachment originalFilePath]]; - XCTAssertFalse(incomingFileStillExists); - - BOOL outgoingFileStillExists = - [[NSFileManager defaultManager] fileExistsAtPath:[outgoingAttachment originalFilePath]]; - XCTAssertFalse(outgoingFileStillExists); -} - -@end diff --git a/SignalServiceKit/tests/Devices/OWSDeviceProvisionerTest.m b/SignalServiceKit/tests/Devices/OWSDeviceProvisionerTest.m deleted file mode 100644 index b7e7a8378..000000000 --- a/SignalServiceKit/tests/Devices/OWSDeviceProvisionerTest.m +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSDeviceProvisioner.h" -#import "OWSDeviceProvisioningCodeService.h" -#import "OWSDeviceProvisioningService.h" -#import "OWSFakeNetworkManager.h" -#import "SSKBaseTestObjC.h" -#import "TSNetworkManager.h" - -@interface OWSFakeDeviceProvisioningService : OWSDeviceProvisioningService - -@end - -@implementation OWSFakeDeviceProvisioningService - -- (void)provisionWithMessageBody:(NSData *)messageBody - ephemeralDeviceId:(NSString *)deviceId - success:(void (^)())successCallback - failure:(void (^)(NSError *))failureCallback -{ - OWSLogInfo(@"faking successful provisioning"); - successCallback(); -} - -@end - -@interface OWSFakeDeviceProvisioningCodeService : OWSDeviceProvisioningCodeService - -@end - -@implementation OWSFakeDeviceProvisioningCodeService - -- (void)requestProvisioningCodeWithSuccess:(void (^)(NSString *))successCallback - failure:(void (^)(NSError *))failureCallback -{ - OWSLogInfo(@"faking successful provisioning code fetching"); - successCallback(@"fake-provisioning-code"); -} - -@end - -@interface OWSDeviceProvisioner (Testing) - -@property OWSDeviceProvisioningCodeService *provisioningCodeService; -@property OWSDeviceProvisioningService *provisioningService; - -@end - -@interface OWSDeviceProvisionerTest : SSKBaseTestObjC - -@end - -@implementation OWSDeviceProvisionerTest - -- (void)testProvisioning -{ - - XCTestExpectation *expectation = [self expectationWithDescription:@"Provisioning Success"]; - - NSData *nullKey = [[NSMutableData dataWithLength:32] copy]; - NSData *myPublicKey = [nullKey copy]; - NSData *myPrivateKey = [nullKey copy]; - NSData *theirPublicKey = [nullKey copy]; - NSData *profileKey = [nullKey copy]; - NSString *accountIdentifier; - NSString *theirEphemeralDeviceId; - - OWSFakeNetworkManager *networkManager = [OWSFakeNetworkManager new]; - - OWSDeviceProvisioner *provisioner = [[OWSDeviceProvisioner alloc] - initWithMyPublicKey:myPublicKey - myPrivateKey:myPrivateKey - theirPublicKey:theirPublicKey - theirEphemeralDeviceId:theirEphemeralDeviceId - accountIdentifier:accountIdentifier - profileKey:profileKey - readReceiptsEnabled:YES - provisioningCodeService:[[OWSFakeDeviceProvisioningCodeService alloc] initWithNetworkManager:networkManager] - provisioningService:[[OWSFakeDeviceProvisioningService alloc] initWithNetworkManager:networkManager]]; - - [provisioner provisionWithSuccess:^{ - [expectation fulfill]; - } - failure:^(NSError *_Nonnull error) { - XCTAssert(NO, @"Failed to provision with error: %@", error); - }]; - - [self waitForExpectationsWithTimeout:5.0 - handler:^(NSError *error) { - if (error) { - OWSLogInfo(@"Timeout Error: %@", error); - } - }]; -} - -@end diff --git a/SignalServiceKit/tests/Devices/OWSProvisioningCipherTest.m b/SignalServiceKit/tests/Devices/OWSProvisioningCipherTest.m deleted file mode 100644 index 77e5df03c..000000000 --- a/SignalServiceKit/tests/Devices/OWSProvisioningCipherTest.m +++ /dev/null @@ -1,155 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "SSKBaseTestObjC.h" -#import -#import -#import - -@interface OWSProvisioningCipher(Testing) - -// Expose private method for testing. -- (instancetype)initWithTheirPublicKey:(NSData *)theirPublicKey - ourKeyPair:(ECKeyPair *)ourKeyPair - initializationVector:(NSData *)initializationVector; - -@end - -@interface OWSProvisioningCipherTest : SSKBaseTestObjC - -@end - -@implementation OWSProvisioningCipherTest - -- (NSData *)knownInitializationVector -{ - uint8_t initilizationVectorBytes[] = { - 0xec, 0x67, 0x0b, 0xb7, - 0x18, 0xe1, 0xe9, 0x0a, - 0xcc, 0x5e, 0xcb, 0x37, - 0xab, 0x79, 0xe0, 0x09 - }; - return [NSData dataWithBytes:initilizationVectorBytes length:16]; -} - -- (NSData *)knownPublicKey -{ - uint8_t knownPublicKeyBytes[] = { - 0x5e, 0x23, 0xe8, 0x49, - 0xb2, 0x23, 0x21, 0xdb, - 0x2e, 0x3a, 0x77, 0x74, - 0x6f, 0x3b, 0x44, 0x18, - 0xcc, 0x6c, 0x81, 0xce, - 0xd5, 0xc2, 0x91, 0xaf, - 0xed, 0xfb, 0x21, 0x4e, - 0x59, 0xcc, 0x19, 0xa4 - }; - return [NSData dataWithBytes:knownPublicKeyBytes length: 32]; -} - -- (ECKeyPair *)knownKeyPair -{ - uint8_t privateKeyBytes[] = { - 0x60, 0xfd, 0xc1, 0xeb, - 0x6a, 0x68, 0x3d, 0x2b, - 0x51, 0x23, 0x1f, 0xea, - 0x1a, 0x5e, 0x80, 0x88, - 0x0c, 0x65, 0x2d, 0x3d, - 0x47, 0x9e, 0x28, 0xc1, - 0x9f, 0x48, 0x2c, 0x66, - 0xde, 0x48, 0x5d, 0x57 - }; - - uint8_t publicKeyBytes[] = { - 0x02, 0x62, 0x7b, 0x5c, - 0x21, 0x15, 0x59, 0x1b, - 0x37, 0xd1, 0xfe, 0xeb, - 0x15, 0x5d, 0xd2, 0x95, - 0x0a, 0xce, 0xe8, 0xb2, - 0x1e, 0x8e, 0xc8, 0xd6, - 0x53, 0x4f, 0x1a, 0xcd, - 0xf2, 0x00, 0x98, 0x32 - }; - - // Righteous hack to build a deterministic ECKeyPair - // The publicKey/privateKey ivars are private but it's possible to `initWithCoder:` given the proper keys. - NSKeyedArchiver *archiver = [NSKeyedArchiver new]; - [archiver encodeBytes:publicKeyBytes length:ECCKeyLength forKey:@"TSECKeyPairPublicKey"]; - [archiver encodeBytes:privateKeyBytes length:ECCKeyLength forKey:@"TSECKeyPairPrivateKey"]; - NSData *serialized = [archiver encodedData]; - - NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:serialized]; - return [[ECKeyPair alloc] initWithCoder:unarchiver]; -} - -- (NSData *)knownData -{ - uint8_t knownBytes[] = { - 0x19, 0x33, 0x78, 0x64, - 0x96, 0x56, 0xa7, 0xd0, - 0x6e, 0xff, 0x37, 0x1d - }; - - return [NSData dataWithBytes:knownBytes length:12]; -} - -- (void)testEncrypt -{ - NSData *theirPublicKey = [self knownPublicKey]; - ECKeyPair *ourKeyPair = [self knownKeyPair]; - NSData *initializationVector = [self knownInitializationVector]; - - OWSProvisioningCipher *cipher = [[OWSProvisioningCipher alloc] initWithTheirPublicKey:theirPublicKey - ourKeyPair:ourKeyPair - initializationVector:initializationVector]; - - NSData *message = [self knownData]; - NSData *actualOutput = [cipher encrypt:message]; - - uint8_t expectedBytes[] = { - 0x01, 0xec, 0x67, 0x0b, - 0xb7, 0x18, 0xe1, 0xe9, - 0x0a, 0xcc, 0x5e, 0xcb, - 0x37, 0xab, 0x79, 0xe0, - 0x09, 0xf7, 0x2b, 0xf7, - 0x14, 0x3d, 0x45, 0xd7, - 0x45, 0x79, 0x1e, 0x4f, - 0x9d, 0x34, 0x8a, 0x2d, - 0x43, 0x64, 0xd4, 0x7d, - 0x48, 0x9a, 0xdc, 0x5a, - 0xc3, 0x72, 0xfa, 0x63, - 0x41, 0x7a, 0xa8, 0x45, - 0x36, 0xe9, 0xc5, 0xcb, - 0xee, 0x9b, 0xc1, 0x1f, - 0xec, 0x31, 0x1e, 0xc2, - 0x33, 0x2d, 0x95, 0x54, - 0xcc - }; - NSData *expectedOutput = [NSData dataWithBytes:expectedBytes length:65]; - - XCTAssertEqualObjects(expectedOutput, actualOutput); -} - -- (void)testPadding -{ - NSUInteger kBlockSize = 16; - for (int i = 0; i <= kBlockSize; i++) { - NSData *message = [Cryptography generateRandomBytes:i]; - - - NSData *theirPublicKey = [self knownPublicKey]; - ECKeyPair *ourKeyPair = [self knownKeyPair]; - NSData *initializationVector = [self knownInitializationVector]; - - OWSProvisioningCipher *cipher = [[OWSProvisioningCipher alloc] initWithTheirPublicKey:theirPublicKey - ourKeyPair:ourKeyPair - initializationVector:initializationVector]; - - - NSData *actualOutput = [cipher encrypt:message]; - XCTAssertNotNil(actualOutput, @"failed for message length: %d", i); - } -} - -@end diff --git a/SignalServiceKit/tests/Messages/Interactions/TSMessageTest.m b/SignalServiceKit/tests/Messages/Interactions/TSMessageTest.m deleted file mode 100644 index 5a382dc33..000000000 --- a/SignalServiceKit/tests/Messages/Interactions/TSMessageTest.m +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSMessage.h" -#import "SSKBaseTestObjC.h" -#import "TSAttachmentStream.h" -#import "TSContactThread.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface TSMessageTest : SSKBaseTestObjC - -@property TSThread *thread; - -@end - -@implementation TSMessageTest - -- (void)setUp { - [super setUp]; - self.thread = [TSContactThread getOrCreateThreadWithContactId:@"fake-thread-id"]; -} - -- (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - -- (void)testExpiresAtWithoutStartedTimer -{ - TSMessage *message = [[TSMessage alloc] initMessageWithTimestamp:1 - inThread:self.thread - messageBody:@"foo" - attachmentIds:@[] - expiresInSeconds:100 - expireStartedAt:0 - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - - XCTAssertEqual(0, message.expiresAt); -} - -- (void)testExpiresAtWithStartedTimer -{ - uint64_t now = [NSDate ows_millisecondTimeStamp]; - const uint32_t expirationSeconds = 10; - const uint32_t expirationMs = expirationSeconds * 1000; - TSMessage *message = [[TSMessage alloc] initMessageWithTimestamp:1 - inThread:self.thread - messageBody:@"foo" - attachmentIds:@[] - expiresInSeconds:expirationSeconds - expireStartedAt:now - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - XCTAssertEqual(now + expirationMs, message.expiresAt); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/tests/Messages/Interactions/TSOutgoingMessageTest.m b/SignalServiceKit/tests/Messages/Interactions/TSOutgoingMessageTest.m deleted file mode 100644 index 56f44d645..000000000 --- a/SignalServiceKit/tests/Messages/Interactions/TSOutgoingMessageTest.m +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "TSOutgoingMessage.h" -#import "OWSPrimaryStorage.h" -#import "SSKBaseTestObjC.h" -#import "TSContactThread.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface TSOutgoingMessageTest : SSKBaseTestObjC - -@property (nonatomic) TSContactThread *thread; - -@end - -@implementation TSOutgoingMessageTest - -#ifdef BROKEN_TESTS - -- (NSString *)contactId -{ - return @"fake-thread-id"; -} - -- (void)setUp -{ - [super setUp]; - self.thread = [[TSContactThread alloc] initWithUniqueId:self.contactId]; -} - -- (void)testShouldNotStartExpireTimerWithMessageThatDoesNotExpire -{ - TSOutgoingMessage *message = - [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:100 - inThread:self.thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - XCTAssertFalse([message shouldStartExpireTimerWithTransaction:transaction]); - }]; -} - -- (void)testShouldStartExpireTimerWithSentMessage -{ - TSOutgoingMessage *message = - [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:100 - inThread:self.thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:10 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [message updateWithSentRecipient:self.contactId wasSentByUD:NO transaction:transaction]; - XCTAssertTrue([message shouldStartExpireTimerWithTransaction:transaction]); - }]; -} - -- (void)testShouldNotStartExpireTimerWithUnsentMessage -{ - TSOutgoingMessage *message = - [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:100 - inThread:self.thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:10 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - XCTAssertFalse([message shouldStartExpireTimerWithTransaction:transaction]); - }]; -} - -- (void)testShouldNotStartExpireTimerWithAttemptingOutMessage -{ - TSOutgoingMessage *message = - [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:100 - inThread:self.thread - messageBody:nil - attachmentIds:[NSMutableArray new] - expiresInSeconds:10 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [message updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:transaction]; - XCTAssertFalse([message shouldStartExpireTimerWithTransaction:transaction]); - }]; -} - -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/tests/Messages/OWSDisappearingMessageFinderTest.m b/SignalServiceKit/tests/Messages/OWSDisappearingMessageFinderTest.m deleted file mode 100644 index 9155f8144..000000000 --- a/SignalServiceKit/tests/Messages/OWSDisappearingMessageFinderTest.m +++ /dev/null @@ -1,192 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "MockSSKEnvironment.h" -#import "OWSDisappearingMessagesFinder.h" -#import "OWSPrimaryStorage.h" -#import "SSKBaseTestObjC.h" -#import "TSContactThread.h" -#import "TSMessage.h" -#import "TestAppContext.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSDisappearingMessagesFinder (Testing) - -- (NSArray *)fetchExpiredMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction; -- (NSArray *)fetchUnstartedExpiringMessagesInThread:(TSThread *)thread - transaction:(YapDatabaseReadTransaction *)transaction; - -@end - -#pragma mark - - -@interface OWSDisappearingMessageFinderTest : SSKBaseTestObjC - -@property (nonatomic, nullable) OWSDisappearingMessagesFinder *finder; -@property (nonatomic, nullable) TSThread *thread; -@property (nonatomic) uint64_t now; - -@end - -#pragma mark - - -@implementation OWSDisappearingMessageFinderTest - -#ifdef BROKEN_TESTS - -- (void)setUp -{ - [super setUp]; - - // TODO: This shouldn't be necessary. - // [OWSDisappearingMessagesFinder blockingRegisterDatabaseExtensions:self.primaryStorage]; - - self.thread = [TSContactThread getOrCreateThreadWithContactId:@"fake-thread-id"]; - self.now = [NSDate ows_millisecondTimeStamp]; - - // Test subject - self.finder = [OWSDisappearingMessagesFinder new]; -} - -- (void)tearDown -{ - self.dbConnection = nil; - - [super tearDown]; -} - -- (TSMessage *)messageWithBody:(NSString *)body - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt -{ - return [[TSMessage alloc] initMessageWithTimestamp:1 - inThread:self.thread - messageBody:body - attachmentIds:@[] - expiresInSeconds:expiresInSeconds - expireStartedAt:expireStartedAt - quotedMessage:nil - contactShare:nil - linkPreview:nil]; -} - -- (void)testExpiredMessages -{ - TSMessage *expiredMessage1 = [[TSMessage alloc] initMessageWithTimestamp:1 - inThread:self.thread - messageBody:@"expiredMessage1" - attachmentIds:@[] - expiresInSeconds:1 - expireStartedAt:self.now - 20000 - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - [expiredMessage1 save]; - - TSMessage *expiredMessage2 = - [self messageWithBody:@"expiredMessage2" expiresInSeconds:2 expireStartedAt:self.now - 2001]; - [expiredMessage2 save]; - - TSMessage *notYetExpiredMessage = - [self messageWithBody:@"notYetExpiredMessage" expiresInSeconds:20 expireStartedAt:self.now - 10000]; - [notYetExpiredMessage save]; - - TSMessage *unreadExpiringMessage = - [self messageWithBody:@"unereadExpiringMessage" expiresInSeconds:10 expireStartedAt:0]; - [unreadExpiringMessage save]; - - TSMessage *unExpiringMessage = [self messageWithBody:@"unexpiringMessage" expiresInSeconds:0 expireStartedAt:0]; - [unExpiringMessage save]; - - TSMessage *unExpiringMessage2 = [self messageWithBody:@"unexpiringMessage2" expiresInSeconds:0 expireStartedAt:0]; - [unExpiringMessage2 save]; - - __block NSArray *actualMessages; - [self readWithBlock:^(YapDatabaseReadTransaction *transaction) { - actualMessages = [self.finder fetchExpiredMessagesWithTransaction:transaction]; - }]; - - NSArray *expectedMessages = @[ expiredMessage1, expiredMessage2 ]; - XCTAssertEqualObjects(expectedMessages, actualMessages); -} - -- (void)testUnstartedExpiredMessagesForThread -{ - TSMessage *expiredMessage = - [self messageWithBody:@"expiredMessage2" expiresInSeconds:2 expireStartedAt:self.now - 2001]; - [expiredMessage save]; - - TSMessage *notYetExpiredMessage = - [self messageWithBody:@"notYetExpiredMessage" expiresInSeconds:20 expireStartedAt:self.now - 10000]; - [notYetExpiredMessage save]; - - TSMessage *unreadExpiringMessage = - [self messageWithBody:@"unereadExpiringMessage" expiresInSeconds:10 expireStartedAt:0]; - [unreadExpiringMessage save]; - - TSMessage *unExpiringMessage = [self messageWithBody:@"unexpiringMessage" expiresInSeconds:0 expireStartedAt:0]; - [unExpiringMessage save]; - - TSMessage *unExpiringMessage2 = [self messageWithBody:@"unexpiringMessage2" expiresInSeconds:0 expireStartedAt:0]; - [unExpiringMessage2 save]; - - __block NSArray *actualMessages; - [self readWithBlock:^(YapDatabaseReadTransaction *transaction) { - actualMessages = [self.finder fetchUnstartedExpiringMessagesInThread:self.thread - transaction:transaction]; - }]; - - NSArray *expectedMessages = @[ unreadExpiringMessage ]; - XCTAssertEqualObjects(expectedMessages, actualMessages); -} - -- (NSNumber *)nextExpirationTimestamp -{ - __block NSNumber *nextExpirationTimestamp; - - [self readWithBlock:^(YapDatabaseReadTransaction *transaction) { - XCTAssertNotNil(self.finder); - nextExpirationTimestamp = [self.finder nextExpirationTimestampWithTransaction:transaction]; - }]; - - return nextExpirationTimestamp; -} - -- (void)testNextExpirationTimestampNilWhenNoExpiringMessages -{ - // Sanity check. - - XCTAssertNil(self.nextExpirationTimestamp); - - TSMessage *unExpiringMessage = [self messageWithBody:@"unexpiringMessage" expiresInSeconds:0 expireStartedAt:0]; - [unExpiringMessage save]; - XCTAssertNil(self.nextExpirationTimestamp); -} - -- (void)testNextExpirationTimestampNotNilWithUpcomingExpiringMessages -{ - TSMessage *soonToExpireMessage = - [self messageWithBody:@"soonToExpireMessage" expiresInSeconds:10 expireStartedAt:self.now - 9000]; - [soonToExpireMessage save]; - - XCTAssertNotNil(self.nextExpirationTimestamp); - XCTAssertEqual(self.now + 1000, [self.nextExpirationTimestamp unsignedLongLongValue]); - - // expired message should take precedence - TSMessage *expiredMessage = - [self messageWithBody:@"expiredMessage" expiresInSeconds:10 expireStartedAt:self.now - 11000]; - [expiredMessage save]; - - XCTAssertNotNil(self.nextExpirationTimestamp); - XCTAssertEqual(self.now - 1000, [self.nextExpirationTimestamp unsignedLongLongValue]); -} - -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/tests/Messages/OWSDisappearingMessagesJobTest.m b/SignalServiceKit/tests/Messages/OWSDisappearingMessagesJobTest.m deleted file mode 100644 index 1c5d24211..000000000 --- a/SignalServiceKit/tests/Messages/OWSDisappearingMessagesJobTest.m +++ /dev/null @@ -1,138 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSDisappearingMessagesJob.h" -#import "OWSDisappearingMessagesConfiguration.h" -#import "OWSDisappearingMessagesFinder.h" -#import "OWSPrimaryStorage.h" -#import "SSKBaseTestObjC.h" -#import "TSContactThread.h" -#import "TSMessage.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -#ifdef BROKEN_TESTS - -@interface OWSDisappearingMessagesJob (Testing) - -- (NSUInteger)runLoop; - -@end - -@interface OWSDisappearingMessagesJobTest : SSKBaseTestObjC - -@property TSThread *thread; - -@end - -@implementation OWSDisappearingMessagesJobTest - -- (void)setUp -{ - [super setUp]; - - // NOTE: Certain parts of the codebase assert that contact ids are valid e164 - // phone numbers. - self.thread = [TSContactThread getOrCreateThreadWithContactId:@"+19999999999"]; -} - -- (TSMessage *)messageWithBody:(NSString *)body - expiresInSeconds:(uint32_t)expiresInSeconds - expireStartedAt:(uint64_t)expireStartedAt -{ - return [[TSMessage alloc] initMessageWithTimestamp:1 - inThread:self.thread - messageBody:body - attachmentIds:@[] - expiresInSeconds:expiresInSeconds - expireStartedAt:expireStartedAt - quotedMessage:nil - contactShare:nil - linkPreview:nil]; -} - -#ifdef BROKEN_TESTS - -- (void)testRemoveAnyExpiredMessage -{ - uint64_t now = [NSDate ows_millisecondTimeStamp]; - TSMessage *expiredMessage1 = - [self messageWithBody:@"expiredMessage1" expiresInSeconds:1 expireStartedAt:now - 20000]; - [expiredMessage1 save]; - - TSMessage *expiredMessage2 = - [self messageWithBody:@"expiredMessage2" expiresInSeconds:2 expireStartedAt:now - 2001]; - [expiredMessage2 save]; - - TSMessage *notYetExpiredMessage = - [self messageWithBody:@"notYetExpiredMessage" expiresInSeconds:20 expireStartedAt:now - 10000]; - [notYetExpiredMessage save]; - - TSMessage *unExpiringMessage = [self messageWithBody:@"unexpiringMessage" expiresInSeconds:0 expireStartedAt:0]; - [unExpiringMessage save]; - - - OWSDisappearingMessagesJob *job = [OWSDisappearingMessagesJob sharedJob]; - - // Sanity Check. - XCTAssertEqual(4, [TSMessage numberOfKeysInCollection]); - [job runLoop]; - - //FIXME remove sleep hack in favor of expiringMessage completion handler - sleep(4); - XCTAssertEqual(2, [TSMessage numberOfKeysInCollection]); -} - -#endif - -- (void)testBecomeConsistentWithMessageConfiguration -{ - OWSDisappearingMessagesJob *job = [OWSDisappearingMessagesJob sharedJob]; - - OWSDisappearingMessagesConfiguration *configuration = - [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:self.thread.uniqueId]; - [configuration remove]; - - TSMessage *expiringMessage = [self messageWithBody:@"notYetExpiredMessage" expiresInSeconds:20 expireStartedAt:0]; - [expiringMessage save]; - - [self - readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [job becomeConsistentWithConfigurationForMessage:expiringMessage - contactsManager:[OWSFakeContactsManager new] - transaction:transaction]; - }]; - configuration = [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:self.thread.uniqueId]; - - XCTAssertNotNil(configuration); - XCTAssert(configuration.isEnabled); - XCTAssertEqual(20, configuration.durationSeconds); -} - -- (void)testBecomeConsistentWithUnexpiringMessageConfiguration -{ - OWSDisappearingMessagesJob *job = [OWSDisappearingMessagesJob sharedJob]; - - OWSDisappearingMessagesConfiguration *configuration = - [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:self.thread.uniqueId]; - [configuration remove]; - - TSMessage *unExpiringMessage = [self messageWithBody:@"unexpiringMessage" expiresInSeconds:0 expireStartedAt:0]; - [unExpiringMessage save]; - [self - readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [job becomeConsistentWithConfigurationForMessage:unExpiringMessage - contactsManager:[OWSFakeContactsManager new] - transaction:transaction]; - }]; - - XCTAssertNil([OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:self.thread.uniqueId]); -} - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/tests/Messages/OWSIncomingMessageFinderTest.m b/SignalServiceKit/tests/Messages/OWSIncomingMessageFinderTest.m deleted file mode 100644 index 9c6d9fca0..000000000 --- a/SignalServiceKit/tests/Messages/OWSIncomingMessageFinderTest.m +++ /dev/null @@ -1,136 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSDevice.h" -#import "OWSIncomingMessageFinder.h" -#import "OWSPrimaryStorage.h" -#import "SSKBaseTestObjC.h" -#import "TSContactThread.h" -#import "TSIncomingMessage.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSIncomingMessageFinder (Testing) - -- (void)registerExtension; - -@end - -@interface OWSIncomingMessageFinderTest : SSKBaseTestObjC - -@property (nonatomic) NSString *sourceId; -@property (nonatomic) TSThread *thread; -@property (nonatomic) OWSIncomingMessageFinder *finder; - -@end - -@implementation OWSIncomingMessageFinderTest - -- (void)setUp -{ - [super setUp]; - self.sourceId = @"+19999999999"; - self.thread = [TSContactThread getOrCreateThreadWithContactId:self.sourceId]; - self.finder = [OWSIncomingMessageFinder new]; -} - -- (void)tearDown -{ - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - - -- (void)createIncomingMessageWithTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - sourceDeviceId:(uint32_t)sourceDeviceId -{ - TSIncomingMessage *incomingMessage = [[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:timestamp - inThread:self.thread - authorId:authorId - sourceDeviceId:sourceDeviceId - messageBody:@"foo" - attachmentIds:@[] - expiresInSeconds:0 - quotedMessage:nil - contactShare:nil - linkPreview:nil - serverTimestamp:nil - wasReceivedByUD:NO]; - [incomingMessage save]; -} - -- (void)testExistingMessages -{ - - uint64_t timestamp = 1234; - __block BOOL result; - - [self readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - result = [self.finder existsMessageWithTimestamp:timestamp - sourceId:self.sourceId - sourceDeviceId:OWSDevicePrimaryDeviceId - transaction:transaction]; - }]; - - - // Sanity check. - XCTAssertFalse(result); - - // Different timestamp - [self createIncomingMessageWithTimestamp:timestamp + 1 - authorId:self.sourceId - sourceDeviceId:OWSDevicePrimaryDeviceId]; - - [self readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - result = [self.finder existsMessageWithTimestamp:timestamp - sourceId:self.sourceId - sourceDeviceId:OWSDevicePrimaryDeviceId - transaction:transaction]; - }]; - - XCTAssertFalse(result); - - // Different authorId - [self createIncomingMessageWithTimestamp:timestamp - authorId:@"some-other-author-id" - sourceDeviceId:OWSDevicePrimaryDeviceId]; - - [self readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - result = [self.finder existsMessageWithTimestamp:timestamp - sourceId:self.sourceId - sourceDeviceId:OWSDevicePrimaryDeviceId - transaction:transaction]; - }]; - XCTAssertFalse(result); - - // Different device - [self createIncomingMessageWithTimestamp:timestamp - authorId:self.sourceId - sourceDeviceId:OWSDevicePrimaryDeviceId + 1]; - - [self readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - result = [self.finder existsMessageWithTimestamp:timestamp - sourceId:self.sourceId - sourceDeviceId:OWSDevicePrimaryDeviceId - transaction:transaction]; - }]; - XCTAssertFalse(result); - - // The real deal... - [self createIncomingMessageWithTimestamp:timestamp authorId:self.sourceId sourceDeviceId:OWSDevicePrimaryDeviceId]; - - [self readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - result = [self.finder existsMessageWithTimestamp:timestamp - sourceId:self.sourceId - sourceDeviceId:OWSDevicePrimaryDeviceId - transaction:transaction]; - }]; - - XCTAssertTrue(result); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/tests/Messages/OWSLinkPreviewTest.swift b/SignalServiceKit/tests/Messages/OWSLinkPreviewTest.swift deleted file mode 100644 index 160ab2734..000000000 --- a/SignalServiceKit/tests/Messages/OWSLinkPreviewTest.swift +++ /dev/null @@ -1,583 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -@testable import SessionServiceKit -import XCTest - -func XCTAssertMatch(expectedPattern: String, actualText: String, file: StaticString = #file, line: UInt = #line) { - let regex = try! NSRegularExpression(pattern: expectedPattern, options: []) - XCTAssert(regex.hasMatch(input: actualText), "\(actualText) did not match pattern \(expectedPattern)", file: file, line: line) -} - -class OWSLinkPreviewTest: SSKBaseTestSwift { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testBuildValidatedLinkPreview_TitleAndImage() { - let url = "https://www.youtube.com/watch?v=tP-Ipsat90c" - let body = "\(url)" - let previewBuilder = SSKProtoDataMessagePreview.builder(url: url) - previewBuilder.setTitle("Some Youtube Video") - let imageAttachmentBuilder = SSKProtoAttachmentPointer.builder(id: 1) - imageAttachmentBuilder.setKey(Randomness.generateRandomBytes(32)) - imageAttachmentBuilder.setContentType(OWSMimeTypeImageJpeg) - previewBuilder.setImage(try! imageAttachmentBuilder.build()) - let dataBuilder = SSKProtoDataMessage.builder() - dataBuilder.addPreview(try! previewBuilder.build()) - - self.readWrite { (transaction) in - XCTAssertNotNil(try! OWSLinkPreview.buildValidatedLinkPreview(dataMessage: try! dataBuilder.build(), - body: body, - transaction: transaction)) - } - } - - func testBuildValidatedLinkPreview_Title() { - let url = "https://www.youtube.com/watch?v=tP-Ipsat90c" - let body = "\(url)" - let previewBuilder = SSKProtoDataMessagePreview.builder(url: url) - previewBuilder.setTitle("Some Youtube Video") - let dataBuilder = SSKProtoDataMessage.builder() - dataBuilder.addPreview(try! previewBuilder.build()) - - self.readWrite { (transaction) in - XCTAssertNotNil(try! OWSLinkPreview.buildValidatedLinkPreview(dataMessage: try! dataBuilder.build(), - body: body, - transaction: transaction)) - } - } - - func testBuildValidatedLinkPreview_Image() { - let url = "https://www.youtube.com/watch?v=tP-Ipsat90c" - let body = "\(url)" - let previewBuilder = SSKProtoDataMessagePreview.builder(url: url) - let imageAttachmentBuilder = SSKProtoAttachmentPointer.builder(id: 1) - imageAttachmentBuilder.setKey(Randomness.generateRandomBytes(32)) - imageAttachmentBuilder.setContentType(OWSMimeTypeImageJpeg) - previewBuilder.setImage(try! imageAttachmentBuilder.build()) - let dataBuilder = SSKProtoDataMessage.builder() - dataBuilder.addPreview(try! previewBuilder.build()) - - self.readWrite { (transaction) in - XCTAssertNotNil(try! OWSLinkPreview.buildValidatedLinkPreview(dataMessage: try! dataBuilder.build(), - body: body, - transaction: transaction)) - } - } - - func testBuildValidatedLinkPreview_NoTitleOrImage() { - let url = "https://www.youtube.com/watch?v=tP-Ipsat90c" - let body = "\(url)" - let previewBuilder = SSKProtoDataMessagePreview.builder(url: url) - let dataBuilder = SSKProtoDataMessage.builder() - dataBuilder.addPreview(try! previewBuilder.build()) - - self.readWrite { (transaction) in - do { - _ = try OWSLinkPreview.buildValidatedLinkPreview(dataMessage: try! dataBuilder.build(), - body: body, - transaction: transaction) - XCTFail("Missing expected error.") - } catch { - // Do nothing. - } - } - } - - func testIsValidLinkUrl() { - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://www.youtube.com/watch?v=tP-Ipsat90c")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://youtube.com/watch?v=tP-Ipsat90c")) - - // Case shouldn't matter. - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://WWW.YOUTUBE.COM/watch?v=tP-Ipsat90c")) - - // Don't allow arbitrary subdomains. - XCTAssertFalse(OWSLinkPreview.isValidMediaUrl("https://some.random.subdomain.youtube.com/watch?v=tP-Ipsat90c")) - - // Don't allow HTTP, only HTTPS - XCTAssertFalse(OWSLinkPreview.isValidLinkUrl("http://youtube.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidLinkUrl("mailto://youtube.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidLinkUrl("ftp://youtube.com/watch?v=tP-Ipsat90c")) - - // Don't allow similar domains. - XCTAssertFalse(OWSLinkPreview.isValidLinkUrl("https://xyoutube.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidLinkUrl("https://youtubex.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidLinkUrl("https://youtube.comx/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidLinkUrl("https://www.xyoutube.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidLinkUrl("https://www.youtubex.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidLinkUrl("https://www.youtube.comx/watch?v=tP-Ipsat90c")) - - // Don't allow media domains. - XCTAssertFalse(OWSLinkPreview.isValidLinkUrl("https://i.ytimg.com/vi/tP-Ipsat90c/maxresdefault.jpg")) - - // Allow all whitelisted domains. - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://www.youtube.com/watch?v=tP-Ipsat90c")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://youtu.be/tP-Ipsat90c")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://www.reddit.com/r/androiddev/comments/a7gctz/androidx_release_notes_this_is_the_first_release/")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://www.reddit.com/r/WhitePeopleTwitter/comments/a7j3mm/why/")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://imgur.com/gallery/KFCL8fm")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://imgur.com/gallery/FMdwTiV")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://www.instagram.com/p/BrgpsUjF9Jo/?utm_source=ig_web_button_share_sheet")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://www.instagram.com/p/BrgpsUjF9Jo/?utm_source=ig_share_sheet&igshid=94c7ihqjfmbm")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://imgur.com/gallery/igHOwDM")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://pinterest.com/something")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://www.pinterest.com/something")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://pin.it/something")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://www.pinterest.com/ohjoy/recipes/")) - - // Strip trailing commas. - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://imgur.com/gallery/igHOwDM,")) - - // Ignore URLs with an empty path. - XCTAssertFalse(OWSLinkPreview.isValidLinkUrl("https://imgur.com")) - XCTAssertFalse(OWSLinkPreview.isValidLinkUrl("https://imgur.com/")) - XCTAssertTrue(OWSLinkPreview.isValidLinkUrl("https://imgur.com/X")) - } - - func testIsValidMediaUrl() { - // Only allow domains on the media whitelist. - XCTAssertFalse(OWSLinkPreview.isValidMediaUrl("https://www.youtube.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidMediaUrl("https://youtube.com/watch?v=tP-Ipsat90c")) - - // Allow arbitrary subdomains. - XCTAssertTrue(OWSLinkPreview.isValidMediaUrl("https://ytimg.com/something")) - XCTAssertTrue(OWSLinkPreview.isValidMediaUrl("https://something.ytimg.com/something")) - - // Don't allow HTTP, only HTTPS - XCTAssertFalse(OWSLinkPreview.isValidMediaUrl("http://ytimg.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidMediaUrl("mailto://ytimg.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidMediaUrl("ftp://ytimg.com/watch?v=tP-Ipsat90c")) - - // Don't allow similar domains. - XCTAssertFalse(OWSLinkPreview.isValidMediaUrl("https://xytimg.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidMediaUrl("https://youtubex.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidMediaUrl("https://ytimg.comx/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidMediaUrl("https://www.xytimg.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidMediaUrl("https://www.ytimgx.com/watch?v=tP-Ipsat90c")) - XCTAssertFalse(OWSLinkPreview.isValidMediaUrl("https://www.ytimg.comx/watch?v=tP-Ipsat90c")) - - // Allow media domains. - XCTAssertTrue(OWSLinkPreview.isValidMediaUrl("https://i.ytimg.com/vi/tP-Ipsat90c/maxresdefault.jpg")) - XCTAssertTrue(OWSLinkPreview.isValidMediaUrl("https://external-preview.redd.it/j5lhdY0huShdzyrbSEdKzOb09BKhNreyEZOLDu1UzBA.jpg?auto=webp&s=2cb8bdb5ac5b54fc9514719030c0c9f08a03f684")) - XCTAssertTrue(OWSLinkPreview.isValidMediaUrl("https://preview.redd.it/ehakvm9vx5521.jpg?auto=webp&s=925fb2d8776ca7102b944ab00e0615ae20c1bd5a")) - XCTAssertTrue(OWSLinkPreview.isValidMediaUrl("https://i.imgur.com/Y3wjlwY.jpg?fb")) - XCTAssertTrue(OWSLinkPreview.isValidMediaUrl("https://i.imgur.com/Vot3iHh.jpg?fbplay")) - XCTAssertTrue(OWSLinkPreview.isValidMediaUrl("https://scontent-mia3-2.cdninstagram.com/vp/9035a7d6b32e6f840856661e4a11e3cf/5CFC285B/t51.2885-15/e35/47690175_2275988962411653_1145978227188801192_n.jpg?_nc_ht=scontent-mia3-2.cdninstagram.com")) - XCTAssertTrue(OWSLinkPreview.isValidMediaUrl("https://scontent-mia3-2.cdninstagram.com/vp/9035a7d6b32e6f840856661e4a11e3cf/5CFC285B/t51.2885-15/e35/47690175_2275988962411653_1145978227188801192_n.jpg?_nc_ht=scontent-mia3-2.cdninstagram.com")) - XCTAssertTrue(OWSLinkPreview.isValidMediaUrl("https://i.imgur.com/PYiyLv1.jpg?fbplay")) - XCTAssertTrue(OWSLinkPreview.isValidMediaUrl("https://pinimg.com/something")) - } - - func testPreviewUrlForMessageBodyText() { - Assert(bodyText: "", extractsLink: nil) - Assert(bodyText: "alice bob jim", extractsLink: nil) - Assert(bodyText: "alice bob jim http://", extractsLink: nil) - Assert(bodyText: "alice bob jim http://a.com", extractsLink: nil) - - Assert(bodyText: "https://www.youtube.com/watch?v=tP-Ipsat90c", - extractsLink: "https://www.youtube.com/watch?v=tP-Ipsat90c") - - Assert(bodyText: "alice bob https://www.youtube.com/watch?v=tP-Ipsat90c jim", - extractsLink: "https://www.youtube.com/watch?v=tP-Ipsat90c") - - // If there are more than one, take the first. - Assert(bodyText: "alice bob https://www.youtube.com/watch?v=tP-Ipsat90c jim https://www.youtube.com/watch?v=other-url carol", - extractsLink: "https://www.youtube.com/watch?v=tP-Ipsat90c") - } - - func testUtils() { - XCTAssertNil(OWSLinkPreview.fileExtension(forImageUrl: "")) - XCTAssertNil(OWSLinkPreview.fileExtension(forImageUrl: "https://www.some.host/path/imagename")) - XCTAssertNil(OWSLinkPreview.fileExtension(forImageUrl: "https://www.some.host/path/imagename.")) - - XCTAssertEqual(OWSLinkPreview.fileExtension(forImageUrl: "https://www.some.host/path/imagename.jpg"), "jpg") - XCTAssertEqual(OWSLinkPreview.fileExtension(forImageUrl: "https://www.some.host/path/imagename.gif"), "gif") - XCTAssertEqual(OWSLinkPreview.fileExtension(forImageUrl: "https://www.some.host/path/imagename.png"), "png") - XCTAssertEqual(OWSLinkPreview.fileExtension(forImageUrl: "https://www.some.host/path/imagename.boink"), "boink") - - XCTAssertNil(OWSLinkPreview.mimetype(forImageFileExtension: "")) - XCTAssertNil(OWSLinkPreview.mimetype(forImageFileExtension: "boink")) - XCTAssertNil(OWSLinkPreview.mimetype(forImageFileExtension: "tiff")) - XCTAssertNil(OWSLinkPreview.mimetype(forImageFileExtension: "gif")) - - XCTAssertEqual(OWSLinkPreview.mimetype(forImageFileExtension: "jpg"), OWSMimeTypeImageJpeg) - XCTAssertEqual(OWSLinkPreview.mimetype(forImageFileExtension: "png"), OWSMimeTypeImagePng) - } - - func testLinkDownloadAndParsing() { - let expectation = self.expectation(description: "link download and parsing") - - OWSLinkPreview.tryToBuildPreviewInfo(previewUrl: "https://www.youtube.com/watch?v=tP-Ipsat90c") - .done { (draft: OWSLinkPreviewDraft) in - XCTAssertNotNil(draft) - - XCTAssertEqual(draft.title, "Randomness is Random - Numberphile") - XCTAssertNotNil(draft.jpegImageData) - - expectation.fulfill() - }.catch { (error) in - Logger.error("error: \(error)") - XCTFail("Unexpected error: \(error)") - expectation.fulfill() - }.retainUntilComplete() - - self.waitForExpectations(timeout: 5.0, handler: nil) - } - - func testLinkDataParsing_Empty() { - let linkText = "" - let linkData = linkText.data(using: .utf8)! - - let content = try! OWSLinkPreview.parse(linkData: linkData) - XCTAssertNotNil(content) - - XCTAssertNil(content.title) - XCTAssertNil(content.imageUrl) - } - - func testLinkDataParsing() { - let linkText = ("" + - "") - let linkData = linkText.data(using: .utf8)! - - let content = try! OWSLinkPreview.parse(linkData: linkData) - XCTAssertNotNil(content) - - XCTAssertEqual(content.title, "Randomness is Random - Numberphile") - XCTAssertEqual(content.imageUrl, "https://i.ytimg.com/vi/tP-Ipsat90c/maxresdefault.jpg") - } - - func testLinkParsingWithRealData1() { - let expectation = self.expectation(description: "link download and parsing") - - OWSLinkPreview.downloadLink(url: "https://www.youtube.com/watch?v=tP-Ipsat90c") - .done { (linkData) in - let content = try! OWSLinkPreview.parse(linkData: linkData) - XCTAssertNotNil(content) - - XCTAssertEqual(content.title, "Randomness is Random - Numberphile") - XCTAssertEqual(content.imageUrl, "https://i.ytimg.com/vi/tP-Ipsat90c/maxresdefault.jpg") - - expectation.fulfill() - }.catch { (error) in - Logger.error("error: \(error)") - XCTFail("Unexpected error: \(error)") - expectation.fulfill() - }.retainUntilComplete() - - self.waitForExpectations(timeout: 5.0, handler: nil) - } - - func testLinkParsingWithRealData2() { - let expectation = self.expectation(description: "link download and parsing") - - OWSLinkPreview.downloadLink(url: "https://youtu.be/tP-Ipsat90c") - .done { (linkData) in - let content = try! OWSLinkPreview.parse(linkData: linkData) - XCTAssertNotNil(content) - - XCTAssertEqual(content.title, "Randomness is Random - Numberphile") - XCTAssertEqual(content.imageUrl, "https://i.ytimg.com/vi/tP-Ipsat90c/maxresdefault.jpg") - - expectation.fulfill() - }.catch { (error) in - Logger.error("error: \(error)") - XCTFail("Unexpected error: \(error)") - expectation.fulfill() - }.retainUntilComplete() - - self.waitForExpectations(timeout: 5.0, handler: nil) - } - - func testLinkParsingWithRealData3() { - let expectation = self.expectation(description: "link download and parsing") - - OWSLinkPreview.downloadLink(url: "https://www.reddit.com/r/androiddev/comments/a7gctz/androidx_release_notes_this_is_the_first_release/") - .done { (linkData) in - let content = try! OWSLinkPreview.parse(linkData: linkData) - XCTAssertNotNil(content) - - XCTAssertEqual(content.title, "r/androiddev - AndroidX release notes | This is the first release of SavedState") - XCTAssertEqual(content.imageUrl, "https://external-preview.redd.it/j5lhdY0huShdzyrbSEdKzOb09BKhNreyEZOLDu1UzBA.jpg?auto=webp&s=2cb8bdb5ac5b54fc9514719030c0c9f08a03f684") - - expectation.fulfill() - }.catch { (error) in - Logger.error("error: \(error)") - XCTFail("Unexpected error: \(error)") - expectation.fulfill() - }.retainUntilComplete() - - self.waitForExpectations(timeout: 5.0, handler: nil) - } - - func testLinkParsingWithRealData4() { - let expectation = self.expectation(description: "link download and parsing") - - OWSLinkPreview.downloadLink(url: "https://www.reddit.com/r/WhitePeopleTwitter/comments/a7j3mm/why/") - .done { (linkData) in - let content = try! OWSLinkPreview.parse(linkData: linkData) - XCTAssertNotNil(content) - - XCTAssertEqual(content.title, "r/WhitePeopleTwitter - Why") - XCTAssertEqual(content.imageUrl, "https://preview.redd.it/ehakvm9vx5521.jpg?auto=webp&s=925fb2d8776ca7102b944ab00e0615ae20c1bd5a") - - expectation.fulfill() - }.catch { (error) in - Logger.error("error: \(error)") - XCTFail("Unexpected error: \(error)") - expectation.fulfill() - }.retainUntilComplete() - - self.waitForExpectations(timeout: 5.0, handler: nil) - } - - func testLinkParsingWithRealData5() { - let expectation = self.expectation(description: "link download and parsing") - - OWSLinkPreview.downloadLink(url: "https://imgur.com/gallery/KFCL8fm") - .done { (linkData) in - let content = try! OWSLinkPreview.parse(linkData: linkData) - XCTAssertNotNil(content) - - XCTAssertNil(content.title) - XCTAssertEqual(content.imageUrl, "https://i.imgur.com/Y3wjlwY.jpg?fb") - - expectation.fulfill() - }.catch { (error) in - Logger.error("error: \(error)") - XCTFail("Unexpected error: \(error)") - expectation.fulfill() - }.retainUntilComplete() - - self.waitForExpectations(timeout: 5.0, handler: nil) - } - - func testLinkParsingWithRealData6() { - let expectation = self.expectation(description: "link download and parsing") - - OWSLinkPreview.downloadLink(url: "https://imgur.com/gallery/FMdwTiV") - .done { (linkData) in - let content = try! OWSLinkPreview.parse(linkData: linkData) - XCTAssertNotNil(content) - - XCTAssertEqual(content.title, "Freddy would be proud!") - XCTAssertEqual(content.imageUrl, "https://i.imgur.com/Vot3iHh.jpg?fbplay") - - expectation.fulfill() - }.catch { (error) in - Logger.error("error: \(error)") - XCTFail("Unexpected error: \(error)") - expectation.fulfill() - }.retainUntilComplete() - - self.waitForExpectations(timeout: 5.0, handler: nil) - } - - func testLinkParsingWithRealData7() { - let expectation = self.expectation(description: "link download and parsing") - - OWSLinkPreview.downloadLink(url: "https://www.instagram.com/p/BrgpsUjF9Jo/?utm_source=ig_web_button_share_sheet") - .done { (linkData) in - let content = try! OWSLinkPreview.parse(linkData: linkData) - XCTAssertNotNil(content) - - XCTAssertEqual(content.title, "Walter \"MFPallytime\" on Instagram: “Lol gg”") - // Actual URL can change based on network response - // https://scontent-mia3-2.cdninstagram.com/vp/9035a7d6b32e6f840856661e4a11e3cf/5CFC285B/t51.2885-15/e35/47690175_2275988962411653_1145978227188801192_n.jpg?_nc_ht=scontent-mia3-2.cdninstagram.com - // It seems like some parts of the URL are stable, so we can pattern match, but if this continues to be brittle we may choose - // to remove it or stub the network response - XCTAssertMatch(expectedPattern: "^https://.*.cdninstagram.com/.*/47690175_2275988962411653_1145978227188801192_n.jpg\\?.*$", - actualText: content.imageUrl!) -// XCTAssertEqual(content.imageUrl, "https://scontent-mia3-2.cdninstagram.com/vp/9035a7d6b32e6f840856661e4a11e3cf/5CFC285B/t51.2885-15/e35/47690175_2275988962411653_1145978227188801192_n.jpg?_nc_ht=scontent-mia3-2.cdninstagram.com") - - expectation.fulfill() - }.catch { (error) in - Logger.error("error: \(error)") - XCTFail("Unexpected error: \(error)") - expectation.fulfill() - }.retainUntilComplete() - - self.waitForExpectations(timeout: 5.0, handler: nil) - } - - func testLinkParsingWithRealData8() { - let expectation = self.expectation(description: "link download and parsing") - - OWSLinkPreview.downloadLink(url: "https://www.instagram.com/p/BrgpsUjF9Jo/?utm_source=ig_share_sheet&igshid=94c7ihqjfmbm") - .done { (linkData) in - let content = try! OWSLinkPreview.parse(linkData: linkData) - XCTAssertNotNil(content) - - XCTAssertEqual(content.title, "Walter \"MFPallytime\" on Instagram: “Lol gg”") - // Actual URL can change based on network response - // https://scontent-mia3-2.cdninstagram.com/vp/9035a7d6b32e6f840856661e4a11e3cf/5CFC285B/t51.2885-15/e35/47690175_2275988962411653_1145978227188801192_n.jpg?_nc_ht=scontent-mia3-2.cdninstagram.com - // It seems like some parts of the URL are stable, so we can pattern match, but if this continues to be brittle we may choose - // to remove it or stub the network response - XCTAssertMatch(expectedPattern: "^https://.*.cdninstagram.com/.*/47690175_2275988962411653_1145978227188801192_n.jpg\\?.*$", - actualText: content.imageUrl!) - - expectation.fulfill() - }.catch { (error) in - Logger.error("error: \(error)") - XCTFail("Unexpected error: \(error)") - expectation.fulfill() - }.retainUntilComplete() - - self.waitForExpectations(timeout: 5.0, handler: nil) - } - - func testLinkParsingWithRealData9() { - let expectation = self.expectation(description: "link download and parsing") - - OWSLinkPreview.downloadLink(url: "https://imgur.com/gallery/igHOwDM") - .done { (linkData) in - let content = try! OWSLinkPreview.parse(linkData: linkData) - XCTAssertNotNil(content) - - XCTAssertEqual(content.title, "Sheet dance") - XCTAssertEqual(content.imageUrl, "https://i.imgur.com/PYiyLv1.jpg?fbplay") - - expectation.fulfill() - }.catch { (error) in - Logger.error("error: \(error)") - XCTFail("Unexpected error: \(error)") - expectation.fulfill() - }.retainUntilComplete() - - self.waitForExpectations(timeout: 5.0, handler: nil) - } - - func testLinkParsingWithRealData10() { - let expectation = self.expectation(description: "link download and parsing") - - OWSLinkPreview.downloadLink(url: "https://www.pinterest.com/ohjoy/recipes/") - .done { (linkData) in - let content = try! OWSLinkPreview.parse(linkData: linkData) - XCTAssertNotNil(content) - - XCTAssertEqual(content.title, "Recipes") - XCTAssertEqual(content.imageUrl, "https://i.pinimg.com/200x150/76/ae/9d/76ae9d3056dbcb295924fdd5db6951c6.jpg") - - expectation.fulfill() - }.catch { (error) in - Logger.error("error: \(error)") - XCTFail("Unexpected error: \(error)") - expectation.fulfill() - }.retainUntilComplete() - - self.waitForExpectations(timeout: 5.0, handler: nil) - } - - // When using regular expressions to parse link titles, we need to use - // String.utf16.count, not String.count in the range. - func testRegexRanges() { - let regex = try! NSRegularExpression(pattern: "bob", options: []) - var text = "bob" - XCTAssertNotNil(regex.firstMatch(in: text, - options: [], - range: NSRange(location: 0, length: text.count))) - XCTAssertNotNil(regex.firstMatch(in: text, - options: [], - range: NSRange(location: 0, length: text.utf16.count))) - text = "😂😘🙂 bob" - XCTAssertNil(regex.firstMatch(in: text, - options: [], - range: NSRange(location: 0, length: text.count))) - XCTAssertNotNil(regex.firstMatch(in: text, - options: [], - range: NSRange(location: 0, length: text.utf16.count))) - } - - func testCursorPositions() { - // sanity check - Assert(bodyText: "https://www.youtube.com/watch?v=testCursorPositionsa", - extractsLink: "https://www.youtube.com/watch?v=testCursorPositionsa", - selectedRange: nil) - - // Don't extract link if cursor is touching text - let text2 = "https://www.youtube.com/watch?v=testCursorPositionsb" - XCTAssertEqual(text2.count, 52) - Assert(bodyText: text2, - extractsLink: nil, - selectedRange: NSRange(location: 51, length: 0)) - - Assert(bodyText: text2, - extractsLink: nil, - selectedRange: NSRange(location: 51, length: 10)) - - Assert(bodyText: text2, - extractsLink: nil, - selectedRange: NSRange(location: 0, length: 0)) - - // Unless the cursor is at the end of the text - Assert(bodyText: text2, - extractsLink: "https://www.youtube.com/watch?v=testCursorPositionsb", - selectedRange: NSRange(location: 52, length: 0)) - - // Once extracted, keep the existing link preview, even if the cursor moves back. - Assert(bodyText: text2, - extractsLink: "https://www.youtube.com/watch?v=testCursorPositionsb", - selectedRange: NSRange(location: 51, length: 0)) - - let text3 = "foo https://www.youtube.com/watch?v=testCursorPositionsc bar" - XCTAssertEqual(text3.count, 60) - - // front edge - Assert(bodyText: text3, - extractsLink: nil, - selectedRange: NSRange(location: 4, length: 0)) - - // middle - Assert(bodyText: text3, - extractsLink: nil, - selectedRange: NSRange(location: 4, length: 0)) - - // rear edge - Assert(bodyText: text3, - extractsLink: nil, - selectedRange: NSRange(location: 56, length: 0)) - - // extract link if selecting after link - Assert(bodyText: text3, - extractsLink: "https://www.youtube.com/watch?v=testCursorPositionsc", - selectedRange: NSRange(location: 57, length: 0)) - - let text4 = "bar https://www.youtube.com/watch?v=testCursorPositionsd foo" - XCTAssertEqual(text4.count, 60) - - // front edge - Assert(bodyText: text4, - extractsLink: nil, - selectedRange: NSRange(location: 4, length: 0)) - - // middle - Assert(bodyText: text4, - extractsLink: nil, - selectedRange: NSRange(location: 20, length: 0)) - - // rear edge - Assert(bodyText: text4, - extractsLink: nil, - selectedRange: NSRange(location: 56, length: 0)) - - // extract link if selecting before link - Assert(bodyText: text4, - extractsLink: "https://www.youtube.com/watch?v=testCursorPositionsd", - selectedRange: NSRange(location: 3, length: 0)) - } - - private func Assert(bodyText: String, extractsLink link: String?, selectedRange: NSRange? = nil, file: StaticString = #file, line: UInt = #line) { - let actual = OWSLinkPreview.previewUrl(forMessageBodyText: bodyText, selectedRange: selectedRange) - XCTAssertEqual(actual, link, file: file, line: line) - } -} diff --git a/SignalServiceKit/tests/Messages/OWSMessageManagerTest.m b/SignalServiceKit/tests/Messages/OWSMessageManagerTest.m deleted file mode 100644 index 2b3ac6f1d..000000000 --- a/SignalServiceKit/tests/Messages/OWSMessageManagerTest.m +++ /dev/null @@ -1,185 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "ContactsManagerProtocol.h" -#import "ContactsUpdater.h" -#import "MockSSKEnvironment.h" -#import "OWSFakeCallMessageHandler.h" -#import "OWSFakeMessageSender.h" -#import "OWSFakeNetworkManager.h" -#import "OWSIdentityManager.h" -#import "OWSMessageManager.h" -#import "OWSMessageSender.h" -#import "OWSPrimaryStorage.h" -#import "SSKBaseTestObjC.h" -#import "TSGroupThread.h" -#import "TSNetworkManager.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSMessageManager (Testing) - -// Private init for stubbing dependencies - -- (instancetype)initWithNetworkManager:(TSNetworkManager *)networkManager - storageManager:(OWSPrimaryStorage *)storageManager - callMessageHandler:(id)callMessageHandler - contactsManager:(id)contactsManager - contactsUpdater:(ContactsUpdater *)contactsUpdater - identityManager:(OWSIdentityManager *)identityManager - messageSender:(OWSMessageSender *)messageSender; - -// private method we are testing -- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)messageEnvelope withSyncMessage:(SSKProtoSyncMessage *)syncMessage; - -- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)messageEnvelope withDataMessage:(SSKProtoDataMessage *)dataMessage; - -@end - -#pragma mark - - -@interface OWSMessageManagerTest : SSKBaseTestObjC - -@end - -#pragma mark - - -@implementation OWSMessageManagerTest - -- (void)setUp -{ - [super setUp]; -} - -#ifdef BROKEN_TESTS - -- (void)testIncomingSyncContactMessage -{ - XCTestExpectation *messageWasSent = [self expectationWithDescription:@"message was sent"]; - - OWSAssert([SSKEnvironment.shared.messageSender isKindOfClass:[OWSFakeMessageSender class]]); - OWSFakeMessageSender *fakeMessageSender = (OWSFakeMessageSender *)SSKEnvironment.shared.messageSender; - fakeMessageSender.sendTemporaryAttachmentWasCalledBlock = ^{ - [messageWasSent fulfill]; - }; - - OWSMessageManager *messagesManager = OWSMessageManager.sharedManager; - - SSKProtoSyncMessageRequestBuilder *requestBuilder = [SSKProtoSyncMessageRequest builder]; - [requestBuilder setType:SSKProtoSyncMessageRequestTypeGroups]; - - SSKProtoSyncMessageBuilder *messageBuilder = [SSKProtoSyncMessage builder]; - [messageBuilder setRequest:[requestBuilder buildIgnoringErrors]]; - - SSKProtoEnvelopeBuilder *envelopeBuilder = [SSKProtoEnvelope builder]; - [envelopeBuilder setType:SSKProtoEnvelopeTypeCiphertext]; - [envelopeBuilder setSource:@"+13213214321"]; - [envelopeBuilder setSourceDevice:1]; - [envelopeBuilder setTimestamp:12345]; - - [messagesManager handleIncomingEnvelope:[envelopeBuilder buildIgnoringErrors] - withSyncMessage:[messageBuilder buildIgnoringErrors]]; - - [self waitForExpectationsWithTimeout:5 - handler:^(NSError *error) { - OWSLogInfo(@"No message submitted."); - }]; -} - -- (void)testGroupUpdate -{ - NSData *groupIdData = [Cryptography generateRandomBytes:32]; - NSString *groupThreadId = [TSGroupThread threadIdFromGroupId:groupIdData]; - TSGroupThread *groupThread = [TSGroupThread fetchObjectWithUniqueID:groupThreadId]; - XCTAssertNil(groupThread); - - OWSMessageManager *messagesManager = SSKEnvironment.shared.messageManager; - - SSKProtoEnvelopeBuilder *envelopeBuilder = [SSKProtoEnvelope builder]; - - SSKProtoGroupContextBuilder *groupContextBuilder = [SSKProtoGroupContext builder]; - groupContextBuilder.name = @"Newly created Group Name"; - groupContextBuilder.id = groupIdData; - groupContextBuilder.type = SSKProtoGroupContextTypeUpdate; - - SSKProtoDataMessageBuilder *messageBuilder = [SSKProtoDataMessage builder]; - messageBuilder.group = [groupContextBuilder buildIgnoringErrors]; - - [messagesManager handleIncomingEnvelope:[envelopeBuilder buildIgnoringErrors] - withDataMessage:[messageBuilder buildIgnoringErrors]]; - - groupThread = [TSGroupThread fetchObjectWithUniqueID:groupThreadId]; - XCTAssertNotNil(groupThread); - XCTAssertEqualObjects(@"Newly created Group Name", groupThread.name); -} - -- (void)testGroupUpdateWithAvatar -{ - NSData *groupIdData = [Cryptography generateRandomBytes:32]; - NSString *groupThreadId = [TSGroupThread threadIdFromGroupId:groupIdData]; - TSGroupThread *groupThread = [TSGroupThread fetchObjectWithUniqueID:groupThreadId]; - XCTAssertNil(groupThread); - - OWSMessageManager *messagesManager = SSKEnvironment.shared.messageManager; - - SSKProtoEnvelopeBuilder *envelopeBuilder = [SSKProtoEnvelope builder]; - - SSKProtoGroupContextBuilder *groupContextBuilder = [SSKProtoGroupContext builder]; - groupContextBuilder.name = @"Newly created Group with Avatar Name"; - groupContextBuilder.id = groupIdData; - groupContextBuilder.type = SSKProtoGroupContextTypeUpdate; - - SSKProtoAttachmentPointerBuilder *attachmentBuilder = [SSKProtoAttachmentPointer builder]; - attachmentBuilder.id = 1234; - attachmentBuilder.contentType = @"image/png"; - attachmentBuilder.key = [NSData new]; - attachmentBuilder.size = 123; - groupContextBuilder.avatar = [attachmentBuilder buildIgnoringErrors]; - - SSKProtoDataMessageBuilder *messageBuilder = [SSKProtoDataMessage builder]; - messageBuilder.group = [groupContextBuilder buildIgnoringErrors]; - - [messagesManager handleIncomingEnvelope:[envelopeBuilder buildIgnoringErrors] - withDataMessage:[messageBuilder buildIgnoringErrors]]; - - groupThread = [TSGroupThread fetchObjectWithUniqueID:groupThreadId]; - XCTAssertNotNil(groupThread); - XCTAssertEqualObjects(@"Newly created Group with Avatar Name", groupThread.name); -} - -- (void)testUnknownGroupMessageIsIgnored -{ - NSData *groupIdData = [Cryptography generateRandomBytes:32]; - TSGroupThread *groupThread = [TSGroupThread getOrCreateThreadWithGroupId:groupIdData]; - - // Sanity check - XCTAssertEqual(0, groupThread.numberOfInteractions); - - OWSMessageManager *messagesManager = SSKEnvironment.shared.messageManager; - - SSKProtoEnvelopeBuilder *envelopeBuilder = [SSKProtoEnvelope builder]; - - SSKProtoGroupContextBuilder *groupContextBuilder = [SSKProtoGroupContext builder]; - groupContextBuilder.name = @"Newly created Group with Avatar Name"; - groupContextBuilder.id = groupIdData; - - // e.g. some future feature sent from another device that we don't yet support. - groupContextBuilder.type = 666; - - SSKProtoDataMessageBuilder *messageBuilder = [SSKProtoDataMessage builder]; - messageBuilder.group = [groupContextBuilder buildIgnoringErrors]; - - [messagesManager handleIncomingEnvelope:[envelopeBuilder buildIgnoringErrors] - withDataMessage:[messageBuilder buildIgnoringErrors]]; - - XCTAssertEqual(0, groupThread.numberOfInteractions); -} - -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/tests/Messages/OWSMessageSenderTest.m b/SignalServiceKit/tests/Messages/OWSMessageSenderTest.m deleted file mode 100644 index 0d28dabc9..000000000 --- a/SignalServiceKit/tests/Messages/OWSMessageSenderTest.m +++ /dev/null @@ -1,546 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageSender.h" -#import "NSError+MessageSending.h" -#import "OWSDisappearingMessagesConfiguration.h" -#import "OWSError.h" -#import "OWSFakeNetworkManager.h" -#import "OWSPrimaryStorage.h" -#import "OWSUploadOperation.h" -#import "SSKBaseTestObjC.h" -#import "TSAccountManager.h" -#import "TSContactThread.h" -#import "TSGroupModel.h" -#import "TSGroupThread.h" -#import "TSNetworkManager.h" -#import "TSOutgoingMessage.h" -#import "TSRequest.h" -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#ifdef BROKEN_TESTS - -@interface OWSUploadOperation (Testing) - -- (instancetype)initWithAttachmentId:(NSString *)attachmentId - dbConnection:(YapDatabaseConnection *)dbConnection - networkManager:(TSNetworkManager *)networkManager; - -@end - -#pragma mark - - -@interface OWSMessageSender (Testing) - -@property (nonatomic) OWSUploadOperation *uploadingService; - -- (void)sendMessageToService:(TSOutgoingMessage *)message - success:(void (^)(void))successHandler - failure:(RetryableFailureHandler)failureHandler; - -@end - -#pragma mark - - -@implementation OWSMessageSender (Testing) - -- (NSArray *)deviceMessages:(TSOutgoingMessage *)message - forRecipient:(SignalRecipient *)recipient - inThread:(TSThread *)thread -{ - OWSLogInfo(@"[OWSFakeMessagesManager] Faking deviceMessages."); - return @[]; -} - -- (void)setUploadingService:(OWSUploadingService *)uploadingService -{ - _uploadingService = uploadingService; -} - -- (OWSUploadingService *)uploadingService -{ - return _uploadingService; -} - -@end - -#pragma mark - - -@interface OWSFakeUploadingService : OWSUploadOperation - -@property (nonatomic, readonly) BOOL shouldSucceed; - -@end - -#pragma mark - - -@implementation OWSFakeUploadingService - -- (instancetype)initWithAttachmentId:(NSString *)attachmentId - dbConnection:(YapDatabaseConnection *)dbConnection - shouldSucceed:(BOOL)shouldSucceed -{ - self = - [super initWithAttachmentId:attachmentId dbConnection:dbConnection networkManager:[OWSFakeNetworkManager new]]; - if (!self) { - return self; - } - - _shouldSucceed = shouldSucceed; - - return self; -} - -- (void)uploadAttachmentStream:(TSAttachmentStream *)attachmentStream - message:(TSOutgoingMessage *)outgoingMessage - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - if (self.shouldSucceed) { - successHandler(); - } else { - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - [error setIsRetryable:NO]; - - failureHandler(error); - } -} - -@end - -#pragma mark - - -@interface OWSFakeURLSessionDataTask : NSURLSessionDataTask - -@property (copy) NSHTTPURLResponse *response; - -- (instancetype)initWithStatusCode:(long)statusCode; - -@end - -#pragma mark - - -@implementation OWSFakeURLSessionDataTask - -@synthesize response = _response; - -- (instancetype)initWithStatusCode:(long)statusCode -{ - self = [super init]; - - if (!self) { - return self; - } - - NSURL *fakeURL = [NSURL URLWithString:@"http://127.0.0.1"]; - _response = [[NSHTTPURLResponse alloc] initWithURL:fakeURL statusCode:statusCode HTTPVersion:nil headerFields:nil]; - - return self; -} - -@end - -#pragma mark - - -@interface OWSMessageSenderFakeNetworkManager : OWSFakeNetworkManager - -- (instancetype)init; -- (instancetype)initWithSuccess:(BOOL)shouldSucceed NS_DESIGNATED_INITIALIZER; - -@property (nonatomic, readonly) BOOL shouldSucceed; - -@end - -#pragma mark - - -@implementation OWSMessageSenderFakeNetworkManager - -- (instancetype)initWithSuccess:(BOOL)shouldSucceed -{ - self = [self init]; - if (!self) { - return self; - } - - _shouldSucceed = shouldSucceed; - - return self; -} - -- (void)makeRequest:(TSRequest *)request - success:(void (^)(NSURLSessionDataTask *task, id responseObject))success - failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure -{ - BOOL isSubmitMessageRequest - = ([request.HTTPMethod isEqualToString:@"PUT"] && [request.URL.path hasPrefix:textSecureMessagesAPI]); - - if (isSubmitMessageRequest) { - if (self.shouldSucceed) { - success([NSURLSessionDataTask new], @{}); - } else { - NSError *error - = OWSErrorWithCodeDescription(OWSErrorCodeFailedToSendOutgoingMessage, @"fake error description"); - OWSFakeURLSessionDataTask *task = [[OWSFakeURLSessionDataTask alloc] initWithStatusCode:500]; - failure(task, error); - } - } else { - [super makeRequest:request success:success failure:failure]; - } -} - -@end - -#pragma mark - - -@interface TSAccountManager (Testing) - -- (void)storeLocalNumber:(NSString *)localNumber; - -@end - -#pragma mark - - -@interface OWSMessageSenderTest : SSKBaseTestObjC - -@property (nonatomic) TSThread *thread; -@property (nonatomic) TSOutgoingMessage *expiringMessage; -@property (nonatomic) TSOutgoingMessage *unexpiringMessage; -@property (nonatomic) OWSMessageSenderFakeNetworkManager *networkManager; -@property (nonatomic) OWSMessageSender *successfulMessageSender; -@property (nonatomic) OWSMessageSender *unsuccessfulMessageSender; - -@end - -#pragma mark - - -@implementation OWSMessageSenderTest - -- (void)setUp -{ - [super setUp]; - - // Hack to make sure we don't explode when sending sync message. - [[TSAccountManager sharedInstance] storeLocalNumber:@"+13231231234"]; - - self.thread = [[TSContactThread alloc] initWithUniqueId:@"fake-thread-id"]; - [self.thread save]; - - self.unexpiringMessage = [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:1 - inThread:self.thread - messageBody:@"outgoing message" - attachmentIds:[NSMutableArray new] - expiresInSeconds:0 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - [self.unexpiringMessage save]; - - self.expiringMessage = [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:1 - inThread:self.thread - messageBody:@"outgoing message" - attachmentIds:[NSMutableArray new] - expiresInSeconds:30 - expireStartedAt:0 - isVoiceMessage:NO - groupMetaMessage:TSGroupMetaMessageUnspecified - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - [self.expiringMessage save]; - - OWSPrimaryStorage *storageManager = [OWSPrimaryStorage sharedManager]; - OWSFakeContactsManager *contactsManager = [OWSFakeContactsManager new]; - - // Successful Sending - TSNetworkManager *successfulNetworkManager = [[OWSMessageSenderFakeNetworkManager alloc] initWithSuccess:YES]; - self.successfulMessageSender = [[OWSMessageSender alloc] initWithNetworkManager:successfulNetworkManager - primaryStorage:primaryStorage - contactsManager:contactsManager]; - - // Unsuccessful Sending - TSNetworkManager *unsuccessfulNetworkManager = [[OWSMessageSenderFakeNetworkManager alloc] initWithSuccess:NO]; - self.unsuccessfulMessageSender = [[OWSMessageSender alloc] initWithNetworkManager:unsuccessfulNetworkManager - primaryStorage:primaryStorage - contactsManager:contactsManager]; -} - -- (void)testExpiringMessageTimerStartsOnSuccessWhenDisappearingMessagesEnabled -{ - [[[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:self.thread.uniqueId enabled:YES durationSeconds:10] - save]; - - OWSMessageSender *messageSender = self.successfulMessageSender; - - // Sanity Check - XCTAssertEqual(0, self.expiringMessage.expiresAt); - - XCTestExpectation *messageStartedExpiration = [self expectationWithDescription:@"messageStartedExpiration"]; - [messageSender sendMessage:self.expiringMessage - success:^() { - //FIXME remove sleep hack in favor of expiringMessage completion handler - sleep(2); - if (self.expiringMessage.expiresAt > 0) { - [messageStartedExpiration fulfill]; - } else { - XCTFail(@"Message expiration was supposed to start."); - } - } - failure:^(NSError *error) { - XCTFail(@"Message failed to send"); - }]; - - [self waitForExpectationsWithTimeout:5 - handler:^(NSError *error) { - OWSLogInfo(@"Expiration timer not set in time."); - }]; -} - -- (void)testExpiringMessageTimerDoesNotStartsWhenDisabled -{ - [[[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:self.thread.uniqueId enabled:NO durationSeconds:10] - save]; - - OWSMessageSender *messageSender = self.successfulMessageSender; - - XCTestExpectation *messageDidNotStartExpiration = [self expectationWithDescription:@"messageDidNotStartExpiration"]; - [messageSender sendMessageToService:self.unexpiringMessage - success:^() { - if (self.unexpiringMessage.isExpiringMessage || self.unexpiringMessage.expiresAt > 0) { - XCTFail(@"Message expiration was not supposed to start."); - } else { - [messageDidNotStartExpiration fulfill]; - } - } - failure:^(NSError *error) { - XCTFail(@"Message failed to send"); - }]; - - [self waitForExpectationsWithTimeout:5 - handler:^(NSError *error) { - OWSLogInfo(@"Expiration timer not set in time."); - }]; -} - -- (void)testExpiringMessageTimerDoesNotStartsOnFailure -{ - OWSMessageSender *messageSender = self.unsuccessfulMessageSender; - - // Sanity Check - XCTAssertEqual(0, self.expiringMessage.expiresAt); - - XCTestExpectation *messageDidNotStartExpiration = [self expectationWithDescription:@"messageStartedExpiration"]; - [messageSender sendMessageToService:self.expiringMessage - success:^() { - XCTFail(@"Message sending was supposed to fail."); - } - failure:^(NSError *error) { - if (self.expiringMessage.expiresAt == 0) { - [messageDidNotStartExpiration fulfill]; - } else { - XCTFail(@"Message expiration was not supposed to start."); - } - }]; - - [self waitForExpectationsWithTimeout:5 handler:nil]; -} - -- (void)testTextMessageIsMarkedAsSentOnSuccess -{ - OWSMessageSender *messageSender = self.successfulMessageSender; - - TSOutgoingMessage *message = [TSOutgoingMessage outgoingMessageInThread:self.thread - messageBody:@"We want punks in the palace." - attachmentId:nil]; - - XCTestExpectation *markedAsSent = [self expectationWithDescription:@"markedAsSent"]; - [messageSender sendMessageToService:message - success:^() { - if (message.messageState == TSOutgoingMessageStateSent) { - [markedAsSent fulfill]; - } else { - XCTFail(@"Unexpected message state"); - } - } - failure:^(NSError *error) { - XCTFail(@"sendMessage should succeed."); - }]; - - [self waitForExpectationsWithTimeout:5 handler:nil]; -} - -- (void)testMediaMessageIsMarkedAsSentOnSuccess -{ - OWSMessageSender *messageSender = self.successfulMessageSender; - messageSender.uploadingService = [[OWSFakeUploadingService alloc] initWithSuccess:YES]; - - TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1 - inThread:self.thread - messageBody:@"We want punks in the palace."]; - - XCTestExpectation *markedAsSent = [self expectationWithDescription:@"markedAsSent"]; - [messageSender sendAttachmentData:[NSData new] - contentType:@"image/gif" - sourceFilename:nil - inMessage:message - success:^() { - if (message.messageState == TSOutgoingMessageStateSentToService) { - [markedAsSent fulfill]; - } else { - XCTFail(@"Unexpected message state"); - } - } - failure:^(NSError *error) { - XCTFail(@"sendMessage should succeed."); - }]; - - [self waitForExpectationsWithTimeout:5 handler:nil]; -} - -- (void)testTextMessageIsMarkedAsUnsentOnFailure -{ - OWSMessageSender *messageSender = self.unsuccessfulMessageSender; - messageSender.uploadingService = [[OWSFakeUploadingService alloc] initWithSuccess:YES]; - - TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1 - inThread:self.thread - messageBody:@"We want punks in the palace."]; - - XCTestExpectation *markedAsUnsent = [self expectationWithDescription:@"markedAsUnsent"]; - [messageSender sendMessage:message - success:^() { - XCTFail(@"sendMessage should fail."); - } - failure:^(NSError *error) { - if (message.messageState == TSOutgoingMessageStateUnsent) { - [markedAsUnsent fulfill]; - } else { - XCTFail(@"Unexpected message state"); - } - }]; - - [self waitForExpectationsWithTimeout:5 handler:nil]; -} - -- (void)testMediaMessageIsMarkedAsUnsentOnFailureToSend -{ - OWSMessageSender *messageSender = self.unsuccessfulMessageSender; - // Assume that upload will go well, but that failure happens elsewhere in message sender. - messageSender.uploadingService = [[OWSFakeUploadingService alloc] initWithSuccess:YES]; - - TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1 - inThread:self.thread - messageBody:@"We want punks in the palace."]; - - XCTestExpectation *markedAsUnsent = [self expectationWithDescription:@"markedAsUnsent"]; - [messageSender sendAttachmentData:[NSData new] - contentType:@"image/gif" - sourceFilename:nil - inMessage:message - success:^{ - XCTFail(@"sendMessage should fail."); - } - failure:^(NSError *_Nonnull error) { - if (message.messageState == TSOutgoingMessageStateUnsent) { - [markedAsUnsent fulfill]; - } else { - XCTFail(@"Unexpected message state"); - } - }]; - - [self waitForExpectationsWithTimeout:5 handler:nil]; -} - -- (void)testMediaMessageIsMarkedAsUnsentOnFailureToUpload -{ - OWSMessageSender *messageSender = self.successfulMessageSender; - // Assume that upload fails, but other sending stuff would succeed. - messageSender.uploadingService = [[OWSFakeUploadingService alloc] initWithSuccess:NO]; - - TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1 - inThread:self.thread - messageBody:@"We want punks in the palace."]; - - XCTestExpectation *markedAsUnsent = [self expectationWithDescription:@"markedAsUnsent"]; - [messageSender sendAttachmentData:[NSData new] - contentType:@"image/gif" - sourceFilename:nil - inMessage:message - success:^{ - XCTFail(@"sendMessage should fail."); - } - failure:^(NSError *_Nonnull error) { - if (message.messageState == TSOutgoingMessageStateUnsent) { - [markedAsUnsent fulfill]; - } else { - XCTFail(@"Unexpected message state"); - } - }]; - - [self waitForExpectationsWithTimeout:5 handler:nil]; -} - -- (void)testGroupSend -{ - OWSMessageSender *messageSender = self.successfulMessageSender; - - - NSData *groupIdData = [Cryptography generateRandomBytes:32]; - SignalRecipient *successfulRecipient = - [[SignalRecipient alloc] initWithTextSecureIdentifier:@"successful-recipient-id" relay:nil]; - SignalRecipient *successfulRecipient2 = - [[SignalRecipient alloc] initWithTextSecureIdentifier:@"successful-recipient-id2" relay:nil]; - - TSGroupModel *groupModel = [[TSGroupModel alloc] - initWithTitle:@"group title" - memberIds:[@[ successfulRecipient.uniqueId, successfulRecipient2.uniqueId ] mutableCopy] - image:nil - groupId:groupIdData]; - TSGroupThread *groupThread = [TSGroupThread getOrCreateThreadWithGroupModel:groupModel]; - TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1 - inThread:groupThread - messageBody:@"We want punks in the palace."]; - - XCTestExpectation *markedAsSent = [self expectationWithDescription:@"markedAsSent"]; - [messageSender sendMessage:message - success:^{ - if (message.messageState == TSOutgoingMessageStateSentToService) { - [markedAsSent fulfill]; - } else { - XCTFail(@"Unexpected message state"); - } - - } - failure:^(NSError *_Nonnull error) { - XCTFail(@"sendMessage should not fail."); - }]; - - [self waitForExpectationsWithTimeout:5 handler:nil]; -} - -- (void)testGetRecipients -{ - SignalRecipient *recipient = [[SignalRecipient alloc] initWithTextSecureIdentifier:@"fake-recipient-id" relay:nil]; - [recipient save]; - - OWSMessageSender *messageSender = self.successfulMessageSender; - - NSError *error; - NSArray *recipients = [messageSender getRecipients:@[ recipient.uniqueId ] error:&error]; - - XCTAssertNil(error); - XCTAssertEqualObjects(recipient, recipients.firstObject); -} - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/tests/Messages/OWSUDManagerTest.swift b/SignalServiceKit/tests/Messages/OWSUDManagerTest.swift deleted file mode 100644 index 50a0904ae..000000000 --- a/SignalServiceKit/tests/Messages/OWSUDManagerTest.swift +++ /dev/null @@ -1,182 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import XCTest -import Foundation -import SessionCoreKit -import SessionMetadataKit -@testable import SessionServiceKit - -class MockCertificateValidator: NSObject, SMKCertificateValidator { - @objc public func throwswrapped_validate(senderCertificate: SMKSenderCertificate, validationTime: UInt64) throws { - // Do not throw - } - - @objc public func throwswrapped_validate(serverCertificate: SMKServerCertificate) throws { - // Do not throw - } -} - -// MARK: - - -class OWSUDManagerTest: SSKBaseTestSwift { - - // MARK: - Singletons - - private var tsAccountManager: TSAccountManager { - return TSAccountManager.sharedInstance() - } - - private var udManager: OWSUDManagerImpl { - return SSKEnvironment.shared.udManager as! OWSUDManagerImpl - } - - private var profileManager: ProfileManagerProtocol { - return SSKEnvironment.shared.profileManager - } - - // MARK: registration - let aliceRecipientId = "+13213214321" - - override func setUp() { - /* - super.setUp() - - tsAccountManager.registerForTests(withLocalNumber: aliceRecipientId) - - // Configure UDManager - profileManager.setProfileKeyData(OWSAES256Key.generateRandom().keyData, forRecipientId: aliceRecipientId) - - udManager.certificateValidator = MockCertificateValidator() - - let serverCertificate = SMKServerCertificate(keyId: 1, - key: try! ECPublicKey(keyData: Randomness.generateRandomBytes(ECCKeyLength)), - signatureData: Randomness.generateRandomBytes(ECCSignatureLength)) - let senderCertificate = SMKSenderCertificate(signer: serverCertificate, - key: try! ECPublicKey(keyData: Randomness.generateRandomBytes(ECCKeyLength)), - senderDeviceId: 1, - senderRecipientId: aliceRecipientId, - expirationTimestamp: NSDate.ows_millisecondTimeStamp() + kWeekInMs, - signatureData: Randomness.generateRandomBytes(ECCSignatureLength)) - - udManager.setSenderCertificate(try! senderCertificate.serialized()) - */ - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testMode_self() { - - XCTAssert(udManager.hasSenderCertificate()) - XCTAssert(tsAccountManager.isRegistered()) - XCTAssertNotNil(tsAccountManager.localNumber()) - XCTAssert(tsAccountManager.localNumber()!.count > 0) - - var udAccess: OWSUDAccess! - - XCTAssertEqual(.enabled, udManager.unidentifiedAccessMode(forRecipientId: aliceRecipientId)) - udAccess = udManager.udAccess(forRecipientId: aliceRecipientId, requireSyncAccess: false) - XCTAssertFalse(udAccess.isRandomKey) - - udManager.setUnidentifiedAccessMode(.unknown, recipientId: aliceRecipientId) - XCTAssertEqual(.unknown, udManager.unidentifiedAccessMode(forRecipientId: aliceRecipientId)) - udAccess = udManager.udAccess(forRecipientId: aliceRecipientId, requireSyncAccess: false)! - XCTAssertFalse(udAccess.isRandomKey) - - udManager.setUnidentifiedAccessMode(.disabled, recipientId: aliceRecipientId) - XCTAssertEqual(.disabled, udManager.unidentifiedAccessMode(forRecipientId: aliceRecipientId)) - XCTAssertNil(udManager.udAccess(forRecipientId: aliceRecipientId, requireSyncAccess: false)) - - udManager.setUnidentifiedAccessMode(.enabled, recipientId: aliceRecipientId) - XCTAssert(UnidentifiedAccessMode.enabled == udManager.unidentifiedAccessMode(forRecipientId: aliceRecipientId)) - udAccess = udManager.udAccess(forRecipientId: aliceRecipientId, requireSyncAccess: false)! - XCTAssertFalse(udAccess.isRandomKey) - - udManager.setUnidentifiedAccessMode(.unrestricted, recipientId: aliceRecipientId) - XCTAssertEqual(.unrestricted, udManager.unidentifiedAccessMode(forRecipientId: aliceRecipientId)) - udAccess = udManager.udAccess(forRecipientId: aliceRecipientId, requireSyncAccess: false)! - XCTAssert(udAccess.isRandomKey) - } - - func testMode_noProfileKey() { - - XCTAssert(udManager.hasSenderCertificate()) - XCTAssert(tsAccountManager.isRegistered()) - XCTAssertNotNil(tsAccountManager.localNumber()) - XCTAssert(tsAccountManager.localNumber()!.count > 0) - - // Ensure UD is enabled by setting our own access level to enabled. - udManager.setUnidentifiedAccessMode(.enabled, recipientId: tsAccountManager.localNumber()!) - - let bobRecipientId = "+13213214322" - XCTAssertNotEqual(bobRecipientId, tsAccountManager.localNumber()!) - - var udAccess: OWSUDAccess! - - XCTAssertEqual(UnidentifiedAccessMode.unknown, udManager.unidentifiedAccessMode(forRecipientId: bobRecipientId)) - udAccess = udManager.udAccess(forRecipientId: bobRecipientId, requireSyncAccess: false)! - XCTAssert(udAccess.isRandomKey) - - udManager.setUnidentifiedAccessMode(.unknown, recipientId: bobRecipientId) - XCTAssertEqual(UnidentifiedAccessMode.unknown, udManager.unidentifiedAccessMode(forRecipientId: bobRecipientId)) - udAccess = udManager.udAccess(forRecipientId: bobRecipientId, requireSyncAccess: false)! - XCTAssert(udAccess.isRandomKey) - - udManager.setUnidentifiedAccessMode(.disabled, recipientId: bobRecipientId) - XCTAssertEqual(UnidentifiedAccessMode.disabled, udManager.unidentifiedAccessMode(forRecipientId: bobRecipientId)) - XCTAssertNil(udManager.udAccess(forRecipientId: bobRecipientId, requireSyncAccess: false)) - - udManager.setUnidentifiedAccessMode(.enabled, recipientId: bobRecipientId) - XCTAssertEqual(UnidentifiedAccessMode.enabled, udManager.unidentifiedAccessMode(forRecipientId: bobRecipientId)) - XCTAssertNil(udManager.udAccess(forRecipientId: bobRecipientId, requireSyncAccess: false)) - - // Bob should work in unrestricted mode, even if he doesn't have a profile key. - udManager.setUnidentifiedAccessMode(.unrestricted, recipientId: bobRecipientId) - XCTAssertEqual(UnidentifiedAccessMode.unrestricted, udManager.unidentifiedAccessMode(forRecipientId: bobRecipientId)) - udAccess = udManager.udAccess(forRecipientId: bobRecipientId, requireSyncAccess: false)! - XCTAssert(udAccess.isRandomKey) - } - - func testMode_withProfileKey() { - XCTAssert(udManager.hasSenderCertificate()) - XCTAssert(tsAccountManager.isRegistered()) - XCTAssertNotNil(tsAccountManager.localNumber()) - XCTAssert(tsAccountManager.localNumber()!.count > 0) - - // Ensure UD is enabled by setting our own access level to enabled. - udManager.setUnidentifiedAccessMode(.enabled, recipientId: tsAccountManager.localNumber()!) - - let bobRecipientId = "+13213214322" - XCTAssertNotEqual(bobRecipientId, tsAccountManager.localNumber()!) - profileManager.setProfileKeyData(OWSAES256Key.generateRandom().keyData, forRecipientId: bobRecipientId) - - var udAccess: OWSUDAccess! - - XCTAssertEqual(.unknown, udManager.unidentifiedAccessMode(forRecipientId: bobRecipientId)) - udAccess = udManager.udAccess(forRecipientId: bobRecipientId, requireSyncAccess: false)! - XCTAssertFalse(udAccess.isRandomKey) - - udManager.setUnidentifiedAccessMode(.unknown, recipientId: bobRecipientId) - XCTAssertEqual(.unknown, udManager.unidentifiedAccessMode(forRecipientId: bobRecipientId)) - udAccess = udManager.udAccess(forRecipientId: bobRecipientId, requireSyncAccess: false)! - XCTAssertFalse(udAccess.isRandomKey) - - udManager.setUnidentifiedAccessMode(.disabled, recipientId: bobRecipientId) - XCTAssertEqual(.disabled, udManager.unidentifiedAccessMode(forRecipientId: bobRecipientId)) - XCTAssertNil(udManager.udAccess(forRecipientId: bobRecipientId, requireSyncAccess: false)) - - udManager.setUnidentifiedAccessMode(.enabled, recipientId: bobRecipientId) - XCTAssertEqual(.enabled, udManager.unidentifiedAccessMode(forRecipientId: bobRecipientId)) - udAccess = udManager.udAccess(forRecipientId: bobRecipientId, requireSyncAccess: false)! - XCTAssertFalse(udAccess.isRandomKey) - - udManager.setUnidentifiedAccessMode(.unrestricted, recipientId: bobRecipientId) - XCTAssertEqual(.unrestricted, udManager.unidentifiedAccessMode(forRecipientId: bobRecipientId)) - udAccess = udManager.udAccess(forRecipientId: bobRecipientId, requireSyncAccess: false)! - XCTAssert(udAccess.isRandomKey) - } -} diff --git a/SignalServiceKit/tests/Network/MessageSendJobQueueTest.swift b/SignalServiceKit/tests/Network/MessageSendJobQueueTest.swift deleted file mode 100644 index 5cd8eaace..000000000 --- a/SignalServiceKit/tests/Network/MessageSendJobQueueTest.swift +++ /dev/null @@ -1,238 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import XCTest -@testable import SessionServiceKit - -class MessageSenderJobQueueTest: SSKBaseTestSwift { - - override func setUp() { - super.setUp() - } - - override func tearDown() { - super.tearDown() - } - - // MARK: Dependencies - - private var messageSender: OWSFakeMessageSender { - return MockSSKEnvironment.shared.messageSender as! OWSFakeMessageSender - } - - // MARK: - - func test_messageIsSent() { - let message: TSOutgoingMessage = OutgoingMessageFactory().create() - - let expectation = sentExpectation(message: message) - - let jobQueue = MessageSenderJobQueue() - jobQueue.setup() - self.readWrite { transaction in - jobQueue.add(message: message, transaction: transaction) - } - - self.wait(for: [expectation], timeout: 0.1) - } - - func test_waitsForSetup() { - let message: TSOutgoingMessage = OutgoingMessageFactory().create() - - let sentBeforeReadyExpectation = sentExpectation(message: message) - sentBeforeReadyExpectation.isInverted = true - - let jobQueue = MessageSenderJobQueue() - - self.readWrite { transaction in - jobQueue.add(message: message, transaction: transaction) - } - - self.wait(for: [sentBeforeReadyExpectation], timeout: 0.1) - - let sentAfterReadyExpectation = sentExpectation(message: message) - - jobQueue.setup() - - self.wait(for: [sentAfterReadyExpectation], timeout: 0.1) - } - - func test_respectsQueueOrder() { - let message1: TSOutgoingMessage = OutgoingMessageFactory().create() - let message2: TSOutgoingMessage = OutgoingMessageFactory().create() - let message3: TSOutgoingMessage = OutgoingMessageFactory().create() - - let jobQueue = MessageSenderJobQueue() - self.readWrite { transaction in - jobQueue.add(message: message1, transaction: transaction) - jobQueue.add(message: message2, transaction: transaction) - jobQueue.add(message: message3, transaction: transaction) - } - - let sendGroup = DispatchGroup() - sendGroup.enter() - sendGroup.enter() - sendGroup.enter() - - var sentMessages: [TSOutgoingMessage] = [] - messageSender.sendMessageWasCalledBlock = { sentMessage in - sentMessages.append(sentMessage) - sendGroup.leave() - } - - jobQueue.setup() - - switch sendGroup.wait(timeout: .now() + 1.0) { - case .timedOut: - XCTFail("timed out waiting for sends") - case .success: - XCTAssertEqual([message1, message2, message3].map { $0.uniqueId }, sentMessages.map { $0.uniqueId }) - } - } - - func test_sendingInvisibleMessage() { - let jobQueue = MessageSenderJobQueue() - jobQueue.setup() - - let message = OutgoingMessageFactory().buildDeliveryReceipt() - let expectation = sentExpectation(message: message) - self.readWrite { transaction in - jobQueue.add(message: message, transaction: transaction) - } - - self.wait(for: [expectation], timeout: 0.1) - } - - func test_retryableFailure() { - let message: TSOutgoingMessage = OutgoingMessageFactory().create() - - let jobQueue = MessageSenderJobQueue() - self.readWrite { transaction in - jobQueue.add(message: message, transaction: transaction) - } - - let finder = JobRecordFinder() - var readyRecords: [SSKJobRecord] = [] - self.readWrite { transaction in - readyRecords = finder.allRecords(label: MessageSenderJobQueue.jobRecordLabel, status: .ready, transaction: transaction) - } - XCTAssertEqual(1, readyRecords.count) - - let jobRecord = readyRecords.first! - XCTAssertEqual(0, jobRecord.failureCount) - - // simulate permanent failure - let error = NSError(domain: "foo", code: 0, userInfo: nil) - error.isRetryable = true - self.messageSender.stubbedFailingError = error - let expectation = sentExpectation(message: message) { - jobQueue.isSetup = false - } - - jobQueue.setup() - self.wait(for: [expectation], timeout: 0.1) - - self.readWrite { transaction in - jobRecord.reload(with: transaction) - } - - XCTAssertEqual(1, jobRecord.failureCount) - XCTAssertEqual(.running, jobRecord.status) - - let retryCount: UInt = MessageSenderJobQueue.maxRetries - (1.. Void = { }) -> XCTestExpectation { - let expectation = self.expectation(description: "sent message") - - messageSender.sendMessageWasCalledBlock = { [weak messageSender] sentMessage in - guard sentMessage == message else { - XCTFail("unexpected sentMessage: \(sentMessage)") - return - } - expectation.fulfill() - block() - guard let strongMessageSender = messageSender else { - return - } - strongMessageSender.sendMessageWasCalledBlock = nil - } - - return expectation - } -} diff --git a/SignalServiceKit/tests/Network/MessageSenderJobRecordTest.swift b/SignalServiceKit/tests/Network/MessageSenderJobRecordTest.swift deleted file mode 100644 index 1b401c9e4..000000000 --- a/SignalServiceKit/tests/Network/MessageSenderJobRecordTest.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import XCTest -@testable import SessionServiceKit - -let kMessageSenderJobRecordLabel = "MessageSender" -class SSKMessageSenderJobRecordTest: SSKBaseTestSwift { - - func test_savedVisibleMessage() { - let message = OutgoingMessageFactory().create() - let jobRecord = try! SSKMessageSenderJobRecord(message: message, removeMessageAfterSending: false, label: MessageSenderJobQueue.jobRecordLabel) - XCTAssertNotNil(jobRecord.messageId) - XCTAssertNotNil(jobRecord.threadId) - XCTAssertNil(jobRecord.invisibleMessage) - } - - func test_unsavedVisibleMessage() { - var message: TSOutgoingMessage! - self.readWrite { transaction in - message = OutgoingMessageFactory().build(transaction: transaction) - } - message.uniqueId = nil - - do { - _ = try SSKMessageSenderJobRecord(message: message, removeMessageAfterSending: false, label: MessageSenderJobQueue.jobRecordLabel) - XCTFail("Should error") - } catch JobRecordError.assertionError { - // expected - } catch { - XCTFail("unexpected error: \(error)") - } - } - - func test_invisibleMessage() { - let message = OutgoingMessageFactory().buildDeliveryReceipt() - - let jobRecord = try! SSKMessageSenderJobRecord(message: message, removeMessageAfterSending: false, label: MessageSenderJobQueue.jobRecordLabel) - XCTAssertNil(jobRecord.messageId) - XCTAssertNotNil(jobRecord.threadId) - XCTAssertNotNil(jobRecord.invisibleMessage) - } -} diff --git a/SignalServiceKit/tests/SSKBaseTestObjC.h b/SignalServiceKit/tests/SSKBaseTestObjC.h deleted file mode 100644 index 40e69f9de..000000000 --- a/SignalServiceKit/tests/SSKBaseTestObjC.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -@interface SSKBaseTestObjC : XCTestCase - -- (void)readWithBlock:(void (^)(YapDatabaseReadTransaction *transaction))block; - -- (void)readWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block; - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/tests/SSKBaseTestObjC.m b/SignalServiceKit/tests/SSKBaseTestObjC.m deleted file mode 100644 index bd7d85e0d..000000000 --- a/SignalServiceKit/tests/SSKBaseTestObjC.m +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "SSKBaseTestObjC.h" -#import "OWSPrimaryStorage.h" -#import "SSKEnvironment.h" -#import "TestAppContext.h" - -@import CocoaLumberjack; -#import - -NS_ASSUME_NONNULL_BEGIN - -#ifdef DEBUG - -@implementation SSKBaseTestObjC - -- (void)setUp -{ - OWSLogInfo(@"%@ setUp", self.logTag); - - [super setUp]; - - [DDLog addLogger:DDTTYLogger.sharedInstance]; - - ClearCurrentAppContextForTests(); - SetCurrentAppContext([TestAppContext new]); - - [MockSSKEnvironment activate]; -} - -- (void)tearDown -{ - OWSLogInfo(@"%@ tearDown", self.logTag); - - [super tearDown]; -} - -- (void)readWithBlock:(void (^)(YapDatabaseReadTransaction *transaction))block -{ - OWSAssert(block); - - [[SSKEnvironment.shared.primaryStorage newDatabaseConnection] readWithBlock:block]; -} - - -- (void)readWriteWithBlock:(void (^)(YapDatabaseReadWriteTransaction *transaction))block -{ - OWSAssert(block); - - [[SSKEnvironment.shared.primaryStorage newDatabaseConnection] readWriteWithBlock:block]; -} - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/tests/SSKBaseTestSwift.swift b/SignalServiceKit/tests/SSKBaseTestSwift.swift deleted file mode 100644 index 6e8163373..000000000 --- a/SignalServiceKit/tests/SSKBaseTestSwift.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import XCTest -import SessionServiceKit -import CocoaLumberjack - -@objc -public class SSKBaseTestSwift: XCTestCase { - - @objc - public override func setUp() { - super.setUp() - - DDLog.add(DDTTYLogger.sharedInstance) - - ClearCurrentAppContextForTests() - SetCurrentAppContext(TestAppContext()) - - MockSSKEnvironment.activate() - } - - @objc - public override func tearDown() { - super.tearDown() - } - - @objc - public func read(_ block: @escaping (YapDatabaseReadTransaction) -> Swift.Void) { - return OWSPrimaryStorage.shared().dbReadConnection.read(block) - } - - @objc - public func readWrite(_ block: @escaping (YapDatabaseReadWriteTransaction) -> Swift.Void) { - return OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite(block) - } -} diff --git a/SignalServiceKit/tests/SSKSwiftTests.swift b/SignalServiceKit/tests/SSKSwiftTests.swift deleted file mode 100644 index 60d8d3aee..000000000 --- a/SignalServiceKit/tests/SSKSwiftTests.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import XCTest - -///// -// Swift Test vs. Cocoapods issue #1 -///// -// -// Cocoapods-generated test targets (like this one) -// fail to link if: -// -// * They only contain Obj-C tests. -// * They depend on pods that use Swift. -// -// The work around is to add (this) empty swift file -// to our test target. -// -// See: https://github.com/CocoaPods/CocoaPods/issues/7170 - -///// -// Swift Test vs. Cocoapods issue #2 -///// -// -// XCode's test runner doesn't copy swift framework's required by dependencies into -// the running test bundle. -// It sounds similar to this issue: https://github.com/CocoaPods/CocoaPods/issues/7985 -// -// The error output looks like this: -// The bundle “SignalServiceKit-Unit-Tests” couldn’t be loaded because it is damaged or missing necessary resources. Try reinstalling the bundle. -// [...]/SignalServiceKit-Unit-Tests.xctest/SignalServiceKit-Unit-Tests): Library not loaded: @rpath/libswiftAVFoundation.dylib -// Referenced from: /Users/[...]/Build/Products/Debug-iphonesimulator/SignalServiceKit/SignalServiceKit.framework/SignalServiceKit -// Reason: image not found) -// Program ended with exit code: 82 -// -// A work around is to redundantly import any swift frameworks used by the dependencies of the test suite into this test file. -// The error message provides a hint, i.e. "Library not loaded: @rpath/libswiftAVFoundation.dylib" is fixed with `import AVFoundation` -import AVFoundation -import CloudKit diff --git a/SignalServiceKit/tests/Security/OWSFingerprintTest.m b/SignalServiceKit/tests/Security/OWSFingerprintTest.m deleted file mode 100644 index 96a960f0f..000000000 --- a/SignalServiceKit/tests/Security/OWSFingerprintTest.m +++ /dev/null @@ -1,80 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSFingerprint.h" -#import "SSKBaseTestObjC.h" -#import - -@interface OWSFingerprintTest : SSKBaseTestObjC - -@end - -#pragma mark - - -@implementation OWSFingerprintTest - -- (void)testDisplayableTextInsertsSpaces -{ - NSString *aliceStableId = @"+13231111111"; - NSData *aliceIdentityKey = [Curve25519 generateKeyPair].publicKey; - NSString *bobStableId = @"+14152222222"; - NSData *bobIdentityKey = [Curve25519 generateKeyPair].publicKey; - - OWSFingerprint *aliceFingerprint = [OWSFingerprint fingerprintWithMyStableId:aliceStableId - myIdentityKey:aliceIdentityKey - theirStableId:bobStableId - theirIdentityKey:bobIdentityKey - theirName:@"Bob" - hashIterations:2]; - - NSString *displayableText = aliceFingerprint.displayableText; - XCTAssertNotEqualObjects(@" ", [displayableText substringWithRange:NSMakeRange(0, 1)]); - XCTAssertNotEqualObjects(@" ", [displayableText substringWithRange:NSMakeRange(1, 1)]); - XCTAssertNotEqualObjects(@" ", [displayableText substringWithRange:NSMakeRange(2, 1)]); - XCTAssertNotEqualObjects(@" ", [displayableText substringWithRange:NSMakeRange(3, 1)]); - XCTAssertNotEqualObjects(@" ", [displayableText substringWithRange:NSMakeRange(4, 1)]); - XCTAssertEqualObjects(@" ", [displayableText substringWithRange:NSMakeRange(5, 1)]); - XCTAssertNotEqualObjects(@" ", [displayableText substringWithRange:NSMakeRange(6, 1)]); - XCTAssertNotEqualObjects(@" ", [displayableText substringWithRange:NSMakeRange(7, 1)]); - XCTAssertNotEqualObjects(@" ", [displayableText substringWithRange:NSMakeRange(8, 1)]); - XCTAssertNotEqualObjects(@" ", [displayableText substringWithRange:NSMakeRange(9, 1)]); - XCTAssertNotEqualObjects(@" ", [displayableText substringWithRange:NSMakeRange(10, 1)]); - XCTAssertEqualObjects(@" ", [displayableText substringWithRange:NSMakeRange(11, 1)]); -} - -- (void)testTextMatchesReciprocally -{ - NSString *aliceStableId = @"+13231111111"; - NSData *aliceIdentityKey = [Curve25519 generateKeyPair].publicKey; - NSString *bobStableId = @"+14152222222"; - NSData *bobIdentityKey = [Curve25519 generateKeyPair].publicKey; - NSString *charlieStableId = @"+14153333333"; - NSData *charlieIdentityKey = [Curve25519 generateKeyPair].publicKey; - - OWSFingerprint *aliceFingerprint = [OWSFingerprint fingerprintWithMyStableId:aliceStableId - myIdentityKey:aliceIdentityKey - theirStableId:bobStableId - theirIdentityKey:bobIdentityKey - theirName:@"Bob" - hashIterations:2]; - - OWSFingerprint *bobFingerprint = [OWSFingerprint fingerprintWithMyStableId:bobStableId - myIdentityKey:bobIdentityKey - theirStableId:aliceStableId - theirIdentityKey:aliceIdentityKey - theirName:@"Alice" - hashIterations:2]; - - OWSFingerprint *charlieFingerprint = [OWSFingerprint fingerprintWithMyStableId:charlieStableId - myIdentityKey:charlieIdentityKey - theirStableId:aliceStableId - theirIdentityKey:aliceIdentityKey - theirName:@"Alice" - hashIterations:2]; - - XCTAssertEqualObjects(aliceFingerprint.displayableText, bobFingerprint.displayableText); - XCTAssertNotEqualObjects(aliceFingerprint.displayableText, charlieFingerprint.displayableText); -} - -@end diff --git a/SignalServiceKit/tests/Storage/TSStorageIdentityKeyStoreTests.m b/SignalServiceKit/tests/Storage/TSStorageIdentityKeyStoreTests.m deleted file mode 100644 index 5844862a5..000000000 --- a/SignalServiceKit/tests/Storage/TSStorageIdentityKeyStoreTests.m +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "MockSSKEnvironment.h" -#import "OWSIdentityManager.h" -#import "OWSPrimaryStorage.h" -#import "OWSRecipientIdentity.h" -#import "SSKBaseTestObjC.h" -#import "SSKEnvironment.h" -#import "YapDatabaseConnection+OWS.h" -#import -#import - -extern NSString *const OWSPrimaryStorageTrustedKeysCollection; - -@interface TSStorageIdentityKeyStoreTests : SSKBaseTestObjC - -@end - -@implementation TSStorageIdentityKeyStoreTests - -- (void)setUp -{ - [super setUp]; -} - -- (void)tearDown -{ - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - -- (void)testNewEmptyKey -{ - NSData *newKey = [Randomness generateRandomBytes:32]; - NSString *recipientId = @"test@gmail.com"; - - [self - readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - XCTAssert([[OWSIdentityManager sharedManager] isTrustedIdentityKey:newKey - recipientId:recipientId - direction:TSMessageDirectionOutgoing - protocolContext:transaction]); - XCTAssert([[OWSIdentityManager sharedManager] isTrustedIdentityKey:newKey - recipientId:recipientId - direction:TSMessageDirectionIncoming - protocolContext:transaction]); - }]; -} - -- (void)testAlreadyRegisteredKey -{ - NSData *newKey = [Randomness generateRandomBytes:32]; - NSString *recipientId = @"test@gmail.com"; - - [self - readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [[OWSIdentityManager sharedManager] saveRemoteIdentity:newKey - recipientId:recipientId - protocolContext:transaction]; - - XCTAssert([[OWSIdentityManager sharedManager] isTrustedIdentityKey:newKey - recipientId:recipientId - direction:TSMessageDirectionOutgoing - protocolContext:transaction]); - XCTAssert([[OWSIdentityManager sharedManager] isTrustedIdentityKey:newKey - recipientId:recipientId - direction:TSMessageDirectionIncoming - protocolContext:transaction]); - }]; -} - - -- (void)testChangedKey -{ - NSData *originalKey = [Randomness generateRandomBytes:32]; - NSString *recipientId = @"test@protonmail.com"; - - [self - readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [[OWSIdentityManager sharedManager] saveRemoteIdentity:originalKey - recipientId:recipientId - protocolContext:transaction]; - - XCTAssert([[OWSIdentityManager sharedManager] isTrustedIdentityKey:originalKey - recipientId:recipientId - direction:TSMessageDirectionOutgoing - protocolContext:transaction]); - XCTAssert([[OWSIdentityManager sharedManager] isTrustedIdentityKey:originalKey - recipientId:recipientId - direction:TSMessageDirectionIncoming - protocolContext:transaction]); - - NSData *otherKey = [Randomness generateRandomBytes:32]; - - XCTAssertFalse([[OWSIdentityManager sharedManager] isTrustedIdentityKey:otherKey - recipientId:recipientId - direction:TSMessageDirectionOutgoing - protocolContext:transaction]); - XCTAssert([[OWSIdentityManager sharedManager] isTrustedIdentityKey:otherKey - recipientId:recipientId - direction:TSMessageDirectionIncoming - protocolContext:transaction]); - }]; -} - -- (void)testIdentityKey -{ - [[OWSIdentityManager sharedManager] generateNewIdentityKeyPair]; - - XCTAssert([[[OWSIdentityManager sharedManager] identityKeyPair].publicKey length] == 32); -} - -@end diff --git a/SignalServiceKit/tests/Storage/TSStoragePreKeyStoreTests.m b/SignalServiceKit/tests/Storage/TSStoragePreKeyStoreTests.m deleted file mode 100644 index ad6888ee0..000000000 --- a/SignalServiceKit/tests/Storage/TSStoragePreKeyStoreTests.m +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSPrimaryStorage+PreKeyStore.h" -#import "SSKBaseTestObjC.h" - -@interface TSStoragePreKeyStoreTests : SSKBaseTestObjC - -@end - -@implementation TSStoragePreKeyStoreTests - -- (void)setUp -{ - [super setUp]; - // Put setup code here. This method is called before the invocation of each test method in the class. -} - -- (void)tearDown -{ - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - -- (void)testGeneratingAndStoringPreKeys -{ - NSArray *generatedKeys = [[OWSPrimaryStorage sharedManager] generatePreKeyRecords]; - - - XCTAssert([generatedKeys count] == 100, @"Not hundred keys generated"); - - [[OWSPrimaryStorage sharedManager] storePreKeyRecords:generatedKeys]; - - PreKeyRecord *lastPreKeyRecord = [generatedKeys lastObject]; - PreKeyRecord *firstPreKeyRecord = [generatedKeys firstObject]; - - XCTAssert([[[OWSPrimaryStorage sharedManager] throws_loadPreKey:lastPreKeyRecord.Id].keyPair.publicKey - isEqualToData:lastPreKeyRecord.keyPair.publicKey]); - - XCTAssert([[[OWSPrimaryStorage sharedManager] throws_loadPreKey:firstPreKeyRecord.Id].keyPair.publicKey - isEqualToData:firstPreKeyRecord.keyPair.publicKey]); -} - - -- (void)testRemovingPreKeys -{ - NSArray *generatedKeys = [[OWSPrimaryStorage sharedManager] generatePreKeyRecords]; - - XCTAssert([generatedKeys count] == 100, @"Not hundred keys generated"); - - [[OWSPrimaryStorage sharedManager] storePreKeyRecords:generatedKeys]; - - PreKeyRecord *lastPreKeyRecord = [generatedKeys lastObject]; - PreKeyRecord *firstPreKeyRecord = [generatedKeys firstObject]; - - [[OWSPrimaryStorage sharedManager] removePreKey:lastPreKeyRecord.Id protocolContext:nil]; - - XCTAssertThrows([[OWSPrimaryStorage sharedManager] throws_loadPreKey:lastPreKeyRecord.Id]); - XCTAssertNoThrow([[OWSPrimaryStorage sharedManager] throws_loadPreKey:firstPreKeyRecord.Id]); -} - -@end diff --git a/SignalServiceKit/tests/Util/DeviceNamesTest.swift b/SignalServiceKit/tests/Util/DeviceNamesTest.swift deleted file mode 100644 index e96d31d12..000000000 --- a/SignalServiceKit/tests/Util/DeviceNamesTest.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import XCTest -import SessionCurve25519Kit - -@testable import SessionServiceKit - -class DeviceNamesTest: SSKBaseTestSwift { - - override func setUp() { - super.setUp() - } - - override func tearDown() { - super.tearDown() - } - - // MARK: - - func testNotEncrypted1() { - - let identityKeyPair = Curve25519.generateKeyPair() - - let plaintext = "alice" - guard let plaintextData = plaintext.data(using: .utf8) else { - XCTFail("Could not convert text to UTF-8.") - return - } - - do { - _ = try DeviceNames.decryptDeviceName(base64Data: plaintextData, - identityKeyPair: identityKeyPair) - XCTFail("Unexpectedly did not throw error.") - } catch DeviceNameError.invalidInput { - // Expected error. - } catch { - owsFailDebug("Unexpected \(error)") - } - } - - func testNotEncrypted2() { - - let identityKeyPair = Curve25519.generateKeyPair() - - let plaintext = "alice" - guard let plaintextData = plaintext.data(using: .utf8) else { - XCTFail("Could not convert text to UTF-8.") - return - } - let base64Data = plaintextData.base64EncodedData() - - do { - _ = try DeviceNames.decryptDeviceName(base64Data: base64Data, - identityKeyPair: identityKeyPair) - XCTFail("Unexpectedly did not throw error.") - } catch DeviceNameError.invalidInput { - // Expected error. - } catch { - owsFailDebug("Unexpected \(error)") - } - } - - func testSimple() { - - let identityKeyPair = Curve25519.generateKeyPair() - - let plaintext = "alice" - let encrypted: Data - do { - encrypted = try DeviceNames.encryptDeviceName(plaintext: plaintext, - identityKeyPair: identityKeyPair) - } catch { - XCTFail("Failed with error: \(error)") - return - } - - let decrypted: String - do { - decrypted = try DeviceNames.decryptDeviceName(base64Data: encrypted, - identityKeyPair: identityKeyPair) - } catch { - XCTFail("Failed with error: \(error)") - return - } - XCTAssertEqual(plaintext, decrypted) - } -} diff --git a/SignalServiceKit/tests/Util/JobQueueTest.swift b/SignalServiceKit/tests/Util/JobQueueTest.swift deleted file mode 100644 index d85ae96cb..000000000 --- a/SignalServiceKit/tests/Util/JobQueueTest.swift +++ /dev/null @@ -1,173 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import XCTest - -@testable import SessionServiceKit - -class TestJobRecord: SSKJobRecord { - -} - -let kJobRecordLabel = "TestJobRecord" -class TestJobQueue: JobQueue { - - // MARK: JobQueue - - typealias DurableOperationType = TestDurableOperation - var jobRecordLabel: String = kJobRecordLabel - static var maxRetries: UInt = 1 - var runningOperations: [TestDurableOperation] = [] - var requiresInternet: Bool = false - - func setup() { - defaultSetup() - } - - func didMarkAsReady(oldJobRecord: TestJobRecord, transaction: YapDatabaseReadWriteTransaction) { - // no special handling - } - - var isSetup: Bool = false - - let operationQueue = OperationQueue() - - func operationQueue(jobRecord: TestJobRecord) -> OperationQueue { - return self.operationQueue - } - - func buildOperation(jobRecord: TestJobRecord, transaction: YapDatabaseReadTransaction) throws -> TestDurableOperation { - return TestDurableOperation(jobRecord: jobRecord, jobBlock: self.jobBlock) - } - - // MARK: - - var jobBlock: (JobRecordType) -> Void = { _ in /* noop */ } - init() { } -} - -class TestDurableOperation: OWSOperation, DurableOperation { - - // MARK: DurableOperation - - var jobRecord: TestJobRecord - - weak var durableOperationDelegate: TestJobQueue? - - var operation: OWSOperation { - return self - } - - // MARK: - - var jobBlock: (TestJobRecord) -> Void - - init(jobRecord: TestJobRecord, jobBlock: @escaping (TestJobRecord) -> Void) { - self.jobRecord = jobRecord - self.jobBlock = jobBlock - } - - override func run() { - jobBlock(jobRecord) - self.reportSuccess() - } -} - -class JobQueueTest: SSKBaseTestSwift { - - override func setUp() { - super.setUp() - } - - override func tearDown() { - super.tearDown() - } - - // MARK: - - func buildJobRecord() -> TestJobRecord { - return TestJobRecord(label: kJobRecordLabel) - } - - // MARK: - - func test_setupMarksInProgressJobsAsReady() { - - let dispatchGroup = DispatchGroup() - - let jobQueue = TestJobQueue() - let jobRecord1 = buildJobRecord() - let jobRecord2 = buildJobRecord() - let jobRecord3 = buildJobRecord() - - var runList: [TestJobRecord] = [] - - jobQueue.jobBlock = { jobRecord in - runList.append(jobRecord) - dispatchGroup.leave() - } - - self.readWrite { transaction in - jobQueue.add(jobRecord: jobRecord1, transaction: transaction) - jobQueue.add(jobRecord: jobRecord2, transaction: transaction) - jobQueue.add(jobRecord: jobRecord3, transaction: transaction) - } - dispatchGroup.enter() - dispatchGroup.enter() - dispatchGroup.enter() - - let finder = JobRecordFinder() - self.readWrite { transaction in - XCTAssertEqual(3, finder.allRecords(label: kJobRecordLabel, status: .ready, transaction: transaction).count) - } - - // start queue - jobQueue.setup() - - if case .timedOut = dispatchGroup.wait(timeout: .now() + 1.0) { - XCTFail("timed out waiting for jobs") - } - - // Normally an operation enqueued for a JobRecord by a JobQueue will mark itself as complete - // by deleting itself. - // For testing, the operations enqueued by the TestJobQueue do *not* delete themeselves upon - // completion, simulating an operation which never compeleted. - - self.readWrite { transaction in - XCTAssertEqual(0, finder.allRecords(label: kJobRecordLabel, status: .ready, transaction: transaction).count) - XCTAssertEqual(3, finder.allRecords(label: kJobRecordLabel, status: .running, transaction: transaction).count) - } - - // Verify re-queue - jobQueue.isSetup = false - jobQueue.setup() - - self.readWrite { transaction in - XCTAssertEqual(3, finder.allRecords(label: kJobRecordLabel, status: .ready, transaction: transaction).count) - XCTAssertEqual(0, finder.allRecords(label: kJobRecordLabel, status: .running, transaction: transaction).count) - } - - let rerunGroup = DispatchGroup() - rerunGroup.enter() - rerunGroup.enter() - rerunGroup.enter() - - var rerunList: [TestJobRecord] = [] - jobQueue.jobBlock = { jobRecord in - rerunList.append(jobRecord) - rerunGroup.leave() - } - - jobQueue.isSetup = true - - switch rerunGroup.wait(timeout: .now() + 1.0) { - case .timedOut: - XCTFail("timed out waiting for retry") - case .success: - // verify order maintained on requeue - XCTAssertEqual([jobRecord1, jobRecord2, jobRecord3].map { $0.uniqueId }, rerunList.map { $0.uniqueId }) - } - } -} diff --git a/SignalServiceKit/tests/Util/TSMessageStorageTests.m b/SignalServiceKit/tests/Util/TSMessageStorageTests.m deleted file mode 100644 index 06773a190..000000000 --- a/SignalServiceKit/tests/Util/TSMessageStorageTests.m +++ /dev/null @@ -1,179 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSPrimaryStorage.h" -#import "SSKBaseTestObjC.h" -#import "TSContactThread.h" -#import "TSGroupThread.h" -#import "TSIncomingMessage.h" -#import "TSMessage.h" -#import "TSOutgoingMessage.h" -#import "TSThread.h" -#import "YapDatabaseConnection+OWS.h" -#import - -@interface TSMessageStorageTests : SSKBaseTestObjC - -@property TSContactThread *thread; - -@end - -@implementation TSMessageStorageTests - -#ifdef BROKEN_TESTS - -- (void)setUp -{ - [super setUp]; - - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - self.thread = [TSContactThread getOrCreateThreadWithContactId:@"aStupidId" transaction:transaction]; - - [self.thread saveWithTransaction:transaction]; - }]; -} - -- (void)tearDown -{ - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - -- (void)testStoreIncomingMessage -{ - __block NSString *messageId; - uint64_t timestamp = 666; - - NSString *body - = @"A child born today will grow up with no conception of privacy at all. They’ll never know what it means to " - @"have a private moment to themselves an unrecorded, unanalyzed thought. And that’s a problem because " - @"privacy matters; privacy is what allows us to determine who we are and who we want to be."; - - TSIncomingMessage *newMessage = - [[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:timestamp - inThread:self.thread - authorId:[self.thread contactIdentifier] - sourceDeviceId:1 - messageBody:body - attachmentIds:@[] - expiresInSeconds:0 - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [newMessage saveWithTransaction:transaction]; - messageId = newMessage.uniqueId; - }]; - - TSIncomingMessage *fetchedMessage = [TSIncomingMessage fetchObjectWithUniqueID:messageId]; - - XCTAssertEqualObjects(body, fetchedMessage.body); - XCTAssertFalse(fetchedMessage.hasAttachments); - XCTAssertEqual(timestamp, fetchedMessage.timestamp); - XCTAssertFalse(fetchedMessage.wasRead); - XCTAssertEqualObjects(self.thread.uniqueId, fetchedMessage.uniqueThreadId); -} - -- (void)testMessagesDeletedOnThreadDeletion -{ - NSString *body - = @"A child born today will grow up with no conception of privacy at all. They’ll never know what it means to " - @"have a private moment to themselves an unrecorded, unanalyzed thought. And that’s a problem because " - @"privacy matters; privacy is what allows us to determine who we are and who we want to be."; - - NSMutableArray *messages = [NSMutableArray new]; - for (int i = 0; i < 10; i++) { - TSIncomingMessage *newMessage = - [[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:i - inThread:self.thread - authorId:[self.thread contactIdentifier] - sourceDeviceId:1 - messageBody:body - attachmentIds:@[] - expiresInSeconds:0 - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - - [messages addObject:newMessage]; - [newMessage save]; - } - - for (TSIncomingMessage *message in messages) { - TSIncomingMessage *fetchedMessage = [TSIncomingMessage fetchObjectWithUniqueID:message.uniqueId]; - - XCTAssertEqualObjects(fetchedMessage.body, body, @"Body of incoming message recovered"); - XCTAssertEqual(0, fetchedMessage.attachmentIds.count, @"attachments are nil"); - XCTAssertEqualObjects(fetchedMessage.uniqueId, message.uniqueId, @"Unique identifier is accurate"); - XCTAssertFalse(fetchedMessage.wasRead, @"Message should originally be unread"); - XCTAssertEqualObjects( - fetchedMessage.uniqueThreadId, self.thread.uniqueId, @"Isn't stored in the right thread!"); - } - - [self.thread remove]; - - for (TSIncomingMessage *message in messages) { - TSIncomingMessage *fetchedMessage = [TSIncomingMessage fetchObjectWithUniqueID:message.uniqueId]; - XCTAssertNil(fetchedMessage, @"Message should be deleted!"); - } -} - - -- (void)testGroupMessagesDeletedOnThreadDeletion -{ - NSString *body - = @"A child born today will grow up with no conception of privacy at all. They’ll never know what it means to " - @"have a private moment to themselves an unrecorded, unanalyzed thought. And that’s a problem because " - @"privacy matters; privacy is what allows us to determine who we are and who we want to be."; - - __block TSGroupThread *thread; - [self readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - thread = [TSGroupThread getOrCreateThreadWithGroupModel:[[TSGroupModel alloc] initWithTitle:@"fdsfsd" - memberIds:[@[] mutableCopy] - image:nil - groupId:[NSData data] - groupType:SIGNAL] - transaction:transaction]; - - [thread saveWithTransaction:transaction]; - }]; - - NSMutableArray *messages = [NSMutableArray new]; - for (uint64_t i = 0; i < 10; i++) { - TSIncomingMessage *newMessage = [[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:i - inThread:thread - authorId:@"Ed" - sourceDeviceId:1 - messageBody:body - attachmentIds:@[] - expiresInSeconds:0 - quotedMessage:nil - contactShare:nil - linkPreview:nil]; - [newMessage save]; - [messages addObject:newMessage]; - } - - for (TSIncomingMessage *message in messages) { - TSIncomingMessage *fetchedMessage = [TSIncomingMessage fetchObjectWithUniqueID:message.uniqueId]; - XCTAssertNotNil(fetchedMessage); - XCTAssertEqualObjects(fetchedMessage.body, body, @"Body of incoming message recovered"); - XCTAssertEqual(0, fetchedMessage.attachmentIds.count, @"attachments are empty"); - XCTAssertEqualObjects(fetchedMessage.uniqueId, message.uniqueId, @"Unique identifier is accurate"); - XCTAssertFalse(fetchedMessage.wasRead, @"Message should originally be unread"); - XCTAssertEqualObjects(fetchedMessage.uniqueThreadId, thread.uniqueId, @"Isn't stored in the right thread!"); - } - - [thread remove]; - - for (TSIncomingMessage *message in messages) { - TSIncomingMessage *fetchedMessage = [TSIncomingMessage fetchObjectWithUniqueID:message.uniqueId]; - XCTAssertNil(fetchedMessage, @"Message should be deleted!"); - } -} - -#endif - -@end diff --git a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h index 9e44cd19f..1ab318f9e 100644 --- a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h +++ b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h @@ -10,21 +10,29 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[]; #import #import +#import #import +#import #import #import #import #import #import #import +#import #import #import #import #import +#import #import +#import #import +#import #import +#import #import +#import #import #import #import @@ -47,10 +55,12 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[]; #import #import #import +#import #import #import #import #import +#import #import #import #import