From 9527efebe26504a3cd11483ab8e59a24aa00b0ba Mon Sep 17 00:00:00 2001 From: Nora Trapp Date: Mon, 1 Jul 2019 19:05:10 -0700 Subject: [PATCH 1/4] Update protos --- SignalServiceKit/protobuf/SignalService.proto | 4 +- .../src/Protos/Generated/SSKProto.swift | 53 +++++++++++++------ .../Protos/Generated/SignalService.pb.swift | 41 +++++++++----- ...oEnvelope+OWS.swift => SSKProto+OWS.swift} | 50 +++++++++++++++++ 4 files changed, 116 insertions(+), 32 deletions(-) rename SignalServiceKit/src/Protos/{SSKProtoEnvelope+OWS.swift => SSKProto+OWS.swift} (51%) diff --git a/SignalServiceKit/protobuf/SignalService.proto b/SignalServiceKit/protobuf/SignalService.proto index 515ef981ad..2a6954fd2f 100644 --- a/SignalServiceKit/protobuf/SignalService.proto +++ b/SignalServiceKit/protobuf/SignalService.proto @@ -317,8 +317,8 @@ message SyncMessage { } message Read { - // @required - optional string sender = 1; + optional string senderE164 = 1; + optional string senderUuid = 3; // @required optional uint64 timestamp = 2; } diff --git a/SignalServiceKit/src/Protos/Generated/SSKProto.swift b/SignalServiceKit/src/Protos/Generated/SSKProto.swift index b4f162cc68..47dd456cc0 100644 --- a/SignalServiceKit/src/Protos/Generated/SSKProto.swift +++ b/SignalServiceKit/src/Protos/Generated/SSKProto.swift @@ -4508,13 +4508,19 @@ extension SSKProtoSyncMessageRequest.SSKProtoSyncMessageRequestBuilder { // MARK: - SSKProtoSyncMessageReadBuilder - @objc public class func builder(sender: String, timestamp: UInt64) -> SSKProtoSyncMessageReadBuilder { - return SSKProtoSyncMessageReadBuilder(sender: sender, timestamp: timestamp) + @objc public class func builder(timestamp: UInt64) -> SSKProtoSyncMessageReadBuilder { + return SSKProtoSyncMessageReadBuilder(timestamp: timestamp) } // asBuilder() constructs a builder that reflects the proto's contents. @objc public func asBuilder() -> SSKProtoSyncMessageReadBuilder { - let builder = SSKProtoSyncMessageReadBuilder(sender: sender, timestamp: timestamp) + let builder = SSKProtoSyncMessageReadBuilder(timestamp: timestamp) + if let _value = senderE164 { + builder.setSenderE164(_value) + } + if let _value = senderUuid { + builder.setSenderUuid(_value) + } return builder } @@ -4524,15 +4530,18 @@ extension SSKProtoSyncMessageRequest.SSKProtoSyncMessageRequestBuilder { @objc fileprivate override init() {} - @objc fileprivate init(sender: String, timestamp: UInt64) { + @objc fileprivate init(timestamp: UInt64) { super.init() - setSender(sender) setTimestamp(timestamp) } - @objc public func setSender(_ valueParam: String) { - proto.sender = valueParam + @objc public func setSenderE164(_ valueParam: String) { + proto.senderE164 = valueParam + } + + @objc public func setSenderUuid(_ valueParam: String) { + proto.senderUuid = valueParam } @objc public func setTimestamp(_ valueParam: UInt64) { @@ -4550,15 +4559,31 @@ extension SSKProtoSyncMessageRequest.SSKProtoSyncMessageRequestBuilder { fileprivate let proto: SignalServiceProtos_SyncMessage.Read - @objc public let sender: String - @objc public let timestamp: UInt64 + @objc public var senderE164: String? { + guard proto.hasSenderE164 else { + return nil + } + return proto.senderE164 + } + @objc public var hasSenderE164: Bool { + return proto.hasSenderE164 + } + + @objc public var senderUuid: String? { + guard proto.hasSenderUuid else { + return nil + } + return proto.senderUuid + } + @objc public var hasSenderUuid: Bool { + return proto.hasSenderUuid + } + private init(proto: SignalServiceProtos_SyncMessage.Read, - sender: String, timestamp: UInt64) { self.proto = proto - self.sender = sender self.timestamp = timestamp } @@ -4573,11 +4598,6 @@ extension SSKProtoSyncMessageRequest.SSKProtoSyncMessageRequestBuilder { } fileprivate class func parseProto(_ proto: SignalServiceProtos_SyncMessage.Read) throws -> SSKProtoSyncMessageRead { - guard proto.hasSender else { - throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: sender") - } - let sender = proto.sender - guard proto.hasTimestamp else { throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: timestamp") } @@ -4588,7 +4608,6 @@ extension SSKProtoSyncMessageRequest.SSKProtoSyncMessageRequestBuilder { // MARK: - End Validation Logic for SSKProtoSyncMessageRead - let result = SSKProtoSyncMessageRead(proto: proto, - sender: sender, timestamp: timestamp) return result } diff --git a/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift b/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift index 5ce347f13a..6cb1c523d4 100644 --- a/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift +++ b/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift @@ -1876,15 +1876,23 @@ struct SignalServiceProtos_SyncMessage { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - /// @required - var sender: String { - get {return _sender ?? String()} - set {_sender = newValue} + var senderE164: String { + get {return _senderE164 ?? String()} + set {_senderE164 = newValue} } - /// Returns true if `sender` has been explicitly set. - var hasSender: Bool {return self._sender != nil} - /// Clears the value of `sender`. Subsequent reads from it will return its default value. - mutating func clearSender() {self._sender = nil} + /// Returns true if `senderE164` has been explicitly set. + var hasSenderE164: Bool {return self._senderE164 != nil} + /// Clears the value of `senderE164`. Subsequent reads from it will return its default value. + mutating func clearSenderE164() {self._senderE164 = nil} + + var senderUuid: String { + get {return _senderUuid ?? String()} + set {_senderUuid = newValue} + } + /// Returns true if `senderUuid` has been explicitly set. + var hasSenderUuid: Bool {return self._senderUuid != nil} + /// Clears the value of `senderUuid`. Subsequent reads from it will return its default value. + mutating func clearSenderUuid() {self._senderUuid = nil} /// @required var timestamp: UInt64 { @@ -1900,7 +1908,8 @@ struct SignalServiceProtos_SyncMessage { init() {} - fileprivate var _sender: String? = nil + fileprivate var _senderE164: String? = nil + fileprivate var _senderUuid: String? = nil fileprivate var _timestamp: UInt64? = nil } @@ -4642,32 +4651,38 @@ extension SignalServiceProtos_SyncMessage.Request.TypeEnum: SwiftProtobuf._Proto extension SignalServiceProtos_SyncMessage.Read: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = SignalServiceProtos_SyncMessage.protoMessageName + ".Read" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "sender"), + 1: .same(proto: "senderE164"), + 3: .same(proto: "senderUuid"), 2: .same(proto: "timestamp"), ] mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { switch fieldNumber { - case 1: try decoder.decodeSingularStringField(value: &self._sender) + case 1: try decoder.decodeSingularStringField(value: &self._senderE164) case 2: try decoder.decodeSingularUInt64Field(value: &self._timestamp) + case 3: try decoder.decodeSingularStringField(value: &self._senderUuid) default: break } } } func traverse(visitor: inout V) throws { - if let v = self._sender { + if let v = self._senderE164 { try visitor.visitSingularStringField(value: v, fieldNumber: 1) } if let v = self._timestamp { try visitor.visitSingularUInt64Field(value: v, fieldNumber: 2) } + if let v = self._senderUuid { + try visitor.visitSingularStringField(value: v, fieldNumber: 3) + } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: SignalServiceProtos_SyncMessage.Read, rhs: SignalServiceProtos_SyncMessage.Read) -> Bool { - if lhs._sender != rhs._sender {return false} + if lhs._senderE164 != rhs._senderE164 {return false} + if lhs._senderUuid != rhs._senderUuid {return false} if lhs._timestamp != rhs._timestamp {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true diff --git a/SignalServiceKit/src/Protos/SSKProtoEnvelope+OWS.swift b/SignalServiceKit/src/Protos/SSKProto+OWS.swift similarity index 51% rename from SignalServiceKit/src/Protos/SSKProtoEnvelope+OWS.swift rename to SignalServiceKit/src/Protos/SSKProto+OWS.swift index 66b3e1ff02..37f3f9d678 100644 --- a/SignalServiceKit/src/Protos/SSKProtoEnvelope+OWS.swift +++ b/SignalServiceKit/src/Protos/SSKProto+OWS.swift @@ -53,3 +53,53 @@ public extension SSKProtoEnvelope { return address } } + +@objc +public extension SSKProtoSyncMessageRead { + var hasValidSender: Bool { + return senderAddress != nil + } + + var senderAddress: SignalServiceAddress? { + let uuidString: String? = { + guard hasSenderUuid else { + return nil + } + + guard let senderUuid = senderUuid else { + owsFailDebug("senderUuid was unexpectedly nil") + return nil + } + + return senderUuid + }() + + let phoneNumber: String? = { + guard hasSenderE164 else { + // Shouldn't happen in prod yet + assert(FeatureFlags.allowUUIDOnlyContacts) + return nil + } + + guard let senderE164 = senderE164 else { + owsFailDebug("senderE164 was unexpectedly nil") + return nil + } + + guard senderE164.count > 0 else { + owsFailDebug("senderE164 was unexpectedly empty") + return nil + } + + return senderE164 + }() + + let address = SignalServiceAddress(uuidString: uuidString, phoneNumber: phoneNumber) + guard address.isValid else { + owsFailDebug("address was unexpectedly invalid") + return nil + } + + return address + } +} From 4f026ed881562b8a48d329ec933f227fbfe5a99f Mon Sep 17 00:00:00 2001 From: Nora Trapp Date: Mon, 1 Jul 2019 19:08:18 -0700 Subject: [PATCH 2/4] Add UUID support to read receipts --- Pods | 2 +- .../OWSLinkedDeviceReadReceipt+SDS.swift | 42 +++-- .../src/Devices/OWSLinkedDeviceReadReceipt.h | 22 ++- .../src/Devices/OWSLinkedDeviceReadReceipt.m | 63 ++++--- .../OWSLinkedDeviceReadReceiptFinder.swift | 168 ++++++++++++++++++ .../OWSReadReceiptsForLinkedDevicesMessage.m | 7 +- .../src/Messages/OWSOutgoingReceiptManager.h | 5 +- .../src/Messages/OWSOutgoingReceiptManager.m | 122 +++++++++---- .../src/Messages/OWSReadReceiptManager.m | 36 ++-- .../Storage/Database/SDSDatabaseStorage.swift | 13 ++ .../src/Storage/Database/SSKAccessors+SDS.h | 10 ++ .../src/Storage/OWSPrimaryStorage.m | 1 + 12 files changed, 396 insertions(+), 95 deletions(-) create mode 100644 SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceiptFinder.swift diff --git a/Pods b/Pods index f5635a9cc5..8c231c2782 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit f5635a9cc5cb9aaec40cc2e3a3760148f6b7597d +Subproject commit 8c231c2782d70885e11d37fbe5b8f1176b19b606 diff --git a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt+SDS.swift b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt+SDS.swift index f24905773f..aab51ebfd1 100644 --- a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt+SDS.swift +++ b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt+SDS.swift @@ -26,17 +26,23 @@ public struct LinkedDeviceReadReceiptRecord: SDSRecord { public let uniqueId: String // Base class properties + public let linkedDeviceReadReceiptSchemaVersion: UInt public let messageIdTimestamp: UInt64 public let readTimestamp: UInt64 - public let senderId: String + public let senderAddress: Data + public let senderPhoneNumber: String? + public let senderUUID: String? public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable { case id case recordType case uniqueId + case linkedDeviceReadReceiptSchemaVersion case messageIdTimestamp case readTimestamp - case senderId + case senderAddress + case senderPhoneNumber + case senderUUID } public static func columnName(_ column: LinkedDeviceReadReceiptRecord.CodingKeys, fullyQualified: Bool = false) -> String { @@ -72,14 +78,21 @@ extension OWSLinkedDeviceReadReceipt { case .linkedDeviceReadReceipt: let uniqueId: String = record.uniqueId + let linkedDeviceReadReceiptSchemaVersion: UInt = record.linkedDeviceReadReceiptSchemaVersion let messageIdTimestamp: UInt64 = record.messageIdTimestamp let readTimestamp: UInt64 = record.readTimestamp - let senderId: String = record.senderId + let senderAddressSerialized: Data = record.senderAddress + let senderAddress: SignalServiceAddress = try SDSDeserialization.unarchive(senderAddressSerialized, name: "senderAddress") + let senderPhoneNumber: String? = record.senderPhoneNumber + let senderUUID: String? = record.senderUUID return OWSLinkedDeviceReadReceipt(uniqueId: uniqueId, + linkedDeviceReadReceiptSchemaVersion: linkedDeviceReadReceiptSchemaVersion, messageIdTimestamp: messageIdTimestamp, readTimestamp: readTimestamp, - senderId: senderId) + senderAddress: senderAddress, + senderPhoneNumber: senderPhoneNumber, + senderUUID: senderUUID) default: owsFailDebug("Unexpected record type: \(record.recordType)") @@ -116,9 +129,12 @@ extension OWSLinkedDeviceReadReceiptSerializer { static let idColumn = SDSColumnMetadata(columnName: "id", columnType: .primaryKey, columnIndex: 1) static let uniqueIdColumn = SDSColumnMetadata(columnName: "uniqueId", columnType: .unicodeString, columnIndex: 2) // Base class properties - static let messageIdTimestampColumn = SDSColumnMetadata(columnName: "messageIdTimestamp", columnType: .int64, columnIndex: 3) - static let readTimestampColumn = SDSColumnMetadata(columnName: "readTimestamp", columnType: .int64, columnIndex: 4) - static let senderIdColumn = SDSColumnMetadata(columnName: "senderId", columnType: .unicodeString, columnIndex: 5) + static let linkedDeviceReadReceiptSchemaVersionColumn = SDSColumnMetadata(columnName: "linkedDeviceReadReceiptSchemaVersion", columnType: .int64, columnIndex: 3) + static let messageIdTimestampColumn = SDSColumnMetadata(columnName: "messageIdTimestamp", columnType: .int64, columnIndex: 4) + static let readTimestampColumn = SDSColumnMetadata(columnName: "readTimestamp", columnType: .int64, columnIndex: 5) + static let senderAddressColumn = SDSColumnMetadata(columnName: "senderAddress", columnType: .blob, columnIndex: 6) + static let senderPhoneNumberColumn = SDSColumnMetadata(columnName: "senderPhoneNumber", columnType: .unicodeString, isOptional: true, columnIndex: 7) + static let senderUUIDColumn = SDSColumnMetadata(columnName: "senderUUID", columnType: .unicodeString, isOptional: true, columnIndex: 8) // TODO: We should decide on a naming convention for // tables that store models. @@ -126,9 +142,12 @@ extension OWSLinkedDeviceReadReceiptSerializer { recordTypeColumn, idColumn, uniqueIdColumn, + linkedDeviceReadReceiptSchemaVersionColumn, messageIdTimestampColumn, readTimestampColumn, - senderIdColumn + senderAddressColumn, + senderPhoneNumberColumn, + senderUUIDColumn ]) } @@ -454,10 +473,13 @@ class OWSLinkedDeviceReadReceiptSerializer: SDSSerializer { } // Base class properties + let linkedDeviceReadReceiptSchemaVersion: UInt = model.linkedDeviceReadReceiptSchemaVersion let messageIdTimestamp: UInt64 = model.messageIdTimestamp let readTimestamp: UInt64 = model.readTimestamp - let senderId: String = model.senderId + let senderAddress: Data = requiredArchive(model.senderAddress) + let senderPhoneNumber: String? = model.senderPhoneNumber + let senderUUID: String? = model.senderUUID - return LinkedDeviceReadReceiptRecord(id: id, recordType: recordType, uniqueId: uniqueId, messageIdTimestamp: messageIdTimestamp, readTimestamp: readTimestamp, senderId: senderId) + return LinkedDeviceReadReceiptRecord(id: id, recordType: recordType, uniqueId: uniqueId, linkedDeviceReadReceiptSchemaVersion: linkedDeviceReadReceiptSchemaVersion, messageIdTimestamp: messageIdTimestamp, readTimestamp: readTimestamp, senderAddress: senderAddress, senderPhoneNumber: senderPhoneNumber, senderUUID: senderUUID) } } diff --git a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h index 5717177678..a4febd0ad7 100644 --- a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h +++ b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h @@ -7,16 +7,17 @@ NS_ASSUME_NONNULL_BEGIN @class SDSAnyReadTransaction; +@class SignalServiceAddress; @interface OWSLinkedDeviceReadReceipt : BaseModel -@property (nonatomic, readonly) NSString *senderId; +@property (nonatomic, readonly) SignalServiceAddress *senderAddress; @property (nonatomic, readonly) uint64_t messageIdTimestamp; @property (nonatomic, readonly) uint64_t readTimestamp; -- (instancetype)initWithSenderId:(NSString *)senderId - messageIdTimestamp:(uint64_t)messageIdtimestamp - readTimestamp:(uint64_t)readTimestamp; +- (instancetype)initWithSenderAddress:(SignalServiceAddress *)address + messageIdTimestamp:(uint64_t)messageIdtimestamp + readTimestamp:(uint64_t)readTimestamp; // --- CODE GENERATION MARKER @@ -26,18 +27,21 @@ NS_ASSUME_NONNULL_BEGIN // clang-format off - (instancetype)initWithUniqueId:(NSString *)uniqueId +linkedDeviceReadReceiptSchemaVersion:(NSUInteger)linkedDeviceReadReceiptSchemaVersion messageIdTimestamp:(uint64_t)messageIdTimestamp readTimestamp:(uint64_t)readTimestamp - senderId:(NSString *)senderId -NS_SWIFT_NAME(init(uniqueId:messageIdTimestamp:readTimestamp:senderId:)); + senderAddress:(SignalServiceAddress *)senderAddress + senderPhoneNumber:(nullable NSString *)senderPhoneNumber + senderUUID:(nullable NSString *)senderUUID +NS_SWIFT_NAME(init(uniqueId:linkedDeviceReadReceiptSchemaVersion:messageIdTimestamp:readTimestamp:senderAddress:senderPhoneNumber:senderUUID:)); // clang-format on // --- CODE GENERATION MARKER -+ (nullable OWSLinkedDeviceReadReceipt *)findLinkedDeviceReadReceiptWithSenderId:(NSString *)senderId - messageIdTimestamp:(uint64_t)messageIdTimestamp - transaction:(SDSAnyReadTransaction *)transaction; ++ (nullable OWSLinkedDeviceReadReceipt *)findLinkedDeviceReadReceiptWithAddress:(SignalServiceAddress *)address + messageIdTimestamp:(uint64_t)messageIdTimestamp + transaction:(SDSAnyReadTransaction *)transaction; @end diff --git a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m index 9025158559..fa8349df5b 100644 --- a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m +++ b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m @@ -7,24 +7,34 @@ NS_ASSUME_NONNULL_BEGIN +NSUInteger const OWSLinkedDeviceReadReceiptSchemaVersion = 1; + +@interface OWSLinkedDeviceReadReceipt () + +@property (nonatomic, nullable, readonly) NSString *senderPhoneNumber; +@property (nonatomic, nullable, readonly) NSString *senderUUID; +@property (nonatomic, readonly) NSUInteger linkedDeviceReadReceiptSchemaVersion; + +@end + @implementation OWSLinkedDeviceReadReceipt -- (instancetype)initWithSenderId:(NSString *)senderId - messageIdTimestamp:(uint64_t)messageIdTimestamp - readTimestamp:(uint64_t)readTimestamp +- (instancetype)initWithSenderAddress:(SignalServiceAddress *)address + messageIdTimestamp:(uint64_t)messageIdTimestamp + readTimestamp:(uint64_t)readTimestamp { - OWSAssertDebug(senderId.length > 0 && messageIdTimestamp > 0); + OWSAssertDebug(address.isValid && messageIdTimestamp > 0); - NSString *receiptId = - [OWSLinkedDeviceReadReceipt uniqueIdForSenderId:senderId messageIdTimestamp:messageIdTimestamp]; - self = [super initWithUniqueId:receiptId]; + self = [super init]; if (!self) { return self; } - _senderId = senderId; + _senderPhoneNumber = address.phoneNumber; + _senderUUID = address.uuidString; _messageIdTimestamp = messageIdTimestamp; _readTimestamp = readTimestamp; + _linkedDeviceReadReceiptSchemaVersion = OWSLinkedDeviceReadReceiptSchemaVersion; return self; } @@ -51,6 +61,13 @@ NS_ASSUME_NONNULL_BEGIN _readTimestamp = _messageIdTimestamp; } + if (_linkedDeviceReadReceiptSchemaVersion < 1) { + _senderPhoneNumber = [coder decodeObjectForKey:@"senderId"]; + OWSAssertDebug(_senderPhoneNumber); + } + + _linkedDeviceReadReceiptSchemaVersion = OWSLinkedDeviceReadReceiptSchemaVersion; + return self; } @@ -62,9 +79,12 @@ NS_ASSUME_NONNULL_BEGIN // clang-format off - (instancetype)initWithUniqueId:(NSString *)uniqueId +linkedDeviceReadReceiptSchemaVersion:(NSUInteger)linkedDeviceReadReceiptSchemaVersion messageIdTimestamp:(uint64_t)messageIdTimestamp readTimestamp:(uint64_t)readTimestamp - senderId:(NSString *)senderId + senderAddress:(SignalServiceAddress *)senderAddress + senderPhoneNumber:(nullable NSString *)senderPhoneNumber + senderUUID:(nullable NSString *)senderUUID { self = [super initWithUniqueId:uniqueId]; @@ -72,9 +92,12 @@ NS_ASSUME_NONNULL_BEGIN return self; } + _linkedDeviceReadReceiptSchemaVersion = linkedDeviceReadReceiptSchemaVersion; _messageIdTimestamp = messageIdTimestamp; _readTimestamp = readTimestamp; - _senderId = senderId; + _senderAddress = senderAddress; + _senderPhoneNumber = senderPhoneNumber; + _senderUUID = senderUUID; return self; } @@ -83,21 +106,21 @@ NS_ASSUME_NONNULL_BEGIN // --- CODE GENERATION MARKER -+ (NSString *)uniqueIdForSenderId:(NSString *)senderId messageIdTimestamp:(uint64_t)messageIdTimestamp ++ (AnyLinkedDeviceReadReceiptFinder *)finder { - OWSAssertDebug(senderId.length > 0 && messageIdTimestamp > 0); - - return [NSString stringWithFormat:@"%@-%llu", senderId, messageIdTimestamp]; + return [AnyLinkedDeviceReadReceiptFinder new]; } -+ (nullable OWSLinkedDeviceReadReceipt *)findLinkedDeviceReadReceiptWithSenderId:(NSString *)senderId - messageIdTimestamp:(uint64_t)messageIdTimestamp - transaction:(SDSAnyReadTransaction *)transaction ++ (nullable OWSLinkedDeviceReadReceipt *)findLinkedDeviceReadReceiptWithAddress:(SignalServiceAddress *)address + messageIdTimestamp:(uint64_t)messageIdTimestamp + transaction:(SDSAnyReadTransaction *)transaction { + OWSAssertDebug(address.isValid); OWSAssertDebug(transaction); - NSString *receiptId = - [OWSLinkedDeviceReadReceipt uniqueIdForSenderId:senderId messageIdTimestamp:messageIdTimestamp]; - return [OWSLinkedDeviceReadReceipt anyFetchWithUniqueId:receiptId transaction:transaction]; + + return [self.finder linkedDeviceReadReceiptForAddress:address + messageIdTimestamp:messageIdTimestamp + transaction:transaction]; } @end diff --git a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceiptFinder.swift b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceiptFinder.swift new file mode 100644 index 0000000000..fdd535311c --- /dev/null +++ b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceiptFinder.swift @@ -0,0 +1,168 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +class AnyLinkedDeviceReadReceiptFinder: NSObject { + let grdbAdapter = GRDBLinkedDeviceReadReceiptFinder() + let yapdbAdapter = YAPDBLinkedDeviceReadReceiptFinder() +} + +extension AnyLinkedDeviceReadReceiptFinder { + @objc(linkedDeviceReadReceiptForAddress:messageIdTimestamp:transaction:) + func linkedDeviceReadReceipt(for address: SignalServiceAddress, and timestamp: UInt64, transaction: SDSAnyReadTransaction) -> OWSLinkedDeviceReadReceipt? { + switch transaction.readTransaction { + case .grdbRead(let transaction): + return grdbAdapter.linkedDeviceReadReceipt(for: address, and: timestamp, transaction: transaction) + case .yapRead(let transaction): + return yapdbAdapter.linkedDeviceReadReceipt(for: address, and: timestamp, transaction: transaction) + } + } +} + +@objc +class GRDBLinkedDeviceReadReceiptFinder: NSObject { + func linkedDeviceReadReceipt(for address: SignalServiceAddress, and timestamp: UInt64, transaction: GRDBReadTransaction) -> OWSLinkedDeviceReadReceipt? { + if let thread = linkedDeviceReadReceiptForUUID(address.uuid, and: timestamp, transaction: transaction) { + return thread + } else if let thread = linkedDeviceReadReceiptForPhoneNumber(address.phoneNumber, and: timestamp, transaction: transaction) { + return thread + } else { + return nil + } + } + + private func linkedDeviceReadReceiptForUUID(_ uuid: UUID?, and timestamp: UInt64, transaction: GRDBReadTransaction) -> OWSLinkedDeviceReadReceipt? { + guard let uuidString = uuid?.uuidString else { return nil } + let sql = "SELECT * FROM \(LinkedDeviceReadReceiptRecord.databaseTableName) WHERE \(linkedDeviceReadReceiptColumn: .senderUUID) = ? AND \(linkedDeviceReadReceiptColumn: .messageIdTimestamp) = ?" + return OWSLinkedDeviceReadReceipt.grdbFetchOne(sql: sql, arguments: [uuidString, timestamp], transaction: transaction) + } + + private func linkedDeviceReadReceiptForPhoneNumber(_ phoneNumber: String?, and timestamp: UInt64, transaction: GRDBReadTransaction) -> OWSLinkedDeviceReadReceipt? { + guard let phoneNumber = phoneNumber else { return nil } + let sql = "SELECT * FROM \(LinkedDeviceReadReceiptRecord.databaseTableName) WHERE \(linkedDeviceReadReceiptColumn: .senderPhoneNumber) = ? AND \(linkedDeviceReadReceiptColumn: .messageIdTimestamp) = ?" + return OWSLinkedDeviceReadReceipt.grdbFetchOne(sql: sql, arguments: [phoneNumber, timestamp], transaction: transaction) + } +} + +@objc +class YAPDBLinkedDeviceReadReceiptFinder: NSObject { + private static let uuidKey = "uuidKey" + private static let phoneNumberKey = "phoneNumberKey" + private static let timestampKey = "timestampKey" + + private static let phoneNumberIndexName = "index_linkedDeviceReadReceipt_on_senderPhoneNumberAndTimestamp" + private static let uuidIndexName = "index_linkedDeviceReadReceipt_on_senderUUIDAndTimestamp" + + func linkedDeviceReadReceipt(for address: SignalServiceAddress, and timestamp: UInt64, transaction: YapDatabaseReadTransaction) -> OWSLinkedDeviceReadReceipt? { + if let result = linkedDeviceReadReceiptForUUID(address.uuid, and: timestamp, transaction: transaction) { + return result + } else if let result = linkedDeviceReadReceiptForPhoneNumber(address.phoneNumber, and: timestamp, transaction: transaction) { + return result + } else { + return nil + } + } + + private func linkedDeviceReadReceiptForUUID(_ uuid: UUID?, and timestamp: UInt64, transaction: YapDatabaseReadTransaction) -> OWSLinkedDeviceReadReceipt? { + guard let uuidString = uuid?.uuidString else { return nil } + + guard let ext = transaction.ext(YAPDBLinkedDeviceReadReceiptFinder.uuidIndexName) as? YapDatabaseSecondaryIndexTransaction else { + owsFailDebug("Unexpected transaction type for extension") + return nil + } + + let queryFormat = String( + format: "WHERE %@ = \"%@\" AND %@ = \"%lld\"", + YAPDBLinkedDeviceReadReceiptFinder.uuidKey, + uuidString, + YAPDBLinkedDeviceReadReceiptFinder.timestampKey, + timestamp + ) + let query = YapDatabaseQuery(string: queryFormat, parameters: []) + + var matchedRecord: OWSLinkedDeviceReadReceipt? + + ext.enumerateKeysAndObjects(matching: query) { _, _, object, stop in + guard let record = object as? OWSLinkedDeviceReadReceipt else { + return + } + matchedRecord = record + stop.pointee = true + } + + return matchedRecord + } + + private func linkedDeviceReadReceiptForPhoneNumber(_ phoneNumber: String?, and timestamp: UInt64, transaction: YapDatabaseReadTransaction) -> OWSLinkedDeviceReadReceipt? { + guard let phoneNumber = phoneNumber else { return nil } + + guard let ext = transaction.ext(YAPDBLinkedDeviceReadReceiptFinder.phoneNumberIndexName) as? YapDatabaseSecondaryIndexTransaction else { + owsFailDebug("Unexpected transaction type for extension") + return nil + } + + let queryFormat = String( + format: "WHERE %@ = \"%@\" AND %@ = \"%lld\"", + YAPDBLinkedDeviceReadReceiptFinder.phoneNumberKey, + phoneNumber, + YAPDBLinkedDeviceReadReceiptFinder.timestampKey, + timestamp + ) + let query = YapDatabaseQuery(string: queryFormat, parameters: []) + + var matchedRecord: OWSLinkedDeviceReadReceipt? + + ext.enumerateKeysAndObjects(matching: query) { _, _, object, stop in + guard let record = object as? OWSLinkedDeviceReadReceipt else { + return + } + matchedRecord = record + stop.pointee = true + } + + return matchedRecord + } + + @objc + static func asyncRegisterDatabaseExtensions(_ storage: OWSStorage) { + storage.asyncRegister(indexUUIDExtension(), withName: uuidIndexName) + storage.asyncRegister(indexPhoneNumberExtension(), withName: phoneNumberIndexName) + } + + private static func indexUUIDExtension() -> YapDatabaseSecondaryIndex { + let setup = YapDatabaseSecondaryIndexSetup() + setup.addColumn(uuidKey, with: .text) + setup.addColumn(timestampKey, with: .integer) + + let handler = YapDatabaseSecondaryIndexHandler.withObjectBlock { _, dict, _, _, object in + guard let indexableObject = object as? OWSLinkedDeviceReadReceipt else { + return + } + + dict[uuidKey] = indexableObject.senderUUID + dict[timestampKey] = indexableObject.messageIdTimestamp + } + + return YapDatabaseSecondaryIndex(setup: setup, handler: handler, versionTag: "1") + } + + private static func indexPhoneNumberExtension() -> YapDatabaseSecondaryIndex { + let setup = YapDatabaseSecondaryIndexSetup() + setup.addColumn(phoneNumberKey, with: .text) + setup.addColumn(timestampKey, with: .integer) + + let handler = YapDatabaseSecondaryIndexHandler.withObjectBlock { _, dict, _, _, object in + guard let indexableObject = object as? OWSLinkedDeviceReadReceipt else { + return + } + + dict[phoneNumberKey] = indexableObject.senderPhoneNumber + dict[timestampKey] = indexableObject.messageIdTimestamp + } + + return YapDatabaseSecondaryIndex(setup: setup, handler: handler, versionTag: "1") + } +} diff --git a/SignalServiceKit/src/Devices/OWSReadReceiptsForLinkedDevicesMessage.m b/SignalServiceKit/src/Devices/OWSReadReceiptsForLinkedDevicesMessage.m index 92f7c5ca78..390fdee124 100644 --- a/SignalServiceKit/src/Devices/OWSReadReceiptsForLinkedDevicesMessage.m +++ b/SignalServiceKit/src/Devices/OWSReadReceiptsForLinkedDevicesMessage.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // #import "OWSReadReceiptsForLinkedDevicesMessage.h" @@ -38,7 +38,10 @@ NS_ASSUME_NONNULL_BEGIN SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; for (OWSLinkedDeviceReadReceipt *readReceipt in self.readReceipts) { SSKProtoSyncMessageReadBuilder *readProtoBuilder = - [SSKProtoSyncMessageRead builderWithSender:readReceipt.senderId timestamp:readReceipt.messageIdTimestamp]; + [SSKProtoSyncMessageRead builderWithTimestamp:readReceipt.messageIdTimestamp]; + + [readProtoBuilder setSenderE164:readReceipt.senderAddress.phoneNumber]; + [readProtoBuilder setSenderUuid:readReceipt.senderAddress.uuidString]; NSError *error; SSKProtoSyncMessageRead *_Nullable readProto = [readProtoBuilder buildAndReturnError:&error]; diff --git a/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.h b/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.h index 74f75baa32..4218557e9f 100644 --- a/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.h +++ b/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.h @@ -1,11 +1,12 @@ // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. // NS_ASSUME_NONNULL_BEGIN @class OWSPrimaryStorage; @class SSKProtoEnvelope; +@class SignalServiceAddress; @interface OWSOutgoingReceiptManager : NSObject @@ -15,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)enqueueDeliveryReceiptForEnvelope:(SSKProtoEnvelope *)envelope; -- (void)enqueueReadReceiptForEnvelope:(NSString *)messageAuthorId timestamp:(uint64_t)timestamp; +- (void)enqueueReadReceiptForAddress:(SignalServiceAddress *)messageAuthorAddress timestamp:(uint64_t)timestamp; @end diff --git a/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.m b/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.m index ee7d7d94db..dacad78171 100644 --- a/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.m +++ b/SignalServiceKit/src/Messages/OWSOutgoingReceiptManager.m @@ -165,15 +165,30 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa NSMutableArray *sendPromises = [NSMutableArray array]; - for (NSString *recipientId in queuedReceiptMap) { - NSSet *timestamps = queuedReceiptMap[recipientId]; + for (NSString *identifier in queuedReceiptMap) { + // The identifier could be either a UUID or a phone number, + // check if it's a valid UUID. If not, assume it's a phone number. + + SignalServiceAddress *address; + NSUUID *_Nullable uuid = [[NSUUID alloc] initWithUUIDString:identifier]; + if (uuid) { + address = [[SignalServiceAddress alloc] initWithUuid:uuid phoneNumber:nil]; + } else { + address = [[SignalServiceAddress alloc] initWithPhoneNumber:identifier]; + } + + if (!address.isValid) { + OWSFailDebug(@"Unexpected identifier."); + continue; + } + + NSSet *timestamps = queuedReceiptMap[identifier]; if (timestamps.count < 1) { OWSFailDebug(@"Missing timestamps."); continue; } - TSThread *thread = - [TSContactThread getOrCreateThreadWithContactAddress:recipientId.transitional_signalServiceAddress]; + TSThread *thread = [TSContactThread getOrCreateThreadWithContactAddress:address]; OWSReceiptsForSenderMessage *message; NSString *receiptName; switch (receiptType) { @@ -198,7 +213,7 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa // DURABLE CLEANUP - we could replace the custom durability logic in this class // with a durable JobQueue. - [self dequeueReceiptsWithRecipientId:recipientId timestamps:timestamps receiptType:receiptType]; + [self dequeueReceiptsForAddress:address timestamps:timestamps receiptType:receiptType]; // The value doesn't matter, we just need any non-NSError value. resolve(@(1)); @@ -208,7 +223,7 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa if (error.domain == OWSSignalServiceKitErrorDomain && error.code == OWSErrorCodeNoSuchSignalRecipient) { - [self dequeueReceiptsWithRecipientId:recipientId timestamps:timestamps receiptType:receiptType]; + [self dequeueReceiptsForAddress:address timestamps:timestamps receiptType:receiptType]; } resolve(error); @@ -222,66 +237,113 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa - (void)enqueueDeliveryReceiptForEnvelope:(SSKProtoEnvelope *)envelope { - [self enqueueReceiptWithRecipientId:envelope.sourceE164 - timestamp:envelope.timestamp - receiptType:OWSReceiptType_Delivery]; + [self enqueueReadReceiptForAddress:envelope.sourceAddress + timestamp:envelope.timestamp + receiptType:OWSReceiptType_Delivery]; } -- (void)enqueueReadReceiptForEnvelope:(NSString *)messageAuthorId timestamp:(uint64_t)timestamp { - [self enqueueReceiptWithRecipientId:messageAuthorId timestamp:timestamp receiptType:OWSReceiptType_Read]; +- (void)enqueueReadReceiptForAddress:(SignalServiceAddress *)address timestamp:(uint64_t)timestamp +{ + [self enqueueReadReceiptForAddress:address timestamp:timestamp receiptType:OWSReceiptType_Read]; } -- (void)enqueueReceiptWithRecipientId:(NSString *)recipientId - timestamp:(uint64_t)timestamp - receiptType:(OWSReceiptType)receiptType { +- (void)enqueueReadReceiptForAddress:(SignalServiceAddress *)address + timestamp:(uint64_t)timestamp + receiptType:(OWSReceiptType)receiptType +{ NSString *collection = [self collectionForReceiptType:receiptType]; - if (recipientId.length < 1) { - OWSFailDebug(@"Invalid recipient id."); - return; - } + OWSAssertDebug(address.isValid); if (timestamp < 1) { OWSFailDebug(@"Invalid timestamp."); return; } + dispatch_async(self.serialQueue, ^{ [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - NSSet *_Nullable oldTimestamps = [transaction objectForKey:recipientId inCollection:collection]; + NSString *identifier = address.uuidString ?: address.phoneNumber; + + NSSet *_Nullable oldUUIDTimestamps; + if (address.uuidString) { + oldUUIDTimestamps = [transaction objectForKey:address.uuidString inCollection:collection]; + } + + NSSet *_Nullable oldPhoneNumberTimestamps; + if (address.phoneNumber) { + oldPhoneNumberTimestamps = [transaction objectForKey:address.phoneNumber inCollection:collection]; + } + + NSSet *_Nullable oldTimestamps; + + // Unexpectedly have entries both on phone number and UUID, defer to UUID + if (oldUUIDTimestamps && oldPhoneNumberTimestamps) { + [transaction removeObjectForKey:address.phoneNumber inCollection:collection]; + + // If we have timestamps only under phone number, but know the UUID, migrate them lazily + } else if (oldPhoneNumberTimestamps && address.uuidString) { + [transaction removeObjectForKey:address.phoneNumber inCollection:collection]; + } + NSMutableSet *newTimestamps = (oldTimestamps ? [oldTimestamps mutableCopy] : [NSMutableSet new]); [newTimestamps addObject:@(timestamp)]; - [transaction setObject:newTimestamps forKey:recipientId inCollection:collection]; + [transaction setObject:newTimestamps forKey:identifier inCollection:collection]; }]; [self process]; }); } -- (void)dequeueReceiptsWithRecipientId:(NSString *)recipientId - timestamps:(NSSet *)timestamps - receiptType:(OWSReceiptType)receiptType { +- (void)dequeueReceiptsForAddress:(SignalServiceAddress *)address + timestamps:(NSSet *)timestamps + receiptType:(OWSReceiptType)receiptType +{ NSString *collection = [self collectionForReceiptType:receiptType]; - if (recipientId.length < 1) { - OWSFailDebug(@"Invalid recipient id."); - return; - } + OWSAssertDebug(address.isValid); if (timestamps.count < 1) { OWSFailDebug(@"Invalid timestamps."); return; } dispatch_async(self.serialQueue, ^{ [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - NSSet *_Nullable oldTimestamps = [transaction objectForKey:recipientId inCollection:collection]; + NSString *identifier = address.uuidString ?: address.phoneNumber; + + NSSet *_Nullable oldUUIDTimestamps; + if (address.uuidString) { + oldUUIDTimestamps = [transaction objectForKey:address.uuidString inCollection:collection]; + } + + NSSet *_Nullable oldPhoneNumberTimestamps; + if (address.phoneNumber) { + oldPhoneNumberTimestamps = [transaction objectForKey:address.phoneNumber inCollection:collection]; + } + + NSSet *_Nullable oldTimestamps = oldUUIDTimestamps; + + // Unexpectedly have entries both on phone number and UUID, defer to UUID + if (oldUUIDTimestamps && oldPhoneNumberTimestamps) { + [transaction removeObjectForKey:address.phoneNumber inCollection:collection]; + + // If we have timestamps only under phone number, but know the UUID, migrate them lazily + } else if (oldPhoneNumberTimestamps && address.uuidString) { + oldTimestamps = oldPhoneNumberTimestamps; + [transaction removeObjectForKey:address.phoneNumber inCollection:collection]; + + // We don't know the UUID, just use the phone number timestamps. + } else if (oldPhoneNumberTimestamps) { + oldTimestamps = oldPhoneNumberTimestamps; + } + NSMutableSet *newTimestamps = (oldTimestamps ? [oldTimestamps mutableCopy] : [NSMutableSet new]); [newTimestamps minusSet:timestamps]; if (newTimestamps.count > 0) { - [transaction setObject:newTimestamps forKey:recipientId inCollection:collection]; + [transaction setObject:newTimestamps forKey:identifier inCollection:collection]; } else { - [transaction removeObjectForKey:recipientId inCollection:collection]; + [transaction removeObjectForKey:identifier inCollection:collection]; } }]; }); diff --git a/SignalServiceKit/src/Messages/OWSReadReceiptManager.m b/SignalServiceKit/src/Messages/OWSReadReceiptManager.m index 4ce288c613..8bb6e0e2e3 100644 --- a/SignalServiceKit/src/Messages/OWSReadReceiptManager.m +++ b/SignalServiceKit/src/Messages/OWSReadReceiptManager.m @@ -303,13 +303,13 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE NSString *threadUniqueId = message.uniqueThreadId; OWSAssertDebug(threadUniqueId.length > 0); - NSString *messageAuthorId = message.authorAddress.transitional_phoneNumber; - OWSAssertDebug(messageAuthorId.length > 0); + SignalServiceAddress *messageAuthorAddress = message.authorAddress; + OWSAssertDebug(messageAuthorAddress.isValid); OWSLinkedDeviceReadReceipt *newReadReceipt = - [[OWSLinkedDeviceReadReceipt alloc] initWithSenderId:messageAuthorId - messageIdTimestamp:message.timestamp - readTimestamp:[NSDate ows_millisecondTimeStamp]]; + [[OWSLinkedDeviceReadReceipt alloc] initWithSenderAddress:messageAuthorAddress + messageIdTimestamp:message.timestamp + readTimestamp:[NSDate ows_millisecondTimeStamp]]; OWSLinkedDeviceReadReceipt *_Nullable oldReadReceipt = self.toLinkedDevicesReadReceiptMap[threadUniqueId]; if (oldReadReceipt && oldReadReceipt.messageIdTimestamp > newReadReceipt.messageIdTimestamp) { @@ -328,7 +328,8 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE if ([self areReadReceiptsEnabled]) { OWSLogVerbose(@"Enqueuing read receipt for sender."); - [self.outgoingReceiptManager enqueueReadReceiptForEnvelope:messageAuthorId timestamp:message.timestamp]; + [self.outgoingReceiptManager enqueueReadReceiptForAddress:messageAuthorAddress + timestamp:message.timestamp]; } [self scheduleProcessing]; @@ -412,10 +413,6 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE { OWSAssertDebug(message); OWSAssertDebug(transaction); - if (SSKFeatureFlags.allowUUIDOnlyContacts) { - // UUID TODO - return; - } SignalServiceAddress *senderAddress = message.authorAddress; uint64_t timestamp = message.timestamp; @@ -425,9 +422,9 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE } OWSLinkedDeviceReadReceipt *_Nullable readReceipt = - [OWSLinkedDeviceReadReceipt findLinkedDeviceReadReceiptWithSenderId:senderAddress.transitional_phoneNumber - messageIdTimestamp:timestamp - transaction:transaction]; + [OWSLinkedDeviceReadReceipt findLinkedDeviceReadReceiptWithAddress:senderAddress + messageIdTimestamp:timestamp + transaction:transaction]; if (!readReceipt) { return; } @@ -444,13 +441,10 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE OWSAssertDebug(transaction); for (SSKProtoSyncMessageRead *readReceiptProto in readReceiptProtos) { - NSString *_Nullable senderId = readReceiptProto.sender; + SignalServiceAddress *_Nullable senderAddress = readReceiptProto.senderAddress; uint64_t messageIdTimestamp = readReceiptProto.timestamp; - if (senderId.length == 0) { - OWSFailDebug(@"senderId was unexpectedly nil"); - continue; - } + OWSAssertDebug(senderAddress.isValid); if (messageIdTimestamp == 0) { OWSFailDebug(@"messageIdTimestamp was unexpectedly 0"); @@ -475,9 +469,9 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE // Received read receipt for unknown incoming message. // Persist in case we receive the incoming message later. OWSLinkedDeviceReadReceipt *readReceipt = - [[OWSLinkedDeviceReadReceipt alloc] initWithSenderId:senderId - messageIdTimestamp:messageIdTimestamp - readTimestamp:readTimestamp]; + [[OWSLinkedDeviceReadReceipt alloc] initWithSenderAddress:senderAddress + messageIdTimestamp:messageIdTimestamp + readTimestamp:readTimestamp]; [readReceipt anyInsertWithTransaction:transaction]; } } diff --git a/SignalServiceKit/src/Storage/Database/SDSDatabaseStorage.swift b/SignalServiceKit/src/Storage/Database/SDSDatabaseStorage.swift index cb6cc16114..da46f171a5 100644 --- a/SignalServiceKit/src/Storage/Database/SDSDatabaseStorage.swift +++ b/SignalServiceKit/src/Storage/Database/SDSDatabaseStorage.swift @@ -541,6 +541,19 @@ public class GRDBDatabaseStorageAdapter: NSObject { on: UserProfileRecord.databaseTableName, columns: [UserProfileRecord.columnName(.recipientUUID)] ) + + // Linked Device Read Receipts + try db.create( + index: "index_linkedDeviceReadReceipt_on_senderPhoneNumberAndTimestamp", + on: LinkedDeviceReadReceiptRecord.databaseTableName, + columns: [LinkedDeviceReadReceiptRecord.columnName(.senderPhoneNumber), LinkedDeviceReadReceiptRecord.columnName(.messageIdTimestamp)] + ) + + try db.create( + index: "index_linkedDeviceReadReceipt_on_senderUUIDAndTimestamp", + on: LinkedDeviceReadReceiptRecord.databaseTableName, + columns: [LinkedDeviceReadReceiptRecord.columnName(.senderUUID), LinkedDeviceReadReceiptRecord.columnName(.messageIdTimestamp)] + ) } return migrator }() diff --git a/SignalServiceKit/src/Storage/Database/SSKAccessors+SDS.h b/SignalServiceKit/src/Storage/Database/SSKAccessors+SDS.h index 34d5836ca8..412169ab83 100644 --- a/SignalServiceKit/src/Storage/Database/SSKAccessors+SDS.h +++ b/SignalServiceKit/src/Storage/Database/SSKAccessors+SDS.h @@ -189,4 +189,14 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - +@interface OWSLinkedDeviceReadReceipt (SDS) + +@property (nonatomic, nullable, readonly) NSString *senderPhoneNumber; +@property (nonatomic, nullable, readonly) NSString *senderUUID; +@property (nonatomic, readonly) NSUInteger linkedDeviceReadReceiptSchemaVersion; + +@end + +#pragma mark - + NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/OWSPrimaryStorage.m b/SignalServiceKit/src/Storage/OWSPrimaryStorage.m index caf6f2e4d4..43ad41adff 100644 --- a/SignalServiceKit/src/Storage/OWSPrimaryStorage.m +++ b/SignalServiceKit/src/Storage/OWSPrimaryStorage.m @@ -231,6 +231,7 @@ void VerifyRegistrationsForPrimaryStorage(OWSStorage *storage, dispatch_block_t [TSDatabaseView asyncRegisterLazyRestoreAttachmentsDatabaseView:self]; [YAPDBJobRecordFinderSetup asyncRegisterDatabaseExtensionObjCWithStorage:self]; [YAPDBSignalServiceAddressIndex asyncRegisterDatabaseExtensions:self]; + [YAPDBLinkedDeviceReadReceiptFinder asyncRegisterDatabaseExtensions:self]; [self.database flushExtensionRequestsWithCompletionQueue:dispatch_get_global_queue( From 324add7897de756f382d34567b7a475821b9d548 Mon Sep 17 00:00:00 2001 From: Nora Trapp Date: Mon, 1 Jul 2019 19:11:00 -0700 Subject: [PATCH 3/4] Allow viewing UUID thread --- .../ConversationViewController.m | 20 +++++---- .../ConversationView/ConversationViewModel.m | 42 ++++++++++--------- .../OWSConversationSettingsViewController.m | 7 ++-- SignalMessaging/Views/AvatarImageView.swift | 18 ++++---- SignalMessaging/contacts/OWSContactsManager.m | 5 +-- .../src/Messages/OWSMessageSender.m | 5 ++- 6 files changed, 53 insertions(+), 44 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 99a660fdf4..92b0887ad8 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -906,9 +906,12 @@ typedef enum : NSUInteger { { NSMutableArray *result = [NSMutableArray new]; for (SignalServiceAddress *address in self.thread.recipientAddresses) { - if ([[OWSIdentityManager sharedManager] verificationStateForRecipientId:address.transitional_phoneNumber] - == OWSVerificationStateNoLongerVerified) { - [result addObject:address.transitional_phoneNumber]; + // TODO UUID + if (!SSKFeatureFlags.allowUUIDOnlyContacts || address.phoneNumber) { + if ([[OWSIdentityManager sharedManager] verificationStateForRecipientId:address.phoneNumber] + == OWSVerificationStateNoLongerVerified) { + [result addObject:address.transitional_phoneNumber]; + } } } return [result copy]; @@ -1592,10 +1595,13 @@ typedef enum : NSUInteger { BOOL isVerified = YES; for (SignalServiceAddress *address in self.thread.recipientAddresses) { - if ([[OWSIdentityManager sharedManager] verificationStateForRecipientId:address.transitional_phoneNumber] - != OWSVerificationStateVerified) { - isVerified = NO; - break; + // TODO UUID + if (!SSKFeatureFlags.allowUUIDOnlyContacts || address.phoneNumber) { + if ([[OWSIdentityManager sharedManager] verificationStateForRecipientId:address.phoneNumber] + != OWSVerificationStateVerified) { + isVerified = NO; + break; + } } } if (isVerified) { diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewModel.m b/Signal/src/ViewControllers/ConversationView/ConversationViewModel.m index cbdbbb4863..59e16fafb8 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewModel.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewModel.m @@ -1215,7 +1215,7 @@ static const int kYapDatabaseRangeMaxLength = 25000; // Don't bother adding self to profile whitelist. shouldHaveAddToProfileWhitelistOffer = NO; } else { - if ([[self.blockingManager blockedPhoneNumbers] containsObject:recipientAddress.transitional_phoneNumber]) { + if ([self.blockingManager isAddressBlocked:recipientAddress]) { // Only create "add to contacts" offers for users which are not already blocked. shouldHaveAddToContactsOffer = NO; // Only create block offers for users which are not already blocked. @@ -1263,6 +1263,12 @@ static const int kYapDatabaseRangeMaxLength = 25000; BOOL shouldHaveContactOffers = (shouldHaveBlockOffer || shouldHaveAddToContactsOffer || shouldHaveAddToProfileWhitelistOffer); + + // We can't add a user to contacts that doesn't have a phone number + if (recipientAddress.phoneNumber == nil) { + shouldHaveContactOffers = NO; + } + if (!shouldHaveContactOffers) { return nil; } @@ -1283,7 +1289,7 @@ static const int kYapDatabaseRangeMaxLength = 25000; hasBlockOffer:shouldHaveBlockOffer hasAddToContactsOffer:shouldHaveAddToContactsOffer hasAddToProfileWhitelistOffer:shouldHaveAddToProfileWhitelistOffer - recipientId:recipientAddress.transitional_phoneNumber + recipientId:recipientAddress.phoneNumber beforeInteractionId:firstCallOrMessage.uniqueId]; OWSLogInfo(@"Creating contact offers: %@ (%llu)", offersMessage.uniqueId, offersMessage.sortId); @@ -1564,15 +1570,15 @@ static const int kYapDatabaseRangeMaxLength = 25000; } else if (interactionType == OWSInteractionType_IncomingMessage) { TSIncomingMessage *incomingMessage = (TSIncomingMessage *)viewItem.interaction; - NSString *incomingSenderId = incomingMessage.authorAddress.transitional_phoneNumber; - OWSAssertDebug(incomingSenderId.length > 0); + SignalServiceAddress *incomingSenderAddress = incomingMessage.authorAddress; + OWSAssertDebug(incomingSenderAddress.isValid); BOOL isDisappearingMessage = incomingMessage.hasPerConversationExpiration; - NSString *_Nullable nextIncomingSenderId = nil; + SignalServiceAddress *_Nullable nextIncomingSenderAddress = nil; if (nextViewItem && nextViewItem.interaction.interactionType == interactionType) { TSIncomingMessage *nextIncomingMessage = (TSIncomingMessage *)nextViewItem.interaction; - nextIncomingSenderId = nextIncomingMessage.authorAddress.transitional_phoneNumber; - OWSAssertDebug(nextIncomingSenderId.length > 0); + nextIncomingSenderAddress = nextIncomingMessage.authorAddress; + OWSAssertDebug(nextIncomingSenderAddress.isValid); } if (nextViewItem && nextViewItem.interaction.interactionType == interactionType) { @@ -1581,8 +1587,7 @@ static const int kYapDatabaseRangeMaxLength = 25000; // has the same footer and no "date break" separates us. // ...but always show the "disappearing messages" animation. shouldHideFooter = ([timestampText isEqualToString:nextTimestampText] && !nextViewItem.hasCellHeader && - [NSObject isNullableObject:nextIncomingSenderId equalTo:incomingSenderId] - && !isDisappearingMessage); + [incomingSenderAddress matchesAddress:nextIncomingSenderAddress] && !isDisappearingMessage); } // clustering @@ -1594,8 +1599,7 @@ static const int kYapDatabaseRangeMaxLength = 25000; isFirstInCluster = YES; } else { TSIncomingMessage *previousIncomingMessage = (TSIncomingMessage *)previousViewItem.interaction; - isFirstInCluster - = ![incomingSenderId isEqual:previousIncomingMessage.authorAddress.transitional_phoneNumber]; + isFirstInCluster = ![incomingSenderAddress matchesAddress:previousIncomingMessage.authorAddress]; } if (nextViewItem == nil) { @@ -1606,8 +1610,7 @@ static const int kYapDatabaseRangeMaxLength = 25000; isLastInCluster = YES; } else { TSIncomingMessage *nextIncomingMessage = (TSIncomingMessage *)nextViewItem.interaction; - isLastInCluster - = ![incomingSenderId isEqual:nextIncomingMessage.authorAddress.transitional_phoneNumber]; + isLastInCluster = ![incomingSenderAddress matchesAddress:nextIncomingMessage.authorAddress]; } if (viewItem.isGroupThread) { @@ -1618,16 +1621,15 @@ static const int kYapDatabaseRangeMaxLength = 25000; if (previousViewItem && previousViewItem.interaction.interactionType == interactionType) { TSIncomingMessage *previousIncomingMessage = (TSIncomingMessage *)previousViewItem.interaction; - NSString *previousIncomingSenderId = previousIncomingMessage.authorAddress.transitional_phoneNumber; - OWSAssertDebug(previousIncomingSenderId.length > 0); + SignalServiceAddress *previousIncomingSenderAddress = previousIncomingMessage.authorAddress; + OWSAssertDebug(previousIncomingSenderAddress.isValid); - shouldShowSenderName - = (![NSObject isNullableObject:previousIncomingSenderId equalTo:incomingSenderId] - || viewItem.hasCellHeader); + shouldShowSenderName = (![incomingSenderAddress matchesAddress:previousIncomingSenderAddress] + || viewItem.hasCellHeader); } if (shouldShowSenderName) { senderName = [self.contactsManager - attributedContactOrProfileNameForAddress:incomingSenderId.transitional_signalServiceAddress + attributedContactOrProfileNameForAddress:incomingSenderAddress primaryAttributes:[OWSMessageBubbleView senderNamePrimaryAttributes] secondaryAttributes:[OWSMessageBubbleView senderNameSecondaryAttributes]]; } @@ -1637,7 +1639,7 @@ static const int kYapDatabaseRangeMaxLength = 25000; // no "date break" separates us. shouldShowSenderAvatar = YES; if (nextViewItem && nextViewItem.interaction.interactionType == interactionType) { - shouldShowSenderAvatar = (![NSObject isNullableObject:nextIncomingSenderId equalTo:incomingSenderId] + shouldShowSenderAvatar = (![incomingSenderAddress matchesAddress:nextIncomingSenderAddress] || nextViewItem.hasCellHeader); } } diff --git a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m index ea96f19242..5df962e1c3 100644 --- a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m +++ b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m @@ -183,9 +183,10 @@ const CGFloat kIconViewLength = 24; contactThread = (TSContactThread *)self.thread; } - if (contactThread && [threadName isEqualToString:contactThread.contactAddress.phoneNumber]) { - threadName = [PhoneNumber - bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:contactThread.contactAddress.phoneNumber]; + NSString *_Nullable phoneNumber = contactThread.contactAddress.phoneNumber; + + if (phoneNumber && [threadName isEqualToString:phoneNumber]) { + threadName = [PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:phoneNumber]; } else if (threadName.length == 0 && [self isGroupThread]) { threadName = [MessageStrings newGroupDefaultTitle]; } diff --git a/SignalMessaging/Views/AvatarImageView.swift b/SignalMessaging/Views/AvatarImageView.swift index 97e2016e78..45ac9ebbb1 100644 --- a/SignalMessaging/Views/AvatarImageView.swift +++ b/SignalMessaging/Views/AvatarImageView.swift @@ -74,7 +74,7 @@ public class ConversationAvatarImageView: AvatarImageView { let contactsManager: OWSContactsManager // nil if group avatar - let recipientId: String? + let recipientAddress: SignalServiceAddress? // nil if contact avatar let groupThreadId: String? @@ -86,20 +86,20 @@ public class ConversationAvatarImageView: AvatarImageView { switch thread { case let contactThread as TSContactThread: - self.recipientId = contactThread.contactAddress.transitional_phoneNumber + self.recipientAddress = contactThread.contactAddress self.groupThreadId = nil case let groupThread as TSGroupThread: - self.recipientId = nil + self.recipientAddress = nil self.groupThreadId = groupThread.uniqueId default: owsFailDebug("unexpected thread type: \(thread)") - self.recipientId = nil + self.recipientAddress = nil self.groupThreadId = nil } super.init(frame: .zero) - if recipientId != nil { + if recipientAddress != nil { NotificationCenter.default.addObserver(self, selector: #selector(handleOtherUsersProfileChanged(notification:)), name: NSNotification.Name(rawValue: kNSNotificationName_OtherUsersProfileDidChange), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleSignalAccountsChanged(notification:)), name: NSNotification.Name.OWSContactsManagerSignalAccountsDidChange, object: nil) @@ -130,17 +130,17 @@ public class ConversationAvatarImageView: AvatarImageView { Logger.debug("") guard let changedAddress = notification.userInfo?[kNSNotificationKey_ProfileAddress] as? SignalServiceAddress else { - owsFailDebug("recipientId was unexpectedly nil") + owsFailDebug("changedAddress was unexpectedly nil") return } - guard let recipientId = self.recipientId else { + guard let recipientAddress = self.recipientAddress else { // shouldn't call this for group threads - owsFailDebug("contactId was unexpectedly nil") + owsFailDebug("recipientAddress was unexpectedly nil") return } - guard recipientId == changedAddress.transitional_phoneNumber else { + guard recipientAddress == changedAddress else { // not this avatar return } diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index 1482b995bf..fe48fde88e 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -1032,11 +1032,8 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan } NSString *_Nullable phoneNumber = [self phoneNumberForAddress:address]; + NSString *_Nullable profileName = [self.profileManager profileNameForAddress:address]; - NSString *_Nullable profileName; - if (!SSKFeatureFlags.allowUUIDOnlyContacts) { - profileName = [self.profileManager profileNameForAddress:address]; - } if (profileName.length > 0) { NSAttributedString *result = [[NSAttributedString alloc] initWithString:phoneNumber ?: address.stringForDisplay attributes:primaryAttributes]; diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index d6c3b218a7..f97e966695 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -574,7 +574,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; return nil; } - [recipientIds addObject:recipientAddress.transitional_phoneNumber]; + // TODO UUID + if (!SSKFeatureFlags.allowUUIDOnlyContacts || recipientAddress.phoneNumber) { + [recipientIds addObject:recipientAddress.phoneNumber]; + } if ([recipientIds containsObject:self.tsAccountManager.localNumber]) { OWSFailDebug(@"Message send recipients should not include self."); From bebe578637fb4d65f4258f91283c08bc092f6cb9 Mon Sep 17 00:00:00 2001 From: Nora Trapp Date: Tue, 2 Jul 2019 12:09:00 -0700 Subject: [PATCH 4/4] PR Feedback --- .../ConversationViewController.m | 10 +++---- .../ConversationView/ConversationViewModel.m | 21 ++++++++------- .../OWSLinkedDeviceReadReceipt+SDS.swift | 14 +++------- .../src/Devices/OWSLinkedDeviceReadReceipt.h | 3 +-- .../src/Devices/OWSLinkedDeviceReadReceipt.m | 7 +++-- .../OWSLinkedDeviceReadReceiptFinder.swift | 26 +++++++++---------- .../OWSContactOffersInteraction+SDS.swift | 2 +- .../OWSContactOffersInteraction.h | 7 +---- .../OWSContactOffersInteraction.m | 5 ---- .../Interactions/TSInteraction+SDS.swift | 4 +-- .../src/Messages/OWSMessageSender.m | 2 +- 11 files changed, 42 insertions(+), 59 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 92b0887ad8..8da38cfab1 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -907,7 +907,7 @@ typedef enum : NSUInteger { NSMutableArray *result = [NSMutableArray new]; for (SignalServiceAddress *address in self.thread.recipientAddresses) { // TODO UUID - if (!SSKFeatureFlags.allowUUIDOnlyContacts || address.phoneNumber) { + if (address.phoneNumber) { if ([[OWSIdentityManager sharedManager] verificationStateForRecipientId:address.phoneNumber] == OWSVerificationStateNoLongerVerified) { [result addObject:address.transitional_phoneNumber]; @@ -1596,7 +1596,7 @@ typedef enum : NSUInteger { BOOL isVerified = YES; for (SignalServiceAddress *address in self.thread.recipientAddresses) { // TODO UUID - if (!SSKFeatureFlags.allowUUIDOnlyContacts || address.phoneNumber) { + if (address.phoneNumber) { if ([[OWSIdentityManager sharedManager] verificationStateForRecipientId:address.phoneNumber] != OWSVerificationStateVerified) { isVerified = NO; @@ -2295,8 +2295,7 @@ typedef enum : NSUInteger { } TSContactThread *contactThread = (TSContactThread *)self.thread; - NSString *displayName = - [self.contactsManager displayNameForAddress:interaction.recipientId.transitional_signalServiceAddress]; + NSString *displayName = [self.contactsManager displayNameForAddress:contactThread.contactAddress]; NSString *title = [NSString stringWithFormat:NSLocalizedString(@"BLOCK_OFFER_ACTIONSHEET_TITLE_FORMAT", @"Title format for action sheet that offers to block an unknown user." @@ -2315,8 +2314,7 @@ typedef enum : NSUInteger { style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) { OWSLogInfo(@"Blocking an unknown user."); - [self.blockingManager - addBlockedAddress:interaction.recipientId.transitional_signalServiceAddress]; + [self.blockingManager addBlockedAddress:contactThread.contactAddress]; // Delete the offers. [self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewModel.m b/Signal/src/ViewControllers/ConversationView/ConversationViewModel.m index 59e16fafb8..99e495d77f 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewModel.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewModel.m @@ -1261,14 +1261,14 @@ static const int kYapDatabaseRangeMaxLength = 25000; shouldHaveAddToProfileWhitelistOffer = NO; } - BOOL shouldHaveContactOffers - = (shouldHaveBlockOffer || shouldHaveAddToContactsOffer || shouldHaveAddToProfileWhitelistOffer); - // We can't add a user to contacts that doesn't have a phone number if (recipientAddress.phoneNumber == nil) { - shouldHaveContactOffers = NO; + shouldHaveAddToContactsOffer = NO; } + BOOL shouldHaveContactOffers + = (shouldHaveBlockOffer || shouldHaveAddToContactsOffer || shouldHaveAddToProfileWhitelistOffer); + if (!shouldHaveContactOffers) { return nil; } @@ -1289,7 +1289,6 @@ static const int kYapDatabaseRangeMaxLength = 25000; hasBlockOffer:shouldHaveBlockOffer hasAddToContactsOffer:shouldHaveAddToContactsOffer hasAddToProfileWhitelistOffer:shouldHaveAddToProfileWhitelistOffer - recipientId:recipientAddress.phoneNumber beforeInteractionId:firstCallOrMessage.uniqueId]; OWSLogInfo(@"Creating contact offers: %@ (%llu)", offersMessage.uniqueId, offersMessage.sortId); @@ -1586,8 +1585,10 @@ static const int kYapDatabaseRangeMaxLength = 25000; // We can skip the "incoming message status" footer in a cluster if the next message // has the same footer and no "date break" separates us. // ...but always show the "disappearing messages" animation. - shouldHideFooter = ([timestampText isEqualToString:nextTimestampText] && !nextViewItem.hasCellHeader && - [incomingSenderAddress matchesAddress:nextIncomingSenderAddress] && !isDisappearingMessage); + shouldHideFooter = ([timestampText isEqualToString:nextTimestampText] && !nextViewItem.hasCellHeader + && ((!incomingSenderAddress && !nextIncomingSenderAddress) || + [incomingSenderAddress matchesAddress:nextIncomingSenderAddress]) + && !isDisappearingMessage); } // clustering @@ -1624,7 +1625,8 @@ static const int kYapDatabaseRangeMaxLength = 25000; SignalServiceAddress *previousIncomingSenderAddress = previousIncomingMessage.authorAddress; OWSAssertDebug(previousIncomingSenderAddress.isValid); - shouldShowSenderName = (![incomingSenderAddress matchesAddress:previousIncomingSenderAddress] + shouldShowSenderName = ((!incomingSenderAddress && !previousIncomingSenderAddress) + || ![incomingSenderAddress matchesAddress:previousIncomingSenderAddress] || viewItem.hasCellHeader); } if (shouldShowSenderName) { @@ -1639,7 +1641,8 @@ static const int kYapDatabaseRangeMaxLength = 25000; // no "date break" separates us. shouldShowSenderAvatar = YES; if (nextViewItem && nextViewItem.interaction.interactionType == interactionType) { - shouldShowSenderAvatar = (![incomingSenderAddress matchesAddress:nextIncomingSenderAddress] + shouldShowSenderAvatar = ((!incomingSenderAddress && !nextIncomingSenderAddress) + || ![incomingSenderAddress matchesAddress:nextIncomingSenderAddress] || nextViewItem.hasCellHeader); } } diff --git a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt+SDS.swift b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt+SDS.swift index aab51ebfd1..310425416d 100644 --- a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt+SDS.swift +++ b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt+SDS.swift @@ -29,7 +29,6 @@ public struct LinkedDeviceReadReceiptRecord: SDSRecord { public let linkedDeviceReadReceiptSchemaVersion: UInt public let messageIdTimestamp: UInt64 public let readTimestamp: UInt64 - public let senderAddress: Data public let senderPhoneNumber: String? public let senderUUID: String? @@ -40,7 +39,6 @@ public struct LinkedDeviceReadReceiptRecord: SDSRecord { case linkedDeviceReadReceiptSchemaVersion case messageIdTimestamp case readTimestamp - case senderAddress case senderPhoneNumber case senderUUID } @@ -81,8 +79,6 @@ extension OWSLinkedDeviceReadReceipt { let linkedDeviceReadReceiptSchemaVersion: UInt = record.linkedDeviceReadReceiptSchemaVersion let messageIdTimestamp: UInt64 = record.messageIdTimestamp let readTimestamp: UInt64 = record.readTimestamp - let senderAddressSerialized: Data = record.senderAddress - let senderAddress: SignalServiceAddress = try SDSDeserialization.unarchive(senderAddressSerialized, name: "senderAddress") let senderPhoneNumber: String? = record.senderPhoneNumber let senderUUID: String? = record.senderUUID @@ -90,7 +86,6 @@ extension OWSLinkedDeviceReadReceipt { linkedDeviceReadReceiptSchemaVersion: linkedDeviceReadReceiptSchemaVersion, messageIdTimestamp: messageIdTimestamp, readTimestamp: readTimestamp, - senderAddress: senderAddress, senderPhoneNumber: senderPhoneNumber, senderUUID: senderUUID) @@ -132,9 +127,8 @@ extension OWSLinkedDeviceReadReceiptSerializer { static let linkedDeviceReadReceiptSchemaVersionColumn = SDSColumnMetadata(columnName: "linkedDeviceReadReceiptSchemaVersion", columnType: .int64, columnIndex: 3) static let messageIdTimestampColumn = SDSColumnMetadata(columnName: "messageIdTimestamp", columnType: .int64, columnIndex: 4) static let readTimestampColumn = SDSColumnMetadata(columnName: "readTimestamp", columnType: .int64, columnIndex: 5) - static let senderAddressColumn = SDSColumnMetadata(columnName: "senderAddress", columnType: .blob, columnIndex: 6) - static let senderPhoneNumberColumn = SDSColumnMetadata(columnName: "senderPhoneNumber", columnType: .unicodeString, isOptional: true, columnIndex: 7) - static let senderUUIDColumn = SDSColumnMetadata(columnName: "senderUUID", columnType: .unicodeString, isOptional: true, columnIndex: 8) + static let senderPhoneNumberColumn = SDSColumnMetadata(columnName: "senderPhoneNumber", columnType: .unicodeString, isOptional: true, columnIndex: 6) + static let senderUUIDColumn = SDSColumnMetadata(columnName: "senderUUID", columnType: .unicodeString, isOptional: true, columnIndex: 7) // TODO: We should decide on a naming convention for // tables that store models. @@ -145,7 +139,6 @@ extension OWSLinkedDeviceReadReceiptSerializer { linkedDeviceReadReceiptSchemaVersionColumn, messageIdTimestampColumn, readTimestampColumn, - senderAddressColumn, senderPhoneNumberColumn, senderUUIDColumn ]) @@ -476,10 +469,9 @@ class OWSLinkedDeviceReadReceiptSerializer: SDSSerializer { let linkedDeviceReadReceiptSchemaVersion: UInt = model.linkedDeviceReadReceiptSchemaVersion let messageIdTimestamp: UInt64 = model.messageIdTimestamp let readTimestamp: UInt64 = model.readTimestamp - let senderAddress: Data = requiredArchive(model.senderAddress) let senderPhoneNumber: String? = model.senderPhoneNumber let senderUUID: String? = model.senderUUID - return LinkedDeviceReadReceiptRecord(id: id, recordType: recordType, uniqueId: uniqueId, linkedDeviceReadReceiptSchemaVersion: linkedDeviceReadReceiptSchemaVersion, messageIdTimestamp: messageIdTimestamp, readTimestamp: readTimestamp, senderAddress: senderAddress, senderPhoneNumber: senderPhoneNumber, senderUUID: senderUUID) + return LinkedDeviceReadReceiptRecord(id: id, recordType: recordType, uniqueId: uniqueId, linkedDeviceReadReceiptSchemaVersion: linkedDeviceReadReceiptSchemaVersion, messageIdTimestamp: messageIdTimestamp, readTimestamp: readTimestamp, senderPhoneNumber: senderPhoneNumber, senderUUID: senderUUID) } } diff --git a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h index a4febd0ad7..91c69272bc 100644 --- a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h +++ b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h @@ -30,10 +30,9 @@ NS_ASSUME_NONNULL_BEGIN linkedDeviceReadReceiptSchemaVersion:(NSUInteger)linkedDeviceReadReceiptSchemaVersion messageIdTimestamp:(uint64_t)messageIdTimestamp readTimestamp:(uint64_t)readTimestamp - senderAddress:(SignalServiceAddress *)senderAddress senderPhoneNumber:(nullable NSString *)senderPhoneNumber senderUUID:(nullable NSString *)senderUUID -NS_SWIFT_NAME(init(uniqueId:linkedDeviceReadReceiptSchemaVersion:messageIdTimestamp:readTimestamp:senderAddress:senderPhoneNumber:senderUUID:)); +NS_SWIFT_NAME(init(uniqueId:linkedDeviceReadReceiptSchemaVersion:messageIdTimestamp:readTimestamp:senderPhoneNumber:senderUUID:)); // clang-format on diff --git a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m index fa8349df5b..cd9acf91f2 100644 --- a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m +++ b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m @@ -82,7 +82,6 @@ NSUInteger const OWSLinkedDeviceReadReceiptSchemaVersion = 1; linkedDeviceReadReceiptSchemaVersion:(NSUInteger)linkedDeviceReadReceiptSchemaVersion messageIdTimestamp:(uint64_t)messageIdTimestamp readTimestamp:(uint64_t)readTimestamp - senderAddress:(SignalServiceAddress *)senderAddress senderPhoneNumber:(nullable NSString *)senderPhoneNumber senderUUID:(nullable NSString *)senderUUID { @@ -95,7 +94,6 @@ linkedDeviceReadReceiptSchemaVersion:(NSUInteger)linkedDeviceReadReceiptSchemaVe _linkedDeviceReadReceiptSchemaVersion = linkedDeviceReadReceiptSchemaVersion; _messageIdTimestamp = messageIdTimestamp; _readTimestamp = readTimestamp; - _senderAddress = senderAddress; _senderPhoneNumber = senderPhoneNumber; _senderUUID = senderUUID; @@ -106,6 +104,11 @@ linkedDeviceReadReceiptSchemaVersion:(NSUInteger)linkedDeviceReadReceiptSchemaVe // --- CODE GENERATION MARKER +- (SignalServiceAddress *)senderAddress +{ + return [[SignalServiceAddress alloc] initWithUuidString:self.senderUUID phoneNumber:self.senderPhoneNumber]; +} + + (AnyLinkedDeviceReadReceiptFinder *)finder { return [AnyLinkedDeviceReadReceiptFinder new]; diff --git a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceiptFinder.swift b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceiptFinder.swift index fdd535311c..fced33d182 100644 --- a/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceiptFinder.swift +++ b/SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceiptFinder.swift @@ -12,35 +12,35 @@ class AnyLinkedDeviceReadReceiptFinder: NSObject { extension AnyLinkedDeviceReadReceiptFinder { @objc(linkedDeviceReadReceiptForAddress:messageIdTimestamp:transaction:) - func linkedDeviceReadReceipt(for address: SignalServiceAddress, and timestamp: UInt64, transaction: SDSAnyReadTransaction) -> OWSLinkedDeviceReadReceipt? { + func linkedDeviceReadReceipt(for address: SignalServiceAddress, andMessageIdTimestamp timestamp: UInt64, transaction: SDSAnyReadTransaction) -> OWSLinkedDeviceReadReceipt? { switch transaction.readTransaction { case .grdbRead(let transaction): - return grdbAdapter.linkedDeviceReadReceipt(for: address, and: timestamp, transaction: transaction) + return grdbAdapter.linkedDeviceReadReceipt(for: address, andMessageIdTimestamp: timestamp, transaction: transaction) case .yapRead(let transaction): - return yapdbAdapter.linkedDeviceReadReceipt(for: address, and: timestamp, transaction: transaction) + return yapdbAdapter.linkedDeviceReadReceipt(for: address, andMessageIdTimestamp: timestamp, transaction: transaction) } } } @objc class GRDBLinkedDeviceReadReceiptFinder: NSObject { - func linkedDeviceReadReceipt(for address: SignalServiceAddress, and timestamp: UInt64, transaction: GRDBReadTransaction) -> OWSLinkedDeviceReadReceipt? { - if let thread = linkedDeviceReadReceiptForUUID(address.uuid, and: timestamp, transaction: transaction) { + func linkedDeviceReadReceipt(for address: SignalServiceAddress, andMessageIdTimestamp timestamp: UInt64, transaction: GRDBReadTransaction) -> OWSLinkedDeviceReadReceipt? { + if let thread = linkedDeviceReadReceiptForUUID(address.uuid, andMessageIdTimestamp: timestamp, transaction: transaction) { return thread - } else if let thread = linkedDeviceReadReceiptForPhoneNumber(address.phoneNumber, and: timestamp, transaction: transaction) { + } else if let thread = linkedDeviceReadReceiptForPhoneNumber(address.phoneNumber, andMessageIdTimestamp: timestamp, transaction: transaction) { return thread } else { return nil } } - private func linkedDeviceReadReceiptForUUID(_ uuid: UUID?, and timestamp: UInt64, transaction: GRDBReadTransaction) -> OWSLinkedDeviceReadReceipt? { + private func linkedDeviceReadReceiptForUUID(_ uuid: UUID?, andMessageIdTimestamp timestamp: UInt64, transaction: GRDBReadTransaction) -> OWSLinkedDeviceReadReceipt? { guard let uuidString = uuid?.uuidString else { return nil } let sql = "SELECT * FROM \(LinkedDeviceReadReceiptRecord.databaseTableName) WHERE \(linkedDeviceReadReceiptColumn: .senderUUID) = ? AND \(linkedDeviceReadReceiptColumn: .messageIdTimestamp) = ?" return OWSLinkedDeviceReadReceipt.grdbFetchOne(sql: sql, arguments: [uuidString, timestamp], transaction: transaction) } - private func linkedDeviceReadReceiptForPhoneNumber(_ phoneNumber: String?, and timestamp: UInt64, transaction: GRDBReadTransaction) -> OWSLinkedDeviceReadReceipt? { + private func linkedDeviceReadReceiptForPhoneNumber(_ phoneNumber: String?, andMessageIdTimestamp timestamp: UInt64, transaction: GRDBReadTransaction) -> OWSLinkedDeviceReadReceipt? { guard let phoneNumber = phoneNumber else { return nil } let sql = "SELECT * FROM \(LinkedDeviceReadReceiptRecord.databaseTableName) WHERE \(linkedDeviceReadReceiptColumn: .senderPhoneNumber) = ? AND \(linkedDeviceReadReceiptColumn: .messageIdTimestamp) = ?" return OWSLinkedDeviceReadReceipt.grdbFetchOne(sql: sql, arguments: [phoneNumber, timestamp], transaction: transaction) @@ -56,17 +56,17 @@ class YAPDBLinkedDeviceReadReceiptFinder: NSObject { private static let phoneNumberIndexName = "index_linkedDeviceReadReceipt_on_senderPhoneNumberAndTimestamp" private static let uuidIndexName = "index_linkedDeviceReadReceipt_on_senderUUIDAndTimestamp" - func linkedDeviceReadReceipt(for address: SignalServiceAddress, and timestamp: UInt64, transaction: YapDatabaseReadTransaction) -> OWSLinkedDeviceReadReceipt? { - if let result = linkedDeviceReadReceiptForUUID(address.uuid, and: timestamp, transaction: transaction) { + func linkedDeviceReadReceipt(for address: SignalServiceAddress, andMessageIdTimestamp timestamp: UInt64, transaction: YapDatabaseReadTransaction) -> OWSLinkedDeviceReadReceipt? { + if let result = linkedDeviceReadReceiptForUUID(address.uuid, andMessageIdTimestamp: timestamp, transaction: transaction) { return result - } else if let result = linkedDeviceReadReceiptForPhoneNumber(address.phoneNumber, and: timestamp, transaction: transaction) { + } else if let result = linkedDeviceReadReceiptForPhoneNumber(address.phoneNumber, andMessageIdTimestamp: timestamp, transaction: transaction) { return result } else { return nil } } - private func linkedDeviceReadReceiptForUUID(_ uuid: UUID?, and timestamp: UInt64, transaction: YapDatabaseReadTransaction) -> OWSLinkedDeviceReadReceipt? { + private func linkedDeviceReadReceiptForUUID(_ uuid: UUID?, andMessageIdTimestamp timestamp: UInt64, transaction: YapDatabaseReadTransaction) -> OWSLinkedDeviceReadReceipt? { guard let uuidString = uuid?.uuidString else { return nil } guard let ext = transaction.ext(YAPDBLinkedDeviceReadReceiptFinder.uuidIndexName) as? YapDatabaseSecondaryIndexTransaction else { @@ -96,7 +96,7 @@ class YAPDBLinkedDeviceReadReceiptFinder: NSObject { return matchedRecord } - private func linkedDeviceReadReceiptForPhoneNumber(_ phoneNumber: String?, and timestamp: UInt64, transaction: YapDatabaseReadTransaction) -> OWSLinkedDeviceReadReceipt? { + private func linkedDeviceReadReceiptForPhoneNumber(_ phoneNumber: String?, andMessageIdTimestamp timestamp: UInt64, transaction: YapDatabaseReadTransaction) -> OWSLinkedDeviceReadReceipt? { guard let phoneNumber = phoneNumber else { return nil } guard let ext = transaction.ext(YAPDBLinkedDeviceReadReceiptFinder.phoneNumberIndexName) as? YapDatabaseSecondaryIndexTransaction else { diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContactOffersInteraction+SDS.swift b/SignalServiceKit/src/Messages/Interactions/OWSContactOffersInteraction+SDS.swift index 2326a136f0..1415f5846e 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContactOffersInteraction+SDS.swift +++ b/SignalServiceKit/src/Messages/Interactions/OWSContactOffersInteraction+SDS.swift @@ -84,7 +84,7 @@ class OWSContactOffersInteractionSerializer: SDSSerializer { let protocolVersion: UInt? = nil let quotedMessage: Data? = nil let read: Bool? = nil - let recipientId: String? = model.recipientId + let recipientId: String? = nil let recipientStateMap: Data? = nil let schemaVersion: UInt? = nil let senderId: String? = nil diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContactOffersInteraction.h b/SignalServiceKit/src/Messages/Interactions/OWSContactOffersInteraction.h index 5c73df3295..2a4c803a16 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContactOffersInteraction.h +++ b/SignalServiceKit/src/Messages/Interactions/OWSContactOffersInteraction.h @@ -14,9 +14,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) BOOL hasAddToContactsOffer; @property (nonatomic, readonly) BOOL hasAddToProfileWhitelistOffer; -// TODO - remove this recipientId param -// it's redundant with the interaction's TSContactThread -@property (nonatomic, readonly) NSString *recipientId; @property (nonatomic, readonly) NSString *beforeInteractionId; - (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread NS_UNAVAILABLE; @@ -30,7 +27,6 @@ NS_ASSUME_NONNULL_BEGIN hasBlockOffer:(BOOL)hasBlockOffer hasAddToContactsOffer:(BOOL)hasAddToContactsOffer hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer - recipientId:(NSString *)recipientId beforeInteractionId:(NSString *)beforeInteractionId NS_DESIGNATED_INITIALIZER; // --- CODE GENERATION MARKER @@ -48,8 +44,7 @@ NS_ASSUME_NONNULL_BEGIN hasAddToContactsOffer:(BOOL)hasAddToContactsOffer hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer hasBlockOffer:(BOOL)hasBlockOffer - recipientId:(NSString *)recipientId -NS_SWIFT_NAME(init(uniqueId:receivedAtTimestamp:sortId:timestamp:uniqueThreadId:beforeInteractionId:hasAddToContactsOffer:hasAddToProfileWhitelistOffer:hasBlockOffer:recipientId:)); +NS_SWIFT_NAME(init(uniqueId:receivedAtTimestamp:sortId:timestamp:uniqueThreadId:beforeInteractionId:hasAddToContactsOffer:hasAddToProfileWhitelistOffer:hasBlockOffer:)); // clang-format on diff --git a/SignalServiceKit/src/Messages/Interactions/OWSContactOffersInteraction.m b/SignalServiceKit/src/Messages/Interactions/OWSContactOffersInteraction.m index 99c61756d5..d640563ba7 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSContactOffersInteraction.m +++ b/SignalServiceKit/src/Messages/Interactions/OWSContactOffersInteraction.m @@ -27,7 +27,6 @@ NS_ASSUME_NONNULL_BEGIN hasBlockOffer:(BOOL)hasBlockOffer hasAddToContactsOffer:(BOOL)hasAddToContactsOffer hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer - recipientId:(NSString *)recipientId beforeInteractionId:(NSString *)beforeInteractionId { self = [super initWithUniqueId:uniqueId timestamp:timestamp inThread:thread]; @@ -39,8 +38,6 @@ NS_ASSUME_NONNULL_BEGIN _hasBlockOffer = hasBlockOffer; _hasAddToContactsOffer = hasAddToContactsOffer; _hasAddToProfileWhitelistOffer = hasAddToProfileWhitelistOffer; - OWSAssertDebug(recipientId.length > 0); - _recipientId = recipientId; _beforeInteractionId = beforeInteractionId; return self; @@ -61,7 +58,6 @@ NS_ASSUME_NONNULL_BEGIN hasAddToContactsOffer:(BOOL)hasAddToContactsOffer hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer hasBlockOffer:(BOOL)hasBlockOffer - recipientId:(NSString *)recipientId { self = [super initWithUniqueId:uniqueId receivedAtTimestamp:receivedAtTimestamp @@ -77,7 +73,6 @@ NS_ASSUME_NONNULL_BEGIN _hasAddToContactsOffer = hasAddToContactsOffer; _hasAddToProfileWhitelistOffer = hasAddToProfileWhitelistOffer; _hasBlockOffer = hasBlockOffer; - _recipientId = recipientId; return self; } diff --git a/SignalServiceKit/src/Messages/Interactions/TSInteraction+SDS.swift b/SignalServiceKit/src/Messages/Interactions/TSInteraction+SDS.swift index c7ff486a76..b4a99f0163 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSInteraction+SDS.swift +++ b/SignalServiceKit/src/Messages/Interactions/TSInteraction+SDS.swift @@ -312,7 +312,6 @@ extension TSInteraction { let hasAddToContactsOffer: Bool = try SDSDeserialization.required(record.hasAddToContactsOffer, name: "hasAddToContactsOffer") let hasAddToProfileWhitelistOffer: Bool = try SDSDeserialization.required(record.hasAddToProfileWhitelistOffer, name: "hasAddToProfileWhitelistOffer") let hasBlockOffer: Bool = try SDSDeserialization.required(record.hasBlockOffer, name: "hasBlockOffer") - let recipientId: String = try SDSDeserialization.required(record.recipientId, name: "recipientId") return OWSContactOffersInteraction(uniqueId: uniqueId, receivedAtTimestamp: receivedAtTimestamp, @@ -322,8 +321,7 @@ extension TSInteraction { beforeInteractionId: beforeInteractionId, hasAddToContactsOffer: hasAddToContactsOffer, hasAddToProfileWhitelistOffer: hasAddToProfileWhitelistOffer, - hasBlockOffer: hasBlockOffer, - recipientId: recipientId) + hasBlockOffer: hasBlockOffer) case .disappearingConfigurationUpdateInfoMessage: diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index f97e966695..ddabf0f691 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -575,7 +575,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; } // TODO UUID - if (!SSKFeatureFlags.allowUUIDOnlyContacts || recipientAddress.phoneNumber) { + if (recipientAddress.phoneNumber) { [recipientIds addObject:recipientAddress.phoneNumber]; }