diff --git a/Pods b/Pods index 2870e676d..67dbced37 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 2870e676deec6a7ddb931edb6f0284f1f5b36085 +Subproject commit 67dbced37481e0011a3df1397ed57711384a4957 diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index 378a5f3ca..9d7c02e10 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -5,9 +5,9 @@ BuildDetails CarthageVersion - 0.33.0 + 0.34.0 OSXVersion - 10.15.3 + 10.15.2 WebRTCCommit 1445d719bf05280270e9f77576f80f973fd847f8 M73 diff --git a/Signal/src/Jobs/ConversationConfigurationSyncOperation.swift b/Signal/src/Jobs/ConversationConfigurationSyncOperation.swift index 783c8af68..0a47382b7 100644 --- a/Signal/src/Jobs/ConversationConfigurationSyncOperation.swift +++ b/Signal/src/Jobs/ConversationConfigurationSyncOperation.swift @@ -64,7 +64,7 @@ class ConversationConfigurationSyncOperation: OWSOperation { // The current implementation works, but seems wasteful. // Does desktop handle single group sync correctly? // What does Android do? - let syncMessage: OWSSyncGroupsMessage = OWSSyncGroupsMessage() + let syncMessage: OWSSyncGroupsMessage = OWSSyncGroupsMessage(groupThread: groupThread) var dataSource: DataSource? self.dbConnection.read { transaction in diff --git a/Signal/src/Loki/View Controllers/DeviceLinkingModal.swift b/Signal/src/Loki/View Controllers/DeviceLinkingModal.swift index 9ce16ef95..195dd5669 100644 --- a/Signal/src/Loki/View Controllers/DeviceLinkingModal.swift +++ b/Signal/src/Loki/View Controllers/DeviceLinkingModal.swift @@ -165,7 +165,8 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate { let linkingAuthorizationMessage = DeviceLinkingUtilities.getLinkingAuthorizationMessage(for: deviceLink) ThreadUtil.enqueue(linkingAuthorizationMessage) SSKEnvironment.shared.messageSender.send(linkingAuthorizationMessage, success: { - let _ = SSKEnvironment.shared.syncManager.syncAllContacts() + let _ = [SSKEnvironment.shared.syncManager.syncAllContacts(), + SSKEnvironment.shared.syncManager.syncAllGroups()] }) { _ in print("[Loki] Failed to send device link authorization message.") } diff --git a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift index 1ea538597..23d1aee5f 100644 --- a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift +++ b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift @@ -168,8 +168,14 @@ final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableV return showError(title: NSLocalizedString("A closed group cannot have more than 10 members", comment: "")) } let userHexEncodedPublicKey = getUserHexEncodedPublicKey() - let members = [String](selectedContacts) + [ userHexEncodedPublicKey ] - let admins = [ userHexEncodedPublicKey ] + let storage = OWSPrimaryStorage.shared() + var masterHexEncodedPublicKey = "" + storage.dbReadConnection.readWrite { transaction in + masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: userHexEncodedPublicKey, in: transaction) ?? userHexEncodedPublicKey + } + let members = [String](selectedContacts) + [ masterHexEncodedPublicKey ] + let admins = [ masterHexEncodedPublicKey ] + let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(Randomness.generateRandomBytes(kGroupIdLength)!.toHexString()) let group = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins) let thread = TSGroupThread.getOrCreateThread(with: group) diff --git a/Signal/src/ViewControllers/DebugUI/DebugUISyncMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUISyncMessages.m index cc493cd89..124f14c86 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUISyncMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUISyncMessages.m @@ -108,20 +108,7 @@ NS_ASSUME_NONNULL_BEGIN + (void)sendGroupSyncMessage { - OWSSyncGroupsMessage *syncGroupsMessage = [[OWSSyncGroupsMessage alloc] init]; - __block DataSource *dataSource; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - dataSource = [DataSourceValue - dataSourceWithSyncMessageData:[syncGroupsMessage buildPlainTextAttachmentDataWithTransaction:transaction]]; - }]; - - [self.messageSenderJobQueue addMediaMessage:syncGroupsMessage - dataSource:dataSource - contentType:OWSMimeTypeApplicationOctetStream - sourceFilename:nil - caption:nil - albumMessageId:nil - isTemporaryAttachment:YES]; + [[self.syncManager syncAllGroups] retainUntilComplete]; } + (void)sendBlockListSyncMessage diff --git a/Signal/src/ViewControllers/ThreadSettings/UpdateGroupViewController.m b/Signal/src/ViewControllers/ThreadSettings/UpdateGroupViewController.m index 6da77bb3b..a55c240d2 100644 --- a/Signal/src/ViewControllers/ThreadSettings/UpdateGroupViewController.m +++ b/Signal/src/ViewControllers/ThreadSettings/UpdateGroupViewController.m @@ -385,7 +385,6 @@ NS_ASSUME_NONNULL_BEGIN groupId:self.thread.groupModel.groupId groupType:self.thread.groupModel.groupType adminIds:self.thread.groupModel.groupAdminIds]; - groupModel.removedMembers = self.removedRecipientIds; [self.conversationSettingsViewDelegate groupWasUpdated:groupModel]; } diff --git a/SignalMessaging/contacts/OWSSyncManager.m b/SignalMessaging/contacts/OWSSyncManager.m index 287adbedd..d5c2c4574 100644 --- a/SignalMessaging/contacts/OWSSyncManager.m +++ b/SignalMessaging/contacts/OWSSyncManager.m @@ -16,12 +16,14 @@ #import #import #import +#import #import #import #import #import #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -280,13 +282,28 @@ NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManag - (AnyPromise *)syncAllContacts { NSMutableArray *friends = @[].mutableCopy; + NSMutableArray *promises = @[].mutableCopy; [TSContactThread enumerateCollectionObjectsUsingBlock:^(TSContactThread *thread, BOOL *stop) { NSString *hexEncodedPublicKey = thread.contactIdentifier; - if (hexEncodedPublicKey != nil && thread.isContactFriend) { + if (hexEncodedPublicKey != nil && thread.isContactFriend && thread.shouldThreadBeVisible && !thread.isForceHidden) { [friends addObject:[[SignalAccount alloc] initWithRecipientId:hexEncodedPublicKey]]; } }]; - return [self syncContactsForSignalAccounts:friends]; + [friends addObject:[[SignalAccount alloc] initWithRecipientId:self.tsAccountManager.localNumber]]; + NSMutableArray *signalAccounts = @[].mutableCopy; + for (SignalAccount *contact in friends) { + [signalAccounts addObject:contact]; + if (signalAccounts.count >= 3) { + [promises addObject:[self syncContactsForSignalAccounts:[signalAccounts copy]]]; + [signalAccounts removeAllObjects]; + } + } + if (signalAccounts.count > 0) { + [promises addObject:[self syncContactsForSignalAccounts:signalAccounts]]; + } + AnyPromise *promise = PMKJoin(promises); + [promise retainUntilComplete]; + return promise; } - (AnyPromise *)syncContactsForSignalAccounts:(NSArray *)signalAccounts @@ -307,6 +324,49 @@ NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManag return promise; } +- (AnyPromise *)syncAllGroups +{ + NSMutableArray *groupThreads = @[].mutableCopy; + NSMutableArray *promises = @[].mutableCopy; + [TSGroupThread enumerateCollectionObjectsUsingBlock:^(id obj, BOOL *stop) { + if (![obj isKindOfClass:[TSGroupThread class]]) { + if (![obj isKindOfClass:[TSContactThread class]]) { + OWSLogWarn(@"Ignoring non group thread in thread collection: %@", obj); + + } + return; + } + TSGroupThread *thread = (TSGroupThread *)obj; + if (thread.groupModel.groupType == closedGroup && thread.shouldThreadBeVisible && !thread.isForceHidden) { + [groupThreads addObject:thread]; + } + }]; + for (TSGroupThread *groupThread in groupThreads) { + [promises addObject:[self syncGroupForThread:groupThread]]; + } + AnyPromise *promise = PMKJoin(promises); + [promise retainUntilComplete]; + return promise; +} + +- (AnyPromise *)syncGroupForThread:(TSGroupThread *)thread +{ + OWSSyncGroupsMessage *syncGroupsMessage = [[OWSSyncGroupsMessage alloc] initWithGroupThread:thread]; + AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) { + [self.messageSender sendMessage:syncGroupsMessage + success:^{ + OWSLogInfo(@"Successfully sent groups sync message."); + resolve(@(1)); + } + failure:^(NSError *error) { + OWSLogError(@"Failed to send groups sync message with error: %@.", error); + resolve(error); + }]; + }]; + [promise retainUntilComplete]; + return promise; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/protobuf/SignalService.proto b/SignalServiceKit/protobuf/SignalService.proto index aad7efdb2..0c9ee9425 100644 --- a/SignalServiceKit/protobuf/SignalService.proto +++ b/SignalServiceKit/protobuf/SignalService.proto @@ -319,6 +319,7 @@ message SyncMessage { message Groups { optional AttachmentPointer blob = 1; + optional bytes data = 101; // Loki } message Blocked { @@ -399,7 +400,7 @@ message GroupContext { optional string name = 3; repeated string members = 4; optional AttachmentPointer avatar = 5; - repeated string admins = 6; + repeated string admins = 6; // Loki } message ContactDetails { @@ -435,6 +436,7 @@ message GroupDetails { optional uint32 expireTimer = 6; optional string color = 7; optional bool blocked = 8; + repeated string admins = 9; // Loki } // Internal - DO NOT SEND diff --git a/SignalServiceKit/src/Contacts/Threads/TSGroupThread.h b/SignalServiceKit/src/Contacts/Threads/TSGroupThread.h index 4b614371f..a243daa7f 100644 --- a/SignalServiceKit/src/Contacts/Threads/TSGroupThread.h +++ b/SignalServiceKit/src/Contacts/Threads/TSGroupThread.h @@ -38,6 +38,8 @@ extern NSString *const TSGroupThread_NotificationKey_UniqueId; - (BOOL)isLocalUserInGroup; - (BOOL)isLocalUserInGroupWithTransaction:(YapDatabaseReadTransaction *)transaction; +- (BOOL)isUserInGroup:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction; +- (BOOL)isUserAdminForGroup:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction; // all group threads containing recipient as a member + (NSArray *)groupThreadsWithRecipientId:(NSString *)recipientId diff --git a/SignalServiceKit/src/Contacts/Threads/TSGroupThread.m b/SignalServiceKit/src/Contacts/Threads/TSGroupThread.m index 31f7cfaeb..98b80ecc3 100644 --- a/SignalServiceKit/src/Contacts/Threads/TSGroupThread.m +++ b/SignalServiceKit/src/Contacts/Threads/TSGroupThread.m @@ -204,6 +204,20 @@ NSString *const TSGroupThread_NotificationKey_UniqueId = @"TSGroupThread_Notific return [linkedDeviceHexEncodedPublicKeys intersectsSet:[NSSet setWithArray:self.groupModel.groupMemberIds]]; } +- (BOOL)isUserInGroup:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + if (hexEncodedPublicKey == nil) { return NO; } + NSSet *linkedDeviceHexEncodedPublicKeys = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:hexEncodedPublicKey in:transaction]; + return [linkedDeviceHexEncodedPublicKeys intersectsSet:[NSSet setWithArray:self.groupModel.groupMemberIds]]; +} + +- (BOOL)isUserAdminForGroup:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + if (hexEncodedPublicKey == nil) { return NO; } + NSSet *linkedDeviceHexEncodedPublicKeys = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:hexEncodedPublicKey in:transaction]; + return [linkedDeviceHexEncodedPublicKeys intersectsSet:[NSSet setWithArray:self.groupModel.groupAdminIds]]; +} + - (NSString *)name { // TODO sometimes groupName is set to the empty string. I'm hesitent to change @@ -227,10 +241,13 @@ NSString *const TSGroupThread_NotificationKey_UniqueId = @"TSGroupThread_Notific - (void)leaveGroupWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { - NSMutableArray *newGroupMemberIds = [self.groupModel.groupMemberIds mutableCopy]; - [newGroupMemberIds removeObject:[TSAccountManager localNumber]]; - - self.groupModel.groupMemberIds = newGroupMemberIds; + NSMutableSet *newGroupMemberIds = [NSMutableSet setWithArray:self.groupModel.groupMemberIds]; + NSString *userHexEncodedPublicKey = TSAccountManager.localNumber; + if (userHexEncodedPublicKey != nil) { + NSSet *linkedDeviceHexEncodedPublicKeys = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:userHexEncodedPublicKey in:transaction]; + [newGroupMemberIds minusSet:linkedDeviceHexEncodedPublicKeys]; + } + self.groupModel.groupMemberIds = newGroupMemberIds.allObjects; [self saveWithTransaction:transaction]; } diff --git a/SignalServiceKit/src/Devices/OWSGroupsOutputStream.m b/SignalServiceKit/src/Devices/OWSGroupsOutputStream.m index 80ba744a7..fe14f15c3 100644 --- a/SignalServiceKit/src/Devices/OWSGroupsOutputStream.m +++ b/SignalServiceKit/src/Devices/OWSGroupsOutputStream.m @@ -26,11 +26,12 @@ NS_ASSUME_NONNULL_BEGIN [groupBuilder setName:group.groupName]; [groupBuilder setMembers:group.groupMemberIds]; [groupBuilder setColor:groupThread.conversationColorName]; + [groupBuilder setAdmins:group.groupAdminIds]; if ([OWSBlockingManager.sharedManager isGroupIdBlocked:group.groupId]) { [groupBuilder setBlocked:YES]; } - + /* NSData *avatarPng; if (group.groupImage) { SSKProtoGroupDetailsAvatarBuilder *avatarBuilder = [SSKProtoGroupDetailsAvatar builder]; @@ -46,7 +47,7 @@ NS_ASSUME_NONNULL_BEGIN } else { [groupBuilder setAvatar:avatarProto]; } - } + } */ OWSDisappearingMessagesConfiguration *_Nullable disappearingMessagesConfiguration = [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:groupThread.uniqueId transaction:transaction]; @@ -69,12 +70,12 @@ NS_ASSUME_NONNULL_BEGIN uint32_t groupDataLength = (uint32_t)groupData.length; - [self writeVariableLengthUInt32:groupDataLength]; + [self writeUInt32:groupDataLength]; [self writeData:groupData]; - if (avatarPng) { - [self writeData:avatarPng]; - } +// if (avatarPng) { +// [self writeData:avatarPng]; +// } } @end diff --git a/SignalServiceKit/src/Loki/Messaging/ContactParser.swift b/SignalServiceKit/src/Loki/Messaging/ContactParser.swift index 7b3dea666..b29e9c218 100644 --- a/SignalServiceKit/src/Loki/Messaging/ContactParser.swift +++ b/SignalServiceKit/src/Loki/Messaging/ContactParser.swift @@ -17,7 +17,7 @@ guard let size = uncheckedSize, size < data.count else { break } let sizeAsInt = Int(size) index += 4 - guard index + sizeAsInt < data.count else { break } + guard index + sizeAsInt <= data.count else { break } let protoAsData = data[index..<(index+sizeAsInt)] guard let proto = try? SSKProtoContactDetails.parseData(protoAsData) else { break } index += sizeAsInt diff --git a/SignalServiceKit/src/Loki/Messaging/GroupParser.swift b/SignalServiceKit/src/Loki/Messaging/GroupParser.swift new file mode 100644 index 000000000..3c3fd68b1 --- /dev/null +++ b/SignalServiceKit/src/Loki/Messaging/GroupParser.swift @@ -0,0 +1,34 @@ + +@objc public final class GroupParser : NSObject { + private let data: Data + + @objc public init(data: Data) { + self.data = data + } + + @objc public func parseGroupModels() -> [TSGroupModel] { + var index = 0 + var result: [TSGroupModel] = [] + while index < data.endIndex { + var uncheckedSize: UInt32? = try? data[index..<(index+4)].withUnsafeBytes { $0.pointee } + if let size = uncheckedSize, size >= data.count, let intermediate = try? data[index..<(index+4)].reversed() { + uncheckedSize = Data(intermediate).withUnsafeBytes { $0.pointee } + } + guard let size = uncheckedSize, size < data.count else { break } + let sizeAsInt = Int(size) + index += 4 + guard index + sizeAsInt <= data.count else { break } + let protoAsData = data[index..<(index+sizeAsInt)] + guard let proto = try? SSKProtoGroupDetails.parseData(protoAsData) else { break } + index += sizeAsInt + var groupModel = TSGroupModel.init(title: proto.name, + memberIds: proto.members, + image: nil, + groupId: proto.id, + groupType: GroupType.closedGroup, + adminIds: proto.admins) + result.append(groupModel) + } + return result + } +} diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.h b/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.h index f31996d6b..87c8ea79e 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.h +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.h @@ -28,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) uint64_t expirationStartedAt; @property (nonatomic, readonly) uint32_t expirationDuration; @property (nonatomic, readonly) BOOL isGroupUpdate; +@property (nonatomic, readonly) BOOL isGroupQuit; @property (nonatomic, readonly) BOOL isExpirationTimerUpdate; @property (nonatomic, readonly) BOOL isEndSessionMessage; @property (nonatomic, readonly, nullable) NSData *groupId; diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m index d3218938a..994c1af86 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m @@ -34,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN _body = _dataMessage.body; _groupId = _dataMessage.group.id; _isGroupUpdate = _dataMessage.group != nil && (_dataMessage.group.type == SSKProtoGroupContextTypeUpdate); + _isGroupQuit = _dataMessage.group != nil && (_dataMessage.group.type == SSKProtoGroupContextTypeQuit); _isExpirationTimerUpdate = (_dataMessage.flags & SSKProtoDataMessageFlagsExpirationTimerUpdate) != 0; _isEndSessionMessage = (_dataMessage.flags & SSKProtoDataMessageFlagsEndSession) != 0; _isRecipientUpdate = sentProto.isRecipientUpdate; diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m index 19bd4263c..2e67f3996 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m @@ -109,21 +109,6 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSData *)buildPlainTextAttachmentDataWithTransaction:(YapDatabaseReadTransaction *)transaction { NSMutableArray *signalAccounts = [self.signalAccounts mutableCopy]; - - NSString *_Nullable localNumber = self.tsAccountManager.localNumber; - OWSAssertDebug(localNumber); - if (localNumber) { - BOOL hasLocalNumber = NO; - for (SignalAccount *signalAccount in signalAccounts) { - hasLocalNumber |= [signalAccount.recipientId isEqualToString:localNumber]; - } - if (!hasLocalNumber) { - SignalAccount *signalAccount = [[SignalAccount alloc] initWithRecipientId:localNumber]; - // OWSContactsOutputStream requires all signalAccount to have a contact. - signalAccount.contact = [[Contact alloc] initWithSystemContact:[CNContact new]]; - [signalAccounts addObject:signalAccount]; - } - } // TODO use temp file stream to avoid loading everything into memory at once // First though, we need to re-engineer our attachment process to accept streams (encrypting with stream, diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.h b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.h index 7f5ba902f..6f7ccc188 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.h +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.h @@ -7,10 +7,14 @@ NS_ASSUME_NONNULL_BEGIN @class YapDatabaseReadTransaction; +@class TSGroupThread; @interface OWSSyncGroupsMessage : OWSOutgoingSyncMessage -- (instancetype)init NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithGroupThread:(TSGroupThread *)thread NS_DESIGNATED_INITIALIZER; + - (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; - (nullable NSData *)buildPlainTextAttachmentDataWithTransaction:(YapDatabaseReadTransaction *)transaction; diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m index f76b36f87..9ee62dd34 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m @@ -11,14 +11,28 @@ #import "TSGroupThread.h" #import #import +#import "OWSPrimaryStorage.h" NS_ASSUME_NONNULL_BEGIN +@interface OWSSyncGroupsMessage () + +@property (nonatomic, readonly) TSGroupThread *groupThread; + +@end + @implementation OWSSyncGroupsMessage -- (instancetype)init +- (instancetype)initWithGroupThread:(TSGroupThread *)thread { - return [super init]; + self = [super init]; + if (!self) { + return self; + } + + _groupThread = thread; + + return self; } - (nullable instancetype)initWithCoder:(NSCoder *)coder @@ -28,22 +42,32 @@ NS_ASSUME_NONNULL_BEGIN - (nullable SSKProtoSyncMessageBuilder *)syncMessageBuilder { - if (self.attachmentIds.count != 1) { - OWSLogError(@"expected sync groups message to have exactly one attachment, but found %lu", - (unsigned long)self.attachmentIds.count); + NSError *error; + if (self.attachmentIds.count > 1) { + OWSLogError(@"Expected sync group message to have one or zero attachments, but found %lu.", (unsigned long)self.attachmentIds.count); } - - SSKProtoAttachmentPointer *_Nullable attachmentProto = - [TSAttachmentStream buildProtoForAttachmentId:self.attachmentIds.firstObject]; - if (!attachmentProto) { - OWSFailDebug(@"could not build protobuf."); - return nil; + + SSKProtoSyncMessageGroupsBuilder *groupsBuilder; + if (self.attachmentIds.count == 0) { + SSKProtoAttachmentPointerBuilder *attachmentProtoBuilder = [SSKProtoAttachmentPointer builderWithId:0]; + SSKProtoAttachmentPointer *attachmentProto = [attachmentProtoBuilder buildAndReturnError:&error]; + groupsBuilder = [SSKProtoSyncMessageGroups builder]; + [groupsBuilder setBlob:attachmentProto]; + __block NSData *data; + [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { + data = [self buildPlainTextAttachmentDataWithTransaction:transaction]; + }]; + [groupsBuilder setData:data]; + } else { + SSKProtoAttachmentPointer *attachmentProto = [TSAttachmentStream buildProtoForAttachmentId:self.attachmentIds.firstObject]; + if (attachmentProto == nil) { + OWSFailDebug(@"Couldn't build protobuf."); + return nil; + } + groupsBuilder = [SSKProtoSyncMessageGroups builder]; + [groupsBuilder setBlob:attachmentProto]; } - SSKProtoSyncMessageGroupsBuilder *groupsBuilder = [SSKProtoSyncMessageGroups builder]; - [groupsBuilder setBlob:attachmentProto]; - - NSError *error; SSKProtoSyncMessageGroups *_Nullable groupsProto = [groupsBuilder buildAndReturnError:&error]; if (error || !groupsProto) { OWSFailDebug(@"could not build protobuf: %@", error); @@ -64,21 +88,7 @@ NS_ASSUME_NONNULL_BEGIN NSOutputStream *dataOutputStream = [NSOutputStream outputStreamToMemory]; [dataOutputStream open]; OWSGroupsOutputStream *groupsOutputStream = [[OWSGroupsOutputStream alloc] initWithOutputStream:dataOutputStream]; - - [TSGroupThread - enumerateCollectionObjectsWithTransaction:transaction - usingBlock:^(id obj, BOOL *stop) { - if (![obj isKindOfClass:[TSGroupThread class]]) { - if (![obj isKindOfClass:[TSContactThread class]]) { - OWSLogWarn( - @"Ignoring non group thread in thread collection: %@", obj); - } - return; - } - TSGroupThread *groupThread = (TSGroupThread *)obj; - [groupsOutputStream writeGroup:groupThread transaction:transaction]; - }]; - + [groupsOutputStream writeGroup:self.groupThread transaction:transaction]; [dataOutputStream close]; if (groupsOutputStream.hasError) { diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index db0bdccb4..ac3d4806d 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -1020,6 +1020,14 @@ NS_ASSUME_NONNULL_BEGIN messageType:TSInfoMessageTypeGroupUpdate customMessage:updateMessage]; [infoMessage saveWithTransaction:transaction]; + } else if (transcript.isGroupQuit) { + TSGroupThread *groupThread = [TSGroupThread getOrCreateThreadWithGroupId:transcript.dataMessage.group.id groupType:closedGroup transaction:transaction]; + [groupThread leaveGroupWithTransaction:transaction]; + TSInfoMessage *infoMessage = [[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp + inThread:groupThread + messageType:TSInfoMessageTypeGroupQuit + customMessage:NSLocalizedString(@"GROUP_YOU_LEFT", nil)]; + [infoMessage saveWithTransaction:transaction]; } else { [OWSRecordTranscriptJob processIncomingSentMessageTranscript:transcript @@ -1043,20 +1051,9 @@ NS_ASSUME_NONNULL_BEGIN [[self.syncManager syncAllContacts] retainUntilComplete]; }); } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeGroups) { - OWSSyncGroupsMessage *syncGroupsMessage = [[OWSSyncGroupsMessage alloc] init]; - NSData *_Nullable syncData = [syncGroupsMessage buildPlainTextAttachmentDataWithTransaction:transaction]; - if (!syncData) { - OWSFailDebug(@"Failed to serialize groups sync message."); - return; - } - DataSource *dataSource = [DataSourceValue dataSourceWithSyncMessageData:syncData]; - [self.messageSenderJobQueue addMediaMessage:syncGroupsMessage - dataSource:dataSource - contentType:OWSMimeTypeApplicationOctetStream - sourceFilename:nil - caption:nil - albumMessageId:nil - isTemporaryAttachment:YES]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [[self.syncManager syncAllGroups] retainUntilComplete]; + }); } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeBlocked) { OWSLogInfo(@"Received request for block list"); [self.blockingManager syncBlockList]; @@ -1080,7 +1077,7 @@ NS_ASSUME_NONNULL_BEGIN [self.identityManager throws_processIncomingSyncMessage:syncMessage.verified transaction:transaction]; } else if (syncMessage.contacts != nil) { if (wasSentByMasterDevice && syncMessage.contacts.data.length > 0) { - NSLog(@"[Loki] Received contact sync message."); + OWSLogInfo(@"[Loki] Received contact sync message."); NSData *data = syncMessage.contacts.data; ContactParser *parser = [[ContactParser alloc] initWithData:data]; NSArray *hexEncodedPublicKeys = [parser parseHexEncodedPublicKeys]; @@ -1091,10 +1088,8 @@ NS_ASSUME_NONNULL_BEGIN switch (friendRequestStatus) { case LKThreadFriendRequestStatusNone: { OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender; - OWSMessageSend *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey]; - dispatch_async(OWSDispatch.sendingQueue, ^{ - [messageSender sendMessage:automatedFriendRequestMessage]; - }); + LKFriendRequestMessage *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey inThread:thread transaction:transaction]; + [self.messageSenderJobQueue addMessage:automatedFriendRequestMessage transaction:transaction]; break; } case LKThreadFriendRequestStatusRequestReceived: { @@ -1108,6 +1103,26 @@ NS_ASSUME_NONNULL_BEGIN } } } + } else if (syncMessage.groups != nil) { + if (wasSentByMasterDevice && syncMessage.groups.data.length > 0) { + OWSLogInfo(@"[Loki] Received group sync message."); + NSData *data = syncMessage.groups.data; + GroupParser *parser = [[GroupParser alloc] initWithData:data]; + NSArray *groupModels = [parser parseGroupModels]; + for (TSGroupModel *groupModel in groupModels) { + TSGroupThread *thread = [TSGroupThread threadWithGroupId:groupModel.groupId transaction:transaction]; + if (thread == nil) { + thread = [TSGroupThread getOrCreateThreadWithGroupModel:groupModel transaction:transaction]; + [thread saveWithTransaction:transaction]; + [self establishSessionsWithMembersIfNeeded:groupModel.groupMemberIds forThread:thread transaction:transaction]; + TSInfoMessage *infoMessage = [[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp + inThread:thread + messageType:TSInfoMessageTypeGroupUpdate + customMessage:@"You have joined the group."]; + [infoMessage saveWithTransaction:transaction]; + } + } + } } else { OWSLogWarn(@"Ignoring unsupported sync message."); } @@ -1325,7 +1340,7 @@ NS_ASSUME_NONNULL_BEGIN } // Ensure sender is in the group. - if (![gThread.groupModel.groupMemberIds containsObject:envelope.source]) { + if (![gThread isUserInGroup:envelope.source transaction:transaction]) { OWSLogWarn(@"Ignoring 'Request Group Info' message for non-member of group. %@ not in %@", envelope.source, gThread.groupModel.groupMemberIds); @@ -1439,7 +1454,7 @@ NS_ASSUME_NONNULL_BEGIN switch (dataMessage.group.type) { case SSKProtoGroupContextTypeUpdate: { - if (oldGroupThread && ![oldGroupThread.groupModel.groupAdminIds containsObject:hexEncodedPublicKey]) { + if (oldGroupThread && ![oldGroupThread isUserAdminForGroup:hexEncodedPublicKey transaction:transaction]) { [LKLogger print:[NSString stringWithFormat:@"[Loki] Received a group update from a non-admin user for %@; ignoring.", [LKGroupUtilities getEncodedGroupID:groupId]]]; return nil; } diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.h b/SignalServiceKit/src/Messages/OWSMessageSender.h index f223aadcf..b6af35dc2 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.h +++ b/SignalServiceKit/src/Messages/OWSMessageSender.h @@ -3,6 +3,8 @@ // #import "DataSource.h" +#import "TSContactThread.h" +#import "LKFriendRequestMessage.h" NS_ASSUME_NONNULL_BEGIN @@ -98,6 +100,7 @@ NS_SWIFT_NAME(MessageSender) failure:(void (^)(NSError *error))failureHandler; - (OWSMessageSend *)getSessionRestoreMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey; +- (LKFriendRequestMessage *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey inThread:(TSContactThread *)thread transaction:(YapDatabaseReadWriteTransaction *)transaction; - (OWSMessageSend *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey; - (void)sendMessage:(OWSMessageSend *)messageSend; diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 066d66206..168e67097 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -937,6 +937,23 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; return [[OWSMessageSend alloc] initWithMessage:message thread:thread recipient:recipient senderCertificate:senderCertificate udAccess:theirUDAccess localNumber:userHexEncodedPublicKey success:^{ } failure:^(NSError *error) { }]; } +- (LKFriendRequestMessage *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey inThread:(TSContactThread *)thread transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction]; + // Force hide slave device thread + NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:hexEncodedPublicKey in:transaction]; + thread.isForceHidden = masterHexEncodedPublicKey != nil && ![masterHexEncodedPublicKey isEqualToString:hexEncodedPublicKey]; + if (thread.friendRequestStatus == LKThreadFriendRequestStatusNone || thread.friendRequestStatus == LKThreadFriendRequestStatusRequestExpired) { + [thread saveFriendRequestStatus:LKThreadFriendRequestStatusRequestSent withTransaction:transaction]; + } + [thread saveWithTransaction:transaction]; + LKFriendRequestMessage *message = [[LKFriendRequestMessage alloc] initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"Please accept to enable messages to be synced across devices" attachmentIds:[NSMutableArray new] + expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil]; + message.skipSave = YES; + [message saveWithTransaction:transaction]; + return message; +} + - (OWSMessageSend *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey { __block TSContactThread *thread; diff --git a/SignalServiceKit/src/Protos/Generated/SSKProto.swift b/SignalServiceKit/src/Protos/Generated/SSKProto.swift index c94ca5861..1884be9fd 100644 --- a/SignalServiceKit/src/Protos/Generated/SSKProto.swift +++ b/SignalServiceKit/src/Protos/Generated/SSKProto.swift @@ -4551,6 +4551,9 @@ extension SSKProtoSyncMessageContacts.SSKProtoSyncMessageContactsBuilder { if let _value = blob { builder.setBlob(_value) } + if let _value = data { + builder.setData(_value) + } return builder } @@ -4563,6 +4566,10 @@ extension SSKProtoSyncMessageContacts.SSKProtoSyncMessageContactsBuilder { @objc public func setBlob(_ valueParam: SSKProtoAttachmentPointer) { proto.blob = valueParam.proto } + + @objc public func setData(_ valueParam: Data) { + proto.data = valueParam + } @objc public func build() throws -> SSKProtoSyncMessageGroups { return try SSKProtoSyncMessageGroups.parseProto(proto) @@ -4576,6 +4583,16 @@ extension SSKProtoSyncMessageContacts.SSKProtoSyncMessageContactsBuilder { fileprivate let proto: SignalServiceProtos_SyncMessage.Groups @objc public let blob: SSKProtoAttachmentPointer? + + @objc public var data: Data? { + guard proto.hasData else { + return nil + } + return proto.data + } + @objc public var hasData: Bool { + return proto.hasData + } private init(proto: SignalServiceProtos_SyncMessage.Groups, blob: SSKProtoAttachmentPointer?) { @@ -6343,6 +6360,16 @@ extension SSKProtoGroupDetailsAvatar.SSKProtoGroupDetailsAvatarBuilder { @objc public func setMembers(_ wrappedItems: [String]) { proto.members = wrappedItems } + + @objc public func addAdmins(_ valueParam: String) { + var items = proto.admins + items.append(valueParam) + proto.admins = items + } + + @objc public func setAdmins(_ wrappedItems: [String]) { + proto.admins = wrappedItems + } @objc public func setAvatar(_ valueParam: SSKProtoGroupDetailsAvatar) { proto.avatar = valueParam.proto @@ -6392,6 +6419,10 @@ extension SSKProtoGroupDetailsAvatar.SSKProtoGroupDetailsAvatarBuilder { @objc public var members: [String] { return proto.members } + + @objc public var admins: [String] { + return proto.admins + } @objc public var active: Bool { return proto.active diff --git a/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift b/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift index 2b433616e..f81521781 100644 --- a/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift +++ b/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift @@ -1945,6 +1945,16 @@ struct SignalServiceProtos_SyncMessage { var hasBlob: Bool {return _storage._blob != nil} /// Clears the value of `blob`. Subsequent reads from it will return its default value. mutating func clearBlob() {_uniqueStorage()._blob = nil} + + /// Loki + var data: Data { + get {return _storage._data ?? SwiftProtobuf.Internal.emptyData} + set {_uniqueStorage()._data = newValue} + } + /// Returns true if `data` has been explicitly set. + var hasData: Bool {return _storage._data != nil} + /// Clears the value of `data`. Subsequent reads from it will return its default value. + mutating func clearData() {_uniqueStorage()._data = nil} var unknownFields = SwiftProtobuf.UnknownStorage() @@ -2534,6 +2544,12 @@ struct SignalServiceProtos_GroupDetails { get {return _storage._members} set {_uniqueStorage()._members = newValue} } + + ///Loki + var admins: [String] { + get {return _storage._admins} + set {_uniqueStorage()._admins = newValue} + } var avatar: SignalServiceProtos_GroupDetails.Avatar { get {return _storage._avatar ?? SignalServiceProtos_GroupDetails.Avatar()} @@ -4627,10 +4643,12 @@ extension SignalServiceProtos_SyncMessage.Groups: SwiftProtobuf.Message, SwiftPr static let protoMessageName: String = SignalServiceProtos_SyncMessage.protoMessageName + ".Groups" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "blob"), + 101: .same(proto: "data"), ] fileprivate class _StorageClass { var _blob: SignalServiceProtos_AttachmentPointer? = nil + var _data: Data? = nil static let defaultInstance = _StorageClass() @@ -4638,6 +4656,7 @@ extension SignalServiceProtos_SyncMessage.Groups: SwiftProtobuf.Message, SwiftPr init(copying source: _StorageClass) { _blob = source._blob + _data = source._data } } @@ -4654,6 +4673,7 @@ extension SignalServiceProtos_SyncMessage.Groups: SwiftProtobuf.Message, SwiftPr while let fieldNumber = try decoder.nextFieldNumber() { switch fieldNumber { case 1: try decoder.decodeSingularMessageField(value: &_storage._blob) + case 101: try decoder.decodeSingularBytesField(value: &_storage._data) default: break } } @@ -4665,6 +4685,9 @@ extension SignalServiceProtos_SyncMessage.Groups: SwiftProtobuf.Message, SwiftPr if let v = _storage._blob { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } + if let v = _storage._data { + try visitor.visitSingularBytesField(value: v, fieldNumber: 101) + } } try unknownFields.traverse(visitor: &visitor) } @@ -4675,6 +4698,7 @@ extension SignalServiceProtos_SyncMessage.Groups: SwiftProtobuf.Message, SwiftPr let _storage = _args.0 let rhs_storage = _args.1 if _storage._blob != rhs_storage._blob {return false} + if _storage._data != rhs_storage._data {return false} return true } if !storagesAreEqual {return false} @@ -5223,6 +5247,7 @@ extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf 6: .same(proto: "expireTimer"), 7: .same(proto: "color"), 8: .same(proto: "blocked"), + 9: .same(proto: "admins"), ] fileprivate class _StorageClass { @@ -5234,6 +5259,7 @@ extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf var _expireTimer: UInt32? = nil var _color: String? = nil var _blocked: Bool? = nil + var _admins: [String] = [] static let defaultInstance = _StorageClass() @@ -5248,6 +5274,7 @@ extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf _expireTimer = source._expireTimer _color = source._color _blocked = source._blocked + _admins = source._admins } } @@ -5271,6 +5298,7 @@ extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf case 6: try decoder.decodeSingularUInt32Field(value: &_storage._expireTimer) case 7: try decoder.decodeSingularStringField(value: &_storage._color) case 8: try decoder.decodeSingularBoolField(value: &_storage._blocked) + case 9: try decoder.decodeRepeatedStringField(value: &_storage._admins) default: break } } @@ -5303,6 +5331,9 @@ extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf if let v = _storage._blocked { try visitor.visitSingularBoolField(value: v, fieldNumber: 8) } + if !_storage._admins.isEmpty { + try visitor.visitRepeatedStringField(value: _storage._admins, fieldNumber: 9) + } } try unknownFields.traverse(visitor: &visitor) } @@ -5320,6 +5351,7 @@ extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf if _storage._expireTimer != rhs_storage._expireTimer {return false} if _storage._color != rhs_storage._color {return false} if _storage._blocked != rhs_storage._blocked {return false} + if _storage._admins != rhs_storage._admins {return false} return true } if !storagesAreEqual {return false} diff --git a/SignalServiceKit/src/TestUtils/OWSMockSyncManager.swift b/SignalServiceKit/src/TestUtils/OWSMockSyncManager.swift index a3dcb9187..e408b8aaf 100644 --- a/SignalServiceKit/src/TestUtils/OWSMockSyncManager.swift +++ b/SignalServiceKit/src/TestUtils/OWSMockSyncManager.swift @@ -9,6 +9,12 @@ import PromiseKit @objc public class OWSMockSyncManager: NSObject, OWSSyncManagerProtocol { + public func syncAllGroups() -> AnyPromise { + Logger.info("") + + return AnyPromise() + } + @objc public func sendConfigurationSyncMessage() { Logger.info("") diff --git a/SignalServiceKit/src/Util/OWSSyncManagerProtocol.h b/SignalServiceKit/src/Util/OWSSyncManagerProtocol.h index eafb2e6fa..e0b78c36b 100644 --- a/SignalServiceKit/src/Util/OWSSyncManagerProtocol.h +++ b/SignalServiceKit/src/Util/OWSSyncManagerProtocol.h @@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN - (AnyPromise *)syncContactsForSignalAccounts:(NSArray *)signalAccounts __attribute__((warn_unused_result)); +- (AnyPromise *)syncAllGroups __attribute__((warn_unused_result)); + @end NS_ASSUME_NONNULL_END