Implement syncing

This commit is contained in:
nielsandriesse 2020-07-07 10:08:43 +10:00
parent 2ef26ab915
commit 9de2cc210d
5 changed files with 83 additions and 23 deletions

View File

@ -302,20 +302,28 @@ NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManag
- (AnyPromise *)syncGroupForThread:(TSGroupThread *)thread
{
OWSSyncGroupsMessage *syncGroupsMessage = [[OWSSyncGroupsMessage alloc] initWithGroupThread:thread];
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[self.messageSender sendMessage:syncGroupsMessage
success:^{
OWSLogInfo(@"Successfully sent group sync message.");
resolve(@(1));
}
failure:^(NSError *error) {
OWSLogError(@"Failed to send group sync message due to error: %@.", error);
resolve(error);
}];
}];
[promise retainUntilComplete];
return promise;
if (thread.usesSharedSenderKeys) {
__block AnyPromise *promise;
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
promise = [LKSyncMessagesProtocol syncClosedGroup:thread transaction:transaction];
} error:nil];
return promise;
} else {
OWSSyncGroupsMessage *syncGroupsMessage = [[OWSSyncGroupsMessage alloc] initWithGroupThread:thread];
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[self.messageSender sendMessage:syncGroupsMessage
success:^{
OWSLogInfo(@"Successfully sent group sync message.");
resolve(@(1));
}
failure:^(NSError *error) {
OWSLogError(@"Failed to send group sync message due to error: %@.", error);
resolve(error);
}];
}];
[promise retainUntilComplete];
return promise;
}
}
- (AnyPromise *)syncAllOpenGroups

View File

@ -101,7 +101,7 @@ public final class ClosedGroupsProtocol : NSObject {
// Send closed group update messages to the new members (and their linked devices) using established channels
let allSenderKeys = [ClosedGroupSenderKey](Storage.getAllClosedGroupSenderKeys(for: groupPublicKey)) // This includes the newly generated sender keys
for member in newMembers { // Not `newMembersAndLinkedDevices` as this internally takes care of multi device already
let thread = TSContactThread.getOrCreateThread(contactId: member)
let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction)
thread.save(with: transaction)
let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name,
groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: allSenderKeys, members: members, admins: admins)

View File

@ -124,6 +124,7 @@ public final class SharedSenderKeysImplementation : NSObject, SharedSenderKeysPr
}
}
// MARK: Public API
@objc(encrypt:forGroupWithPublicKey:senderPublicKey:protocolContext:error:)
public func encrypt(_ plaintext: Data, forGroupWithPublicKey groupPublicKey: String, senderPublicKey: String, protocolContext: Any) throws -> [Any] {
let transaction = protocolContext as! YapDatabaseReadWriteTransaction
@ -152,7 +153,7 @@ public final class SharedSenderKeysImplementation : NSObject, SharedSenderKeysPr
let iv = ivAndCiphertext[0..<Int(SharedSenderKeysImplementation.ivSize)]
let ciphertext = ivAndCiphertext[Int(SharedSenderKeysImplementation.ivSize)...]
let gcm = GCM(iv: iv.bytes, tagLength: Int(SharedSenderKeysImplementation.gcmTagSize), mode: .combined)
let messageKey = ratchet.messageKeys.last!
guard let messageKey = ratchet.messageKeys.last else { throw RatchetingError.messageKeyMissing(targetKeyIndex: keyIndex, groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) }
let aes = try AES(key: Data(hex: messageKey).bytes, blockMode: gcm, padding: .noPadding)
return Data(try aes.decrypt(ciphertext.bytes))
}

View File

@ -17,6 +17,14 @@ public final class SyncMessagesProtocol : NSObject {
internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
// MARK: - Error
@objc(LKSyncMessagesProtocolError)
public class SyncMessagesProtocolError : NSError { // Not called `Error` for Obj-C interoperablity
@objc public static let privateKeyMissing = SyncMessagesProtocolError(domain: "SyncMessagesProtocolErrorDomain", code: 1, userInfo: [ NSLocalizedDescriptionKey : "Couldn't get private key for SSK based closed group." ])
}
// MARK: - Sending
@objc public static func syncProfile() {
@ -59,6 +67,51 @@ public final class SyncMessagesProtocol : NSObject {
return AnyPromise.from(when(fulfilled: promises))
}
@objc(syncClosedGroup:transaction:)
public static func syncClosedGroup(_ thread: TSGroupThread, using transaction: YapDatabaseReadWriteTransaction) -> AnyPromise {
// Prepare
let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
let group = thread.groupModel
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(group.groupId)
let name = group.groupName!
let members = group.groupMemberIds
let admins = group.groupAdminIds
guard let groupPrivateKey = Storage.getClosedGroupPrivateKey(for: groupPublicKey) else {
print("[Loki] Couldn't get private key for SSK based closed group.")
return AnyPromise.from(Promise<Void>(error: SyncMessagesProtocolError.privateKeyMissing))
}
// Generate ratchets for the user's linked devices
let userPublicKey = getUserHexEncodedPublicKey()
let masterPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? userPublicKey
let deviceLinks = storage.getDeviceLinks(for: userPublicKey, in: transaction)
let linkedDevices = deviceLinks.flatMap { [ $0.master.hexEncodedPublicKey, $0.slave.hexEncodedPublicKey ] }.filter { $0 != userPublicKey }
let senderKeys: [ClosedGroupSenderKey] = linkedDevices.map { publicKey in
let ratchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: publicKey, using: transaction)
return ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: publicKey)
}
// Send a closed group update message to the existing members with the linked devices' ratchets (this message is aimed at the group)
func sendMessageToGroup() {
let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: senderKeys,
members: members, admins: admins)
let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind)
messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction)
}
sendMessageToGroup()
// Send closed group update messages to the linked devices using established channels
func sendMessageToLinkedDevices() {
let allSenderKeys = [ClosedGroupSenderKey](Storage.getAllClosedGroupSenderKeys(for: groupPublicKey)) // This includes the newly generated sender keys
let thread = TSContactThread.getOrCreateThread(withContactId: masterPublicKey, transaction: transaction)
thread.save(with: transaction)
let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name,
groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: allSenderKeys, members: members, admins: admins)
let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind)
messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // This internally takes care of multi device
}
sendMessageToLinkedDevices()
// Return a dummy promise
return AnyPromise.from(Promise<Void> { $0.fulfill(()) })
}
@objc public static func syncAllClosedGroups() -> AnyPromise {
var closedGroups: [TSGroupThread] = []
TSGroupThread.enumerateCollectionObjects { object, _ in

View File

@ -543,10 +543,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
[recipientIds addObject:recipientContactId];
if ([recipientIds containsObject:self.tsAccountManager.localNumber]) {
OWSFailDebug(@"Message send recipients should not include self.");
}
} else {
OWSFailDebug(@"Unknown message type: %@", [message class]);
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
@ -692,7 +688,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
// In the "self-send" special case, we ony need to send a sync message with a delivery receipt
// Loki: Take into account multi device
if ([LKSessionMetaProtocol isThreadNoteToSelf:thread] && !([message isKindOfClass:LKDeviceLinkMessage.class])) {
if ([LKSessionMetaProtocol isThreadNoteToSelf:thread]
&& !([message isKindOfClass:LKDeviceLinkMessage.class]) && !([message isKindOfClass:LKClosedGroupUpdateMessage.class])) {
// Don't mark self-sent messages as read (or sent) until the sync transcript is sent
successHandler();
return;
@ -1135,7 +1132,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
if ([messageSend.thread isKindOfClass:TSGroupThread.class] && ((TSGroupThread *)messageSend.thread).usesSharedSenderKeys) {
senderID = [LKGroupUtilities getDecodedGroupID:((TSGroupThread *)messageSend.thread).groupModel.groupId];
} else {
[LKLogger print:@"Non-UD send"];
[LKLogger print:@"[Loki] Non-UD send"];
}
uint32_t senderDeviceID = type == SSKProtoEnvelopeTypeUnidentifiedSender ? 0 : OWSDevicePrimaryDeviceId;
NSString *content = signalMessageInfo[@"content"];
@ -1436,7 +1433,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
// Don't mark self-sent messages as read (or sent) until the sync transcript is sent
// Loki: Take into account multi device
BOOL isNoteToSelf = [LKSessionMetaProtocol isThreadNoteToSelf:message.thread];
if (isNoteToSelf && !([message isKindOfClass:LKDeviceLinkMessage.class])) {
if (isNoteToSelf && !([message isKindOfClass:LKDeviceLinkMessage.class])
&& ![message isKindOfClass:LKClosedGroupUpdateMessage.class]) {
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *recipientId in message.sendingRecipientIds) {
[message updateWithReadRecipientId:recipientId readTimestamp:message.timestamp transaction:transaction];