366 lines
16 KiB
Swift
366 lines
16 KiB
Swift
//
|
|
// Copyright 2020 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import LibSignalClient
|
|
import SignalCoreKit
|
|
import SignalServiceKit
|
|
import SignalUI
|
|
|
|
#if USE_DEBUG_UI
|
|
|
|
class DebugUIStress: DebugUIPage, Dependencies {
|
|
|
|
let name = "Stress"
|
|
|
|
func section(thread: TSThread?) -> OWSTableSection? {
|
|
var items = [OWSTableItem]()
|
|
|
|
if let thread {
|
|
items.append(OWSTableItem(title: "Send empty message", actionBlock: {
|
|
DebugUIStress.sendStressMessage(toThread: thread, block: { return Data() })
|
|
}))
|
|
items.append(OWSTableItem(title: "Send random noise message", actionBlock: {
|
|
DebugUIStress.sendStressMessage(toThread: thread, block: {
|
|
return Cryptography.generateRandomBytes(.random(in: 0...31))
|
|
})
|
|
}))
|
|
items.append(OWSTableItem(title: "Send no payload message", actionBlock: {
|
|
DebugUIStress.sendStressMessage(toThread: thread, block: {
|
|
let contentBuilder = SSKProtoContent.builder()
|
|
return (contentBuilder.buildIgnoringErrors()?.serializedDataIgnoringErrors())!
|
|
})
|
|
}))
|
|
items.append(OWSTableItem(title: "Send empty null message", actionBlock: {
|
|
DebugUIStress.sendStressMessage(toThread: thread, block: {
|
|
let contentBuilder = SSKProtoContent.builder()
|
|
if let nullMessage = SSKProtoNullMessage.builder().buildIgnoringErrors() {
|
|
contentBuilder.setNullMessage(nullMessage)
|
|
}
|
|
return (contentBuilder.buildIgnoringErrors()?.serializedDataIgnoringErrors())!
|
|
})
|
|
}))
|
|
|
|
// Sync
|
|
|
|
items.append(OWSTableItem(title: "Send empty sync message", actionBlock: {
|
|
DebugUIStress.sendStressMessage(toThread: thread, block: {
|
|
let contentBuilder = SSKProtoContent.builder()
|
|
if let syncMessage = SSKProtoSyncMessage.builder().buildIgnoringErrors() {
|
|
contentBuilder.setSyncMessage(syncMessage)
|
|
}
|
|
return (contentBuilder.buildIgnoringErrors()?.serializedDataIgnoringErrors())!
|
|
})
|
|
}))
|
|
items.append(OWSTableItem(title: "Send empty sync sent message", actionBlock: {
|
|
DebugUIStress.sendStressMessage(toThread: thread, block: {
|
|
let contentBuilder = SSKProtoContent.builder()
|
|
let syncMessageBuilder = SSKProtoSyncMessage.builder()
|
|
if let syncSentMessage = SSKProtoSyncMessageSent.builder().buildIgnoringErrors() {
|
|
syncMessageBuilder.setSent(syncSentMessage)
|
|
}
|
|
if let syncMessage = SSKProtoSyncMessage.builder().buildIgnoringErrors() {
|
|
contentBuilder.setSyncMessage(syncMessage)
|
|
}
|
|
return (contentBuilder.buildIgnoringErrors()?.serializedDataIgnoringErrors())!
|
|
})
|
|
}))
|
|
items.append(OWSTableItem(title: "Send malformed sync sent message 1", actionBlock: {
|
|
DebugUIStress.sendStressMessage(toThread: thread, block: {
|
|
let contentBuilder = SSKProtoContent.builder()
|
|
let syncMessageBuilder = SSKProtoSyncMessage.builder()
|
|
let syncSentMessageBuilder = SSKProtoSyncMessageSent.builder()
|
|
syncSentMessageBuilder.setDestinationServiceID("abc")
|
|
syncSentMessageBuilder.setTimestamp(.random(in: 1...32))
|
|
if let message = SSKProtoDataMessage.builder().buildIgnoringErrors() {
|
|
syncSentMessageBuilder.setMessage(message)
|
|
}
|
|
if let syncSentMessage = syncSentMessageBuilder.buildIgnoringErrors() {
|
|
syncMessageBuilder.setSent(syncSentMessage)
|
|
}
|
|
if let syncMessage = SSKProtoSyncMessage.builder().buildIgnoringErrors() {
|
|
contentBuilder.setSyncMessage(syncMessage)
|
|
}
|
|
return (contentBuilder.buildIgnoringErrors()?.serializedDataIgnoringErrors())!
|
|
})
|
|
}))
|
|
items.append(OWSTableItem(title: "Send malformed sync sent message 2", actionBlock: {
|
|
DebugUIStress.sendStressMessage(toThread: thread, block: {
|
|
let contentBuilder = SSKProtoContent.builder()
|
|
let syncMessageBuilder = SSKProtoSyncMessage.builder()
|
|
let syncSentMessageBuilder = SSKProtoSyncMessageSent.builder()
|
|
syncSentMessageBuilder.setDestinationServiceID("abc")
|
|
syncSentMessageBuilder.setTimestamp(0)
|
|
if let message = SSKProtoDataMessage.builder().buildIgnoringErrors() {
|
|
syncSentMessageBuilder.setMessage(message)
|
|
}
|
|
if let syncSentMessage = syncSentMessageBuilder.buildIgnoringErrors() {
|
|
syncMessageBuilder.setSent(syncSentMessage)
|
|
}
|
|
if let syncMessage = SSKProtoSyncMessage.builder().buildIgnoringErrors() {
|
|
contentBuilder.setSyncMessage(syncMessage)
|
|
}
|
|
return (contentBuilder.buildIgnoringErrors()?.serializedDataIgnoringErrors())!
|
|
})
|
|
}))
|
|
items.append(OWSTableItem(title: "Send malformed sync sent message 3", actionBlock: {
|
|
DebugUIStress.sendStressMessage(toThread: thread, block: {
|
|
let contentBuilder = SSKProtoContent.builder()
|
|
let syncMessageBuilder = SSKProtoSyncMessage.builder()
|
|
let syncSentMessageBuilder = SSKProtoSyncMessageSent.builder()
|
|
syncSentMessageBuilder.setDestinationServiceID("abc")
|
|
syncSentMessageBuilder.setTimestamp(0)
|
|
let dataMessageBuilder = SSKProtoDataMessage.builder()
|
|
dataMessageBuilder.setBody(" ")
|
|
if let message = dataMessageBuilder.buildIgnoringErrors() {
|
|
syncSentMessageBuilder.setMessage(message)
|
|
}
|
|
if let syncSentMessage = syncSentMessageBuilder.buildIgnoringErrors() {
|
|
syncMessageBuilder.setSent(syncSentMessage)
|
|
}
|
|
if let syncMessage = SSKProtoSyncMessage.builder().buildIgnoringErrors() {
|
|
contentBuilder.setSyncMessage(syncMessage)
|
|
}
|
|
return (contentBuilder.buildIgnoringErrors()?.serializedDataIgnoringErrors())!
|
|
})
|
|
}))
|
|
items.append(OWSTableItem(title: "Send malformed sync sent message 5", actionBlock: {
|
|
DebugUIStress.sendStressMessage(toThread: thread, block: {
|
|
let contentBuilder = SSKProtoContent.builder()
|
|
let syncMessageBuilder = SSKProtoSyncMessage.builder()
|
|
let syncSentMessageBuilder = SSKProtoSyncMessageSent.builder()
|
|
syncSentMessageBuilder.setDestinationServiceID("abc")
|
|
if let syncSentMessage = syncSentMessageBuilder.buildIgnoringErrors() {
|
|
syncMessageBuilder.setSent(syncSentMessage)
|
|
}
|
|
if let syncMessage = SSKProtoSyncMessage.builder().buildIgnoringErrors() {
|
|
contentBuilder.setSyncMessage(syncMessage)
|
|
}
|
|
return (contentBuilder.buildIgnoringErrors()?.serializedDataIgnoringErrors())!
|
|
})
|
|
}))
|
|
}
|
|
|
|
// Groups
|
|
|
|
if let groupThread = thread as? TSGroupThread {
|
|
items.append(OWSTableItem(title: "Copy members to another group", actionBlock: {
|
|
guard let fromViewController = UIApplication.shared.frontmostViewController else { return }
|
|
DebugUIStress.copyToAnotherGroup(groupThread, fromViewController: fromViewController)
|
|
}))
|
|
items.append(OWSTableItem(title: "Log membership", actionBlock: {
|
|
DebugUIStress.logMembership(groupThread)
|
|
}))
|
|
}
|
|
|
|
items.append(OWSTableItem(title: "Make group w. unregistered users", actionBlock: {
|
|
Task {
|
|
try await DebugUIStress.makeUnregisteredGroup()
|
|
}
|
|
}))
|
|
|
|
// Other
|
|
items.append(OWSTableItem(title: "Delete other profiles", actionBlock: {
|
|
DebugUIStress.deleteOtherProfiles()
|
|
}))
|
|
if let contactThread = thread as? TSContactThread {
|
|
items.append(OWSTableItem(title: "Log groups for contact", actionBlock: {
|
|
DebugUIStress.logGroupsForAddress(contactThread.contactAddress)
|
|
}))
|
|
}
|
|
|
|
return OWSTableSection(title: name, items: items)
|
|
}
|
|
|
|
static private let shared = DebugUIStress()
|
|
|
|
// MARK: -
|
|
|
|
private static func sendStressMessage(
|
|
toThread thread: TSThread,
|
|
timestamp: UInt64 = Date.ows_millisecondTimestamp(),
|
|
block: @escaping DynamicOutgoingMessageBlock
|
|
) {
|
|
let message = databaseStorage.read { transaction in
|
|
OWSDynamicOutgoingMessage(
|
|
thread: thread,
|
|
timestamp: timestamp,
|
|
transaction: transaction,
|
|
plainTextDataBlock: block
|
|
)
|
|
}
|
|
Task {
|
|
do {
|
|
let preparedMessage = PreparedOutgoingMessage.preprepared(transientMessageWithoutAttachments: message)
|
|
try await self.messageSender.sendMessage(preparedMessage)
|
|
Logger.info("Success.")
|
|
} catch {
|
|
owsFailDebug("Error: \(error)")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Groups
|
|
|
|
@MainActor
|
|
private static func makeUnregisteredGroup() async throws {
|
|
var recipientAddresses = [SignalServiceAddress]()
|
|
|
|
recipientAddresses.append(contentsOf: (0...2).map { _ in SignalServiceAddress(Aci(fromUUID: UUID())) })
|
|
|
|
if let localAddress = DependenciesBridge.shared.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction?.aciAddress {
|
|
recipientAddresses.append(localAddress)
|
|
}
|
|
|
|
recipientAddresses.append(contentsOf: (0...2).map { _ in SignalServiceAddress(Aci(fromUUID: UUID())) })
|
|
|
|
let groupThread = try await GroupManager.localCreateNewGroup(
|
|
members: recipientAddresses,
|
|
name: UUID().uuidString,
|
|
disappearingMessageToken: .disabledToken,
|
|
shouldSendMessage: false
|
|
)
|
|
SignalApp.shared.presentConversationForThread(groupThread, animated: true)
|
|
}
|
|
|
|
private static func copyToAnotherGroup(_ srcGroupThread: TSGroupThread, fromViewController: UIViewController) {
|
|
let groupThreads = self.databaseStorage.read { (transaction: SDSAnyReadTransaction) -> [TSGroupThread] in
|
|
TSThread.anyFetchAll(transaction: transaction).compactMap { $0 as? TSGroupThread }
|
|
}
|
|
guard !groupThreads.isEmpty else {
|
|
owsFailDebug("No groups.")
|
|
return
|
|
}
|
|
let groupThreadPicker = GroupThreadPicker(groupThreads: groupThreads) { (dstGroupThread: TSGroupThread) in
|
|
Self.copyToAnotherGroup(srcGroupThread: srcGroupThread, dstGroupThread: dstGroupThread)
|
|
}
|
|
fromViewController.present(groupThreadPicker, animated: true)
|
|
}
|
|
|
|
private static func copyToAnotherGroup(srcGroupThread: TSGroupThread, dstGroupThread: TSGroupThread) {
|
|
let membersToAdd = srcGroupThread.groupMembership.allMembersOfAnyKind.subtracting(dstGroupThread.groupMembership.allMembersOfAnyKind)
|
|
let serviceIdsToAdd = membersToAdd.compactMap { $0.serviceId }
|
|
for serviceId in serviceIdsToAdd {
|
|
Logger.verbose("Adding: \(serviceId)")
|
|
}
|
|
firstly {
|
|
GroupManager.addOrInvite(
|
|
serviceIds: serviceIdsToAdd,
|
|
toExistingGroup: dstGroupThread.groupModel
|
|
)
|
|
}.done { (groupThread) in
|
|
Logger.info("Complete.")
|
|
|
|
SignalApp.shared.presentConversationForThread(groupThread, animated: true)
|
|
}.catch(on: DispatchQueue.global()) { error in
|
|
owsFailDebug("Error: \(error)")
|
|
}
|
|
}
|
|
|
|
private static func logMembership(_ groupThread: TSGroupThread) {
|
|
let groupMembership = groupThread.groupModel.groupMembership
|
|
let addressStrings = groupMembership.allMembersOfAnyKind.map { $0.description }
|
|
Logger.info("addresses: \(addressStrings.joined(separator: "\n"))")
|
|
}
|
|
|
|
private static func deleteOtherProfiles() {
|
|
databaseStorage.write { transaction in
|
|
let profiles = OWSUserProfile.anyFetchAll(transaction: transaction)
|
|
for profile in profiles {
|
|
guard !OWSUserProfile.isLocalProfileAddress(profile.internalAddress) else {
|
|
continue
|
|
}
|
|
Logger.verbose("Deleting: \(profile.internalAddress)")
|
|
profile.anyRemove(transaction: transaction)
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func logGroupsForAddress(_ address: SignalServiceAddress) {
|
|
Self.databaseStorage.read { transaction in
|
|
TSGroupThread.enumerateGroupThreads(
|
|
with: address,
|
|
transaction: transaction
|
|
) { thread, _ in
|
|
let displayName = Self.contactsManager.displayName(for: thread, transaction: transaction)
|
|
Logger.verbose("Group[\(thread.groupId.hexadecimalString)]: \(displayName)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
private class GroupThreadPicker: OWSTableViewController {
|
|
|
|
private let groupThreads: [TSGroupThread]
|
|
private let completion: (TSGroupThread) -> Void
|
|
|
|
init(groupThreads: [TSGroupThread], completion: @escaping (TSGroupThread) -> Void) {
|
|
self.groupThreads = groupThreads
|
|
self.completion = completion
|
|
|
|
super.init()
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
self.title = "Select Destination Group"
|
|
|
|
rebuildTableContents()
|
|
setupNavigationBar()
|
|
applyTheme()
|
|
}
|
|
|
|
// MARK: - Data providers
|
|
|
|
private func setupNavigationBar() {
|
|
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
|
barButtonSystemItem: .cancel,
|
|
target: self,
|
|
action: #selector(didTapCancel)
|
|
)
|
|
}
|
|
|
|
private func rebuildTableContents() {
|
|
let contactsManager = Self.contactsManager
|
|
let databaseStorage = Self.databaseStorage
|
|
|
|
let contents = OWSTableContents()
|
|
let section = OWSTableSection()
|
|
section.headerTitle = "Select a group to add the members to"
|
|
|
|
databaseStorage.read { transaction in
|
|
let sortedGroupThreads = self.groupThreads.sorted { (left, right) -> Bool in
|
|
left.lastInteractionRowId > right.lastInteractionRowId
|
|
}
|
|
for groupThread in sortedGroupThreads {
|
|
let groupName = contactsManager.displayName(for: groupThread, transaction: transaction)
|
|
section.add(OWSTableItem.actionItem(withText: groupName) { [weak self] in
|
|
self?.didSelectGroupThread(groupThread)
|
|
})
|
|
}
|
|
}
|
|
contents.add(section)
|
|
self.contents = contents
|
|
}
|
|
|
|
// MARK: - Actions
|
|
|
|
@objc
|
|
private func didTapCancel() {
|
|
presentingViewController?.dismiss(animated: true, completion: nil)
|
|
}
|
|
|
|
private func didSelectGroupThread(_ groupThread: TSGroupThread) {
|
|
let completion = self.completion
|
|
presentingViewController?.dismiss(animated: true) {
|
|
completion(groupThread)
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|