// // Copyright (c) 2020 Open Whisper Systems. All rights reserved. // #import "DebugUIStress.h" #import "OWSMessageSender.h" #import "OWSTableViewController.h" #import "Signal-Swift.h" #import "SignalApp.h" #import "ThreadUtil.h" #import #import #import #import #import #import #import #import #import #import #ifdef DEBUG NS_ASSUME_NONNULL_BEGIN @interface DebugUIStress () @property (nonatomic, nullable) NSTimer *thrashTimer; @end #pragma mark - @implementation DebugUIStress + (instancetype)shared { static DebugUIStress *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [self new]; }); return instance; } #pragma mark - Dependencies + (MessageSenderJobQueue *)messageSenderJobQueue { return SSKEnvironment.shared.messageSenderJobQueue; } - (MessageSenderJobQueue *)messageSenderJobQueue { return self.class.messageSenderJobQueue; } + (SDSDatabaseStorage *)databaseStorage { return SDSDatabaseStorage.shared; } - (SDSDatabaseStorage *)databaseStorage { return SDSDatabaseStorage.shared; } + (TSAccountManager *)tsAccountManager { return TSAccountManager.sharedInstance; } #pragma mark - Factory Methods - (NSString *)name { return @"Stress"; } - (nullable OWSTableSection *)sectionForThread:(nullable TSThread *)thread { OWSAssertDebug(thread); NSMutableArray *items = [NSMutableArray new]; [items addObject:[OWSTableItem itemWithTitle:@"Send empty message" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { return [NSData new]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send random noise message" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { NSUInteger contentLength = arc4random_uniform(32); return [Cryptography generateRandomBytes:contentLength]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send no payload message" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send empty null message" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoNullMessageBuilder *nullMessageBuilder = [SSKProtoNullMessage builder]; contentBuilder.nullMessage = [nullMessageBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send random null message" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoNullMessageBuilder *nullMessageBuilder = [SSKProtoNullMessage builder]; NSUInteger contentLength = arc4random_uniform(32); nullMessageBuilder.padding = [Cryptography generateRandomBytes:contentLength]; contentBuilder.nullMessage = [nullMessageBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send empty sync message" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; contentBuilder.syncMessage = [syncMessageBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send empty sync sent message" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; SSKProtoSyncMessageSentBuilder *sentBuilder = [SSKProtoSyncMessageSent builder]; syncMessageBuilder.sent = [sentBuilder buildIgnoringErrors]; contentBuilder.syncMessage = [syncMessageBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send whitespace text data message" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; dataBuilder.body = @" "; [DebugUIStress ensureGroupOfDataBuilder:dataBuilder thread:thread]; contentBuilder.dataMessage = [dataBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send bad attachment data message" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; SSKProtoAttachmentPointerBuilder *attachmentPointer = [SSKProtoAttachmentPointer builder]; attachmentPointer.cdnID = arc4random_uniform(32) + 1; [attachmentPointer setContentType:@"1"]; [attachmentPointer setSize:arc4random_uniform(32) + 1]; [attachmentPointer setDigest:[Cryptography generateRandomBytes:1]]; [attachmentPointer setFileName:@" "]; [DebugUIStress ensureGroupOfDataBuilder:dataBuilder thread:thread]; contentBuilder.dataMessage = [dataBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send normal text data message" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; dataBuilder.body = @"alice"; [DebugUIStress ensureGroupOfDataBuilder:dataBuilder thread:thread]; contentBuilder.dataMessage = [dataBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send N text messages with same timestamp" actionBlock:^{ uint64_t timestamp = [NSDate ows_millisecondTimeStamp]; for (int i = 0; i < 3; i++) { [DebugUIStress sendStressMessage:thread timestamp:timestamp block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; dataBuilder.body = [NSString stringWithFormat:@"%@ %d", [NSUUID UUID].UUIDString, i]; [DebugUIStress ensureGroupOfDataBuilder:dataBuilder thread:thread]; contentBuilder.dataMessage = [dataBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; } }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send text message with current timestamp" actionBlock:^{ uint64_t timestamp = [NSDate ows_millisecondTimeStamp]; [DebugUIStress sendStressMessage:thread timestamp:timestamp block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; dataBuilder.body = [[NSUUID UUID].UUIDString stringByAppendingString:@" now"]; [DebugUIStress ensureGroupOfDataBuilder:dataBuilder thread:thread]; contentBuilder.dataMessage = [dataBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send text message with future timestamp" actionBlock:^{ uint64_t timestamp = [NSDate ows_millisecondTimeStamp]; timestamp += kHourInMs; [DebugUIStress sendStressMessage:thread timestamp:timestamp block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; dataBuilder.body = [[NSUUID UUID].UUIDString stringByAppendingString:@" now"]; [DebugUIStress ensureGroupOfDataBuilder:dataBuilder thread:thread]; contentBuilder.dataMessage = [dataBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send text message with past timestamp" actionBlock:^{ uint64_t timestamp = [NSDate ows_millisecondTimeStamp]; timestamp -= kHourInMs; [DebugUIStress sendStressMessage:thread timestamp:timestamp block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; dataBuilder.body = [[NSUUID UUID].UUIDString stringByAppendingString:@" now"]; [DebugUIStress ensureGroupOfDataBuilder:dataBuilder thread:thread]; contentBuilder.dataMessage = [dataBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send N text messages with same timestamp" actionBlock:^{ SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; dataBuilder.body = @"alice"; contentBuilder.dataMessage = [dataBuilder buildIgnoringErrors]; [DebugUIStress ensureGroupOfDataBuilder:dataBuilder thread:thread]; NSData *data = [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; uint64_t timestamp = [NSDate ows_millisecondTimeStamp]; for (int i = 0; i < 3; i++) { [DebugUIStress sendStressMessage:thread timestamp:timestamp block:^(SignalRecipient *recipient) { return data; }]; } }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send malformed sync sent message 1" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; SSKProtoSyncMessageSentBuilder *sentBuilder = [SSKProtoSyncMessageSent builder]; sentBuilder.destinationE164 = @"abc"; sentBuilder.timestamp = arc4random_uniform(32) + 1; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; sentBuilder.message = [dataBuilder buildIgnoringErrors]; syncMessageBuilder.sent = [sentBuilder buildIgnoringErrors]; contentBuilder.syncMessage = [syncMessageBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send malformed sync sent message 2" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; SSKProtoSyncMessageSentBuilder *sentBuilder = [SSKProtoSyncMessageSent builder]; sentBuilder.destinationE164 = @"abc"; sentBuilder.timestamp = 0; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; sentBuilder.message = [dataBuilder buildIgnoringErrors]; syncMessageBuilder.sent = [sentBuilder buildIgnoringErrors]; contentBuilder.syncMessage = [syncMessageBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send malformed sync sent message 3" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; SSKProtoSyncMessageSentBuilder *sentBuilder = [SSKProtoSyncMessageSent builder]; sentBuilder.destinationE164 = @"abc"; sentBuilder.timestamp = 0; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; dataBuilder.body = @" "; sentBuilder.message = [dataBuilder buildIgnoringErrors]; syncMessageBuilder.sent = [sentBuilder buildIgnoringErrors]; contentBuilder.syncMessage = [syncMessageBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send malformed sync sent message 4" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; SSKProtoSyncMessageSentBuilder *sentBuilder = [SSKProtoSyncMessageSent builder]; sentBuilder.destinationE164 = @"abc"; sentBuilder.timestamp = 0; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; dataBuilder.body = @" "; SSKProtoGroupContextBuilder *groupBuilder = [SSKProtoGroupContext builderWithId:[Cryptography generateRandomBytes:1]]; [groupBuilder setType:SSKProtoGroupContextTypeDeliver]; dataBuilder.group = [groupBuilder buildIgnoringErrors]; sentBuilder.message = [dataBuilder buildIgnoringErrors]; syncMessageBuilder.sent = [sentBuilder buildIgnoringErrors]; contentBuilder.syncMessage = [syncMessageBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send malformed sync sent message 5" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; SSKProtoSyncMessageSentBuilder *sentBuilder = [SSKProtoSyncMessageSent builder]; sentBuilder.destinationE164 = @"abc"; sentBuilder.timestamp = 0; SSKProtoDataMessageBuilder *dataBuilder = [SSKProtoDataMessage builder]; dataBuilder.body = @" "; SSKProtoGroupContextBuilder *groupBuilder = [SSKProtoGroupContext builderWithId:[Cryptography generateRandomBytes:1]]; [groupBuilder setType:SSKProtoGroupContextTypeDeliver]; dataBuilder.group = [groupBuilder buildIgnoringErrors]; sentBuilder.message = [dataBuilder buildIgnoringErrors]; syncMessageBuilder.sent = [sentBuilder buildIgnoringErrors]; contentBuilder.syncMessage = [syncMessageBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Send empty sync sent message 6" actionBlock:^{ [DebugUIStress sendStressMessage:thread block:^(SignalRecipient *recipient) { SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder]; SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; SSKProtoSyncMessageSentBuilder *sentBuilder = [SSKProtoSyncMessageSent builder]; sentBuilder.destinationE164 = @"abc"; syncMessageBuilder.sent = [sentBuilder buildIgnoringErrors]; contentBuilder.syncMessage = [syncMessageBuilder buildIgnoringErrors]; return [[contentBuilder buildIgnoringErrors] serializedDataIgnoringErrors]; }]; }]]; if ([thread isKindOfClass:[TSGroupThread class]]) { TSGroupThread *groupThread = (TSGroupThread *)thread; [items addObject:[OWSTableItem itemWithTitle:@"Clone as v1/v2 group" actionBlock:^{ [DebugUIStress cloneAsV1orV2Group:groupThread]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Clone as v1 group" actionBlock:^{ [DebugUIStress cloneAsV1Group:groupThread]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Clone as v2 group" actionBlock:^{ [DebugUIStress cloneAsV2Group:groupThread]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Add debug members to group" actionBlock:^{ [DebugUIStress addDebugMembersToGroup:groupThread]; }]]; } [items addObject:[OWSTableItem itemWithTitle:@"Make group w. unregistered users" actionBlock:^{ [DebugUIStress makeUnregisteredGroup]; }]]; __weak DebugUIStress *weakSelf = self; [items addObject:[OWSTableItem itemWithTitle:@"Thrash writes 10/second" actionBlock:^{ [weakSelf thrashWithMaxWritesPerSecond:10 thread:thread]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Thrash writes 100/second" actionBlock:^{ [weakSelf thrashWithMaxWritesPerSecond:100 thread:thread]; }]]; [items addObject:[OWSTableItem itemWithTitle:@"Stop thrash" actionBlock:^{ [weakSelf stopThrash]; }]]; return [OWSTableSection sectionWithTitle:self.name items:items]; } + (void)ensureGroupOfDataBuilder:(SSKProtoDataMessageBuilder *)dataBuilder thread:(TSThread *)thread { OWSAssertDebug(dataBuilder); OWSAssertDebug(thread); if (![thread isKindOfClass:[TSGroupThread class]]) { return; } TSGroupThread *groupThread = (TSGroupThread *)thread; SSKProtoGroupContextBuilder *groupBuilder = [SSKProtoGroupContext builderWithId:groupThread.groupModel.groupId]; [groupBuilder setType:SSKProtoGroupContextTypeDeliver]; [groupBuilder setId:groupThread.groupModel.groupId]; [dataBuilder setGroup:groupBuilder.buildIgnoringErrors]; } + (void)sendStressMessage:(TSOutgoingMessage *)message { OWSAssertDebug(message); DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) { [self.messageSenderJobQueue addMessage:message.asPreparer transaction:transaction]; }); } + (void)sendStressMessage:(TSThread *)thread block:(DynamicOutgoingMessageBlock)block { OWSAssertDebug(thread); OWSAssertDebug(block); OWSDynamicOutgoingMessage *message = [[OWSDynamicOutgoingMessage alloc] initWithThread:thread plainTextDataBlock:block]; [self sendStressMessage:message]; } + (void)sendStressMessage:(TSThread *)thread timestamp:(uint64_t)timestamp block:(DynamicOutgoingMessageBlock)block { OWSAssertDebug(thread); OWSAssertDebug(block); OWSDynamicOutgoingMessage *message = [[OWSDynamicOutgoingMessage alloc] initWithThread:thread timestamp:timestamp plainTextDataBlock:block]; [self sendStressMessage:message]; } + (void)makeUnregisteredGroup { NSMutableArray *recipientAddresses = [NSMutableArray new]; for (int i = 0; i < 3; i++) { NSMutableString *recipientNumber = [@"+1999" mutableCopy]; for (int j = 0; j < 3; j++) { uint32_t digit = arc4random_uniform(10); [recipientNumber appendFormat:@"%d", (int)digit]; } [recipientAddresses addObject:[[SignalServiceAddress alloc] initWithUuid:[NSUUID UUID] phoneNumber:recipientNumber]]; } [recipientAddresses addObject:self.tsAccountManager.localAddress]; if (RemoteConfig.allowUUIDOnlyContacts) { for (int i = 0; i < 3; i++) { [recipientAddresses addObject:[[SignalServiceAddress alloc] initWithUuid:[NSUUID UUID] phoneNumber:nil]]; } } [GroupManager localCreateNewGroupObjcWithMembers:recipientAddresses groupId:nil name:NSUUID.UUID.UUIDString avatarData:nil newGroupSeed:nil shouldSendMessage:NO success:^(TSGroupThread *thread) { [SignalApp.sharedApp presentConversationForThread:thread animated:YES]; } failure:^(NSError *error) { OWSFailDebug(@"Error: %@", error); }]; } - (void)thrashWithMaxWritesPerSecond:(NSUInteger)maxWritesPerSecond thread:(TSThread *)thread { NSTimeInterval delaySeconds = kSecondInterval / maxWritesPerSecond; __block uint64_t counter = 0; [self stopThrash]; DebugUIStress.shared.thrashTimer = [NSTimer scheduledTimerWithTimeInterval:delaySeconds repeats:YES block:^(NSTimer *timer) { counter = counter + 1; OWSLogVerbose(@"counter: %llu", counter); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self thrashWritesWithThread:thread]; }); }]; } - (void)thrashWritesWithThread:(TSThread *)thread { __block TSThread *interactionThread = thread; __block TSThread *_Nullable otherThread = nil; BOOL shouldUseOtherThread = arc4random_uniform(2) == 0; if (shouldUseOtherThread) { DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) { BOOL shouldUseGroupThread = arc4random_uniform(2) == 0; if (shouldUseGroupThread) { NSError *_Nullable error; otherThread = [GroupManager remoteUpsertExistingGroupV1WithGroupId:[TSGroupModel generateRandomV1GroupId] name:NSUUID.UUID.UUIDString avatarData:nil members:@[] disappearingMessageToken:nil groupUpdateSourceAddress:nil infoMessagePolicy:InfoMessagePolicyAlways transaction:transaction error:&error] .groupThread; if (error != nil) { OWSFailDebug(@"error: %@", error); } } else { SignalServiceAddress *otherAddress = [[SignalServiceAddress alloc] initWithUuid:NSUUID.UUID]; otherThread = [TSContactThread getOrCreateThreadWithContactAddress:otherAddress transaction:transaction]; } interactionThread = otherThread; }); } NSString *text = NSUUID.UUID.UUIDString; TSOutgoingMessageBuilder *messageBuilder = [TSOutgoingMessageBuilder outgoingMessageBuilderWithThread:interactionThread messageBody:text]; TSOutgoingMessage *message = [messageBuilder build]; DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) { [message anyInsertWithTransaction:transaction]; }); DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) { [message updateWithFakeMessageState:TSOutgoingMessageStateSending transaction:transaction]; [message updateWithFakeMessageState:TSOutgoingMessageStateFailed transaction:transaction]; }); BOOL shouldDelete = arc4random_uniform(2) == 0; if (shouldDelete) { DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) { [message anyRemoveWithTransaction:transaction]; if (otherThread != nil) { [otherThread anyRemoveWithTransaction:transaction]; } }); } } - (void)stopThrash { [DebugUIStress.shared.thrashTimer invalidate]; DebugUIStress.shared.thrashTimer = nil; } @end NS_ASSUME_NONNULL_END #endif