Revision a4bb25f6
| ChatSecure.xcodeproj/project.pbxproj | ||
|---|---|---|
| 454 | 454 |
D967CA8E1E516B8D005FBB49 /* PushAccountTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D967CA8D1E516B8D005FBB49 /* PushAccountTableViewCell.xib */; };
|
| 455 | 455 |
D97070921EEF382D004FEBDE /* MediaDownloadView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D97070911EEF382D004FEBDE /* MediaDownloadView.xib */; };
|
| 456 | 456 |
D97070A71EEF3909004FEBDE /* MediaDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D97070A61EEF3909004FEBDE /* MediaDownloadView.swift */; };
|
| 457 |
D97097A21FC4DB3D008ED04B /* MessageStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D97097A11FC4DB3D008ED04B /* MessageStorage.swift */; };
|
|
| 457 | 458 |
D97175531E60D59300675DDE /* YapDatabaseViewConnection+ChatSecure.h in Headers */ = {isa = PBXBuildFile; fileRef = D97175511E60D59300675DDE /* YapDatabaseViewConnection+ChatSecure.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
| 458 | 459 |
D97175541E60D59300675DDE /* YapDatabaseViewConnection+ChatSecure.m in Sources */ = {isa = PBXBuildFile; fileRef = D97175521E60D59300675DDE /* YapDatabaseViewConnection+ChatSecure.m */; };
|
| 459 | 460 |
D978BC921BABE0F4009246CF /* OTRTheme.h in Headers */ = {isa = PBXBuildFile; fileRef = D978BC901BABE0F4009246CF /* OTRTheme.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
| ... | ... | |
| 1094 | 1095 |
D96F8E671EFC7209003DE8AE /* DTFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DTFoundation.framework; path = Carthage/Build/iOS/DTFoundation.framework; sourceTree = "<group>"; };
|
| 1095 | 1096 |
D97070911EEF382D004FEBDE /* MediaDownloadView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MediaDownloadView.xib; path = Interface/MediaDownloadView.xib; sourceTree = "<group>"; };
|
| 1096 | 1097 |
D97070A61EEF3909004FEBDE /* MediaDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDownloadView.swift; sourceTree = "<group>"; };
|
| 1098 |
D97097A11FC4DB3D008ED04B /* MessageStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStorage.swift; sourceTree = "<group>"; };
|
|
| 1097 | 1099 |
D97175511E60D59300675DDE /* YapDatabaseViewConnection+ChatSecure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "YapDatabaseViewConnection+ChatSecure.h"; sourceTree = "<group>"; };
|
| 1098 | 1100 |
D97175521E60D59300675DDE /* YapDatabaseViewConnection+ChatSecure.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "YapDatabaseViewConnection+ChatSecure.m"; sourceTree = "<group>"; };
|
| 1099 | 1101 |
D973D7051A2D4094004D353E /* Pods-ChatSecureCorePods-ChatSecure-acknowledgements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Pods-ChatSecureCorePods-ChatSecure-acknowledgements.plist"; path = "Pods/Target Support Files/Pods-ChatSecureCorePods-ChatSecure/Pods-ChatSecureCorePods-ChatSecure-acknowledgements.plist"; sourceTree = SOURCE_ROOT; };
|
| ... | ... | |
| 1412 | 1414 |
633105321A16D1A300C17BAE /* XMPP */ = {
|
| 1413 | 1415 |
isa = PBXGroup; |
| 1414 | 1416 |
children = ( |
| 1417 |
D97097A01FC4DA85008ED04B /* Storage */, |
|
| 1415 | 1418 |
D9F8C3C11FBFD2CA00D4B857 /* RoomManager.swift */, |
| 1416 | 1419 |
D98B8E301E4CF90400A713E1 /* OTRServerCapabilities.h */, |
| 1417 | 1420 |
D98B8E311E4CF90400A713E1 /* OTRServerCapabilities.m */, |
| ... | ... | |
| 1420 | 1423 |
D9A7756D1E43F8A200027864 /* ProxyXMPPStream.h */, |
| 1421 | 1424 |
D9A7756E1E43F8A200027864 /* ProxyXMPPStream.m */, |
| 1422 | 1425 |
D9BEF8DF1DCE6E12009945D1 /* OTRXMPPManager_Private.h */, |
| 1423 |
63B916E01B743198003B778F /* OTRStreamManagementYapStorage.h */, |
|
| 1424 |
63B916E11B743198003B778F /* OTRStreamManagementYapStorage.m */, |
|
| 1425 | 1426 |
63D639E11D12124F002B4175 /* OTRStreamManagementDelegate.swift */, |
| 1426 |
637DBB1A1B7D5A23003845B7 /* OTRXMPPMessageYapStorage.h */, |
|
| 1427 |
637DBB1B1B7D5A23003845B7 /* OTRXMPPMessageYapStorage.m */, |
|
| 1428 |
633105331A16D1A300C17BAE /* OTRvCardYapDatabaseStorage.h */, |
|
| 1429 |
633105341A16D1A300C17BAE /* OTRvCardYapDatabaseStorage.m */, |
|
| 1430 | 1427 |
633105351A16D1A300C17BAE /* OTRXMPPBuddyTimers.h */, |
| 1431 | 1428 |
633105361A16D1A300C17BAE /* OTRXMPPBuddyTimers.m */, |
| 1432 | 1429 |
633105371A16D1A300C17BAE /* OTRXMPPManager.h */, |
| ... | ... | |
| 1435 | 1432 |
636985591BC875110083FC53 /* OTRXMPPRoomManager.m */, |
| 1436 | 1433 |
6331053B1A16D1A300C17BAE /* OTRXMPPTorManager.h */, |
| 1437 | 1434 |
6331053C1A16D1A300C17BAE /* OTRXMPPTorManager.m */, |
| 1438 |
6331053D1A16D1A300C17BAE /* OTRYapDatabaseRosterStorage.h */, |
|
| 1439 |
6331053E1A16D1A300C17BAE /* OTRYapDatabaseRosterStorage.m */, |
|
| 1440 |
63BB66AD1BC5D2F40004A619 /* OTRXMPPRoomYapStorage.h */, |
|
| 1441 |
63BB66AE1BC5D2F40004A619 /* OTRXMPPRoomYapStorage.m */, |
|
| 1442 | 1435 |
639C35281C3DDDDE00132330 /* OTRXMPPBuddyManager.h */, |
| 1443 | 1436 |
639C35291C3DDDDE00132330 /* OTRXMPPBuddyManager.m */, |
| 1444 | 1437 |
); |
| ... | ... | |
| 2045 | 2038 |
path = OTRResources; |
| 2046 | 2039 |
sourceTree = "<group>"; |
| 2047 | 2040 |
}; |
| 2041 |
D97097A01FC4DA85008ED04B /* Storage */ = {
|
|
| 2042 |
isa = PBXGroup; |
|
| 2043 |
children = ( |
|
| 2044 |
D97097A11FC4DB3D008ED04B /* MessageStorage.swift */, |
|
| 2045 |
637DBB1A1B7D5A23003845B7 /* OTRXMPPMessageYapStorage.h */, |
|
| 2046 |
637DBB1B1B7D5A23003845B7 /* OTRXMPPMessageYapStorage.m */, |
|
| 2047 |
633105331A16D1A300C17BAE /* OTRvCardYapDatabaseStorage.h */, |
|
| 2048 |
633105341A16D1A300C17BAE /* OTRvCardYapDatabaseStorage.m */, |
|
| 2049 |
6331053D1A16D1A300C17BAE /* OTRYapDatabaseRosterStorage.h */, |
|
| 2050 |
6331053E1A16D1A300C17BAE /* OTRYapDatabaseRosterStorage.m */, |
|
| 2051 |
63BB66AD1BC5D2F40004A619 /* OTRXMPPRoomYapStorage.h */, |
|
| 2052 |
63BB66AE1BC5D2F40004A619 /* OTRXMPPRoomYapStorage.m */, |
|
| 2053 |
63B916E01B743198003B778F /* OTRStreamManagementYapStorage.h */, |
|
| 2054 |
63B916E11B743198003B778F /* OTRStreamManagementYapStorage.m */, |
|
| 2055 |
); |
|
| 2056 |
path = Storage; |
|
| 2057 |
sourceTree = "<group>"; |
|
| 2058 |
}; |
|
| 2048 | 2059 |
D9AE3A0F1BA8CBFA00255537 /* OTRAssets */ = {
|
| 2049 | 2060 |
isa = PBXGroup; |
| 2050 | 2061 |
children = ( |
| ... | ... | |
| 3203 | 3214 |
D91C866B1E4E7EEA008BD763 /* ServerCapabilityTableViewCell.swift in Sources */, |
| 3204 | 3215 |
631C79931E56846700B30CB4 /* NSData+ChatSecure.swift in Sources */, |
| 3205 | 3216 |
D93DDAFB1BA79A2900CD8331 /* OTRBuddyViewController.m in Sources */, |
| 3217 |
D97097A21FC4DB3D008ED04B /* MessageStorage.swift in Sources */, |
|
| 3206 | 3218 |
D93DDAFC1BA79A2900CD8331 /* OTRCertificateDomainViewController.m in Sources */, |
| 3207 | 3219 |
D93DDAFD1BA79A2900CD8331 /* OTRCertificatesViewController.m in Sources */, |
| 3208 | 3220 |
D91E641A1DB156E90074B2D4 /* OMEMODeviceFingerprintCell.swift in Sources */, |
| ChatSecure/Classes/Categories/XMPPMessage+ChatSecure.swift | ||
|---|---|---|
| 10 | 10 |
|
| 11 | 11 |
public extension XMPPMessage {
|
| 12 | 12 |
/// Safely extracts XEP-0359 stanza-id |
| 13 |
@objc public func extractStanzaId(account: OTRXMPPAccount) -> String? {
|
|
| 13 |
@objc public func extractStanzaId(account: OTRXMPPAccount, capabilities: XMPPCapabilities) -> String? {
|
|
| 14 | 14 |
let stanzaIds = self.stanzaIds |
| 15 | 15 |
guard stanzaIds.count > 0, |
| 16 |
let xmpp = OTRProtocolManager.shared.protocol(for: account) as? OTRXMPPManager, |
|
| 17 |
xmpp.xmppCapabilities.hasValidStanzaId(self) else {
|
|
| 16 |
capabilities.hasValidStanzaId(self) else {
|
|
| 18 | 17 |
return nil |
| 19 | 18 |
} |
| 20 | 19 |
var byJID: XMPPJID? = nil |
| ChatSecure/Classes/Controllers/XMPP/OTRStreamManagementYapStorage.h | ||
|---|---|---|
| 1 |
// |
|
| 2 |
// OTRStreamManagementYapStorage.h |
|
| 3 |
// ChatSecure |
|
| 4 |
// |
|
| 5 |
// Created by David Chiles on 11/19/14. |
|
| 6 |
// Copyright (c) 2014 Chris Ballinger. All rights reserved. |
|
| 7 |
// |
|
| 8 |
|
|
| 9 |
@import Foundation; |
|
| 10 |
@import XMPPFramework; |
|
| 11 |
|
|
| 12 |
@class YapDatabaseConnection; |
|
| 13 |
|
|
| 14 |
NS_ASSUME_NONNULL_BEGIN |
|
| 15 |
@interface OTRStreamManagementYapStorage : NSObject <XMPPStreamManagementStorage> |
|
| 16 |
|
|
| 17 |
- (instancetype)initWithDatabaseConnection:(YapDatabaseConnection *)databaseConnection; |
|
| 18 |
|
|
| 19 |
@property (nonatomic, strong, readonly) YapDatabaseConnection *databaseConnection; |
|
| 20 |
|
|
| 21 |
@end |
|
| 22 |
NS_ASSUME_NONNULL_END |
|
| ChatSecure/Classes/Controllers/XMPP/OTRStreamManagementYapStorage.m | ||
|---|---|---|
| 1 |
// |
|
| 2 |
// OTRStreamManagementYapStorage.m |
|
| 3 |
// ChatSecure |
|
| 4 |
// |
|
| 5 |
// Created by David Chiles on 11/19/14. |
|
| 6 |
// Copyright (c) 2014 Chris Ballinger. All rights reserved. |
|
| 7 |
// |
|
| 8 |
|
|
| 9 |
#import "OTRStreamManagementYapStorage.h" |
|
| 10 |
@import XMPPFramework; |
|
| 11 |
@import YapDatabase; |
|
| 12 |
#import "OTRStreamManagementStorageObject.h" |
|
| 13 |
|
|
| 14 |
@interface OTRStreamManagementYapStorage () |
|
| 15 |
|
|
| 16 |
@property (nonatomic, readonly) dispatch_queue_t parentQueue; |
|
| 17 |
|
|
| 18 |
@end |
|
| 19 |
|
|
| 20 |
@implementation OTRStreamManagementYapStorage |
|
| 21 |
|
|
| 22 |
- (instancetype)initWithDatabaseConnection:(YapDatabaseConnection *)databaseConnection |
|
| 23 |
{
|
|
| 24 |
if (self = [super init]) {
|
|
| 25 |
_databaseConnection = databaseConnection; |
|
| 26 |
} |
|
| 27 |
return self; |
|
| 28 |
} |
|
| 29 |
|
|
| 30 |
- (NSString *)accountUniqueIdForStream:(XMPPStream *)stream |
|
| 31 |
{
|
|
| 32 |
return stream.tag; |
|
| 33 |
} |
|
| 34 |
|
|
| 35 |
#pragma - mark XMPPStramManagementDelegate Methods |
|
| 36 |
// |
|
| 37 |
// |
|
| 38 |
// -- PRIVATE METHODS -- |
|
| 39 |
// |
|
| 40 |
// These methods are designed to be used ONLY by the XMPPStreamManagement class. |
|
| 41 |
// |
|
| 42 |
// |
|
| 43 |
|
|
| 44 |
/** |
|
| 45 |
* Configures the storage class, passing it's parent and the parent's dispatch queue. |
|
| 46 |
* |
|
| 47 |
* This method is called by the init methods of the XMPPStreamManagement class. |
|
| 48 |
* This method is designed to inform the storage class of it's parent |
|
| 49 |
* and of the dispatch queue the parent will be operating on. |
|
| 50 |
* |
|
| 51 |
* A storage class may choose to operate on the same queue as it's parent, |
|
| 52 |
* as the majority of the time it will be getting called by the parent. |
|
| 53 |
* If both are operating on the same queue, the combination may run faster. |
|
| 54 |
* |
|
| 55 |
* Some storage classes support multiple xmppStreams, |
|
| 56 |
* and may choose to operate on their own internal queue. |
|
| 57 |
* |
|
| 58 |
* This method should return YES if it was configured properly. |
|
| 59 |
* It should return NO only if configuration failed. |
|
| 60 |
* For example, a storage class designed to be used only with a single xmppStream is being added to a second stream. |
|
| 61 |
**/ |
|
| 62 |
- (BOOL)configureWithParent:(XMPPStreamManagement *)parent queue:(dispatch_queue_t)queue |
|
| 63 |
{
|
|
| 64 |
_parentQueue = queue; |
|
| 65 |
return YES; |
|
| 66 |
} |
|
| 67 |
|
|
| 68 |
- (OTRStreamManagementStorageObject *)fetchOrCreateStorageObjectWithStream:(XMPPStream *)stream transaction:(YapDatabaseReadTransaction *)transaction |
|
| 69 |
{
|
|
| 70 |
NSString *accountUniqueId = [self accountUniqueIdForStream:stream]; |
|
| 71 |
OTRStreamManagementStorageObject *storageObject = [OTRStreamManagementStorageObject fetchObjectWithUniqueID:accountUniqueId transaction:transaction]; |
|
| 72 |
|
|
| 73 |
if (!storageObject) {
|
|
| 74 |
storageObject = [[OTRStreamManagementStorageObject alloc] initWithUniqueId:accountUniqueId]; |
|
| 75 |
} else {
|
|
| 76 |
storageObject = [storageObject copy]; |
|
| 77 |
} |
|
| 78 |
|
|
| 79 |
return storageObject; |
|
| 80 |
} |
|
| 81 |
|
|
| 82 |
/** |
|
| 83 |
* Invoked after we receive <enabled/> from the server. |
|
| 84 |
* |
|
| 85 |
* @param resumptionId |
|
| 86 |
* The ID required to resume the session, given to us by the server. |
|
| 87 |
* |
|
| 88 |
* @param timeout |
|
| 89 |
* The timeout in seconds. |
|
| 90 |
* After a disconnect, the server will maintain our state for this long. |
|
| 91 |
* If we attempt to resume the session after this timeout it likely won't work. |
|
| 92 |
* |
|
| 93 |
* @param lastDisconnect |
|
| 94 |
* Used to reset the lastDisconnect value. |
|
| 95 |
* This value is often updated during the session, to ensure it closely resemble the date the server will use. |
|
| 96 |
* That is, if the client application is killed (or crashes) we want a relatively accurate lastDisconnect date. |
|
| 97 |
* |
|
| 98 |
* @param stream |
|
| 99 |
* The associated xmppStream (standard parameter for storage classes) |
|
| 100 |
* |
|
| 101 |
* This method should also nil out the following values (if needed) associated with the account: |
|
| 102 |
* - lastHandledByClient |
|
| 103 |
* - lastHandledByServer |
|
| 104 |
* - pendingOutgoingStanzas |
|
| 105 |
**/ |
|
| 106 |
- (void)setResumptionId:(NSString *)resumptionId |
|
| 107 |
timeout:(uint32_t)timeout |
|
| 108 |
lastDisconnect:(NSDate *)date |
|
| 109 |
forStream:(XMPPStream *)stream |
|
| 110 |
{
|
|
| 111 |
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
| 112 |
OTRStreamManagementStorageObject *storageObject = [self fetchOrCreateStorageObjectWithStream:stream transaction:transaction]; |
|
| 113 |
storageObject.timeout = timeout; |
|
| 114 |
storageObject.lastDisconnectDate = date; |
|
| 115 |
storageObject.resumptionId = resumptionId; |
|
| 116 |
|
|
| 117 |
[storageObject saveWithTransaction:transaction]; |
|
| 118 |
}]; |
|
| 119 |
|
|
| 120 |
} |
|
| 121 |
|
|
| 122 |
/** |
|
| 123 |
* This method is invoked ** often ** during stream operation. |
|
| 124 |
* It is not invoked when the xmppStream is disconnected. |
|
| 125 |
* |
|
| 126 |
* Important: See the note below: "Optimizing storage demands during active stream usage" |
|
| 127 |
* |
|
| 128 |
* @param date |
|
| 129 |
* Updates the previous lastDisconnect value. |
|
| 130 |
* |
|
| 131 |
* @param lastHandledByClient |
|
| 132 |
* The most recent 'h' value we can safely send to the server. |
|
| 133 |
* |
|
| 134 |
* @param stream |
|
| 135 |
* The associated xmppStream (standard parameter for storage classes) |
|
| 136 |
**/ |
|
| 137 |
- (void)setLastDisconnect:(NSDate *)date |
|
| 138 |
lastHandledByClient:(uint32_t)lastHandledByClient |
|
| 139 |
forStream:(XMPPStream *)stream |
|
| 140 |
{
|
|
| 141 |
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
| 142 |
OTRStreamManagementStorageObject *storageObject = [self fetchOrCreateStorageObjectWithStream:stream transaction:transaction]; |
|
| 143 |
storageObject.lastDisconnectDate = date; |
|
| 144 |
storageObject.lastHandledByClient = lastHandledByClient; |
|
| 145 |
|
|
| 146 |
[storageObject saveWithTransaction:transaction]; |
|
| 147 |
}]; |
|
| 148 |
} |
|
| 149 |
|
|
| 150 |
/** |
|
| 151 |
* This method is invoked ** often ** during stream operation. |
|
| 152 |
* It is not invoked when the xmppStream is disconnected. |
|
| 153 |
* |
|
| 154 |
* Important: See the note below: "Optimizing storage demands during active stream usage" |
|
| 155 |
* |
|
| 156 |
* @param date |
|
| 157 |
* Updates the previous lastDisconnect value. |
|
| 158 |
* |
|
| 159 |
* @param lastHandledByServer |
|
| 160 |
* The most recent 'h' value we've received from the server. |
|
| 161 |
* |
|
| 162 |
* @param pendingOutgoingStanzas |
|
| 163 |
* An array of XMPPStreamManagementOutgoingStanza objects. |
|
| 164 |
* The storage layer is in charge of properly persisting this array, including: |
|
| 165 |
* - the array count |
|
| 166 |
* - the stanzaId of each element, including those that are nil |
|
| 167 |
* |
|
| 168 |
* @param stream |
|
| 169 |
* The associated xmppStream (standard parameter for storage classes) |
|
| 170 |
**/ |
|
| 171 |
- (void)setLastDisconnect:(NSDate *)date |
|
| 172 |
lastHandledByServer:(uint32_t)lastHandledByServer |
|
| 173 |
pendingOutgoingStanzas:(NSArray *)pendingOutgoingStanzas |
|
| 174 |
forStream:(XMPPStream *)stream |
|
| 175 |
{
|
|
| 176 |
//TODO: only do saves every so often |
|
| 177 |
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
| 178 |
OTRStreamManagementStorageObject *storageObject = [self fetchOrCreateStorageObjectWithStream:stream transaction:transaction]; |
|
| 179 |
storageObject.lastDisconnectDate = date; |
|
| 180 |
storageObject.lastHandledByServer = lastHandledByServer; |
|
| 181 |
storageObject.pendingOutgoingStanzasArray = pendingOutgoingStanzas; |
|
| 182 |
|
|
| 183 |
[storageObject saveWithTransaction:transaction]; |
|
| 184 |
}]; |
|
| 185 |
} |
|
| 186 |
|
|
| 187 |
|
|
| 188 |
/// ***** Optimizing storage demands during active stream usage ***** |
|
| 189 |
/// |
|
| 190 |
/// There are 2 methods that are invoked frequently during stream activity: |
|
| 191 |
/// |
|
| 192 |
/// - setLastDisconnect:lastHandledByClient:forStream: |
|
| 193 |
/// - setLastDisconnect:lastHandledByServer:pendingOutgoingStanzas:forStream: |
|
| 194 |
/// |
|
| 195 |
/// They are invoked any time the 'h' values change, or whenver the pendingStanzaIds change. |
|
| 196 |
/// In other words, they are invoked continually as stanzas get sent and received. |
|
| 197 |
/// And it is the job of the storage layer to decide how to handle the traffic. |
|
| 198 |
/// There are a few things to consider here: |
|
| 199 |
/// |
|
| 200 |
/// - How much chatter does the xmppStream do? |
|
| 201 |
/// - How fast is the storage layer? |
|
| 202 |
/// - How does the overhead on the storage layer affect the rest of the app? |
|
| 203 |
/// |
|
| 204 |
/// If your xmppStream isn't very chatty, and you've got a fast concurrent database, |
|
| 205 |
/// then you may be able to simply pipe all these method calls to the database without thinking. |
|
| 206 |
/// However, if your xmppStream is always constantly sending/receiving presence stanzas, and pinging the server, |
|
| 207 |
/// then you might consider a bit of optimzation here. Below is a simple recommendation for how to accomplish this. |
|
| 208 |
/// |
|
| 209 |
/// You could choose to queue the changes from these method calls, and dump them to the database after a timeout. |
|
| 210 |
/// Thus you'll be able to consolidate a large traffic surge into a small handful of database operations. |
|
| 211 |
/// |
|
| 212 |
/// Also, you could expose a 'flush' operation on the storage layer. |
|
| 213 |
/// And invoke the flush operation when the app is backgrounded, or about to quit. |
|
| 214 |
|
|
| 215 |
|
|
| 216 |
/** |
|
| 217 |
* This method is invoked immediately after an accidental disconnect. |
|
| 218 |
* And may be invoked post-disconnect if the state changes, such as for the following edge cases: |
|
| 219 |
* |
|
| 220 |
* - due to continued processing of stanzas received pre-disconnect, |
|
| 221 |
* that are just now being marked as handled by the delegate(s) |
|
| 222 |
* - due to a delayed response from the delegate(s), |
|
| 223 |
* such that we didn't receive the stanzaId for an outgoing stanza until after the disconnect occurred. |
|
| 224 |
* |
|
| 225 |
* This method is not invoked if stream management is started on a connected xmppStream. |
|
| 226 |
* |
|
| 227 |
* @param date |
|
| 228 |
* This value will be the actual disconnect date. |
|
| 229 |
* |
|
| 230 |
* @param lastHandledByClient |
|
| 231 |
* The most recent 'h' value we can safely send to the server. |
|
| 232 |
* |
|
| 233 |
* @param lastHandledByServer |
|
| 234 |
* The most recent 'h' value we've received from the server. |
|
| 235 |
* |
|
| 236 |
* @param pendingOutgoingStanzas |
|
| 237 |
* An array of XMPPStreamManagementOutgoingStanza objects. |
|
| 238 |
* The storage layer is in charge of properly persisting this array, including: |
|
| 239 |
* - the array count |
|
| 240 |
* - the stanzaId of each element, including those that are nil |
|
| 241 |
* |
|
| 242 |
* @param stream |
|
| 243 |
* The associated xmppStream (standard parameter for storage classes) |
|
| 244 |
**/ |
|
| 245 |
- (void)setLastDisconnect:(NSDate *)date |
|
| 246 |
lastHandledByClient:(uint32_t)lastHandledByClient |
|
| 247 |
lastHandledByServer:(uint32_t)lastHandledByServer |
|
| 248 |
pendingOutgoingStanzas:(NSArray *)pendingOutgoingStanzas |
|
| 249 |
forStream:(XMPPStream *)stream |
|
| 250 |
{
|
|
| 251 |
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
| 252 |
OTRStreamManagementStorageObject *storageObject = [self fetchOrCreateStorageObjectWithStream:stream transaction:transaction]; |
|
| 253 |
storageObject.lastDisconnectDate = date; |
|
| 254 |
storageObject.lastHandledByClient = lastHandledByClient; |
|
| 255 |
storageObject.lastHandledByServer = lastHandledByServer; |
|
| 256 |
storageObject.pendingOutgoingStanzasArray = pendingOutgoingStanzas; |
|
| 257 |
|
|
| 258 |
[storageObject saveWithTransaction:transaction]; |
|
| 259 |
}]; |
|
| 260 |
} |
|
| 261 |
|
|
| 262 |
/** |
|
| 263 |
* Invoked when the extension needs values from a previous session. |
|
| 264 |
* This method is used to get values needed in order to determine if it can resume a previous stream. |
|
| 265 |
**/ |
|
| 266 |
- (void)getResumptionId:(NSString * __autoreleasing *)resumptionIdPtr |
|
| 267 |
timeout:(uint32_t *)timeoutPtr |
|
| 268 |
lastDisconnect:(NSDate * __autoreleasing *)lastDisconnectPtr |
|
| 269 |
forStream:(XMPPStream *)stream |
|
| 270 |
{
|
|
| 271 |
[self.databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
| 272 |
OTRStreamManagementStorageObject *storageObject = [OTRStreamManagementStorageObject fetchObjectWithUniqueID:[self accountUniqueIdForStream:stream] transaction:transaction]; |
|
| 273 |
if (storageObject) {
|
|
| 274 |
*resumptionIdPtr = storageObject.resumptionId; |
|
| 275 |
*timeoutPtr = storageObject.timeout; |
|
| 276 |
*lastDisconnectPtr = storageObject.lastDisconnectDate; |
|
| 277 |
} |
|
| 278 |
}]; |
|
| 279 |
} |
|
| 280 |
|
|
| 281 |
/** |
|
| 282 |
* Invoked when the extension needs values from a previous session. |
|
| 283 |
* This method is used to get values needed in order to resume a previous stream. |
|
| 284 |
**/ |
|
| 285 |
- (void)getLastHandledByClient:(uint32_t * _Nullable)lastHandledByClientPtr |
|
| 286 |
lastHandledByServer:(uint32_t * _Nullable)lastHandledByServerPtr |
|
| 287 |
pendingOutgoingStanzas:(NSArray<XMPPStreamManagementOutgoingStanza*> * _Nullable __autoreleasing * _Nullable)pendingOutgoingStanzasPtr |
|
| 288 |
forStream:(XMPPStream *)stream; |
|
| 289 |
{
|
|
| 290 |
[self.databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
| 291 |
OTRStreamManagementStorageObject *storageObject = [OTRStreamManagementStorageObject fetchObjectWithUniqueID:[self accountUniqueIdForStream:stream] transaction:transaction]; |
|
| 292 |
if (storageObject) {
|
|
| 293 |
*lastHandledByClientPtr = storageObject.lastHandledByClient; |
|
| 294 |
*lastHandledByServerPtr = storageObject.lastHandledByServer; |
|
| 295 |
*pendingOutgoingStanzasPtr = storageObject.pendingOutgoingStanzasArray; |
|
| 296 |
} |
|
| 297 |
}]; |
|
| 298 |
} |
|
| 299 |
|
|
| 300 |
/** |
|
| 301 |
* Instructs the storage layer to remove all values stored for the given stream. |
|
| 302 |
* This occurs after the extension detects a "cleanly closed stream", |
|
| 303 |
* in which case the stream cannot be resumed next time. |
|
| 304 |
**/ |
|
| 305 |
- (void)removeAllForStream:(XMPPStream *)stream |
|
| 306 |
{
|
|
| 307 |
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
| 308 |
[transaction removeObjectForKey:[self accountUniqueIdForStream:stream] |
|
| 309 |
inCollection:[OTRStreamManagementStorageObject collection]]; |
|
| 310 |
}]; |
|
| 311 |
} |
|
| 312 |
|
|
| 313 |
@end |
|
| ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager.h | ||
|---|---|---|
| 40 | 40 |
|
| 41 | 41 |
@property (nonatomic, strong, readonly) OTRXMPPAccount *account; |
| 42 | 42 |
|
| 43 |
@property (nonatomic, strong, readonly) XMPPMessageArchiveManagement *xmppMAM; |
|
| 44 | 43 |
@property (nonatomic, strong, readonly) XMPPRoster *xmppRoster; |
| 45 | 44 |
@property (nonatomic, strong, readonly) XMPPCapabilities *xmppCapabilities; |
| 46 | 45 |
@property (nonatomic, strong, readonly) OTRXMPPRoomManager *roomManager; |
| ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager.m | ||
|---|---|---|
| 242 | 242 |
_fileTransferManager = [[FileTransferManager alloc] initWithConnection:self.databaseConnection serverCapabilities:self.serverCapabilities sessionConfiguration:sessionConfiguration]; |
| 243 | 243 |
|
| 244 | 244 |
// Message storage |
| 245 |
_messageStorage = [[OTRXMPPMessageYapStorage alloc] initWithDatabaseConnection:self.databaseConnection];
|
|
| 245 |
_messageStorage = [[MessageStorage alloc] initWithConnection:self.databaseConnection capabilities:self.xmppCapabilities dispatchQueue:nil];
|
|
| 246 | 246 |
[self.messageStorage activate:self.xmppStream]; |
| 247 | 247 |
|
| 248 |
// Message Carbons |
|
| 249 |
_messageCarbons = [[XMPPMessageCarbons alloc] init]; |
|
| 250 |
[self.messageCarbons addDelegate:self.messageStorage delegateQueue:self.messageStorage.moduleDelegateQueue]; |
|
| 251 |
[self.messageCarbons activate:self.xmppStream]; |
|
| 252 |
|
|
| 253 |
// Message archiving |
|
| 254 |
_xmppMAM = [[XMPPMessageArchiveManagement alloc] init]; |
|
| 255 |
[self.xmppMAM addDelegate:self.messageStorage delegateQueue:self.messageStorage.moduleDelegateQueue]; |
|
| 256 |
[self.xmppMAM activate:self.xmppStream]; |
|
| 257 |
|
|
| 258 |
|
|
| 259 | 248 |
//Stream Management |
| 260 | 249 |
_streamManagementDelegate = [[OTRStreamManagementDelegate alloc] initWithDatabaseConnection:self.databaseConnection]; |
| 261 | 250 |
|
| ... | ... | |
| 269 | 258 |
[self.streamManagement activate:self.xmppStream]; |
| 270 | 259 |
|
| 271 | 260 |
//MUC |
| 272 |
_roomManager = [[OTRXMPPRoomManager alloc] init]; |
|
| 273 |
self.roomManager.databaseConnection = [OTRDatabaseManager sharedInstance].readWriteDatabaseConnection; |
|
| 261 |
_roomManager = [[OTRXMPPRoomManager alloc] initWithDatabaseConnection:[OTRDatabaseManager sharedInstance].readWriteDatabaseConnection capabilities:self.xmppCapabilities dispatchQueue:nil]; |
|
| 274 | 262 |
[self.roomManager activate:self.xmppStream]; |
| 275 | 263 |
|
| 276 | 264 |
//Buddy Manager (for deleting) |
| ... | ... | |
| 316 | 304 |
[_xmppvCardAvatarModule deactivate]; |
| 317 | 305 |
[_xmppCapabilities deactivate]; |
| 318 | 306 |
[_streamManagement deactivate]; |
| 319 |
[_messageCarbons deactivate]; |
|
| 320 | 307 |
[_messageStorage deactivate]; |
| 321 | 308 |
[_certificatePinningModule deactivate]; |
| 322 | 309 |
[_deliveryReceipts deactivate]; |
| ... | ... | |
| 894 | 881 |
//[self.xmppvCardTempModule fetchvCardTempForJID:self.JID ignoreStorage:YES]; |
| 895 | 882 |
|
| 896 | 883 |
// Testing MAM |
| 897 |
[self.xmppMAM retrieveMessageArchiveWithFields:nil withResultSet:nil];
|
|
| 884 |
[self.messageStorage.archiving retrieveMessageArchiveWithFields:nil withResultSet:nil];
|
|
| 898 | 885 |
} |
| 899 | 886 |
|
| 900 | 887 |
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(NSXMLElement *)error |
| ChatSecure/Classes/Controllers/XMPP/OTRXMPPManager_Private.h | ||
|---|---|---|
| 28 | 28 |
@property (nonatomic, strong) OTRCertificatePinning * certificatePinningModule; |
| 29 | 29 |
|
| 30 | 30 |
@property (nonatomic, strong, readonly) XMPPStreamManagement *streamManagement; |
| 31 |
@property (nonatomic, strong, readonly) XMPPMessageCarbons *messageCarbons; |
|
| 32 |
@property (nonatomic, strong, readonly) OTRXMPPMessageYapStorage *messageStorage; |
|
| 33 | 31 |
|
| 32 |
@property (nonatomic, strong, readonly) MessageStorage *messageStorage; |
|
| 34 | 33 |
@property (nonatomic, strong, readonly) OTRXMPPBuddyManager* xmppBuddyManager; |
| 35 | 34 |
@property (nonatomic, strong, readonly) OMEMOModule *omemoModule; |
| 36 | 35 |
@property (nonatomic, strong, nullable) OTRXMPPChangePasswordManager *changePasswordManager; |
| ChatSecure/Classes/Controllers/XMPP/OTRXMPPMessageYapStorage.h | ||
|---|---|---|
| 1 |
// |
|
| 2 |
// OTRXMPPMessageYapStroage.h |
|
| 3 |
// ChatSecure |
|
| 4 |
// |
|
| 5 |
// Created by David Chiles on 8/13/15. |
|
| 6 |
// Copyright (c) 2015 Chris Ballinger. All rights reserved. |
|
| 7 |
// |
|
| 8 |
|
|
| 9 |
@import XMPPFramework; |
|
| 10 |
@import YapDatabase; |
|
| 11 |
@class XMPPMessage; |
|
| 12 |
|
|
| 13 |
NS_ASSUME_NONNULL_BEGIN |
|
| 14 |
@interface OTRXMPPMessageYapStorage : XMPPModule <XMPPMessageCarbonsDelegate> |
|
| 15 |
|
|
| 16 |
@property (nonatomic, strong, readonly) YapDatabaseConnection *databaseConnection; |
|
| 17 |
@property (nonatomic, readonly) dispatch_queue_t moduleDelegateQueue; |
|
| 18 |
|
|
| 19 |
/** This connection is only used for readWrites */ |
|
| 20 |
- (instancetype)initWithDatabaseConnection:(YapDatabaseConnection *)databaseConnection; |
|
| 21 |
|
|
| 22 |
@end |
|
| 23 |
NS_ASSUME_NONNULL_END |
|
| ChatSecure/Classes/Controllers/XMPP/OTRXMPPMessageYapStorage.m | ||
|---|---|---|
| 1 |
// |
|
| 2 |
// OTRXMPPMessageYapStroage.m |
|
| 3 |
// ChatSecure |
|
| 4 |
// |
|
| 5 |
// Created by David Chiles on 8/13/15. |
|
| 6 |
// Copyright (c) 2015 Chris Ballinger. All rights reserved. |
|
| 7 |
// |
|
| 8 |
|
|
| 9 |
#import "OTRXMPPMessageYapStorage.h" |
|
| 10 |
@import XMPPFramework; |
|
| 11 |
#import "OTRLog.h" |
|
| 12 |
@import OTRKit; |
|
| 13 |
#import "OTRXMPPBuddy.h" |
|
| 14 |
#import "OTRIncomingMessage.h" |
|
| 15 |
#import "OTROutgoingMessage.h" |
|
| 16 |
#import "OTRAccount.h" |
|
| 17 |
#import "OTRConstants.h" |
|
| 18 |
#import <ChatSecureCore/ChatSecureCore-Swift.h> |
|
| 19 |
#import "OTRThreadOwner.h" |
|
| 20 |
#import "OTRBuddyCache.h" |
|
| 21 |
#import "OTRXMPPError.h" |
|
| 22 |
#import "OTRXMPPManager_Private.h" |
|
| 23 |
|
|
| 24 |
@implementation OTRXMPPMessageYapStorage |
|
| 25 |
|
|
| 26 |
- (instancetype)initWithDatabaseConnection:(YapDatabaseConnection *)connection |
|
| 27 |
{
|
|
| 28 |
if (self = [self init]) {
|
|
| 29 |
_databaseConnection = connection; |
|
| 30 |
_moduleDelegateQueue = dispatch_queue_create("OTRXMPPMessageYapStroage-delegateQueue", 0);
|
|
| 31 |
} |
|
| 32 |
return self; |
|
| 33 |
} |
|
| 34 |
|
|
| 35 |
|
|
| 36 |
- (OTRXMPPBuddy *)buddyForJID:(XMPPJID *)jid stream:(XMPPStream *)stream transaction:(YapDatabaseReadTransaction *)transaction |
|
| 37 |
{
|
|
| 38 |
NSParameterAssert(jid); |
|
| 39 |
NSParameterAssert(stream.tag); |
|
| 40 |
NSParameterAssert(transaction); |
|
| 41 |
if (!stream.tag || !jid || !transaction) { return nil; }
|
|
| 42 |
return [OTRXMPPBuddy fetchBuddyWithJid:jid accountUniqueId:stream.tag transaction:transaction]; |
|
| 43 |
} |
|
| 44 |
|
|
| 45 |
- (OTRBaseMessage *)baseMessageFromXMPPMessage:(XMPPMessage *)xmppMessage buddyId:(NSString *)buddyId class:(Class)class {
|
|
| 46 |
NSString *body = [xmppMessage body]; |
|
| 47 |
|
|
| 48 |
NSDate * date = [xmppMessage delayedDeliveryDate]; |
|
| 49 |
|
|
| 50 |
OTRBaseMessage *message = [[class alloc] init]; |
|
| 51 |
message.text = body; |
|
| 52 |
message.buddyUniqueId = buddyId; |
|
| 53 |
if (date) {
|
|
| 54 |
message.date = date; |
|
| 55 |
} |
|
| 56 |
|
|
| 57 |
message.messageId = [xmppMessage elementID]; |
|
| 58 |
return message; |
|
| 59 |
} |
|
| 60 |
|
|
| 61 |
- (OTROutgoingMessage *)outgoingMessageFromXMPPMessage:(XMPPMessage *)xmppMessage buddyId:(NSString *)buddyId {
|
|
| 62 |
OTROutgoingMessage *outgoingMessage = (OTROutgoingMessage *)[self baseMessageFromXMPPMessage:xmppMessage buddyId:buddyId class:[OTROutgoingMessage class]]; |
|
| 63 |
// Fill in current data so it looks like this 'outgoing' message was really sent (but of course this is a message we received through carbons). |
|
| 64 |
outgoingMessage.dateSent = [NSDate date]; |
|
| 65 |
return outgoingMessage; |
|
| 66 |
} |
|
| 67 |
|
|
| 68 |
- (OTRIncomingMessage *)incomingMessageFromXMPPMessage:(XMPPMessage *)xmppMessage buddyId:(NSString *)buddyId |
|
| 69 |
{
|
|
| 70 |
return (OTRIncomingMessage *)[self baseMessageFromXMPPMessage:xmppMessage buddyId:buddyId class:[OTRIncomingMessage class]]; |
|
| 71 |
} |
|
| 72 |
|
|
| 73 |
- (void)xmppStream:(XMPPStream *)stream didReceiveMessage:(XMPPMessage *)xmppMessage |
|
| 74 |
{
|
|
| 75 |
// We don't handle incoming group chat messages here |
|
| 76 |
// Check out OTRXMPPRoomYapStorage instead |
|
| 77 |
if ([[xmppMessage type] isEqualToString:@"groupchat"] || |
|
| 78 |
[xmppMessage elementForName:@"x" xmlns:XMPPMUCUserNamespace] || |
|
| 79 |
[xmppMessage elementForName:@"x" xmlns:@"jabber:x:conference"]) {
|
|
| 80 |
return; |
|
| 81 |
} |
|
| 82 |
// We handle carbons elsewhere via XMPPMessageCarbonsDelegate |
|
| 83 |
// We handle MAM elsewhere as well |
|
| 84 |
if (xmppMessage.isMessageCarbon || |
|
| 85 |
xmppMessage.mamResult) {
|
|
| 86 |
return; |
|
| 87 |
} |
|
| 88 |
|
|
| 89 |
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
| 90 |
if (![stream.tag isKindOfClass:[NSString class]]) {
|
|
| 91 |
DDLogError(@"Error - No account tag on stream %@", stream); |
|
| 92 |
return; |
|
| 93 |
} |
|
| 94 |
NSString *accountId = stream.tag; |
|
| 95 |
NSString *username = [[xmppMessage from] bare]; |
|
| 96 |
XMPPJID *fromJID = xmppMessage.from; |
|
| 97 |
if (!fromJID) {
|
|
| 98 |
DDLogWarn(@"No from for message: %@", xmppMessage); |
|
| 99 |
return; |
|
| 100 |
} |
|
| 101 |
OTRXMPPAccount *account = [OTRXMPPAccount fetchObjectWithUniqueID:accountId transaction:transaction]; |
|
| 102 |
if (!account) {
|
|
| 103 |
DDLogWarn(@"No account for message: %@", xmppMessage); |
|
| 104 |
return; |
|
| 105 |
} |
|
| 106 |
OTRXMPPBuddy *messageBuddy = [self buddyForJID:fromJID stream:stream transaction:transaction]; |
|
| 107 |
if (!messageBuddy) {
|
|
| 108 |
// message from server |
|
| 109 |
|
|
| 110 |
DDLogWarn(@"No buddy for message: %@", xmppMessage); |
|
| 111 |
return; |
|
| 112 |
} |
|
| 113 |
[self handleChatState:xmppMessage fromJID:fromJID stream:stream transaction:transaction]; |
|
| 114 |
[self handleDeliverResponse:xmppMessage transaction:transaction]; |
|
| 115 |
|
|
| 116 |
// If we receive a message from an online buddy that counts as them interacting with us |
|
| 117 |
OTRThreadStatus status = [OTRBuddyCache.shared threadStatusForBuddy:messageBuddy]; |
|
| 118 |
if (status != OTRThreadStatusOffline && |
|
| 119 |
![xmppMessage hasReceiptResponse] && |
|
| 120 |
![xmppMessage isErrorMessage]) {
|
|
| 121 |
[OTRBuddyCache.shared setLastSeenDate:[NSDate date] forBuddy:messageBuddy]; |
|
| 122 |
} |
|
| 123 |
|
|
| 124 |
// Check if this is a bounced outgoing message / error |
|
| 125 |
NSString *eid = [xmppMessage elementID]; |
|
| 126 |
if (eid && [xmppMessage isErrorMessage]) {
|
|
| 127 |
id<OTRMessageProtocol> existingMessage = [OTROutgoingMessage messageForMessageId:eid transaction:transaction]; |
|
| 128 |
if ([existingMessage isKindOfClass:[OTROutgoingMessage class]]) {
|
|
| 129 |
OTROutgoingMessage *message = (OTROutgoingMessage*)existingMessage; |
|
| 130 |
message.error = [OTRXMPPError errorForXMLElement:xmppMessage]; |
|
| 131 |
[message saveWithTransaction:transaction]; |
|
| 132 |
} else if ([existingMessage isKindOfClass:[OTRIncomingMessage class]]) {
|
|
| 133 |
NSString *errorText = [[xmppMessage elementForName:@"error"] elementForName:@"text"].stringValue; |
|
| 134 |
if ([errorText containsString:@"OTR Error"]) {
|
|
| 135 |
// automatically renegotiate a new session when there's an error |
|
| 136 |
[[OTRProtocolManager sharedInstance].encryptionManager.otrKit initiateEncryptionWithUsername:username accountName:account.username protocol:account.protocolTypeString]; |
|
| 137 |
} |
|
| 138 |
} |
|
| 139 |
return; |
|
| 140 |
} |
|
| 141 |
|
|
| 142 |
OTRIncomingMessage *message = [self incomingMessageFromXMPPMessage:xmppMessage buddyId:messageBuddy.uniqueId]; |
|
| 143 |
NSString *activeThreadYapKey = [[OTRAppDelegate appDelegate] activeThreadYapKey]; |
|
| 144 |
if([activeThreadYapKey isEqualToString:message.threadId]) {
|
|
| 145 |
message.read = YES; |
|
| 146 |
} |
|
| 147 |
|
|
| 148 |
// Extract XEP-0359 stanza-id |
|
| 149 |
NSString *originId = xmppMessage.originId; |
|
| 150 |
NSString *stanzaId = [xmppMessage extractStanzaIdWithAccount:account]; |
|
| 151 |
message.originId = originId; |
|
| 152 |
message.stanzaId = stanzaId; |
|
| 153 |
|
|
| 154 |
if ([self isDuplicateMessage:xmppMessage stanzaId:stanzaId buddyUniqueId:messageBuddy.uniqueId transaction:transaction]) {
|
|
| 155 |
DDLogWarn(@"Duplicate message received: %@", xmppMessage); |
|
| 156 |
return; |
|
| 157 |
} |
|
| 158 |
|
|
| 159 |
if (message.text) {
|
|
| 160 |
[[OTRProtocolManager sharedInstance].encryptionManager.otrKit decodeMessage:message.text username:messageBuddy.username accountName:account.username protocol:kOTRProtocolTypeXMPP tag:message]; |
|
| 161 |
} |
|
| 162 |
}]; |
|
| 163 |
} |
|
| 164 |
|
|
| 165 |
- (void)handleChatState:(XMPPMessage *)xmppMessage fromJID:(XMPPJID *)fromJID stream:(XMPPStream *)stream transaction:(YapDatabaseReadTransaction *)transaction |
|
| 166 |
{
|
|
| 167 |
// Saves aren't needed when setting chatState or status because OTRBuddyCache is used internally |
|
| 168 |
|
|
| 169 |
OTRXMPPBuddy *messageBuddy = [self buddyForJID:fromJID stream:stream transaction:transaction]; |
|
| 170 |
if (!messageBuddy) { return; }
|
|
| 171 |
OTRChatState chatState = OTRChatStateUnknown; |
|
| 172 |
if([xmppMessage hasChatState]) |
|
| 173 |
{
|
|
| 174 |
if([xmppMessage hasComposingChatState]) |
|
| 175 |
chatState = OTRChatStateComposing; |
|
| 176 |
else if([xmppMessage hasPausedChatState]) |
|
| 177 |
chatState = OTRChatStatePaused; |
|
| 178 |
else if([xmppMessage hasActiveChatState]) |
|
| 179 |
chatState = OTRChatStateActive; |
|
| 180 |
else if([xmppMessage hasInactiveChatState]) |
|
| 181 |
chatState = OTRChatStateInactive; |
|
| 182 |
else if([xmppMessage hasGoneChatState]) |
|
| 183 |
chatState = OTRChatStateGone; |
|
| 184 |
} |
|
| 185 |
[OTRBuddyCache.shared setChatState:chatState forBuddy:messageBuddy]; |
|
| 186 |
} |
|
| 187 |
|
|
| 188 |
- (void)handleDeliverResponse:(XMPPMessage *)xmppMessage transaction:(YapDatabaseReadWriteTransaction *)transaction |
|
| 189 |
{
|
|
| 190 |
if ([xmppMessage hasReceiptResponse] && ![xmppMessage isErrorMessage]) {
|
|
| 191 |
[OTROutgoingMessage receivedDeliveryReceiptForMessageId:[xmppMessage receiptResponseID] transaction:transaction]; |
|
| 192 |
} |
|
| 193 |
} |
|
| 194 |
|
|
| 195 |
/** It is a violation of the XMPP spec to discard messages with duplicate stanza elementIds. We must use XEP-0359 stanza-id only. */ |
|
| 196 |
- (BOOL)isDuplicateMessage:(XMPPMessage *)message stanzaId:(NSString*)stanzaId buddyUniqueId:(NSString *)buddyUniqueId transaction:(YapDatabaseReadWriteTransaction *)transaction |
|
| 197 |
{
|
|
| 198 |
__block BOOL result = NO; |
|
| 199 |
if (!stanzaId.length) {
|
|
| 200 |
return NO; |
|
| 201 |
} |
|
| 202 |
|
|
| 203 |
[transaction enumerateMessagesWithElementId:nil originId:nil stanzaId:stanzaId block:^(id<OTRMessageProtocol> _Nonnull databaseMessage, BOOL * _Null_unspecified stop) {
|
|
| 204 |
if ([[databaseMessage threadId] isEqualToString:buddyUniqueId]) {
|
|
| 205 |
*stop = YES; |
|
| 206 |
result = YES; |
|
| 207 |
} |
|
| 208 |
}]; |
|
| 209 |
return result; |
|
| 210 |
} |
|
| 211 |
|
|
| 212 |
/// Handles both Carbons and MAM |
|
| 213 |
- (void)handleForwardedMessage:(XMPPMessage *)forwardedMessage delayedDeliveryDate:(nullable NSDate*)delayedDeliveryDate stream:(XMPPStream *)stream outgoing:(BOOL)isOutgoing |
|
| 214 |
{
|
|
| 215 |
if (!forwardedMessage.isMessageWithBody || |
|
| 216 |
forwardedMessage.isErrorMessage || |
|
| 217 |
[OTRKit stringStartsWithOTRPrefix:forwardedMessage.body]) {
|
|
| 218 |
DDLogWarn(@"Discarding forwarded message: %@", forwardedMessage.prettyXMLString); |
|
| 219 |
return; |
|
| 220 |
} |
|
| 221 |
//Sent Message Carbons are sent by our account to another |
|
| 222 |
//So from is our JID and to is buddy |
|
| 223 |
BOOL incoming = !isOutgoing; |
|
| 224 |
|
|
| 225 |
|
|
| 226 |
XMPPJID *jid = nil; |
|
| 227 |
if (incoming) {
|
|
| 228 |
jid = forwardedMessage.from; |
|
| 229 |
} else {
|
|
| 230 |
jid = forwardedMessage.to; |
|
| 231 |
} |
|
| 232 |
if (!jid) { return; }
|
|
| 233 |
NSString *accountId = stream.tag; |
|
| 234 |
if (!accountId.length) { return; }
|
|
| 235 |
|
|
| 236 |
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * __nonnull transaction) {
|
|
| 237 |
OTRXMPPAccount *account = [OTRXMPPAccount fetchObjectWithUniqueID:accountId transaction:transaction]; |
|
| 238 |
OTRXMPPBuddy *buddy = [self buddyForJID:jid stream:stream transaction:transaction]; |
|
| 239 |
if (!buddy || !account) {
|
|
| 240 |
return; |
|
| 241 |
} |
|
| 242 |
// Extract XEP-0359 stanza-id |
|
| 243 |
NSString *originId = forwardedMessage.originId; |
|
| 244 |
NSString *stanzaId = [forwardedMessage extractStanzaIdWithAccount:account]; |
|
| 245 |
|
|
| 246 |
if (incoming) {
|
|
| 247 |
[self handleChatState:forwardedMessage fromJID:jid stream:stream transaction:transaction]; |
|
| 248 |
[self handleDeliverResponse:forwardedMessage transaction:transaction]; |
|
| 249 |
} |
|
| 250 |
|
|
| 251 |
if ([self isDuplicateMessage:forwardedMessage stanzaId:stanzaId buddyUniqueId:buddy.uniqueId transaction:transaction]) {
|
|
| 252 |
DDLogWarn(@"Duplicate message received: %@", forwardedMessage); |
|
| 253 |
return; |
|
| 254 |
} |
|
| 255 |
OTRBaseMessage *message = nil; |
|
| 256 |
if (incoming) {
|
|
| 257 |
OTRIncomingMessage *incomingMessage = [self incomingMessageFromXMPPMessage:forwardedMessage buddyId:buddy.uniqueId]; |
|
| 258 |
NSString *activeThreadYapKey = [[OTRAppDelegate appDelegate] activeThreadYapKey]; |
|
| 259 |
if([activeThreadYapKey isEqualToString:message.threadId]) {
|
|
| 260 |
incomingMessage.read = YES; |
|
| 261 |
} |
|
| 262 |
message = incomingMessage; |
|
| 263 |
} else {
|
|
| 264 |
message = [self outgoingMessageFromXMPPMessage:forwardedMessage buddyId:buddy.uniqueId]; |
|
| 265 |
} |
|
| 266 |
if (delayedDeliveryDate) {
|
|
| 267 |
message.date = delayedDeliveryDate; |
|
| 268 |
} |
|
| 269 |
message.originId = originId; |
|
| 270 |
message.stanzaId = stanzaId; |
|
| 271 |
[message saveWithTransaction:transaction]; |
|
| 272 |
}]; |
|
| 273 |
} |
|
| 274 |
|
|
| 275 |
#pragma mark - XMPPMessageCarbonsDelegate |
|
| 276 |
|
|
| 277 |
- (void)xmppMessageCarbons:(XMPPMessageCarbons *)xmppMessageCarbons willReceiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing { }
|
|
| 278 |
|
|
| 279 |
- (void)xmppMessageCarbons:(XMPPMessageCarbons *)xmppMessageCarbons didReceiveMessage:(XMPPMessage *)message outgoing:(BOOL)isOutgoing |
|
| 280 |
{
|
|
| 281 |
[self handleForwardedMessage:message delayedDeliveryDate:nil stream:xmppMessageCarbons.xmppStream outgoing:isOutgoing]; |
|
| 282 |
} |
|
| 283 |
|
|
| 284 |
#pragma mark - XMPPMessageArchiveManagementDelegate |
|
| 285 |
|
|
| 286 |
- (void)xmppMessageArchiveManagement:(XMPPMessageArchiveManagement *)xmppMessageArchiveManagement didFinishReceivingMessagesWithSet:(XMPPResultSet *)resultSet {
|
|
| 287 |
DDLogVerbose(@"MAM didFinishReceivingMessagesWithSet: %@", resultSet.prettyXMLString); |
|
| 288 |
} |
|
| 289 |
- (void)xmppMessageArchiveManagement:(XMPPMessageArchiveManagement *)xmppMessageArchiveManagement didReceiveMAMMessage:(XMPPMessage *)message {
|
|
| 290 |
DDLogVerbose(@"MAM didReceiveMAMMessage: %@", message.prettyXMLString); |
|
| 291 |
NSXMLElement *result = message.mamResult; |
|
| 292 |
XMPPMessage *forwardedMessage = result.forwardedMessage; |
|
| 293 |
if (!forwardedMessage) { return; }
|
|
| 294 |
NSDate *delayedDeliveryDate = result.forwardedStanzaDelayedDeliveryDate; |
|
| 295 |
XMPPJID *fromJID = forwardedMessage.from; |
|
| 296 |
if (!fromJID) { return; }
|
|
| 297 |
BOOL isOutgoing = [fromJID isEqualToJID:xmppMessageArchiveManagement.xmppStream.myJID options:XMPPJIDCompareBare]; |
|
| 298 |
[self handleForwardedMessage:forwardedMessage delayedDeliveryDate:delayedDeliveryDate stream:xmppMessageArchiveManagement.xmppStream outgoing:isOutgoing]; |
|
| 299 |
} |
|
| 300 |
- (void)xmppMessageArchiveManagement:(XMPPMessageArchiveManagement *)xmppMessageArchiveManagement didFailToReceiveMessages:(XMPPIQ *)error {
|
|
| 301 |
DDLogError(@"MAM didFailToReceiveMessages: %@", error.prettyXMLString); |
|
| 302 |
} |
|
| 303 |
|
|
| 304 |
- (void)xmppMessageArchiveManagement:(XMPPMessageArchiveManagement *)xmppMessageArchiveManagement didReceiveFormFields:(XMPPIQ *)iq {
|
|
| 305 |
DDLogVerbose(@"MAM didReceiveFormFields: %@", iq.prettyXMLString); |
|
| 306 |
} |
|
| 307 |
- (void)xmppMessageArchiveManagement:(XMPPMessageArchiveManagement *)xmppMessageArchiveManagement didFailToReceiveFormFields:(XMPPIQ *)iq {
|
|
| 308 |
DDLogError(@"MAM didFailToReceiveFormFields: %@", iq.prettyXMLString); |
|
| 309 |
} |
|
| 310 |
|
|
| 311 |
@end |
|
| ChatSecure/Classes/Controllers/XMPP/OTRXMPPRoomManager.h | ||
|---|---|---|
| 16 | 16 |
NS_ASSUME_NONNULL_BEGIN |
| 17 | 17 |
@interface OTRXMPPRoomManager : XMPPModule |
| 18 | 18 |
|
| 19 |
@property (nonatomic, strong, readonly) XMPPCapabilities *capabilities; |
|
| 19 | 20 |
@property (nonatomic, strong, readonly) XMPPBookmarksModule *bookmarksModule; |
| 20 | 21 |
@property (nonatomic, strong, readonly, nullable) NSArray<NSString*> *conferenceServicesJID; |
| 21 |
@property (nonatomic, strong, nullable) YapDatabaseConnection * databaseConnection; |
|
| 22 |
@property (nonatomic, strong, readonly) YapDatabaseConnection * databaseConnection; |
|
| 23 |
|
|
| 24 |
- (instancetype) init NS_UNAVAILABLE; |
|
| 25 |
- (instancetype) initWithDatabaseConnection:(YapDatabaseConnection*)databaseConnection |
|
| 26 |
capabilities:(XMPPCapabilities*)capabilities |
|
| 27 |
dispatchQueue:(nullable dispatch_queue_t)dispatchQueue; |
|
| 22 | 28 |
|
| 23 | 29 |
/** All room joining should go through this method. This ensures the delegates are setup properly and database is in sync. Returns OTRThreadOwner.threadIdentifier */ |
| 24 | 30 |
- (nullable NSString *)joinRoom:(XMPPJID *)jid |
| ChatSecure/Classes/Controllers/XMPP/OTRXMPPRoomManager.m | ||
|---|---|---|
| 34 | 34 |
|
| 35 | 35 |
@implementation OTRXMPPRoomManager |
| 36 | 36 |
|
| 37 |
- (instancetype)init {
|
|
| 38 |
if (self = [super init]) {
|
|
| 37 |
- (instancetype) initWithDatabaseConnection:(YapDatabaseConnection*)databaseConnection |
|
| 38 |
capabilities:(XMPPCapabilities*)capabilities |
|
| 39 |
dispatchQueue:(nullable dispatch_queue_t)dispatchQueue {
|
|
| 40 |
if (self = [super initWithDispatchQueue:dispatchQueue]) {
|
|
| 41 |
_databaseConnection = databaseConnection; |
|
| 42 |
_capabilities = capabilities; |
|
| 39 | 43 |
_mucModule = [[XMPPMUC alloc] init]; |
| 40 | 44 |
_inviteDictionary = [[NSMutableDictionary alloc] init]; |
| 41 | 45 |
_tempRoomSubject = [[NSMutableDictionary alloc] init]; |
| ... | ... | |
| 82 | 86 |
__block NSString *nickname = name; |
| 83 | 87 |
|
| 84 | 88 |
if (!room) {
|
| 85 |
OTRXMPPRoomYapStorage *storage = [[OTRXMPPRoomYapStorage alloc] initWithDatabaseConnection:self.databaseConnection]; |
|
| 89 |
OTRXMPPRoomYapStorage *storage = [[OTRXMPPRoomYapStorage alloc] initWithDatabaseConnection:self.databaseConnection capabilities:self.capabilities];
|
|
| 86 | 90 |
room = [[XMPPRoom alloc] initWithRoomStorage:storage jid:jid]; |
| 87 | 91 |
[self setRoom:room forJID:room.roomJID]; |
| 88 | 92 |
[room activate:self.xmppStream]; |
| ChatSecure/Classes/Controllers/XMPP/OTRXMPPRoomYapStorage.h | ||
|---|---|---|
| 1 |
// |
|
| 2 |
// OTRXMPPRoomYapStorage.h |
|
| 3 |
// ChatSecure |
|
| 4 |
// |
|
| 5 |
// Created by David Chiles on 10/7/15. |
|
| 6 |
// Copyright ยฉ 2015 Chris Ballinger. All rights reserved. |
|
| 7 |
// |
|
| 8 |
|
|
| 9 |
@import Foundation; |
|
| 10 |
@import XMPPFramework; |
|
| 11 |
@import YapDatabase; |
|
| 12 |
#import "OTRIncomingMessage.h" |
|
| 13 |
#import "OTROutgoingMessage.h" |
|
| 14 |
|
|
| 15 |
@class OTRXMPPRoomOccupant; |
|
| 16 |
|
|
| 17 |
NS_ASSUME_NONNULL_BEGIN |
|
| 18 |
@interface OTRXMPPRoomYapStorage : NSObject <XMPPRoomStorage> |
|
| 19 |
|
|
| 20 |
@property (nonatomic, strong) YapDatabaseConnection *databaseConnection; |
|
| 21 |
|
|
| 22 |
- (instancetype)initWithDatabaseConnection:(YapDatabaseConnection *)databaseConnection; |
|
| 23 |
|
|
| 24 |
- (id <OTRMessageProtocol>)lastMessageInRoom:(XMPPRoom *)room accountKey:(NSString *)accountKey; |
|
| 25 |
@end |
|
| 26 |
NS_ASSUME_NONNULL_END |
|
| ChatSecure/Classes/Controllers/XMPP/OTRXMPPRoomYapStorage.m | ||
|---|---|---|
| 1 |
// |
|
| 2 |
// OTRXMPPRoomYapStorage.m |
|
| 3 |
// ChatSecure |
|
| 4 |
// |
|
| 5 |
// Created by David Chiles on 10/7/15. |
|
| 6 |
// Copyright ยฉ 2015 Chris Ballinger. All rights reserved. |
|
| 7 |
// |
|
| 8 |
|
|
| 9 |
#import "OTRXMPPRoomYapStorage.h" |
|
| 10 |
#import "OTRDatabaseManager.h" |
|
| 11 |
#import "OTRAccount.h" |
|
| 12 |
#import <ChatSecureCore/ChatSecureCore-Swift.h> |
|
| 13 |
#import "OTRLog.h" |
|
| 14 |
#import "OTRXMPPManager_Private.h" |
|
| 15 |
@import YapDatabase; |
|
| 16 |
@import XMPPFramework; |
|
| 17 |
|
|
| 18 |
@interface OTRXMPPRoomYapStorage () |
|
| 19 |
|
|
| 20 |
@property (nonatomic) dispatch_queue_t parentQueue; |
|
| 21 |
|
|
| 22 |
@end |
|
| 23 |
|
|
| 24 |
@implementation OTRXMPPRoomYapStorage |
|
| 25 |
|
|
| 26 |
- (instancetype)initWithDatabaseConnection:(YapDatabaseConnection *)databaseConnection |
|
| 27 |
{
|
|
| 28 |
if (self = [self init]){
|
|
| 29 |
self.databaseConnection = databaseConnection; |
|
| 30 |
} |
|
| 31 |
return self; |
|
| 32 |
} |
|
| 33 |
|
|
| 34 |
- (OTRXMPPRoom *)fetchRoomWithXMPPRoomJID:(NSString *)roomJID accountId:(NSString *)accountId inTransaction:(YapDatabaseReadTransaction *)transaction {
|
|
| 35 |
return [OTRXMPPRoom fetchObjectWithUniqueID:[OTRXMPPRoom createUniqueId:accountId jid:roomJID] transaction:transaction]; |
|
| 36 |
} |
|
| 37 |
|
|
| 38 |
- (BOOL)existsMessage:(XMPPMessage *)message from:(XMPPJID *)fromJID stanzaId:(nullable NSString*)stanzaId transaction:(YapDatabaseReadTransaction *)transaction |
|
| 39 |
{
|
|
| 40 |
NSDate *remoteTimestamp = [message delayedDeliveryDate]; |
|
| 41 |
if (!remoteTimestamp) |
|
| 42 |
{
|
|
| 43 |
// When the xmpp server sends us a room message, it will always timestamp delayed messages. |
|
| 44 |
// For example, when retrieving the discussion history, all messages will include the original timestamp. |
|
| 45 |
// If a message doesn't include such timestamp, then we know we're getting it in "real time". |
|
| 46 |
|
|
| 47 |
return NO; |
|
| 48 |
} |
|
| 49 |
NSString *elementID = message.elementID; |
|
| 50 |
__block BOOL result = NO; |
|
| 51 |
[transaction enumerateMessagesWithElementId:elementID originId:nil stanzaId:stanzaId block:^(id<OTRMessageProtocol> _Nonnull databaseMessage, BOOL * _Null_unspecified stop) {
|
|
| 52 |
//Need to check room JID |
|
| 53 |
//So if message has same ID and same room jid that's got to be the same message, right? |
|
| 54 |
if ([databaseMessage isKindOfClass:[OTRXMPPRoomMessage class]]) {
|
|
| 55 |
OTRXMPPRoomMessage *msg = (OTRXMPPRoomMessage *)databaseMessage; |
|
| 56 |
if ([msg.roomJID isEqualToString:fromJID.bare]) {
|
|
| 57 |
*stop = YES; |
|
| 58 |
result = YES; |
|
| 59 |
}} |
|
| 60 |
}]; |
|
| 61 |
return result; |
|
| 62 |
} |
|
| 63 |
|
|
| 64 |
- (void)insertIncomingMessage:(XMPPMessage *)message intoRoom:(XMPPRoom *)room |
|
| 65 |
{
|
|
| 66 |
NSString *accountId = room.xmppStream.tag; |
|
| 67 |
NSString *roomJID = room.roomJID.bare; |
|
| 68 |
XMPPJID *fromJID = [message from]; |
|
| 69 |
if (!accountId || !roomJID || !fromJID) {
|
|
| 70 |
return; |
|
| 71 |
} |
|
| 72 |
__block OTRXMPPRoomMessage *databaseMessage = nil; |
|
| 73 |
__block OTRXMPPRoom *databaseRoom = nil; |
|
| 74 |
__block OTRXMPPAccount *account = nil; |
|
| 75 |
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
|
|
| 76 |
account = [OTRXMPPAccount fetchObjectWithUniqueID:accountId transaction:transaction]; |
|
| 77 |
// Sends a response receipt when receiving a delivery receipt request |
|
| 78 |
[OTRXMPPRoomMessage handleDeliveryReceiptRequestWithMessage:message xmppStream:room.xmppStream]; |
|
| 79 |
|
|
| 80 |
// Extract XEP-0359 stanza-id |
|
| 81 |
NSString *stanzaId = [message extractStanzaIdWithAccount:account]; |
|
| 82 |
NSString *originId = message.originId; |
|
| 83 |
databaseMessage.originId = originId; |
|
| 84 |
databaseMessage.stanzaId = stanzaId; |
|
| 85 |
|
|
| 86 |
if ([self existsMessage:message from:fromJID stanzaId:stanzaId transaction:transaction]) {
|
|
| 87 |
// This message already exists and shouldn't be inserted |
|
| 88 |
DDLogVerbose(@"%@: %@ - Duplicate MUC message %@", THIS_FILE, THIS_METHOD, message); |
|
| 89 |
return; |
|
| 90 |
} |
|
| 91 |
databaseRoom = [self fetchRoomWithXMPPRoomJID:roomJID accountId:accountId inTransaction:transaction]; |
|
| 92 |
if(!databaseRoom) {
|
|
| 93 |
databaseRoom = [[OTRXMPPRoom alloc] init]; |
|
| 94 |
databaseRoom.lastRoomMessageId = @""; // Hack to make it show up in list |
|
| 95 |
databaseRoom.accountUniqueId = accountId; |
|
| 96 |
databaseRoom.jid = roomJID; |
|
| 97 |
} |
|
| 98 |
if (databaseRoom.joined && |
|
| 99 |
([message elementForName:@"x" xmlns:XMPPMUCUserNamespace] || |
|
| 100 |
[message elementForName:@"x" xmlns:@"jabber:x:conference"])) {
|
|
| 101 |
DDLogWarn(@"Received invitation to current room: %@", message); |
|
| 102 |
return; |
|
| 103 |
} |
|
| 104 |
|
|
| 105 |
databaseMessage = [[OTRXMPPRoomMessage alloc] init]; |
|
| 106 |
databaseMessage.xmppId = [message elementID]; |
|
| 107 |
databaseMessage.messageText = [message body]; |
|
| 108 |
NSDate *messageDate = [message delayedDeliveryDate]; |
|
| 109 |
if (!messageDate) {
|
|
| 110 |
messageDate = [NSDate date]; |
|
| 111 |
} |
|
| 112 |
databaseMessage.messageDate = messageDate; |
|
| 113 |
databaseMessage.senderJID = [fromJID full]; |
|
| 114 |
databaseMessage.roomJID = databaseRoom.jid; |
|
| 115 |
databaseMessage.state = RoomMessageStateReceived; |
|
| 116 |
databaseMessage.roomUniqueId = databaseRoom.uniqueId; |
|
| 117 |
|
|
| 118 |
databaseRoom.lastRoomMessageId = [databaseMessage uniqueId]; |
|
| 119 |
NSString *activeThreadYapKey = [[OTRAppDelegate appDelegate] activeThreadYapKey]; |
|
| 120 |
if([activeThreadYapKey isEqualToString:databaseMessage.threadId]) {
|
|
| 121 |
databaseMessage.read = YES; |
|
| 122 |
} else {
|
|
| 123 |
databaseMessage.read = NO; |
|
| 124 |
} |
|
| 125 |
|
|
| 126 |
[databaseRoom saveWithTransaction:transaction]; |
|
| 127 |
[databaseMessage saveWithTransaction:transaction]; |
|
| 128 |
} completionBlock:^{
|
|
| 129 |
if(databaseMessage) {
|
|
| 130 |
OTRXMPPManager *xmpp = (OTRXMPPManager*)[OTRProtocolManager.shared protocolForAccount:account]; |
|
| 131 |
[xmpp.fileTransferManager createAndDownloadItemsIfNeededWithMessage:databaseMessage readConnection:OTRDatabaseManager.shared.readOnlyDatabaseConnection force:NO]; |
|
| 132 |
// If delayedDeliveryDate is set we are retrieving history. Don't show |
|
| 133 |
// notifications in that case. Also, don't show notifications for archived |
|
| 134 |
// rooms. |
|
| 135 |
if (!message.delayedDeliveryDate && !databaseRoom.isArchived) {
|
|
| 136 |
[[UIApplication sharedApplication] showLocalNotification:databaseMessage]; |
|
| 137 |
} |
|
| 138 |
} |
|
| 139 |
}]; |
|
| 140 |
} |
|
| 141 |
|
|
| 142 |
- (id <OTRMessageProtocol>)lastMessageInRoom:(XMPPRoom *)room accountKey:(NSString *)accountKey |
|
| 143 |
{
|
|
| 144 |
__block id<OTRMessageProtocol> message = nil; |
|
| 145 |
[self.databaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
|
|
| 146 |
OTRXMPPRoom *databaseRoom = [self fetchRoomWithXMPPRoomJID:room.roomJID.bare accountId:accountKey inTransaction:transaction]; |
|
| 147 |
message = [databaseRoom lastMessageWithTransaction:transaction]; |
|
| 148 |
}]; |
|
| 149 |
return message; |
|
| 150 |
} |
|
| 151 |
|
|
| 152 |
//MARK: XMPPRoomStorage |
|
| 153 |
|
|
| 154 |
- (BOOL)configureWithParent:(XMPPRoom *)aParent queue:(dispatch_queue_t)queue |
|
| 155 |
{
|
|
| 156 |
self.parentQueue = queue; |
|
| 157 |
return YES; |
|
| 158 |
} |
|
| 159 |
|
|
| 160 |
/** |
|
| 161 |
* Updates and returns the occupant for the given presence element. |
|
| 162 |
* If the presence type is "available", and the occupant doesn't already exist, then one should be created. |
|
| 163 |
**/ |
|
| 164 |
- (void)handlePresence:(XMPPPresence *)presence room:(XMPPRoom *)room {
|
|
| 165 |
NSString *accountId = room.xmppStream.tag; |
|
| 166 |
XMPPJID *presenceJID = [presence from]; |
|
| 167 |
|
|
| 168 |
DDXMLElement *item = nil; |
|
| 169 |
DDXMLElement *mucElement = [presence elementForName:@"x" xmlns:XMPPMUCUserNamespace]; |
|
| 170 |
if (mucElement) {
|
|
| 171 |
item = [mucElement elementForName:@"item"]; |
|
| 172 |
} |
|
| 173 |
if (!item) {
|
|
| 174 |
return; // Unexpected presence format |
|
| 175 |
} |
|
| 176 |
|
|
| 177 |
XMPPJID *buddyRealJID = nil; |
|
| 178 |
NSString *buddyJIDString = [item attributeStringValueForName:@"jid"]; |
|
| 179 |
if (buddyJIDString) {
|
|
| 180 |
// Will be nil in anonymous rooms (and semi-anonymous rooms if we are not moderators) |
|
| 181 |
buddyRealJID = [[XMPPJID jidWithString:buddyJIDString] bareJID]; |
|
| 182 |
} |
|
| 183 |
NSString *buddyRole = [item attributeStringValueForName:@"role"]; |
|
| 184 |
NSString *buddyAffiliation = [item attributeStringValueForName:@"affiliation"]; |
|
| 185 |
|
|
| 186 |
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
|
|
| 187 |
|
|
| 188 |
OTRXMPPRoomOccupant *occupant = [OTRXMPPRoomOccupant occupantWithJid:presenceJID realJID:buddyRealJID roomJID:room.roomJID accountId:accountId createIfNeeded:YES transaction:transaction]; |
|
| 189 |
if ([[presence type] isEqualToString:@"unavailable"]) {
|
|
| 190 |
occupant.available = NO; |
|
| 191 |
} else {
|
|
| 192 |
occupant.available = YES; |
|
| 193 |
} |
|
| 194 |
occupant.jid = [presenceJID full]; // Nicknames can change, so update |
|
| 195 |
occupant.roomName = [presenceJID resource]; |
|
| 196 |
|
|
| 197 |
// Role |
|
| 198 |
if ([buddyRole isEqualToString:@"moderator"]) {
|
|
| 199 |
occupant.role = RoomOccupantRoleModerator; |
|
| 200 |
} else if ([buddyRole isEqualToString:@"participant"]) {
|
|
| 201 |
occupant.role = RoomOccupantRoleParticipant; |
|
| 202 |
} else if ([buddyRole isEqualToString:@"visitor"]) {
|
|
| 203 |
occupant.role = RoomOccupantRoleVisitor; |
|
| 204 |
} else {
|
|
| 205 |
occupant.role = RoomOccupantRoleNone; |
|
| 206 |
} |
|
| 207 |
|
|
| 208 |
// Affiliation |
|
| 209 |
if ([buddyAffiliation isEqualToString:@"owner"]) {
|
|
| 210 |
occupant.affiliation = RoomOccupantAffiliationOwner; |
|
| 211 |
} else if ([buddyAffiliation isEqualToString:@"admin"]) {
|
|
| 212 |
occupant.affiliation = RoomOccupantAffiliationAdmin; |
|
| 213 |
} else if ([buddyAffiliation isEqualToString:@"member"]) {
|
|
| 214 |
occupant.affiliation = RoomOccupantAffiliationMember; |
|
| 215 |
} else if ([buddyAffiliation isEqualToString:@"outcast"]) {
|
|
| 216 |
occupant.affiliation = RoomOccupantAffiliationOutcast; |
|
| 217 |
} else {
|
|
| 218 |
occupant.affiliation = RoomOccupantAffiliationNone; |
|
| 219 |
} |
|
| 220 |
[occupant saveWithTransaction:transaction]; |
|
| 221 |
}]; |
|
| 222 |
} |
|
| 223 |
|
|
| 224 |
/** |
|
| 225 |
* Stores or otherwise handles the given message element. |
|
| 226 |
**/ |
|
| 227 |
- (void)handleIncomingMessage:(XMPPMessage *)message room:(XMPPRoom *)room {
|
|
| 228 |
//DDLogVerbose(@"OTRXMPPRoomYapStorage handleIncomingMessage: %@", message); |
|
| 229 |
XMPPJID *myRoomJID = room.myRoomJID; |
|
| 230 |
XMPPJID *messageJID = [message from]; |
|
| 231 |
|
|
| 232 |
if ([myRoomJID isEqualToJID:messageJID]) |
|
| 233 |
{
|
|
| 234 |
if (![message wasDelayed]) |
|
| 235 |
{
|
|
| 236 |
// Ignore - we already stored message in handleOutgoingMessage:room: |
|
| 237 |
return; |
|
| 238 |
} |
|
| 239 |
} |
|
| 240 |
|
|
| 241 |
//May need to check if the message is unique. Unsure if this is a real problem. Look at XMPPRoomCoreDataStorage.m existsMessage: |
|
| 242 |
|
|
| 243 |
[self insertIncomingMessage:message intoRoom:room]; |
|
| 244 |
} |
|
| 245 |
- (void)handleOutgoingMessage:(XMPPMessage *)message room:(XMPPRoom *)room {
|
|
| 246 |
//DDLogVerbose(@"OTRXMPPRoomYapStorage handleOutgoingMessage: %@", message); |
|
| 247 |
} |
|
| 248 |
|
|
| 249 |
/** |
|
| 250 |
* Handles leaving the room, which generally means clearing the list of occupants. |
|
| 251 |
**/ |
|
| 252 |
- (void)handleDidLeaveRoom:(XMPPRoom *)room {
|
|
| 253 |
NSString *roomJID = room.roomJID.bare; |
|
| 254 |
NSString *accountId = room.xmppStream.tag; |
|
| 255 |
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
|
|
| 256 |
OTRXMPPRoom *databaseRoom = [self fetchRoomWithXMPPRoomJID:roomJID accountId:accountId inTransaction:transaction]; |
|
| 257 |
databaseRoom.joined = NO; |
|
| 258 |
[databaseRoom saveWithTransaction:transaction]; |
|
| 259 |
}]; |
|
| 260 |
} |
|
| 261 |
|
|
| 262 |
- (void)handleDidJoinRoom:(XMPPRoom *)room withNickname:(NSString *)nickname {
|
|
| 263 |
NSString *roomJID = room.roomJID.bare; |
|
| 264 |
NSString *accountId = room.xmppStream.tag; |
|
| 265 |
[self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
|
|
| 266 |
OTRXMPPRoom *databaseRoom = [self fetchRoomWithXMPPRoomJID:roomJID accountId:accountId inTransaction:transaction]; |
|
| 267 |
|
|
| 268 |
databaseRoom.joined = YES; |
|
| 269 |
databaseRoom.ownJID = room.myRoomJID.full; |
|
| 270 |
[databaseRoom saveWithTransaction:transaction]; |
|
| 271 |
}]; |
|
| 272 |
} |
|
| 273 |
|
|
| 274 |
|
|
| 275 |
|
|
| 276 |
@end |
|
| ChatSecure/Classes/Controllers/XMPP/OTRYapDatabaseRosterStorage.h | ||
|---|---|---|
| 1 |
// |
|
| 2 |
// OTRYapDatabaseRosterStorage.h |
|
| 3 |
// Off the Record |
|
| 4 |
// |
|
| 5 |
// Created by David Chiles on 3/31/14. |
|
| 6 |
// Copyright (c) 2014 Chris Ballinger. All rights reserved. |
|
| 7 |
// |
|
| 8 |
|
|
| 9 |
@import Foundation; |
|
| 10 |
@import XMPPFramework; |
|
| 11 |
|
|
| 12 |
@interface OTRYapDatabaseRosterStorage : NSObject <XMPPRosterStorage> |
|
| 13 |
|
|
| 14 |
@end |
|
| ChatSecure/Classes/Controllers/XMPP/OTRYapDatabaseRosterStorage.m | ||
|---|---|---|
| 1 |
// |
|
| 2 |
// OTRYapDatabaseRosterStorage.m |
|
| 3 |
// Off the Record |
|
| 4 |
// |
|
| 5 |
// Created by David Chiles on 3/31/14. |
|
| 6 |
// Copyright (c) 2014 Chris Ballinger. All rights reserved. |
|
| 7 |
// |
|
| 8 |
|
|
| 9 |
#import "OTRYapDatabaseRosterStorage.h" |
|
| 10 |
|
|
| 11 |
@import YapDatabase; |
|
| 12 |
#import "OTRDatabaseManager.h" |
|
| 13 |
#import "OTRLog.h" |
|
| 14 |
#import "OTRXMPPBuddy.h" |
|
| 15 |
#import "OTRXMPPAccount.h" |
|
| 16 |
|
|
| 17 |
#import "OTRBuddyCache.h" |
|
| 18 |
#import <ChatSecureCore/ChatSecureCore-Swift.h> |
|
| 19 |
|
|
| 20 |
@import OTRAssets; |
|
| 21 |
|
|
| 22 |
@interface OTRYapDatabaseRosterStorage () |
|
| 23 |
|
|
| 24 |
@property (nonatomic, strong, readonly, nonnull) YapDatabaseConnection *readConnection; |
|
| 25 |
@property (nonatomic, strong, readonly, nonnull) YapDatabaseConnection *writeConnection; |
|
| 26 |
|
|
| 27 |
@end |
|
| 28 |
|
|
| 29 |
/** |
|
| 30 |
The possible values for a subscription value |
|
| 31 |
|
|
| 32 |
https://xmpp.org/rfcs/rfc6121.html#roster-syntax-items-subscription |
|
| 33 |
*/ |
|
| 34 |
typedef NS_ENUM(NSInteger, OTRSubscriptionAttribute) {
|
|
| 35 |
OTRSubscriptionAttributeUnknown, |
|
| 36 |
OTRSubscriptionAttributeNone, |
|
| 37 |
OTRSubscriptionAttributeTo, |
|
| 38 |
OTRSubscriptionAttributeFrom, |
|
| 39 |
OTRSubscriptionAttributeBoth |
|
| 40 |
}; |
|
| 41 |
|
|
| 42 |
@implementation OTRYapDatabaseRosterStorage |
|
| 43 |
|
|
| 44 |
-(instancetype)init |
|
| 45 |
{
|
|
| 46 |
if (self = [super init]) {
|
|
| 47 |
_readConnection = OTRDatabaseManager.shared.readOnlyDatabaseConnection; |
|
| 48 |
_writeConnection = OTRDatabaseManager.shared.readWriteDatabaseConnection; |
|
| 49 |
} |
|
| 50 |
return self; |
|
| 51 |
} |
|
| 52 |
|
|
| 53 |
#pragma - mark Helper Methods |
|
| 54 |
|
|
| 55 |
/** Turns out buddies are created during account creation before the account object is saved to the database. oh brother */ |
|
| 56 |
- (nonnull NSString*)accountUniqueIdForStream:(XMPPStream*)stream {
|
|
| 57 |
NSParameterAssert(stream.tag); |
|
| 58 |
return stream.tag; |
|
| 59 |
} |
|
| 60 |
|
|
| 61 |
- (nullable OTRXMPPBuddy *)fetchBuddyWithJID:(XMPPJID *)jid stream:(XMPPStream *)stream transaction:(YapDatabaseReadTransaction *)transaction |
|
| 62 |
{
|
|
| 63 |
NSString *accountUniqueId = [self accountUniqueIdForStream:stream]; |
|
| 64 |
OTRXMPPBuddy *buddy = [OTRXMPPBuddy fetchBuddyWithJid:jid accountUniqueId:accountUniqueId transaction:transaction]; |
|
| 65 |
return buddy; |
|
| 66 |
} |
|
| 67 |
|
|
| 68 |
/** When created, it is still unsaved and must be manually saved within a yap transaction. */ |
|
| 69 |
- (nullable OTRXMPPBuddy *)createBuddyWithJID:(XMPPJID *)jid stream:(XMPPStream *)stream {
|
|
| 70 |
NSString *accountUniqueId = [self accountUniqueIdForStream:stream]; |
|
| 71 |
OTRXMPPBuddy *buddy = [[OTRXMPPBuddy alloc] init]; |
|
| 72 |
buddy.username = [jid bare]; |
|
| 73 |
buddy.accountUniqueId = accountUniqueId; |
|
| 74 |
return buddy; |
|
| 75 |
} |
|
| 76 |
|
|
| 77 |
- (nullable OTRXMPPBuddy*) createBuddyFromRosterItem:(NSXMLElement *)rosterItem stream:(XMPPStream *)stream {
|
|
| 78 |
NSString *jidStr = [rosterItem attributeStringValueForName:@"jid"]; |
|
| 79 |
XMPPJID *jid = [[XMPPJID jidWithString:jidStr] bareJID]; |
|
| 80 |
return [self createBuddyWithJID:jid stream:stream]; |
|
| 81 |
} |
|
| 82 |
|
|
| 83 |
|
|
| 84 |
/** Compares two buddy objects to see if there are changes worth saving */ |
|
| 85 |
- (BOOL) shouldSaveUpdatedBuddy:(nonnull OTRXMPPBuddy*)buddy oldBuddy:(nullable OTRXMPPBuddy*)oldBuddy {
|
|
| 86 |
NSParameterAssert(buddy); |
|
| 87 |
if (!buddy) { return NO; }
|
|
| 88 |
if (!oldBuddy) { return YES; }
|
|
| 89 |
NSAssert(buddy.uniqueId == oldBuddy.uniqueId, @"Comparing two different buddies! Noooooooo."); |
|
| 90 |
if (buddy.uniqueId != oldBuddy.uniqueId) {
|
|
| 91 |
// I guess we should still save if the uniqueId is different |
|
| 92 |
return YES; |
|
| 93 |
} |
|
| 94 |
if (![buddy.displayName isEqualToString:oldBuddy.displayName]) {
|
|
| 95 |
return YES; |
|
| 96 |
} |
|
| 97 |
if (buddy.pendingApproval != oldBuddy.pendingApproval) {
|
|
| 98 |
return YES; |
|
| 99 |
} |
|
| 100 |
return NO; |
|
| 101 |
} |
|
| 102 |
|
|
| 103 |
- (BOOL)existsBuddyWithJID:(XMPPJID *)jid xmppStram:(XMPPStream *)stream |
|
| 104 |
{
|
|
| 105 |
__block BOOL result = NO; |
|
| 106 |
[self.readConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
| 107 |
OTRBuddy *buddy = [self fetchBuddyWithJID:jid stream:stream transaction:transaction]; |
|
| 108 |
if (buddy) {
|
|
| 109 |
result = YES; |
|
| 110 |
} |
|
| 111 |
}]; |
|
| 112 |
return result; |
|
| 113 |
} |
|
| 114 |
|
|
| 115 |
- (OTRSubscriptionAttribute)subscriptionAttribute:(NSXMLElement *)item {
|
|
| 116 |
NSString *subscription = [item attributeStringValueForName:@"subscription"]; |
|
| 117 |
if (subscription ==nil || [subscription isEqualToString:@"none"]) {
|
|
| 118 |
return OTRSubscriptionAttributeNone; |
|
| 119 |
} else if ([subscription isEqualToString:@"to"]) {
|
|
| 120 |
return OTRSubscriptionAttributeTo; |
|
| 121 |
} else if ([subscription isEqualToString:@"from"]) {
|
|
| 122 |
return OTRSubscriptionAttributeFrom; |
|
| 123 |
}else if ([subscription isEqualToString:@"both"]) {
|
|
| 124 |
return OTRSubscriptionAttributeBoth; |
|
| 125 |
} |
|
| 126 |
|
|
| 127 |
return OTRSubscriptionAttributeUnknown; |
|
| 128 |
} |
|
| 129 |
|
|
| 130 |
-(BOOL)isPendingApproval:(NSXMLElement *)item |
|
| 131 |
{
|
|
| 132 |
NSString *ask = [item attributeStringValueForName:@"ask"]; |
|
| 133 |
if ([ask isEqualToString:@"subscribe"]) {
|
|
| 134 |
return YES; |
|
| 135 |
} |
|
| 136 |
|
|
| 137 |
OTRSubscriptionAttribute subscriptionAttribute = [self subscriptionAttribute:item]; |
|
| 138 |
|
|
| 139 |
// If you are subscribed to or are mutually subscribed then you are not pending approval. |
|
| 140 |
if (subscriptionAttribute == OTRSubscriptionAttributeTo || subscriptionAttribute == OTRSubscriptionAttributeBoth) {
|
|
| 141 |
return NO; |
|
| 142 |
} |
|
| 143 |
return YES; |
|
| 144 |
} |
|
| 145 |
|
|
| 146 |
/** Buddy can be nil, which indicates a new buddy should be saved. */ |
|
| 147 |
- (void)updateBuddy:(nullable OTRXMPPBuddy *)buddy withItem:(nonnull NSXMLElement *)item stream:(XMPPStream*)stream |
|
| 148 |
{
|
|
| 149 |
if (!item) { return; }
|
|
| 150 |
BOOL newlyCreatedBuddy = NO; |
|
| 151 |
if (!buddy) {
|
|
| 152 |
buddy = [self createBuddyFromRosterItem:item stream:stream]; |
|
| 153 |
if (!buddy) {
|
|
| 154 |
return; |
|
| 155 |
} |
|
| 156 |
newlyCreatedBuddy = YES; |
|
| 157 |
} |
|
| 158 |
// Fixing a potential migration issue from ages past. Maybe can be removed? |
|
| 159 |
if (![buddy isKindOfClass:[OTRXMPPBuddy class]]) {
|
|
| 160 |
OTRXMPPBuddy *xmppBuddy = [[OTRXMPPBuddy alloc] init]; |
|
| 161 |
[xmppBuddy mergeValuesForKeysFromModel:buddy]; |
|
| 162 |
buddy = xmppBuddy; |
|
| 163 |
newlyCreatedBuddy = YES; |
|
| 164 |
} |
|
| 165 |
OTRXMPPBuddy *newBuddy = [buddy copy]; |
|
| 166 |
|
|
| 167 |
|
|
| 168 |
NSString *name = [item attributeStringValueForName:@"name"]; |
|
| 169 |
if (name.length) {
|
|
| 170 |
newBuddy.displayName = name; |
|
| 171 |
} |
|
| 172 |
newBuddy.pendingApproval = [self isPendingApproval:item]; |
|
| 173 |
|
|
| 174 |
// Save if there were changes, or it's a new buddy |
|
| 175 |
BOOL shouldSave = [self shouldSaveUpdatedBuddy:newBuddy oldBuddy:buddy] || newlyCreatedBuddy; |
|
| 176 |
if (!shouldSave) {
|
|
| 177 |
return; |
|
| 178 |
} |
|
| 179 |
|
|
| 180 |
[self.writeConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
| 181 |
[newBuddy saveWithTransaction:transaction]; |
|
| 182 |
}]; |
|
| 183 |
|
|
| 184 |
if (buddy.pendingApproval && !newBuddy.pendingApproval) {
|
|
| 185 |
// Buddy has approved us |
|
| 186 |
[[NSNotificationCenter defaultCenter] postNotificationName:OTRBuddyPendingApprovalDidChangeNotification object:self userInfo:@{@"buddy": newBuddy}];
|
|
| 187 |
} |
|
| 188 |
} |
|
| 189 |
|
|
| 190 |
#pragma - mark XMPPRosterStorage Methods |
|
| 191 |
|
|
| 192 |
- (BOOL)configureWithParent:(XMPPRoster *)aParent queue:(dispatch_queue_t)queue |
|
| 193 |
{
|
|
| 194 |
return YES; |
|
| 195 |
} |
|
| 196 |
|
|
| 197 |
- (void)beginRosterPopulationForXMPPStream:(XMPPStream *)stream withVersion:(NSString *)version |
|
| 198 |
{
|
|
| 199 |
DDLogVerbose(@"%@ - %@",THIS_FILE,THIS_METHOD); |
|
| 200 |
} |
|
| 201 |
- (void)endRosterPopulationForXMPPStream:(XMPPStream *)stream |
|
| 202 |
{
|
|
| 203 |
DDLogVerbose(@"%@ - %@",THIS_FILE,THIS_METHOD); |
|
| 204 |
} |
|
| 205 |
|
|
| 206 |
- (void)handleRosterItem:(NSXMLElement *)item xmppStream:(XMPPStream *)stream |
|
| 207 |
{
|
|
| 208 |
DDLogVerbose(@"%@ - %@",THIS_FILE,THIS_METHOD); |
|
| 209 |
NSString *jidStr = [item attributeStringValueForName:@"jid"]; |
|
| 210 |
XMPPJID *jid = [[XMPPJID jidWithString:jidStr] bareJID]; |
|
| 211 |
|
|
| 212 |
if([[jid bare] isEqualToString:[[stream myJID] bare]]) |
|
| 213 |
{
|
|
| 214 |
// ignore self buddy |
|
| 215 |
return; |
|
| 216 |
} |
|
| 217 |
|
|
| 218 |
__block OTRXMPPBuddy *buddy = nil; |
|
| 219 |
[self.readConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
|
|
| 220 |
buddy = [self fetchBuddyWithJID:jid stream:stream transaction:transaction]; |
|
| 221 |
}]; |
|
| 222 |
NSString *subscription = [item attributeStringValueForName:@"subscription"]; |
|
| 223 |
if ([subscription isEqualToString:@"remove"]) |
|
| 224 |
{
|
|
| 225 |
if (buddy) {
|
|
| 226 |
[self.writeConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
| 227 |
[transaction removeObjectForKey:buddy.uniqueId inCollection:[OTRXMPPBuddy collection]]; |
|
| 228 |
}]; |
|
| 229 |
} |
|
Also available in: Unified diff