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