You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m

2595 lines
130 KiB
Objective-C

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "DebugUIMessages.h"
#import "DebugUIContacts.h"
#import "DebugUIMessagesAction.h"
#import "DebugUIMessagesAssetLoader.h"
#import "OWSTableViewController.h"
#import "Signal-Swift.h"
#import <Curve25519Kit/Randomness.h>
#import <SignalServiceKit/MIMETypeUtil.h>
#import <SignalServiceKit/OWSDisappearingConfigurationUpdateInfoMessage.h>
#import <SignalServiceKit/OWSSyncGroupsRequestMessage.h>
#import <SignalServiceKit/OWSVerificationStateChangeMessage.h>
#import <SignalServiceKit/TSIncomingMessage.h>
#import <SignalServiceKit/TSInvalidIdentityKeyReceivingErrorMessage.h>
#import <SignalServiceKit/TSOutgoingMessage.h>
#import <SignalServiceKit/TSThread.h>
NS_ASSUME_NONNULL_BEGIN
@interface TSOutgoingMessage (PostDatingDebug)
- (void)setReceivedAtTimestamp:(uint64_t)value;
@end
#pragma mark -
@implementation DebugUIMessages
#pragma mark - Factory Methods
- (NSString *)name
{
return @"Messages";
}
- (nullable OWSTableSection *)sectionForThread:(nullable TSThread *)thread
{
OWSAssert(thread);
NSMutableArray<OWSTableItem *> *items = [NSMutableArray new];
for (DebugUIMessagesAction *action in @[
[DebugUIMessages sendMessageVariationsAction:thread],
// Send Media
[DebugUIMessages sendJpegAction:thread],
[DebugUIMessages sendGifAction:thread],
[DebugUIMessages sendMp3Action:thread],
[DebugUIMessages sendMp4Action:thread],
[DebugUIMessages sendAllMediaAction:thread],
[DebugUIMessages sendRandomMediaAction:thread],
// Fake Media
[DebugUIMessages fakeAllMediaAction:thread],
[DebugUIMessages fakeRandomMediaAction:thread],
// Fake Text
[DebugUIMessages fakeAllTextAction:thread],
[DebugUIMessages fakeRandomTextAction:thread],
// Exemplary
[DebugUIMessages allExemplaryAction:thread],
]) {
[items addObject:[OWSTableItem itemWithTitle:action.label
actionBlock:^{
// For "all in group" actions, do each subaction in the group
// exactly once, in a predictable order.
if ([action isKindOfClass:[DebugUIMessagesGroupAction class]]) {
DebugUIMessagesGroupAction *groupAction
= (DebugUIMessagesGroupAction *)action;
if (groupAction.subactionMode == SubactionMode_Ordered) {
[action prepareAndPerformNTimes:groupAction.subactions.count];
return;
}
}
[DebugUIMessages performActionNTimes:action];
}]];
}
[items addObjectsFromArray:@[
#pragma mark - Actions
[OWSTableItem itemWithTitle:@"Send N text messages (1/sec.)"
actionBlock:^{
[DebugUIMessages sendNTextMessagesInThread:thread];
}],
// [OWSTableItem itemWithTitle:@"Send N Random Media (1/sec.)"
// actionBlock:^{
// [DebugUIMessages sendSelectedMediaTypeInThread:thread];
// }],
[OWSTableItem itemWithTitle:@"Select Action"
actionBlock:^{
[DebugUIMessages selectExemplaryAction:thread];
}],
#pragma mark - Misc.
[OWSTableItem itemWithTitle:@"Perform 100 random actions"
actionBlock:^{
[DebugUIMessages performRandomActions:100 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Perform 1,000 random actions"
actionBlock:^{
[DebugUIMessages performRandomActions:1000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 10 tiny text messages (1/sec.)"
actionBlock:^{
[DebugUIMessages sendTinyTextMessages:10 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 100 tiny text messages (1/sec.)"
actionBlock:^{
[DebugUIMessages sendTinyTextMessages:100 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 10 tiny attachments"
actionBlock:^{
[DebugUIMessages sendTinyAttachments:10 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 100 tiny attachments"
actionBlock:^{
[DebugUIMessages sendTinyAttachments:100 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 1,000 tiny attachments"
actionBlock:^{
[DebugUIMessages sendTinyAttachments:1000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 3,000 tiny attachments"
actionBlock:^{
[DebugUIMessages sendTinyAttachments:3000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 10 fake messages"
actionBlock:^{
[DebugUIMessages sendFakeMessages:10 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 1 fake thread with 1 message"
actionBlock:^{
[DebugUIMessages createFakeThreads:1 withFakeMessages:1];
}],
[OWSTableItem itemWithTitle:@"Create 100 fake threads with 10 messages"
actionBlock:^{
[DebugUIMessages createFakeThreads:100 withFakeMessages:10];
}],
[OWSTableItem itemWithTitle:@"Create 10 fake threads with 100 messages"
actionBlock:^{
[DebugUIMessages createFakeThreads:10 withFakeMessages:100];
}],
[OWSTableItem itemWithTitle:@"Create 10 fake threads with 10 messages"
actionBlock:^{
[DebugUIMessages createFakeThreads:10 withFakeMessages:10];
}],
[OWSTableItem itemWithTitle:@"Create 100 fake threads with 100 messages"
actionBlock:^{
[DebugUIMessages createFakeThreads:100 withFakeMessages:100];
}],
[OWSTableItem itemWithTitle:@"Create 1k fake threads with 1 message"
actionBlock:^{
[DebugUIMessages createFakeThreads:1000 withFakeMessages:1];
}],
[OWSTableItem itemWithTitle:@"Create 1k fake messages"
actionBlock:^{
[DebugUIMessages sendFakeMessages:1000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 10k fake messages"
actionBlock:^{
[DebugUIMessages sendFakeMessages:10 * 1000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 100k fake messages"
actionBlock:^{
[DebugUIMessages sendFakeMessages:100 * 1000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 100k fake text messages"
actionBlock:^{
[DebugUIMessages sendFakeMessages:100 * 1000 thread:thread isTextOnly:YES];
}],
[OWSTableItem itemWithTitle:@"Create 1 fake unread messages"
actionBlock:^{
[DebugUIMessages createFakeUnreadMessages:1 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 10 fake unread messages"
actionBlock:^{
[DebugUIMessages createFakeUnreadMessages:10 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 10 fake large attachments"
actionBlock:^{
[DebugUIMessages createFakeLargeOutgoingAttachments:10 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 100 fake large attachments"
actionBlock:^{
[DebugUIMessages createFakeLargeOutgoingAttachments:100 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 1k fake large attachments"
actionBlock:^{
[DebugUIMessages createFakeLargeOutgoingAttachments:1000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Create 10k fake large attachments"
actionBlock:^{
[DebugUIMessages createFakeLargeOutgoingAttachments:10000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Send text/x-signal-plain"
actionBlock:^{
[DebugUIMessages sendOversizeTextMessage:thread];
}],
[OWSTableItem itemWithTitle:@"Send unknown mimetype"
actionBlock:^{
[DebugUIMessages sendRandomAttachment:thread uti:kUnknownTestAttachmentUTI];
}],
[OWSTableItem itemWithTitle:@"Send pdf"
actionBlock:^{
[DebugUIMessages sendRandomAttachment:thread uti:(NSString *)kUTTypePDF];
}],
[OWSTableItem itemWithTitle:@"Create all system messages"
actionBlock:^{
[DebugUIMessages createSystemMessagesInThread:thread];
}],
[OWSTableItem itemWithTitle:@"Create messages with variety of timestamps"
actionBlock:^{
[DebugUIMessages createTimestampMessagesInThread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 10 text and system messages"
actionBlock:^{
[DebugUIMessages sendTextAndSystemMessages:10 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 100 text and system messages"
actionBlock:^{
[DebugUIMessages sendTextAndSystemMessages:100 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Send 1,000 text and system messages"
actionBlock:^{
[DebugUIMessages sendTextAndSystemMessages:1000 thread:thread];
}],
[OWSTableItem
itemWithTitle:@"Request Bogus group info"
actionBlock:^{
DDLogInfo(@"%@ Requesting bogus group info for thread: %@", self.logTag, thread);
OWSSyncGroupsRequestMessage *syncGroupsRequestMessage =
[[OWSSyncGroupsRequestMessage alloc] initWithThread:thread
groupId:[Randomness generateRandomBytes:16]];
[[Environment current].messageSender enqueueMessage:syncGroupsRequestMessage
success:^{
DDLogWarn(@"%@ Successfully sent Request Group Info message.", self.logTag);
}
failure:^(NSError *error) {
DDLogError(
@"%@ Failed to send Request Group Info message with error: %@", self.logTag, error);
}];
}],
[OWSTableItem itemWithTitle:@"Inject 10 fake incoming messages"
actionBlock:^{
[DebugUIMessages injectFakeIncomingMessages:10 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Inject 100 fake incoming messages"
actionBlock:^{
[DebugUIMessages injectFakeIncomingMessages:100 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Inject 1,000 fake incoming messages"
actionBlock:^{
[DebugUIMessages injectFakeIncomingMessages:1000 thread:thread];
}],
[OWSTableItem itemWithTitle:@"Test Indic Scripts"
actionBlock:^{
[DebugUIMessages testIndicScriptsInThread:thread];
}],
[OWSTableItem itemWithTitle:@"Test Zalgo"
actionBlock:^{
[DebugUIMessages testZalgoTextInThread:thread];
}],
[OWSTableItem itemWithTitle:@"Test Directional Filenames"
actionBlock:^{
[DebugUIMessages testDirectionalFilenamesInThread:thread];
}],
]];
if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;
NSString *recipientId = contactThread.contactIdentifier;
[items addObject:[OWSTableItem itemWithTitle:@"Create 10 new groups"
actionBlock:^{
[DebugUIMessages createNewGroups:10 recipientId:recipientId];
}]];
[items addObject:[OWSTableItem itemWithTitle:@"Create 100 new groups"
actionBlock:^{
[DebugUIMessages createNewGroups:100 recipientId:recipientId];
}]];
[items addObject:[OWSTableItem itemWithTitle:@"Create 1,000 new groups"
actionBlock:^{
[DebugUIMessages createNewGroups:1000 recipientId:recipientId];
}]];
}
if ([thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *groupThread = (TSGroupThread *)thread;
[items addObject:[OWSTableItem itemWithTitle:@"Send message to all members"
actionBlock:^{
[DebugUIMessages sendMessages:1 toAllMembersOfGroup:groupThread];
}]];
}
return [OWSTableSection sectionWithTitle:self.name items:items];
}
+ (void)sendMessages:(NSUInteger)count toAllMembersOfGroup:(TSGroupThread *)groupThread
{
for (NSString *recipientId in groupThread.groupModel.groupMemberIds) {
TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:recipientId];
[[self sendTextMessagesActionInThread:contactThread] prepareAndPerformNTimes:count];
}
}
+ (void)sendTextMessageInThread:(TSThread *)thread counter:(NSUInteger)counter
{
DDLogInfo(@"%@ sendTextMessageInThread: %zd", self.logTag, counter);
[DDLog flushLog];
NSString *randomText = [self randomText];
NSString *text = [[[@(counter) description] stringByAppendingString:@" "] stringByAppendingString:randomText];
OWSMessageSender *messageSender = [Environment current].messageSender;
TSOutgoingMessage *message = [ThreadUtil sendMessageWithText:text inThread:thread messageSender:messageSender];
DDLogError(@"%@ sendTextMessageInThread timestamp: %llu.", self.logTag, message.timestamp);
}
+ (void)sendNTextMessagesInThread:(TSThread *)thread
{
[self performActionNTimes:[self sendTextMessagesActionInThread:thread]];
}
+ (DebugUIMessagesAction *)sendTextMessagesActionInThread:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction actionWithLabel:@"Send Text Message"
staggeredActionBlock:^(NSUInteger index,
YapDatabaseReadWriteTransaction *transaction,
ActionSuccessBlock success,
ActionFailureBlock failure) {
dispatch_async(dispatch_get_main_queue(), ^{
[self sendTextMessageInThread:thread counter:index];
// TODO:
success();
});
}];
}
+ (void)sendTinyTextMessageInThread:(TSThread *)thread counter:(NSUInteger)counter
{
NSString *randomText = [[self randomText] substringToIndex:arc4random_uniform(4)];
NSString *text = [[[@(counter) description] stringByAppendingString:@" "] stringByAppendingString:randomText];
OWSMessageSender *messageSender = [Environment current].messageSender;
[ThreadUtil sendMessageWithText:text inThread:thread messageSender:messageSender];
}
+ (void)sendTinyTextMessages:(NSUInteger)counter thread:(TSThread *)thread
{
if (counter < 1) {
return;
}
[self sendTinyTextMessageInThread:thread counter:counter];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self sendTinyTextMessages:counter - 1 thread:thread];
});
}
+ (void)sendAttachment:(NSString *)filePath
thread:(TSThread *)thread
success:(nullable void (^)(void))success
failure:(nullable void (^)(void))failure
{
OWSAssert(filePath);
OWSAssert(thread);
OWSMessageSender *messageSender = [Environment current].messageSender;
NSString *filename = [filePath lastPathComponent];
NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:filename.pathExtension];
DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath];
[dataSource setSourceFilename:filename];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType imageQuality:TSImageQualityOriginal];
if (arc4random_uniform(100) > 50) {
attachment.captionText = [self randomCaptionText];
}
OWSAssert(attachment);
if ([attachment hasError]) {
DDLogError(@"attachment[%@]: %@", [attachment sourceFilename], [attachment errorName]);
[DDLog flushLog];
}
OWSAssert(![attachment hasError]);
[ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender completion:nil];
success();
}
#pragma mark - Infrastructure
+ (void)performActionNTimes:(DebugUIMessagesAction *)action
{
OWSAssertIsOnMainThread();
OWSAssert(action);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"How many?"
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
for (NSNumber *countValue in @[
@(1),
@(10),
@(100),
@(1 * 1000),
@(10 * 1000),
]) {
[alert addAction:[UIAlertAction actionWithTitle:countValue.stringValue
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *ignore) {
[action prepareAndPerformNTimes:countValue.unsignedIntegerValue];
}]];
}
[alert addAction:[OWSAlerts cancelAction]];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentViewController:alert animated:YES completion:nil];
}
#pragma mark - Send Media
+ (NSArray<DebugUIMessagesAction *> *)allSendMediaActions:(TSThread *)thread
{
OWSAssert(thread);
NSArray<DebugUIMessagesAction *> *actions = @[
[self sendJpegAction:thread],
[self sendGifAction:thread],
[self sendMp3Action:thread],
[self sendMp4Action:thread],
];
return actions;
}
+ (DebugUIMessagesAction *)sendJpegAction:(TSThread *)thread
{
OWSAssert(thread);
return [self sendMediaAction:@"Send Jpeg" fakeAssetLoader:[DebugUIMessagesAssetLoader jpegInstance] thread:thread];
}
+ (DebugUIMessagesAction *)sendGifAction:(TSThread *)thread
{
OWSAssert(thread);
return [self sendMediaAction:@"Send Gif" fakeAssetLoader:[DebugUIMessagesAssetLoader gifInstance] thread:thread];
}
+ (DebugUIMessagesAction *)sendMp3Action:(TSThread *)thread
{
OWSAssert(thread);
return [self sendMediaAction:@"Send Mp3" fakeAssetLoader:[DebugUIMessagesAssetLoader mp3Instance] thread:thread];
}
+ (DebugUIMessagesAction *)sendMp4Action:(TSThread *)thread
{
OWSAssert(thread);
return [self sendMediaAction:@"Send Mp4" fakeAssetLoader:[DebugUIMessagesAssetLoader mp4Instance] thread:thread];
}
+ (DebugUIMessagesAction *)sendMediaAction:(NSString *)label
fakeAssetLoader:(DebugUIMessagesAssetLoader *)fakeAssetLoader
thread:(TSThread *)thread
{
OWSAssert(label.length > 0);
OWSAssert(fakeAssetLoader);
OWSAssert(thread);
return [DebugUIMessagesSingleAction
actionWithLabel:label
staggeredActionBlock:^(NSUInteger index,
YapDatabaseReadWriteTransaction *transaction,
ActionSuccessBlock success,
ActionFailureBlock failure) {
dispatch_async(dispatch_get_main_queue(), ^{
OWSAssert(fakeAssetLoader.filePath.length > 0);
[self sendAttachment:fakeAssetLoader.filePath thread:thread success:success failure:failure];
});
}
prepareBlock:fakeAssetLoader.prepareBlock];
}
+ (DebugUIMessagesAction *)sendAllMediaAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction allGroupActionWithLabel:@"Send All Media"
subactions:[self allSendMediaActions:thread]];
}
+ (DebugUIMessagesAction *)sendRandomMediaAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction randomGroupActionWithLabel:@"Send Random Media"
subactions:[self allSendMediaActions:thread]];
}
#pragma mark - Fake Outgoing Media
+ (DebugUIMessagesAction *)fakeOutgoingJpegAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Jpeg"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader jpegInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingGifAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Gif"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader gifInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingMp3Action:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Mp3"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader mp3Instance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingMp4Action:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Mp4"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader mp4Instance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingCompactPortraitPngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Portrait Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader compactLandscapePngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingCompactLandscapePngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Landscape Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader compactPortraitPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingTallPortraitPngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Tall Portrait Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader tallPortraitPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingWideLandscapePngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Wide Landscape Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader wideLandscapePngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingLargePngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Large Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader largePngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingTinyPngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Tiny Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader tinyPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingTinyPdfAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Tiny Pdf"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader tinyPdfInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingLargePdfAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Large Pdf"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader largePdfInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingMissingPngAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Missing Png"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader missingPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingMissingPdfAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Missing Pdf"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader missingPdfInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingOversizeTextAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeOutgoingMediaAction:@"Fake Outgoing Oversize Text"
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader oversizeTextInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeOutgoingMediaAction:(NSString *)labelParam
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
fakeAssetLoader:(DebugUIMessagesAssetLoader *)fakeAssetLoader
thread:(TSThread *)thread
{
OWSAssert(labelParam.length > 0);
OWSAssert(fakeAssetLoader);
OWSAssert(thread);
NSString *label = labelParam;
if (hasCaption) {
label = [label stringByAppendingString:@" 🔤"];
}
if (messageState == TSOutgoingMessageStateUnsent) {
label = [label stringByAppendingString:@" (Unsent)"];
} else if (messageState == TSOutgoingMessageStateAttemptingOut) {
label = [label stringByAppendingString:@" (Sending)"];
} else if (messageState == TSOutgoingMessageStateSentToService) {
label = [label stringByAppendingString:@" (Sent)"];
} else {
OWSFail(@"%@ unknown message state.", self.logTag)
}
return
[DebugUIMessagesSingleAction actionWithLabel:label
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
OWSAssert(fakeAssetLoader.filePath.length > 0);
[self createFakeOutgoingMedia:index
messageState:messageState
hasCaption:hasCaption
fakeAssetLoader:fakeAssetLoader
thread:thread
transaction:transaction];
}
prepareBlock:fakeAssetLoader.prepareBlock];
}
+ (void)createFakeOutgoingMedia:(NSUInteger)index
messageState:(TSOutgoingMessageState)messageState
hasCaption:(BOOL)hasCaption
fakeAssetLoader:(DebugUIMessagesAssetLoader *)fakeAssetLoader
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(thread);
OWSAssert(fakeAssetLoader.filePath);
OWSAssert(transaction);
// Random time within last n years. Helpful for filling out a media gallery over time.
// double yearsMillis = 4.0 * kYearsInMs;
// uint64_t millisAgo = (uint64_t)(((double)arc4random() / ((double)0xffffffff)) * yearsMillis);
// uint64_t timestamp = [NSDate ows_millisecondTimeStamp] - millisAgo;
uint64_t timestamp = [NSDate ows_millisecondTimeStamp];
NSString *messageBody = nil;
if (hasCaption) {
messageBody = [[@(index).stringValue stringByAppendingString:@" "] stringByAppendingString:[self randomText]];
if (hasCaption) {
messageBody = [messageBody stringByAppendingString:@" 🔤"];
}
if (messageState == TSOutgoingMessageStateUnsent) {
messageBody = [messageBody stringByAppendingString:@" (Unsent)"];
} else if (messageState == TSOutgoingMessageStateAttemptingOut) {
messageBody = [messageBody stringByAppendingString:@" (Sending)"];
} else if (messageState == TSOutgoingMessageStateSentToService) {
messageBody = [messageBody stringByAppendingString:@" (Sent)"];
} else {
OWSFail(@"%@ unknown message state.", self.logTag)
}
}
NSString *_Nullable randomCaption;
if (arc4random() % 2 == 2) {
randomCaption = [self randomText];
}
TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:timestamp
inThread:thread
messageBody:randomCaption
isVoiceMessage:NO
expiresInSeconds:0];
[message setReceivedAtTimestamp:timestamp];
DataSource *dataSource = [DataSourcePath dataSourceWithFilePath:fakeAssetLoader.filePath];
NSString *filename = dataSource.sourceFilename;
// To support "fake missing" attachments, we sometimes lie about the
// length of the data.
UInt32 nominalDataLength = (UInt32)MAX((NSUInteger)1, dataSource.dataLength);
TSAttachmentStream *attachmentStream = [[TSAttachmentStream alloc] initWithContentType:fakeAssetLoader.mimeType
byteCount:nominalDataLength
sourceFilename:filename];
NSError *error;
BOOL success = [attachmentStream writeData:dataSource.data error:&error];
OWSAssert(success && !error);
[attachmentStream saveWithTransaction:transaction];
[message.attachmentIds addObject:attachmentStream.uniqueId];
if (filename) {
message.attachmentFilenameMap[attachmentStream.uniqueId] = filename;
}
[message saveWithTransaction:transaction];
[message updateWithMessageState:messageState transaction:transaction];
}
#pragma mark - Fake Incoming Media
+ (DebugUIMessagesAction *)fakeIncomingJpegAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Jpeg"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader jpegInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingGifAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Gif"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader gifInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingMp3Action:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Mp3"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader mp3Instance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingMp4Action:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Mp4"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader mp4Instance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingCompactPortraitPngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Portrait Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader compactPortraitPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingCompactLandscapePngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Landscape Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader compactLandscapePngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingTallPortraitPngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Tall Portrait Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader tallPortraitPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingWideLandscapePngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Wide Landscape Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader wideLandscapePngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingLargePngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Large Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader largePngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingTinyPngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Tiny Incoming Large Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader tinyPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingTinyPdfAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Tiny Pdf"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader tinyPdfInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingLargePdfAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Large Pdf"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader largePdfInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingMissingPngAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Missing Png"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader missingPngInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingMissingPdfAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Missing Pdf"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader missingPdfInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingOversizeTextAction:(TSThread *)thread
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
{
OWSAssert(thread);
return [self fakeIncomingMediaAction:@"Fake Incoming Oversize Text"
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:[DebugUIMessagesAssetLoader oversizeTextInstance]
thread:thread];
}
+ (DebugUIMessagesAction *)fakeIncomingMediaAction:(NSString *)labelParam
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
fakeAssetLoader:(DebugUIMessagesAssetLoader *)fakeAssetLoader
thread:(TSThread *)thread
{
OWSAssert(labelParam.length > 0);
OWSAssert(fakeAssetLoader);
OWSAssert(thread);
NSString *label = labelParam;
if (hasCaption) {
label = [label stringByAppendingString:@" 🔤"];
}
if (isAttachmentDownloaded) {
label = [label stringByAppendingString:@" 👍"];
}
return
[DebugUIMessagesSingleAction actionWithLabel:label
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
OWSAssert(fakeAssetLoader.filePath.length > 0);
[self createFakeIncomingMedia:index
isAttachmentDownloaded:isAttachmentDownloaded
hasCaption:hasCaption
fakeAssetLoader:fakeAssetLoader
thread:thread
transaction:transaction];
}
prepareBlock:fakeAssetLoader.prepareBlock];
}
+ (void)createFakeIncomingMedia:(NSUInteger)index
isAttachmentDownloaded:(BOOL)isAttachmentDownloaded
hasCaption:(BOOL)hasCaption
fakeAssetLoader:(DebugUIMessagesAssetLoader *)fakeAssetLoader
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(thread);
OWSAssert(fakeAssetLoader.filePath);
OWSAssert(transaction);
// // Random time within last n years. Helpful for filling out a media gallery over time.
// double yearsMillis = 4.0 * kYearsInMs;
// uint64_t millisAgo = (uint64_t)(((double)arc4random() / ((double)0xffffffff)) * yearsMillis);
// uint64_t timestamp = [NSDate ows_millisecondTimeStamp] - millisAgo;
NSString *messageBody = nil;
if (hasCaption) {
messageBody = [[@(index).stringValue stringByAppendingString:@" "] stringByAppendingString:[self randomText]];
if (hasCaption) {
messageBody = [messageBody stringByAppendingString:@" 🔤"];
}
if (isAttachmentDownloaded) {
messageBody = [messageBody stringByAppendingString:@" 👍"];
}
}
NSString *attachmentId;
if (isAttachmentDownloaded) {
DataSource *dataSource = [DataSourcePath dataSourceWithFilePath:fakeAssetLoader.filePath];
NSString *filename = dataSource.sourceFilename;
// To support "fake missing" attachments, we sometimes lie about the
// length of the data.
UInt32 nominalDataLength = (UInt32)MAX((NSUInteger)1, dataSource.dataLength);
TSAttachmentStream *attachmentStream = [[TSAttachmentStream alloc] initWithContentType:fakeAssetLoader.mimeType
byteCount:nominalDataLength
sourceFilename:filename];
NSError *error;
BOOL success = [attachmentStream writeData:dataSource.data error:&error];
OWSAssert(success && !error);
[attachmentStream saveWithTransaction:transaction];
attachmentId = attachmentStream.uniqueId;
} else {
UInt32 filesize = 64;
TSAttachmentPointer *pointer =
[[TSAttachmentPointer alloc] initWithServerId:237391539706350548
key:[self createRandomNSDataOfSize:filesize]
digest:nil
byteCount:filesize
contentType:fakeAssetLoader.mimeType
relay:@""
sourceFilename:fakeAssetLoader.filename
attachmentType:TSAttachmentTypeDefault];
pointer.state = TSAttachmentPointerStateFailed;
[pointer saveWithTransaction:transaction];
attachmentId = pointer.uniqueId;
}
TSIncomingMessage *message = [[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
authorId:@"+19174054215"
sourceDeviceId:0
messageBody:messageBody
attachmentIds:@[
attachmentId,
]
expiresInSeconds:0];
[message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO];
}
#pragma mark - Fake Media
+ (NSArray<DebugUIMessagesAction *> *)allFakeMediaActions:(TSThread *)thread
{
OWSAssert(thread);
NSArray<DebugUIMessagesAction *> *actions = @[
// Outgoing
[self fakeOutgoingJpegAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:NO],
[self fakeOutgoingJpegAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:YES],
[self fakeOutgoingJpegAction:thread messageState:TSOutgoingMessageStateAttemptingOut hasCaption:NO],
[self fakeOutgoingJpegAction:thread messageState:TSOutgoingMessageStateAttemptingOut hasCaption:YES],
[self fakeOutgoingJpegAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:NO],
[self fakeOutgoingJpegAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:YES],
// Don't bother with multiple GIF states.
[self fakeOutgoingGifAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:NO],
//
[self fakeOutgoingMp3Action:thread messageState:TSOutgoingMessageStateAttemptingOut hasCaption:YES],
[self fakeOutgoingMp3Action:thread messageState:TSOutgoingMessageStateAttemptingOut hasCaption:NO],
[self fakeOutgoingMp3Action:thread messageState:TSOutgoingMessageStateUnsent hasCaption:NO],
[self fakeOutgoingMp3Action:thread messageState:TSOutgoingMessageStateUnsent hasCaption:YES],
[self fakeOutgoingMp3Action:thread messageState:TSOutgoingMessageStateSentToService hasCaption:NO],
[self fakeOutgoingMp3Action:thread messageState:TSOutgoingMessageStateSentToService hasCaption:YES],
//
[self fakeOutgoingMp4Action:thread messageState:TSOutgoingMessageStateAttemptingOut hasCaption:NO],
[self fakeOutgoingMp4Action:thread messageState:TSOutgoingMessageStateAttemptingOut hasCaption:YES],
[self fakeOutgoingMp4Action:thread messageState:TSOutgoingMessageStateUnsent hasCaption:NO],
[self fakeOutgoingMp4Action:thread messageState:TSOutgoingMessageStateUnsent hasCaption:YES],
[self fakeOutgoingMp4Action:thread messageState:TSOutgoingMessageStateSentToService hasCaption:NO],
[self fakeOutgoingMp4Action:thread messageState:TSOutgoingMessageStateSentToService hasCaption:YES],
//
[self fakeOutgoingCompactLandscapePngAction:thread
messageState:TSOutgoingMessageStateAttemptingOut
hasCaption:NO],
[self fakeOutgoingCompactLandscapePngAction:thread
messageState:TSOutgoingMessageStateAttemptingOut
hasCaption:YES],
[self fakeOutgoingCompactLandscapePngAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:NO],
[self fakeOutgoingCompactLandscapePngAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:YES],
[self fakeOutgoingCompactLandscapePngAction:thread
messageState:TSOutgoingMessageStateSentToService
hasCaption:NO],
[self fakeOutgoingCompactLandscapePngAction:thread
messageState:TSOutgoingMessageStateSentToService
hasCaption:YES],
//
[self fakeOutgoingCompactPortraitPngAction:thread
messageState:TSOutgoingMessageStateAttemptingOut
hasCaption:NO],
[self fakeOutgoingCompactPortraitPngAction:thread
messageState:TSOutgoingMessageStateAttemptingOut
hasCaption:YES],
[self fakeOutgoingCompactPortraitPngAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:NO],
[self fakeOutgoingCompactPortraitPngAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:YES],
[self fakeOutgoingCompactPortraitPngAction:thread
messageState:TSOutgoingMessageStateSentToService
hasCaption:NO],
[self fakeOutgoingCompactPortraitPngAction:thread
messageState:TSOutgoingMessageStateSentToService
hasCaption:YES],
//
[self fakeOutgoingWideLandscapePngAction:thread messageState:TSOutgoingMessageStateAttemptingOut hasCaption:NO],
[self fakeOutgoingWideLandscapePngAction:thread
messageState:TSOutgoingMessageStateAttemptingOut
hasCaption:YES],
[self fakeOutgoingWideLandscapePngAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:NO],
[self fakeOutgoingWideLandscapePngAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:YES],
[self fakeOutgoingWideLandscapePngAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:NO],
[self fakeOutgoingWideLandscapePngAction:thread
messageState:TSOutgoingMessageStateSentToService
hasCaption:YES],
//
[self fakeOutgoingTallPortraitPngAction:thread messageState:TSOutgoingMessageStateAttemptingOut hasCaption:NO],
[self fakeOutgoingTallPortraitPngAction:thread messageState:TSOutgoingMessageStateAttemptingOut hasCaption:YES],
[self fakeOutgoingTallPortraitPngAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:NO],
[self fakeOutgoingTallPortraitPngAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:YES],
[self fakeOutgoingTallPortraitPngAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:NO],
[self fakeOutgoingTallPortraitPngAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:YES],
//
[self fakeOutgoingLargePngAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:NO],
[self fakeOutgoingLargePngAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:YES],
//
[self fakeOutgoingTinyPngAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:NO],
[self fakeOutgoingTinyPngAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:YES],
//
[self fakeOutgoingTinyPdfAction:thread messageState:TSOutgoingMessageStateAttemptingOut hasCaption:NO],
[self fakeOutgoingTinyPdfAction:thread messageState:TSOutgoingMessageStateAttemptingOut hasCaption:YES],
[self fakeOutgoingTinyPdfAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:NO],
[self fakeOutgoingTinyPdfAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:YES],
[self fakeOutgoingTinyPdfAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:NO],
[self fakeOutgoingTinyPdfAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:YES],
//
[self fakeOutgoingLargePdfAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:NO],
//
[self fakeOutgoingMissingPngAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:NO],
//
[self fakeOutgoingMissingPdfAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:NO],
//
[self fakeOutgoingOversizeTextAction:thread messageState:TSOutgoingMessageStateUnsent hasCaption:NO],
[self fakeOutgoingOversizeTextAction:thread messageState:TSOutgoingMessageStateAttemptingOut hasCaption:NO],
[self fakeOutgoingOversizeTextAction:thread messageState:TSOutgoingMessageStateSentToService hasCaption:NO],
// Incoming
[self fakeIncomingJpegAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingJpegAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingJpegAction:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingJpegAction:thread isAttachmentDownloaded:YES hasCaption:YES],
// Don't bother with multiple GIF states.
[self fakeIncomingGifAction:thread isAttachmentDownloaded:YES hasCaption:NO],
//
[self fakeIncomingMp3Action:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingMp3Action:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingMp3Action:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingMp3Action:thread isAttachmentDownloaded:YES hasCaption:YES],
//
[self fakeIncomingMp4Action:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingMp4Action:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingMp4Action:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingMp4Action:thread isAttachmentDownloaded:YES hasCaption:YES],
//
[self fakeIncomingCompactLandscapePngAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingCompactLandscapePngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingCompactLandscapePngAction:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingCompactLandscapePngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
//
[self fakeIncomingCompactPortraitPngAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingCompactPortraitPngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingCompactPortraitPngAction:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingCompactPortraitPngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
//
[self fakeIncomingWideLandscapePngAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingWideLandscapePngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingWideLandscapePngAction:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingWideLandscapePngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
//
[self fakeIncomingTallPortraitPngAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingTallPortraitPngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingTallPortraitPngAction:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingTallPortraitPngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
//
[self fakeIncomingLargePngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingLargePngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
//
[self fakeIncomingTinyPngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingTinyPngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
//
[self fakeIncomingTinyPdfAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingTinyPdfAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingTinyPdfAction:thread isAttachmentDownloaded:NO hasCaption:YES],
[self fakeIncomingTinyPdfAction:thread isAttachmentDownloaded:YES hasCaption:YES],
//
[self fakeIncomingLargePdfAction:thread isAttachmentDownloaded:YES hasCaption:NO],
//
[self fakeIncomingMissingPngAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingMissingPngAction:thread isAttachmentDownloaded:YES hasCaption:YES],
//
[self fakeIncomingMissingPdfAction:thread isAttachmentDownloaded:YES hasCaption:NO],
[self fakeIncomingMissingPdfAction:thread isAttachmentDownloaded:YES hasCaption:YES],
//
[self fakeIncomingOversizeTextAction:thread isAttachmentDownloaded:NO hasCaption:NO],
[self fakeIncomingOversizeTextAction:thread isAttachmentDownloaded:YES hasCaption:NO],
];
return actions;
}
+ (DebugUIMessagesAction *)fakeAllMediaAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction allGroupActionWithLabel:@"Fake All Media"
subactions:[self allFakeMediaActions:thread]];
}
+ (DebugUIMessagesAction *)fakeRandomMediaAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction randomGroupActionWithLabel:@"Fake Random Media"
subactions:[self allFakeMediaActions:thread]];
}
#pragma mark - Send Text Messages
+ (DebugUIMessagesAction *)sendShortTextMessageAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction actionWithLabel:@"Send Short Text Message"
staggeredActionBlock:^(NSUInteger index,
YapDatabaseReadWriteTransaction *transaction,
ActionSuccessBlock success,
ActionFailureBlock failure) {
dispatch_async(dispatch_get_main_queue(), ^{
[self sendTextMessageInThread:thread counter:index];
});
}];
}
+ (DebugUIMessagesAction *)sendOversizeTextMessageAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction actionWithLabel:@"Send Oversize Text Message"
staggeredActionBlock:^(NSUInteger index,
YapDatabaseReadWriteTransaction *transaction,
ActionSuccessBlock success,
ActionFailureBlock failure) {
dispatch_async(dispatch_get_main_queue(), ^{
[self sendOversizeTextMessage:thread];
});
}];
}
+ (DebugUIMessagesAction *)sendMessageVariationsAction:(TSThread *)thread
{
OWSAssert(thread);
NSArray<DebugUIMessagesAction *> *actions = @[
[self sendShortTextMessageAction:thread],
[self sendOversizeTextMessageAction:thread],
];
return [DebugUIMessagesGroupAction allGroupActionWithLabel:@"Send Conversation Cell Variations" subactions:actions];
}
#pragma mark - Fake Text Messages
+ (DebugUIMessagesAction *)fakeShortIncomingTextMessageAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction
actionWithLabel:@"Fake Short Incoming Text Message"
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
NSString *messageBody =
[[@(index).stringValue stringByAppendingString:@" "] stringByAppendingString:[self randomText]];
TSIncomingMessage *message = [[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
authorId:@"+19174054215"
sourceDeviceId:0
messageBody:messageBody];
[message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO];
}];
}
+ (DebugUIMessagesAction *)fakeIncomingTextMessageAction:(TSThread *)thread text:(NSString *)text
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction
actionWithLabel:[NSString stringWithFormat:@"Fake Incoming Text Message (%@)", text]
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
TSIncomingMessage *message = [[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
authorId:@"+19174054215"
sourceDeviceId:0
messageBody:text];
[message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO];
}];
}
+ (DebugUIMessagesAction *)fakeOutgoingTextMessageAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
text:(NSString *)text
{
OWSAssert(thread);
return [DebugUIMessagesSingleAction
actionWithLabel:[NSString stringWithFormat:@"Fake Incoming Text Message (%@)", text]
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:text];
[message saveWithTransaction:transaction];
[message updateWithMessageState:messageState transaction:transaction];
}];
}
+ (DebugUIMessagesAction *)fakeShortOutgoingTextMessageAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
{
return [self fakeShortOutgoingTextMessageAction:thread messageState:messageState isDelivered:NO isRead:NO];
}
+ (DebugUIMessagesAction *)fakeShortOutgoingTextMessageAction:(TSThread *)thread
messageState:(TSOutgoingMessageState)messageState
isDelivered:(BOOL)isDelivered
isRead:(BOOL)isRead
{
OWSAssert(thread);
NSString *label = @"Fake Short Incoming Text Message";
if (messageState == TSOutgoingMessageStateUnsent) {
label = [label stringByAppendingString:@" (Unsent)"];
} else if (messageState == TSOutgoingMessageStateAttemptingOut) {
label = [label stringByAppendingString:@" (Sending)"];
} else if (messageState == TSOutgoingMessageStateSentToService) {
if (isRead) {
label = [label stringByAppendingString:@" (Read)"];
} else if (isDelivered) {
label = [label stringByAppendingString:@" (Delivered)"];
} else {
label = [label stringByAppendingString:@" (Sent)"];
}
} else {
OWSFail(@"%@ unknown message state.", self.logTag)
}
return [DebugUIMessagesSingleAction
actionWithLabel:label
unstaggeredActionBlock:^(NSUInteger index, YapDatabaseReadWriteTransaction *transaction) {
NSString *messageBody =
[[@(index).stringValue stringByAppendingString:@" "] stringByAppendingString:[self randomText]];
TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:messageBody];
[message saveWithTransaction:transaction];
[message updateWithMessageState:messageState transaction:transaction];
if (isDelivered) {
NSString *_Nullable recipientId = thread.recipientIdentifiers.lastObject;
OWSAssert(recipientId.length > 0);
[message updateWithDeliveredToRecipientId:recipientId
deliveryTimestamp:@([NSDate ows_millisecondTimeStamp])
transaction:transaction];
}
if (isRead) {
NSString *_Nullable recipientId = thread.recipientIdentifiers.lastObject;
OWSAssert(recipientId.length > 0);
[message updateWithReadRecipientId:recipientId
readTimestamp:[NSDate ows_millisecondTimeStamp]
transaction:transaction];
}
}];
}
+ (NSArray<DebugUIMessagesAction *> *)allFakeTextActions:(TSThread *)thread
{
OWSAssert(thread);
NSArray<DebugUIMessagesAction *> *actions = @[
[self fakeShortIncomingTextMessageAction:thread],
[self fakeIncomingTextMessageAction:thread text:@"Hi"],
[self fakeIncomingTextMessageAction:thread text:@"1"],
[self fakeIncomingTextMessageAction:thread text:@"1⃣2"],
[self fakeIncomingTextMessageAction:thread text:@"1⃣2⃣3"],
[self fakeIncomingTextMessageAction:thread text:@""],
[self fakeIncomingTextMessageAction:thread text:@""],
[self fakeShortOutgoingTextMessageAction:thread messageState:TSOutgoingMessageStateUnsent],
[self fakeShortOutgoingTextMessageAction:thread messageState:TSOutgoingMessageStateAttemptingOut],
[self fakeShortOutgoingTextMessageAction:thread messageState:TSOutgoingMessageStateSentToService],
[self fakeShortOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSentToService
isDelivered:YES
isRead:NO],
[self fakeShortOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSentToService
isDelivered:YES
isRead:YES],
[self fakeOutgoingTextMessageAction:thread messageState:TSOutgoingMessageStateSentToService text:@"Hi"],
[self fakeOutgoingTextMessageAction:thread messageState:TSOutgoingMessageStateSentToService text:@"1"],
[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSentToService
text:@"1⃣2"],
[self fakeOutgoingTextMessageAction:thread
messageState:TSOutgoingMessageStateSentToService
text:@"1⃣2⃣3"],
[self fakeOutgoingTextMessageAction:thread messageState:TSOutgoingMessageStateSentToService text:@"1"],
[self fakeOutgoingTextMessageAction:thread messageState:TSOutgoingMessageStateSentToService text:@""],
[self fakeOutgoingTextMessageAction:thread messageState:TSOutgoingMessageStateSentToService text:@""],
];
return actions;
}
+ (DebugUIMessagesAction *)fakeAllTextAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction allGroupActionWithLabel:@"Fake All Text"
subactions:[self allFakeTextActions:thread]];
}
+ (DebugUIMessagesAction *)fakeRandomTextAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction randomGroupActionWithLabel:@"Fake Random Text"
subactions:[self allFakeTextActions:thread]];
}
#pragma mark - Exemplary
+ (NSArray<DebugUIMessagesAction *> *)allExemplaryActions:(TSThread *)thread
{
OWSAssert(thread);
NSMutableArray<DebugUIMessagesAction *> *actions = [NSMutableArray new];
[actions addObjectsFromArray:[self allFakeMediaActions:thread]];
[actions addObjectsFromArray:[self allFakeTextActions:thread]];
return actions;
}
+ (DebugUIMessagesAction *)allExemplaryAction:(TSThread *)thread
{
OWSAssert(thread);
return [DebugUIMessagesGroupAction allGroupActionWithLabel:@"Exemplary Permutations"
subactions:[self allExemplaryActions:thread]];
}
+ (void)selectExemplaryAction:(TSThread *)thread
{
OWSAssertIsOnMainThread() OWSAssert(thread);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Select Action"
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
for (DebugUIMessagesAction *action in [self allExemplaryActions:thread]) {
[alert addAction:[UIAlertAction actionWithTitle:action.label
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *ignore) {
[self performActionNTimes:action];
}]];
}
[alert addAction:[OWSAlerts cancelAction]];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentViewController:alert animated:YES completion:nil];
}
#pragma mark -
+ (void)sendOversizeTextMessage:(TSThread *)thread
{
OWSMessageSender *messageSender = [Environment current].messageSender;
NSMutableString *message = [NSMutableString new];
for (NSUInteger i = 0; i < 32; i++) {
[message appendString:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rutrum, nulla "
@"vitae pretium hendrerit, tellus turpis pharetra libero, vitae sodales tortor ante vel "
@"sem. Fusce sed nisl a lorem gravida tincidunt. Suspendisse efficitur non quam ac "
@"sodales. Aenean ut velit maximus, posuere sem a, accumsan nunc. Donec ullamcorper "
@"turpis lorem. Quisque dignissim purus eu placerat ultricies. Proin at urna eget mi "
@"semper congue. Aenean non elementum ex. Praesent pharetra quam at sem vestibulum, "
@"vestibulum ornare dolor elementum. Vestibulum massa tortor, scelerisque sit amet "
@"pulvinar a, rhoncus vitae nisl. Sed mi nunc, tempus at varius in, malesuada vitae "
@"dui. Vivamus efficitur pulvinar erat vitae congue. Proin vehicula turpis non felis "
@"congue facilisis. Nullam aliquet dapibus ligula ac mollis. Etiam sit amet posuere "
@"lorem, in rhoncus nisi.\n\n"];
}
DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:message];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI];
[ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender completion:nil];
}
+ (NSData *)createRandomNSDataOfSize:(size_t)size
{
OWSAssert(size % 4 == 0);
return [Randomness generateRandomBytes:(int)size];
}
+ (void)sendRandomAttachment:(TSThread *)thread uti:(NSString *)uti
{
[self sendRandomAttachment:thread uti:uti length:256];
}
+ (NSString *)randomCaptionText
{
return [NSString stringWithFormat:@"%@ (caption)", [self randomText]];
}
+ (void)sendRandomAttachment:(TSThread *)thread uti:(NSString *)uti length:(NSUInteger)length
{
OWSMessageSender *messageSender = [Environment current].messageSender;
DataSource *_Nullable dataSource =
[DataSourceValue dataSourceWithData:[self createRandomNSDataOfSize:length] utiType:uti];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:uti imageQuality:TSImageQualityOriginal];
if (arc4random_uniform(100) > 50) {
// give 1/2 our attachments captions, and add a hint that it's a caption since we
// style them indistinguishably from a separate text message.
attachment.captionText = [self randomCaptionText];
}
[ThreadUtil sendMessageWithAttachment:attachment
inThread:thread
messageSender:messageSender
ignoreErrors:YES
completion:nil];
}
+ (OWSSignalServiceProtosEnvelope *)createEnvelopeForThread:(TSThread *)thread
{
OWSAssert(thread);
OWSSignalServiceProtosEnvelopeBuilder *builder = [OWSSignalServiceProtosEnvelopeBuilder new];
[builder setTimestamp:[NSDate ows_millisecondTimeStamp]];
if ([thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *gThread = (TSGroupThread *)thread;
[builder setSource:gThread.groupModel.groupMemberIds[0]];
} else if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;
[builder setSource:contactThread.contactIdentifier];
}
return [builder build];
}
+ (NSArray<TSInteraction *> *)unsavedSystemMessagesInThread:(TSThread *)thread
{
OWSAssert(thread);
NSMutableArray<TSInteraction *> *result = [NSMutableArray new];
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeIncoming
inThread:contactThread]];
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeOutgoing
inThread:contactThread]];
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeMissed
inThread:contactThread]];
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeMissedBecauseOfChangedIdentity
inThread:contactThread]];
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeOutgoingIncomplete
inThread:contactThread]];
[result addObject:[[TSCall alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
withCallNumber:@"+19174054215"
callType:RPRecentCallTypeIncomingIncomplete
inThread:contactThread]];
}
{
NSNumber *durationSeconds = [OWSDisappearingMessagesConfiguration validDurationsSeconds][0];
OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration =
[[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId
enabled:YES
durationSeconds:(uint32_t)[durationSeconds intValue]];
[result addObject:[[OWSDisappearingConfigurationUpdateInfoMessage alloc]
initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
configuration:disappearingMessagesConfiguration
createdByRemoteName:@"Alice"]];
}
{
NSNumber *durationSeconds = [[OWSDisappearingMessagesConfiguration validDurationsSeconds] lastObject];
OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration =
[[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId
enabled:YES
durationSeconds:(uint32_t)[durationSeconds intValue]];
[result addObject:[[OWSDisappearingConfigurationUpdateInfoMessage alloc]
initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
configuration:disappearingMessagesConfiguration
createdByRemoteName:@"Alice"]];
}
{
OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration =
[[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId
enabled:NO
durationSeconds:0];
[result addObject:[[OWSDisappearingConfigurationUpdateInfoMessage alloc]
initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
configuration:disappearingMessagesConfiguration
createdByRemoteName:@"Alice"]];
}
[result addObject:[TSInfoMessage userNotRegisteredMessageInThread:thread]];
[result addObject:[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageType:TSInfoMessageTypeSessionDidEnd]];
// TODO: customMessage?
[result addObject:[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageType:TSInfoMessageTypeGroupUpdate]];
// TODO: customMessage?
[result addObject:[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageType:TSInfoMessageTypeGroupQuit]];
[result addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
recipientId:@"+19174054215"
verificationState:OWSVerificationStateDefault
isLocalChange:YES]];
[result addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
recipientId:@"+19174054215"
verificationState:OWSVerificationStateVerified
isLocalChange:YES]];
[result
addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
recipientId:@"+19174054215"
verificationState:OWSVerificationStateNoLongerVerified
isLocalChange:YES]];
[result addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
recipientId:@"+19174054215"
verificationState:OWSVerificationStateDefault
isLocalChange:NO]];
[result addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
recipientId:@"+19174054215"
verificationState:OWSVerificationStateVerified
isLocalChange:NO]];
[result
addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:thread
recipientId:@"+19174054215"
verificationState:OWSVerificationStateNoLongerVerified
isLocalChange:NO]];
[result addObject:[TSErrorMessage missingSessionWithEnvelope:[self createEnvelopeForThread:thread]
withTransaction:transaction]];
[result addObject:[TSErrorMessage invalidKeyExceptionWithEnvelope:[self createEnvelopeForThread:thread]
withTransaction:transaction]];
[result addObject:[TSErrorMessage invalidVersionWithEnvelope:[self createEnvelopeForThread:thread]
withTransaction:transaction]];
[result addObject:[TSInvalidIdentityKeyReceivingErrorMessage
untrustedKeyWithEnvelope:[self createEnvelopeForThread:thread]
withTransaction:transaction]];
[result addObject:[TSErrorMessage corruptedMessageWithEnvelope:[self createEnvelopeForThread:thread]
withTransaction:transaction]];
[result addObject:[[TSErrorMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
failedMessageType:TSErrorMessageNonBlockingIdentityChange
recipientId:@"+19174054215"]];
}];
return result;
}
+ (void)createSystemMessagesInThread:(TSThread *)thread
{
OWSAssert(thread);
NSArray<TSInteraction *> *messages = [self unsavedSystemMessagesInThread:thread];
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (TSInteraction *message in messages) {
[message saveWithTransaction:transaction];
}
}];
}
+ (void)createSystemMessageInThread:(TSThread *)thread
{
OWSAssert(thread);
NSArray<TSInteraction *> *messages = [self unsavedSystemMessagesInThread:thread];
TSInteraction *message = messages[(NSUInteger)arc4random_uniform((uint32_t)messages.count)];
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[message saveWithTransaction:transaction];
}];
}
+ (void)sendTextAndSystemMessages:(NSUInteger)counter thread:(TSThread *)thread
{
if (counter < 1) {
return;
}
if (arc4random_uniform(2) == 0) {
[self sendTextMessageInThread:thread counter:counter];
} else {
[self createSystemMessageInThread:thread];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self sendTextAndSystemMessages:counter - 1 thread:thread];
});
}
+ (NSString *)randomText
{
NSArray<NSString *> *randomTexts = @[
@"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."
];
NSString *randomText = randomTexts[(NSUInteger)arc4random_uniform((uint32_t)randomTexts.count)];
return randomText;
}
+ (void)createFakeUnreadMessages:(NSUInteger)counter thread:(TSThread *)thread
{
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSUInteger i = 0; i < counter; i++) {
NSString *randomText = [self randomText];
TSIncomingMessage *message = [[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
authorId:@"+19174054215"
sourceDeviceId:0
messageBody:randomText];
[message saveWithTransaction:transaction];
}
}];
}
+ (void)createFakeThreads:(NSUInteger)threadCount withFakeMessages:(NSUInteger)messageCount
{
[DebugUIContacts
createRandomContacts:threadCount
contactHandler:^(CNContact *_Nonnull contact, NSUInteger idx, BOOL *_Nonnull stop) {
NSString *phoneNumberText = contact.phoneNumbers.firstObject.value.stringValue;
OWSAssert(phoneNumberText);
PhoneNumber *phoneNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumberText];
OWSAssert(phoneNumber);
OWSAssert(phoneNumber.toE164);
TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:phoneNumber.toE164];
[self sendFakeMessages:messageCount thread:contactThread];
DDLogError(@"Create fake thread: %@, interactions: %zd",
phoneNumber.toE164,
contactThread.numberOfInteractions);
}];
}
+ (void)sendFakeMessages:(NSUInteger)counter thread:(TSThread *)thread
{
[self sendFakeMessages:counter thread:thread isTextOnly:NO];
}
+ (void)sendFakeMessages:(NSUInteger)counter thread:(TSThread *)thread isTextOnly:(BOOL)isTextOnly
{
const NSUInteger kMaxBatchSize = 2500;
if (counter < kMaxBatchSize) {
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self sendFakeMessages:counter thread:thread isTextOnly:isTextOnly transaction:transaction];
}];
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSUInteger remainder = counter;
while (remainder > 0) {
NSUInteger batchSize = MIN(kMaxBatchSize, remainder);
[OWSPrimaryStorage.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self sendFakeMessages:batchSize thread:thread isTextOnly:isTextOnly transaction:transaction];
}];
remainder -= batchSize;
DDLogInfo(@"%@ sendFakeMessages %zd / %zd", self.logTag, counter - remainder, counter);
}
});
}
}
+ (void)sendFakeMessages:(NSUInteger)counter
thread:(TSThread *)thread
isTextOnly:(BOOL)isTextOnly
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
DDLogInfo(@"%@ sendFakeMessages: %zd", self.logTag, counter);
for (NSUInteger i = 0; i < counter; i++) {
NSString *randomText = [self randomText];
switch (arc4random_uniform(isTextOnly ? 2 : 4)) {
case 0: {
TSIncomingMessage *message =
[[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
authorId:@"+19174054215"
sourceDeviceId:0
messageBody:randomText];
[message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO];
break;
}
case 1: {
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:randomText];
[message saveWithTransaction:transaction];
[message updateWithMessageState:TSOutgoingMessageStateUnsent transaction:transaction];
break;
}
case 2: {
UInt32 filesize = 64;
TSAttachmentPointer *pointer =
[[TSAttachmentPointer alloc] initWithServerId:237391539706350548
key:[self createRandomNSDataOfSize:filesize]
digest:nil
byteCount:filesize
contentType:@"audio/mp3"
relay:@""
sourceFilename:@"test.mp3"
attachmentType:TSAttachmentTypeDefault];
pointer.state = TSAttachmentPointerStateFailed;
[pointer saveWithTransaction:transaction];
TSIncomingMessage *message =
[[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
authorId:@"+19174054215"
sourceDeviceId:0
messageBody:nil
attachmentIds:@[
pointer.uniqueId,
]
expiresInSeconds:0];
[message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO];
break;
}
case 3: {
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:nil
isVoiceMessage:NO
expiresInSeconds:0];
NSString *filename = @"test.mp3";
UInt32 filesize = 16;
TSAttachmentStream *attachmentStream = [[TSAttachmentStream alloc] initWithContentType:@"audio/mp3"
byteCount:filesize
sourceFilename:filename];
NSError *error;
BOOL success = [attachmentStream writeData:[self createRandomNSDataOfSize:filesize] error:&error];
OWSAssert(success && !error);
[attachmentStream saveWithTransaction:transaction];
[message.attachmentIds addObject:attachmentStream.uniqueId];
if (filename) {
message.attachmentFilenameMap[attachmentStream.uniqueId] = filename;
}
[message saveWithTransaction:transaction];
[message updateWithMessageState:TSOutgoingMessageStateUnsent transaction:transaction];
break;
}
}
}
}
#pragma mark -
+ (void)createFakeLargeOutgoingAttachments:(NSUInteger)counter thread:(TSThread *)thread
{
if (counter < 1) {
return;
}
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:nil
isVoiceMessage:NO
expiresInSeconds:0];
DDLogError(@"%@ sendFakeMessages outgoing attachment timestamp: %llu.", self.logTag, message.timestamp);
NSString *filename = @"test.mp3";
UInt32 filesize = 8 * 1024 * 1024;
TSAttachmentStream *attachmentStream =
[[TSAttachmentStream alloc] initWithContentType:@"audio/mp3" byteCount:filesize sourceFilename:filename];
NSError *error;
BOOL success = [attachmentStream writeData:[self createRandomNSDataOfSize:filesize] error:&error];
OWSAssert(success && !error);
[attachmentStream saveWithTransaction:transaction];
[message.attachmentIds addObject:attachmentStream.uniqueId];
if (filename) {
message.attachmentFilenameMap[attachmentStream.uniqueId] = filename;
}
[message updateWithMessageState:TSOutgoingMessageStateUnsent transaction:transaction];
[message saveWithTransaction:transaction];
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self createFakeLargeOutgoingAttachments:counter - 1 thread:thread];
});
}
+ (void)sendTinyAttachments:(NSUInteger)counter thread:(TSThread *)thread
{
if (counter < 1) {
return;
}
NSArray<NSString *> *utis = @[
(NSString *)kUTTypePDF,
(NSString *)kUTTypeMP3,
(NSString *)kUTTypeGIF,
(NSString *)kUTTypeMPEG4,
(NSString *)kUTTypeJPEG,
];
NSString *uti = utis[(NSUInteger)arc4random_uniform((uint32_t)utis.count)];
[self sendRandomAttachment:thread uti:uti length:16];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self sendTinyAttachments:counter - 1 thread:thread];
});
}
+ (void)createNewGroups:(NSUInteger)counter recipientId:(NSString *)recipientId
{
if (counter < 1) {
return;
}
NSString *groupName = [NSUUID UUID].UUIDString;
NSMutableArray<NSString *> *recipientIds = [@[
recipientId,
[TSAccountManager localNumber],
] mutableCopy];
NSData *groupId = [SecurityUtils generateRandomBytes:16];
TSGroupModel *groupModel =
[[TSGroupModel alloc] initWithTitle:groupName memberIds:recipientIds image:nil groupId:groupId];
__block TSGroupThread *thread;
[OWSPrimaryStorage.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
thread = [TSGroupThread getOrCreateThreadWithGroupModel:groupModel transaction:transaction];
}];
OWSAssert(thread);
TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
groupMetaMessage:TSGroupMessageNew];
[message updateWithCustomMessage:NSLocalizedString(@"GROUP_CREATED", nil)];
OWSMessageSender *messageSender = [Environment current].messageSender;
void (^completion)(void) = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[ThreadUtil sendMessageWithText:[@(counter) description] inThread:thread messageSender:messageSender];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self createNewGroups:counter - 1 recipientId:recipientId];
});
});
};
[messageSender enqueueMessage:message
success:completion
failure:^(NSError *error) {
completion();
}];
}
+ (void)injectFakeIncomingMessages:(NSUInteger)counter thread:(TSThread *)thread
{
// Wait 5 seconds so debug user has time to navigate to another
// view before message processing occurs.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.f * NSEC_PER_SEC)),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
for (NSUInteger i = 0; i < counter; i++) {
[self injectIncomingMessageInThread:thread counter:counter - i];
}
});
}
+ (void)injectIncomingMessageInThread:(TSThread *)thread counter:(NSUInteger)counter
{
OWSAssert(thread);
DDLogInfo(@"%@ injectIncomingMessageInThread: %zd", self.logTag, counter);
NSString *randomText = [self randomText];
NSString *text = [[[@(counter) description] stringByAppendingString:@" "] stringByAppendingString:randomText];
OWSSignalServiceProtosDataMessageBuilder *dataMessageBuilder = [OWSSignalServiceProtosDataMessageBuilder new];
[dataMessageBuilder setBody:text];
if ([thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *groupThread = (TSGroupThread *)thread;
OWSSignalServiceProtosGroupContextBuilder *groupBuilder = [OWSSignalServiceProtosGroupContextBuilder new];
[groupBuilder setType:OWSSignalServiceProtosGroupContextTypeDeliver];
[groupBuilder setId:groupThread.groupModel.groupId];
[dataMessageBuilder setGroup:groupBuilder.build];
}
OWSSignalServiceProtosContentBuilder *payloadBuilder = [OWSSignalServiceProtosContentBuilder new];
[payloadBuilder setDataMessage:dataMessageBuilder.build];
NSData *plaintextData = [payloadBuilder build].data;
// Try to use an arbitrary member of the current thread that isn't
// ourselves as the sender.
NSString *_Nullable recipientId = [[thread recipientIdentifiers] firstObject];
// This might be an "empty" group with no other members. If so, use a fake
// sender id.
if (!recipientId) {
recipientId = @"+12345678901";
}
OWSSignalServiceProtosEnvelopeBuilder *envelopeBuilder = [OWSSignalServiceProtosEnvelopeBuilder new];
[envelopeBuilder setType:OWSSignalServiceProtosEnvelopeTypeCiphertext];
[envelopeBuilder setSource:recipientId];
[envelopeBuilder setSourceDevice:1];
[envelopeBuilder setTimestamp:[NSDate ows_millisecondTimeStamp]];
[envelopeBuilder setContent:plaintextData];
NSData *envelopeData = [envelopeBuilder build].data;
OWSAssert(envelopeData);
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[[OWSBatchMessageProcessor sharedInstance] enqueueEnvelopeData:envelopeData
plaintextData:plaintextData
transaction:transaction];
}];
}
+ (void)performRandomActions:(NSUInteger)counter thread:(TSThread *)thread
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)),
dispatch_get_main_queue(),
^{
[self performRandomActionInThread:thread counter:counter];
if (counter > 0) {
[self performRandomActions:counter - 1 thread:thread];
}
});
}
+ (void)performRandomActionInThread:(TSThread *)thread counter:(NSUInteger)counter
{
typedef void (^TransactionBlock)(YapDatabaseReadWriteTransaction *transaction);
NSArray<TransactionBlock> *actionBlocks = @[
^(YapDatabaseReadWriteTransaction *transaction) {
// injectIncomingMessageInThread doesn't take a transaction.
dispatch_async(dispatch_get_main_queue(), ^{
[self injectIncomingMessageInThread:thread counter:counter];
});
},
^(YapDatabaseReadWriteTransaction *transaction) {
// sendTextMessageInThread doesn't take a transaction.
dispatch_async(dispatch_get_main_queue(), ^{
[self sendTextMessageInThread:thread counter:counter];
});
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self sendFakeMessages:messageCount thread:thread isTextOnly:NO transaction:transaction];
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self deleteRandomMessages:messageCount thread:thread transaction:transaction];
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self deleteLastMessages:messageCount thread:thread transaction:transaction];
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self deleteRandomRecentMessages:messageCount thread:thread transaction:transaction];
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self insertAndDeleteNewOutgoingMessages:messageCount thread:thread transaction:transaction];
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self resurrectNewOutgoingMessages1:messageCount thread:thread transaction:transaction];
},
^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4));
[self resurrectNewOutgoingMessages2:messageCount thread:thread transaction:transaction];
},
];
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSUInteger actionCount = 1 + (NSUInteger)arc4random_uniform(3);
for (NSUInteger actionIdx = 0; actionIdx < actionCount; actionIdx++) {
TransactionBlock actionBlock = actionBlocks[(NSUInteger)arc4random_uniform((uint32_t)actionBlocks.count)];
actionBlock(transaction);
}
}];
}
+ (void)deleteRandomMessages:(NSUInteger)count
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
DDLogInfo(@"%@ deleteRandomMessages: %zd", self.logTag, count);
YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName];
NSUInteger messageCount = [interactionsByThread numberOfItemsInGroup:thread.uniqueId];
NSMutableArray<NSNumber *> *messageIndices = [NSMutableArray new];
for (NSUInteger messageIdx = 0; messageIdx < messageCount; messageIdx++) {
[messageIndices addObject:@(messageIdx)];
}
NSMutableArray<TSInteraction *> *interactions = [NSMutableArray new];
for (NSUInteger i = 0; i < count && messageIndices.count > 0; i++) {
NSUInteger idx = (NSUInteger)arc4random_uniform((uint32_t)messageIndices.count);
NSNumber *messageIdx = messageIndices[idx];
[messageIndices removeObjectAtIndex:idx];
TSInteraction *_Nullable interaction =
[interactionsByThread objectAtIndex:messageIdx.unsignedIntegerValue inGroup:thread.uniqueId];
OWSAssert(interaction);
[interactions addObject:interaction];
}
for (TSInteraction *interaction in interactions) {
[interaction removeWithTransaction:transaction];
}
}
+ (void)deleteLastMessages:(NSUInteger)count
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
DDLogInfo(@"%@ deleteLastMessages", self.logTag);
YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName];
NSUInteger messageCount = (NSUInteger)[interactionsByThread numberOfItemsInGroup:thread.uniqueId];
NSMutableArray<NSNumber *> *messageIndices = [NSMutableArray new];
for (NSUInteger i = 0; i < count && i < messageCount; i++) {
NSUInteger messageIdx = messageCount - (1 + i);
[messageIndices addObject:@(messageIdx)];
}
NSMutableArray<TSInteraction *> *interactions = [NSMutableArray new];
for (NSNumber *messageIdx in messageIndices) {
TSInteraction *_Nullable interaction =
[interactionsByThread objectAtIndex:messageIdx.unsignedIntegerValue inGroup:thread.uniqueId];
OWSAssert(interaction);
[interactions addObject:interaction];
}
for (TSInteraction *interaction in interactions) {
[interaction removeWithTransaction:transaction];
}
}
+ (void)deleteRandomRecentMessages:(NSUInteger)count
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
DDLogInfo(@"%@ deleteRandomRecentMessages: %zd", self.logTag, count);
YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName];
NSInteger messageCount = (NSInteger)[interactionsByThread numberOfItemsInGroup:thread.uniqueId];
NSMutableArray<NSNumber *> *messageIndices = [NSMutableArray new];
const NSInteger kRecentMessageCount = 10;
for (NSInteger i = 0; i < kRecentMessageCount; i++) {
NSInteger messageIdx = messageCount - (1 + i);
if (messageIdx >= 0) {
[messageIndices addObject:@(messageIdx)];
}
}
NSMutableArray<TSInteraction *> *interactions = [NSMutableArray new];
for (NSUInteger i = 0; i < count && messageIndices.count > 0; i++) {
NSUInteger idx = (NSUInteger)arc4random_uniform((uint32_t)messageIndices.count);
NSNumber *messageIdx = messageIndices[idx];
[messageIndices removeObjectAtIndex:idx];
TSInteraction *_Nullable interaction =
[interactionsByThread objectAtIndex:messageIdx.unsignedIntegerValue inGroup:thread.uniqueId];
OWSAssert(interaction);
[interactions addObject:interaction];
}
for (TSInteraction *interaction in interactions) {
[interaction removeWithTransaction:transaction];
}
}
+ (void)insertAndDeleteNewOutgoingMessages:(NSUInteger)count
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
DDLogInfo(@"%@ insertAndDeleteNewOutgoingMessages: %zd", self.logTag, count);
NSMutableArray<TSOutgoingMessage *> *messages = [NSMutableArray new];
for (NSUInteger i =0; i < count; i++) {
NSString *text = [self randomText];
OWSDisappearingMessagesConfiguration *configuration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId transaction:transaction];
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:text
attachmentIds:[NSMutableArray new]
expiresInSeconds:(configuration.isEnabled ? configuration.durationSeconds : 0)];
DDLogError(@"%@ insertAndDeleteNewOutgoingMessages timestamp: %llu.", self.logTag, message.timestamp);
[messages addObject:message];
}
for (TSOutgoingMessage *message in messages) {
[message saveWithTransaction:transaction];
}
for (TSOutgoingMessage *message in messages) {
[message removeWithTransaction:transaction];
}
}
+ (void)resurrectNewOutgoingMessages1:(NSUInteger)count
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)initialTransaction
{
DDLogInfo(@"%@ resurrectNewOutgoingMessages1.1: %zd", self.logTag, count);
NSMutableArray<TSOutgoingMessage *> *messages = [NSMutableArray new];
for (NSUInteger i =0; i < count; i++) {
NSString *text = [self randomText];
OWSDisappearingMessagesConfiguration *configuration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId
transaction:initialTransaction];
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:text
attachmentIds:[NSMutableArray new]
expiresInSeconds:(configuration.isEnabled ? configuration.durationSeconds : 0)];
DDLogError(@"%@ resurrectNewOutgoingMessages1 timestamp: %llu.", self.logTag, message.timestamp);
[messages addObject:message];
}
for (TSOutgoingMessage *message in messages) {
[message saveWithTransaction:initialTransaction];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
DDLogInfo(@"%@ resurrectNewOutgoingMessages1.2: %zd", self.logTag, count);
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (TSOutgoingMessage *message in messages) {
[message removeWithTransaction:transaction];
}
for (TSOutgoingMessage *message in messages) {
[message saveWithTransaction:transaction];
}
}];
});
}
+ (void)resurrectNewOutgoingMessages2:(NSUInteger)count
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)initialTransaction
{
DDLogInfo(@"%@ resurrectNewOutgoingMessages2.1: %zd", self.logTag, count);
NSMutableArray<TSOutgoingMessage *> *messages = [NSMutableArray new];
for (NSUInteger i =0; i < count; i++) {
NSString *text = [self randomText];
OWSDisappearingMessagesConfiguration *configuration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId
transaction:initialTransaction];
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:text
attachmentIds:[NSMutableArray new]
expiresInSeconds:(configuration.isEnabled ? configuration.durationSeconds : 0)];
DDLogError(@"%@ resurrectNewOutgoingMessages2 timestamp: %llu.", self.logTag, message.timestamp);
[messages addObject:message];
}
for (TSOutgoingMessage *message in messages) {
[message updateWithMessageState:TSOutgoingMessageStateAttemptingOut transaction:initialTransaction];
[message saveWithTransaction:initialTransaction];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
DDLogInfo(@"%@ resurrectNewOutgoingMessages2.2: %zd", self.logTag, count);
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (TSOutgoingMessage *message in messages) {
[message removeWithTransaction:transaction];
}
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
DDLogInfo(@"%@ resurrectNewOutgoingMessages2.3: %zd", self.logTag, count);
[OWSPrimaryStorage.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (TSOutgoingMessage *message in messages) {
[message saveWithTransaction:transaction];
}
}];
});
});
}
+ (void)createTimestampMessagesInThread:(TSThread *)thread
{
OWSAssert(thread);
long long now = (long long)[NSDate ows_millisecondTimeStamp];
NSArray<NSNumber *> *timestamps = @[
@(now + 1 * (long long)kHourInMs),
@(now),
@(now - 1 * (long long)kHourInMs),
@(now - 12 * (long long)kHourInMs),
@(now - 1 * (long long)kDayInMs),
@(now - 2 * (long long)kDayInMs),
@(now - 3 * (long long)kDayInMs),
@(now - 6 * (long long)kDayInMs),
@(now - 7 * (long long)kDayInMs),
@(now - 8 * (long long)kDayInMs),
@(now - 2 * (long long)kWeekInMs),
@(now - 1 * (long long)kMonthInMs),
@(now - 2 * (long long)kMonthInMs),
];
NSMutableArray<NSString *> *recipientIds = [thread.recipientIdentifiers mutableCopy];
[recipientIds removeObject:[TSAccountManager localNumber]];
NSString *recipientId = (recipientIds.count > 0 ? recipientIds.firstObject : @"+19174054215");
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSNumber *timestamp in timestamps) {
NSString *randomText = [self randomText];
{
TSIncomingMessage *message =
[[TSIncomingMessage alloc] initWithTimestamp:timestamp.unsignedLongLongValue
inThread:thread
authorId:recipientId
sourceDeviceId:0
messageBody:randomText];
[message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO];
}
{
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initWithTimestamp:timestamp.unsignedLongLongValue
inThread:thread
messageBody:randomText];
[message saveWithTransaction:transaction];
[message updateWithMessageState:TSOutgoingMessageStateSentToService transaction:transaction];
[message updateWithSentRecipient:recipientId transaction:transaction];
[message updateWithDeliveredToRecipientId:recipientId
deliveryTimestamp:timestamp
transaction:transaction];
[message updateWithReadRecipientId:recipientId
readTimestamp:timestamp.unsignedLongLongValue
transaction:transaction];
}
}
}];
}
+ (void)testIndicScriptsInThread:(TSThread *)thread
{
NSArray<NSString *> *strings = @[
@"\u0C1C\u0C4D\u0C1E\u200C\u0C3E",
@"\u09B8\u09CD\u09B0\u200C\u09C1",
@"non-crashing string",
];
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *string in strings) {
// DO NOT log these strings with the debugger attached.
// DDLogInfo(@"%@ %@", self.logTag, string);
{
TSIncomingMessage *message =
[[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
authorId:@"+19174054215"
sourceDeviceId:0
messageBody:string];
[message saveWithTransaction:transaction];
[message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO];
}
{
NSString *recipientId = @"+19174054215";
NSString *groupName = string;
NSMutableArray<NSString *> *recipientIds = [@[
recipientId,
[TSAccountManager localNumber],
] mutableCopy];
NSData *groupId = [SecurityUtils generateRandomBytes:16];
TSGroupModel *groupModel =
[[TSGroupModel alloc] initWithTitle:groupName memberIds:recipientIds image:nil groupId:groupId];
TSGroupThread *groupThread =
[TSGroupThread getOrCreateThreadWithGroupModel:groupModel transaction:transaction];
OWSAssert(groupThread);
}
}
}];
}
+ (void)testZalgoTextInThread:(TSThread *)thread
{
NSArray<NSString *> *strings = @[
@"Ṱ̴̤̺̣͚͚̭̰̤̮̑̓̀͂͘͡h̵̢̤͔̼̗̦̖̬͌̀͒̀͘i̴̮̤͎͎̝̖̻͓̅̆͆̓̎͘͡ͅŝ̡̡̳͔̓͗̾̀̇͒͘͢͢͡͡ ỉ̛̲̩̫̝͉̀̒͐͋̾͘͢͡͞s̶̨̫̞̜̹͛́̇͑̅̒̊̈ s̵͍̲̗̠̗͈̦̬̉̿͂̏̐͆̾͐͊̾ǫ̶͍̼̝̉͊̉͢͜͞͝ͅͅṁ̵̡̨̬̤̝͔̣̄̍̋͊̿̄͋̈ͅe̪̪̻̱͖͚͈̲̍̃͘͠͝ z̷̢̢̛̩̦̱̺̼͑́̉̾ą͕͎̠̮̹̱̓̔̓̈̈́̅̐͢l̵̨͚̜͉̟̜͉͎̃͆͆͒͑̍̈̚͜͞ğ͔̖̫̞͎͍̒̂́̒̿̽̆͟o̶̢̬͚̘̤̪͇̻̒̋̇̊̏͢͡͡͠ͅ t̡̛̥̦̪̮̅̓̑̈́̉̓̽͛͢͡ȩ̡̩͓͈̩͎͗̔͑̌̓͊͆͝x̫̦͓̤͓̘̝̪͊̆͌͊̽̃̏͒͘͘͢ẗ̶̢̨̛̰̯͕͔́̐͗͌͟͠.̷̩̼̼̩̞̘̪́͗̅͊̎̾̅̏̀̕͟ͅ",
@"This is some normal text",
];
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *string in strings) {
DDLogInfo(@"%@ sending zalgo", self.logTag);
{
TSIncomingMessage *message =
[[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
authorId:@"+19174054215"
sourceDeviceId:0
messageBody:string];
[message saveWithTransaction:transaction];
[message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO];
}
{
NSString *recipientId = @"+19174054215";
NSString *groupName = string;
NSMutableArray<NSString *> *recipientIds = [@[
recipientId,
[TSAccountManager localNumber],
] mutableCopy];
NSData *groupId = [SecurityUtils generateRandomBytes:16];
TSGroupModel *groupModel =
[[TSGroupModel alloc] initWithTitle:groupName memberIds:recipientIds image:nil groupId:groupId];
TSGroupThread *groupThread =
[TSGroupThread getOrCreateThreadWithGroupModel:groupModel transaction:transaction];
OWSAssert(groupThread);
}
}
}];
}
+ (void)testDirectionalFilenamesInThread:(TSThread *)thread
{
NSMutableArray<NSString *> *filenames = [@[
@"a_test\u202Dabc.exe",
@"b_test\u202Eabc.exe",
@"c_testabc.exe",
] mutableCopy];
__block void (^sendUnsafeFile)(void);
sendUnsafeFile = ^{
if (filenames.count < 1) {
return;
}
NSString *filename = filenames.lastObject;
[filenames removeLastObject];
OWSMessageSender *messageSender = [Environment current].messageSender;
NSString *utiType = (NSString *)kUTTypeData;
const NSUInteger kDataLength = 32;
DataSource *_Nullable dataSource =
[DataSourceValue dataSourceWithData:[self createRandomNSDataOfSize:kDataLength] utiType:utiType];
[dataSource setSourceFilename:filename];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType imageQuality:TSImageQualityOriginal];
OWSAssert(attachment);
if ([attachment hasError]) {
DDLogError(@"attachment[%@]: %@", [attachment sourceFilename], [attachment errorName]);
[DDLog flushLog];
}
OWSAssert(![attachment hasError]);
[ThreadUtil sendMessageWithAttachment:attachment inThread:thread messageSender:messageSender completion:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
sendUnsafeFile();
});
};
}
@end
NS_ASSUME_NONNULL_END