Revision a4bb25f6

View differences:

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
        }
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff