Statistics
| Branch: | Tag: | Revision:

chatsecureios / ChatSecure / Classes / Controllers / XMPP / OTRXMPPManager.m @ 8d76e2e3

History | View | Annotate | Download (60 KB)

1
//
2
//  OTRXMPPManager.m
3
//  Off the Record
4
//
5
//  Created by Chris Ballinger on 9/7/11.
6
//  Copyright (c) 2011 Chris Ballinger. All rights reserved.
7
//
8
//  This file is part of ChatSecure.
9
//
10
//  ChatSecure is free software: you can redistribute it and/or modify
11
//  it under the terms of the GNU General Public License as published by
12
//  the Free Software Foundation, either version 3 of the License, or
13
//  (at your option) any later version.
14
//
15
//  ChatSecure is distributed in the hope that it will be useful,
16
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
//  GNU General Public License for more details.
19
//
20
//  You should have received a copy of the GNU General Public License
21
//  along with ChatSecure.  If not, see <http://www.gnu.org/licenses/>.
22

    
23
#import "OTRXMPPManager.h"
24

    
25
@import CocoaAsyncSocket;
26
@import XMPPFramework;
27
#import "OTRYapDatabaseRosterStorage.h"
28

    
29
#import "OTRLog.h"
30

    
31
#import <CFNetwork/CFNetwork.h>
32

    
33
#import "OTRSettingsManager.h"
34
#import "OTRConstants.h"
35
#import "OTRProtocolManager.h"
36
#include <stdlib.h>
37
#import "OTRConstants.h"
38
#import "OTRUtilities.h"
39

    
40
#import "OTRDatabaseManager.h"
41
@import YapDatabase;
42
#import "OTRXMPPBuddy.h"
43
#import "OTRXMPPAccount.h"
44
#import "OTRIncomingMessage.h"
45
#import "OTROutgoingMessage.h"
46
#import "OTRAccount.h"
47
#import "OTRXMPPPresenceSubscriptionRequest.h"
48
#import "OTRvCardYapDatabaseStorage.h"
49
#import "OTRNotificationController.h"
50
#import "OTRStreamManagementYapStorage.h"
51
@import OTRKit;
52
#import "OTRXMPPRoomManager.h"
53
#import "OTRXMPPBuddyTimers.h"
54
#import "OTRXMPPError.h"
55
#import "OTRXMPPManager_Private.h"
56
#import "OTRBuddyCache.h"
57
#import "UIImage+ChatSecure.h"
58
#import "XMPPPushModule.h"
59
#import "OTRXMPPTorManager.h"
60
#import "OTRTorManager.h"
61
@import OTRAssets;
62

    
63
NSString *const OTRXMPPRegisterSucceededNotificationName = @"OTRXMPPRegisterSucceededNotificationName";
64
NSString *const OTRXMPPRegisterFailedNotificationName    = @"OTRXMPPRegisterFailedNotificationName";
65

    
66
NSTimeInterval const kOTRChatStatePausedTimeout   = 5;
67
NSTimeInterval const kOTRChatStateInactiveTimeout = 120;
68

    
69
NSString *const OTRXMPPLoginStatusNotificationName = @"OTRXMPPLoginStatusNotificationName";
70

    
71
NSString *const OTRXMPPOldLoginStatusKey = @"OTRXMPPOldLoginStatusKey";
72
NSString *const OTRXMPPNewLoginStatusKey = @"OTRXMPPNewLoginStatusKey";
73
NSString *const OTRXMPPLoginErrorKey = @"OTRXMPPLoginErrorKey";
74

    
75
/** For XEP-0352 CSI Client State Indication */
76
typedef NS_ENUM(NSInteger, XMPPClientState) {
77
    XMPPClientStateInactive,
78
    XMPPClientStateActive,
79
};
80

    
81
@implementation OTRXMPPManager
82

    
83
- (instancetype)init
84
{
85
    if (self = [super init]) {
86
        NSString * queueLabel = [NSString stringWithFormat:@"%@.work.%@",[self class],self];
87
        _workQueue = dispatch_queue_create([queueLabel UTF8String], 0);
88
        self.connectionStatus = OTRProtocolConnectionStatusDisconnected;
89
        _buddyTimers = [NSMutableDictionary dictionary];
90
        _databaseConnection = [OTRDatabaseManager sharedInstance].readWriteDatabaseConnection;
91
    }
92
    return self;
93
}
94

    
95
- (instancetype) initWithAccount:(OTRAccount *)newAccount {
96
    if(self = [self init])
97
    {
98
        NSAssert([newAccount isKindOfClass:[OTRXMPPAccount class]], @"Must have XMPP account");
99
        self.isRegisteringNewAccount = NO;
100
        _account = (OTRXMPPAccount *)newAccount;
101
        
102
        // Setup the XMPP stream
103
        [self setupStream];        
104
    }
105
    
106
    return self;
107
}
108

    
109
- (void)dealloc
110
{
111
	[self teardownStream];
112
}
113

    
114
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
115
#pragma mark Private
116
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
117

    
118
- (OTRXMPPStream*) newStream {
119
    return [[OTRXMPPStream alloc] init];
120
}
121

    
122
- (void)setupStream
123
{
124
	NSAssert(_xmppStream == nil, @"Method setupStream invoked multiple times");
125
    
126
	_xmppStream = [self newStream];
127

    
128
    //Used to fetch correct account from XMPPStream in delegate methods especailly
129
    self.xmppStream.tag = self.account.uniqueId;
130
    self.xmppStream.startTLSPolicy = XMPPStreamStartTLSPolicyRequired;
131
    
132
    [self.certificatePinningModule activate:self.xmppStream];
133
    
134
    _deliveryReceipts = [[XMPPMessageDeliveryReceipts alloc] init];
135
    // We want to check if OTR messages can be decrypted
136
    self.deliveryReceipts.autoSendMessageDeliveryReceipts = NO;
137
    self.deliveryReceipts.autoSendMessageDeliveryRequests = YES;
138
    [self.deliveryReceipts activate:self.xmppStream];
139
	
140
	// Setup reconnect
141
	// 
142
	// The XMPPReconnect module monitors for "accidental disconnections" and
143
	// automatically reconnects the stream for you.
144
	// There's a bunch more information in the XMPPReconnect header file.
145
	
146
	_xmppReconnect = [[XMPPReconnect alloc] init];
147
	
148
	// Setup roster
149
	// 
150
	// The XMPPRoster handles the xmpp protocol stuff related to the roster.
151
	// The storage for the roster is abstracted.
152
	// So you can use any storage mechanism you want.
153
	// You can store it all in memory, or use core data and store it on disk, or use core data with an in-memory store,
154
	// or setup your own using raw SQLite, or create your own storage mechanism.
155
	// You can do it however you like! It's your application.
156
	// But you do need to provide the roster with some storage facility.
157
    
158
    //DDLogInfo(@"Unique Identifier: %@",self.account.uniqueIdentifier);
159
	
160
    _xmppRosterStorage = [[OTRYapDatabaseRosterStorage alloc] init];
161
	
162
	_xmppRoster = [[XMPPRoster alloc] initWithRosterStorage:self.xmppRosterStorage];
163
	
164
	self.xmppRoster.autoFetchRoster = YES;
165
    self.xmppRoster.autoClearAllUsersAndResources = NO;
166
	self.xmppRoster.autoAcceptKnownPresenceSubscriptionRequests = YES;
167
	
168
	// Setup vCard support
169
	// 
170
	// The vCard Avatar module works in conjuction with the standard vCard Temp module to download user avatars.
171
	// The XMPPRoster will automatically integrate with XMPPvCardAvatarModule to cache roster photos in the roster.
172
	
173
    OTRvCardYapDatabaseStorage * vCardStorage  = [[OTRvCardYapDatabaseStorage alloc] init];
174
	_xmppvCardTempModule = [[XMPPvCardTempModule alloc] initWithvCardStorage:vCardStorage];
175
	
176
	_xmppvCardAvatarModule = [[XMPPvCardAvatarModule alloc] initWithvCardTempModule:self.xmppvCardTempModule];
177
	
178
	// Setup capabilities
179
	// 
180
	// The XMPPCapabilities module handles all the complex hashing of the caps protocol (XEP-0115).
181
	// Basically, when other clients broadcast their presence on the network
182
	// they include information about what capabilities their client supports (audio, video, file transfer, etc).
183
	// But as you can imagine, this list starts to get pretty big.
184
	// This is where the hashing stuff comes into play.
185
	// Most people running the same version of the same client are going to have the same list of capabilities.
186
	// So the protocol defines a standardized way to hash the list of capabilities.
187
	// Clients then broadcast the tiny hash instead of the big list.
188
	// The XMPPCapabilities protocol automatically handles figuring out what these hashes mean,
189
	// and also persistently storing the hashes so lookups aren't needed in the future.
190
	// 
191
	// Similarly to the roster, the storage of the module is abstracted.
192
	// You are strongly encouraged to persist caps information across sessions.
193
	// 
194
	// The XMPPCapabilitiesCoreDataStorage is an ideal solution.
195
	// It can also be shared amongst multiple streams to further reduce hash lookups.
196
    
197
    _serverCapabilities = [[OTRServerCapabilities alloc] init];
198
    [self.serverCapabilities activate:self.xmppStream];
199
    
200
    // Add push registration module
201
    _xmppPushModule = [[XMPPPushModule alloc] init];
202
    [self.xmppPushModule activate:self.xmppStream];
203
    [self.xmppPushModule addDelegate:self delegateQueue:self.workQueue];
204
    
205
    _xmppCapabilitiesStorage = [[XMPPCapabilitiesCoreDataStorage alloc] initWithInMemoryStore];
206
    _xmppCapabilities = [[XMPPCapabilities alloc] initWithCapabilitiesStorage:self.xmppCapabilitiesStorage];
207
    
208
    self.xmppCapabilities.autoFetchHashedCapabilities = YES;
209
    self.xmppCapabilities.autoFetchNonHashedCapabilities = YES;
210
    self.xmppCapabilities.autoFetchMyServerCapabilities = YES;
211
    
212
	// Activate xmpp modules
213
    
214
	[self.xmppReconnect         activate:self.xmppStream];
215
	[self.xmppRoster            activate:self.xmppStream];
216
	[self.xmppvCardTempModule   activate:self.xmppStream];
217
	[self.xmppvCardAvatarModule activate:self.xmppStream];
218
	[self.xmppCapabilities      activate:self.xmppStream];
219
    
220
    _stanzaIdModule = [[XMPPStanzaIdModule alloc] init];
221
    [self.stanzaIdModule activate:self.xmppStream];
222
    
223
	// Add ourself as a delegate to anything we may be interested in
224
    
225
	[self.xmppStream addDelegate:self delegateQueue:self.workQueue];
226
	[self.xmppRoster addDelegate:self delegateQueue:self.workQueue];
227
    [self.xmppCapabilities addDelegate:self delegateQueue:self.workQueue];
228
    [self.xmppvCardTempModule addDelegate:self delegateQueue:self.workQueue];
229
    
230
    // File Transfer
231
    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
232
    if ([self isKindOfClass:[OTRXMPPTorManager class]]) {
233
        CPAProxyManager *tor = [OTRTorManager sharedInstance].torManager;
234
        NSString *proxyHost = tor.SOCKSHost;
235
        NSUInteger proxyPort = tor.SOCKSPort;
236
        NSDictionary *proxyDict = @{
237
                                    (NSString *)kCFStreamPropertySOCKSProxyHost : proxyHost,
238
                                    (NSString *)kCFStreamPropertySOCKSProxyPort : @(proxyPort)
239
                                    };
240
        sessionConfiguration.connectionProxyDictionary = proxyDict;
241
    }
242
    _fileTransferManager = [[FileTransferManager alloc] initWithConnection:self.databaseConnection serverCapabilities:self.serverCapabilities sessionConfiguration:sessionConfiguration];
243
    
244
    // Message storage
245
    _messageStorage = [[MessageStorage alloc] initWithConnection:self.databaseConnection capabilities:self.xmppCapabilities fileTransfer:self.fileTransferManager dispatchQueue:nil];
246
    [self.messageStorage activate:self.xmppStream];
247
    
248
    //Stream Management
249
    _streamManagementDelegate = [[OTRStreamManagementDelegate alloc] initWithDatabaseConnection:self.databaseConnection];
250
    
251
    //OTRStreamManagementYapStorage *streamManagementStorage = [[OTRStreamManagementYapStorage alloc] initWithDatabaseConnection:self.databaseConnection];
252
    XMPPStreamManagementMemoryStorage *memoryStorage = [[XMPPStreamManagementMemoryStorage alloc] init];
253
    _streamManagement = [[XMPPStreamManagement alloc] initWithStorage:memoryStorage];
254
    [self.streamManagement addDelegate:self.streamManagementDelegate delegateQueue:self.workQueue];
255
    [self.streamManagement automaticallyRequestAcksAfterStanzaCount:10 orTimeout:5];
256
    [self.streamManagement automaticallySendAcksAfterStanzaCount:30 orTimeout:5];
257
    self.streamManagement.autoResume = YES;
258
    [self.streamManagement activate:self.xmppStream];
259
    
260
    //MUC
261
    _roomManager = [[OTRXMPPRoomManager alloc] initWithDatabaseConnection:[OTRDatabaseManager sharedInstance].readWriteDatabaseConnection capabilities:self.xmppCapabilities dispatchQueue:nil];
262
    [self.roomManager activate:self.xmppStream];
263
    
264
    //Buddy Manager (for deleting)
265
    _xmppBuddyManager = [[OTRXMPPBuddyManager alloc] init];
266
    self.xmppBuddyManager.databaseConnection = [OTRDatabaseManager sharedInstance].longLivedReadOnlyConnection;
267
    self.xmppBuddyManager.protocol = self;
268
    [self.xmppBuddyManager activate:self.xmppStream];
269
    
270
    //Message Queue Module
271
    MessageQueueHandler *queueHandler = [OTRDatabaseManager sharedInstance].messageQueueHandler;
272
    _messageStatusModule = [[OTRXMPPMessageStatusModule alloc] initWithDatabaseConnection:self.databaseConnection delegate:queueHandler];
273
    [self.messageStatusModule activate:self.xmppStream];
274
    
275
    //OMEMO
276
    if ([[OTRAppDelegate appDelegate].theme enableOMEMO]) {
277
        self.omemoSignalCoordinator = [[OTROMEMOSignalCoordinator alloc] initWithAccountYapKey:self.account.uniqueId databaseConnection:self.databaseConnection messageStorage:self.messageStorage error:nil];
278
        _omemoModule = [[OMEMOModule alloc] initWithOMEMOStorage:self.omemoSignalCoordinator xmlNamespace:OMEMOModuleNamespaceConversationsLegacy];
279
        [self.omemoModule addDelegate:self.omemoSignalCoordinator delegateQueue:self.workQueue];
280
        [self.omemoModule activate:self.xmppStream];
281
    }
282
    
283
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pushAccountChanged:) name:OTRPushAccountDeviceChanged object:[OTRProtocolManager sharedInstance].pushController];
284
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pushAccountChanged:) name:OTRPushAccountTokensChanged object:[OTRProtocolManager sharedInstance].pushController];
285
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(buddyPendingApprovalStateChanged:) name:OTRBuddyPendingApprovalDidChangeNotification object:self.xmppRosterStorage];
286
    
287
    _serverCheck = [[ServerCheck alloc] initWithXmpp:self push:[OTRProtocolManager sharedInstance].pushController];
288
}
289

    
290
- (void)teardownStream
291
{
292
    [[NSNotificationCenter defaultCenter] removeObserver:self name:OTRBuddyPendingApprovalDidChangeNotification object:self.xmppRosterStorage];
293

    
294
    [_xmppStream removeDelegate:self];
295
    [_xmppRoster removeDelegate:self];
296
    [_xmppCapabilities removeDelegate:self];
297
    [_xmppvCardTempModule removeDelegate:self];
298
    [_xmppPushModule removeDelegate:self];
299

    
300
    [_xmppPushModule deactivate];
301
    [_xmppReconnect         deactivate];
302
    [_xmppRoster            deactivate];
303
    [_xmppvCardTempModule   deactivate];
304
    [_xmppvCardAvatarModule deactivate];
305
    [_xmppCapabilities      deactivate];
306
    [_streamManagement      deactivate];
307
    [_messageStorage        deactivate];
308
    [_certificatePinningModule deactivate];
309
    [_deliveryReceipts deactivate];
310
    [_streamManagement deactivate];
311
    [_roomManager deactivate];
312
    [_xmppBuddyManager deactivate];
313
    [_messageStatusModule deactivate];
314
    [_omemoModule deactivate];
315
    [_serverCapabilities deactivate];
316
    _serverCheck = nil;
317
    _fileTransferManager = nil;
318

    
319
    [_xmppStream disconnect];
320
}
321

    
322
- (void) addIdleDate:(NSDate*)date toPresence:(XMPPPresence*)presence {
323
    // Don't leak any extra info over Tor or non-autologin accounts
324
    if (self.account.accountType == OTRAccountTypeXMPPTor ||
325
        !self.account.autologin) {
326
        return;
327
    }
328
    NSString *nowString = [date xmppDateTimeString];
329
    if (nowString) {
330
        /*
331
         <presence from='juliet@capulet.com/balcony'>
332
         <show>away</show>
333
         <idle xmlns='urn:xmpp:idle:1' since='1969-07-21T02:56:15Z'/>
334
         </presence>
335
         */
336
        // https://xmpp.org/extensions/xep-0319.html
337
        NSXMLElement *idle = [NSXMLElement elementWithName:@"idle" xmlns:@"urn:xmpp:idle:1"];
338
        [idle addAttributeWithName:@"since" stringValue:nowString];
339
        [presence addChild:idle];
340
    }
341
}
342

    
343
/** Sends "away" presence with last idle time */
344
- (void) goAway {
345
    // Don't leak any extra info over Tor
346
    if (self.account.accountType == OTRAccountTypeXMPPTor) {
347
        return;
348
    }
349
    XMPPPresence *presence = [XMPPPresence presence];
350
    NSXMLElement *show = [NSXMLElement elementWithName:@"show" stringValue:@"away"];
351
    [presence addChild:show];
352
    NSDate *idleDate = [OTRProtocolManager sharedInstance].lastInteractionDate;
353
    [self addIdleDate:idleDate toPresence:presence];
354
    [self.xmppStream sendElement:presence];
355
    
356
    [self updateClientState:XMPPClientStateInactive];
357
}
358

    
359
- (void)goActive {
360
    XMPPPresence *presence = [XMPPPresence presence]; // type="available" is implicit
361
    [self.xmppStream sendElement:presence];
362
    
363
    [self updateClientState:XMPPClientStateActive];
364
}
365

    
366
/** This will choose "active" or "away" based on UIApplication applicationState */
367
- (void)goOnline
368
{
369
    dispatch_async(dispatch_get_main_queue(), ^{
370
        self->_lastConnectionError = nil;
371
        if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
372
            [self goActive];
373
        } else {
374
            [self goAway];
375
        }
376
    });
377
}
378

    
379
- (void)goOffline
380
{
381
	XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];
382
    NSDate *idleDate = [OTRProtocolManager sharedInstance].lastInteractionDate;
383
    [self addIdleDate:idleDate toPresence:presence]; // I don't think this does anything
384
	[self.xmppStream sendElement:presence];
385
    
386
    [self updateClientState:XMPPClientStateInactive];
387
}
388

    
389

    
390

    
391
/** XEP-0352 Client State Indication */
392
- (void) updateClientState:(XMPPClientState)clientState {
393
    NSXMLElement *csiFeature = [self.serverCapabilities.streamFeatures elementForName:@"csi" xmlns:@"urn:xmpp:csi:0"];
394
    if (!csiFeature) {
395
        return;
396
    }
397
    
398
    NSXMLElement *csi = nil;
399
    switch (clientState) {
400
        case XMPPClientStateActive:
401
            csi = [NSXMLElement indicateActiveElement];
402
            break;
403
        case XMPPClientStateInactive:
404
            csi = [NSXMLElement indicateInactiveElement];
405
            break;
406
    }
407
    [self.xmppStream sendElement:csi];
408
}
409

    
410
- (NSString *)accountDomainWithError:(id)error;
411
{
412
    return self.account.domain;
413
}
414

    
415
- (void)didRegisterNewAccountWithStream:(XMPPStream *)stream
416
{
417
    self.isRegisteringNewAccount = NO;
418
    [self authenticateWithStream:stream];
419
    [[NSNotificationCenter defaultCenter] postNotificationName:OTRXMPPRegisterSucceededNotificationName object:self];
420
}
421
- (void)failedToRegisterNewAccount:(NSError *)error
422
{
423
    if (error) {
424
        [[NSNotificationCenter defaultCenter]
425
         postNotificationName:OTRXMPPRegisterFailedNotificationName object:self userInfo:@{kOTRNotificationErrorKey:error}];
426
    }
427
    else {
428
        [[NSNotificationCenter defaultCenter]
429
         postNotificationName:OTRXMPPRegisterFailedNotificationName object:self];
430
    }
431
}
432

    
433

    
434
- (void)authenticateWithStream:(XMPPStream *)stream {
435
    NSError * error = nil;
436
    BOOL status = YES;
437
    if ([stream supportsXOAuth2GoogleAuthentication] && self.account.accountType == OTRAccountTypeGoogleTalk) {
438
        status = [stream authenticateWithGoogleAccessToken:self.account.password error:&error];
439
    }
440
    else {
441
        status = [stream authenticateWithPassword:self.account.password error:&error];
442
    }
443
}
444

    
445
///////////////////////////////
446
#pragma mark Capabilities Collected
447
////////////////////////////////////////////
448

    
449
- (NSArray *)myFeaturesForXMPPCapabilities:(XMPPCapabilities *)sender
450
{
451
    return @[@"http://jabber.org/protocol/chatstates"];
452
}
453

    
454
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
455
#pragma mark Connect/disconnect
456
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
457

    
458
- (BOOL)startConnection
459
{
460
    self.connectionStatus = OTRProtocolConnectionStatusConnecting;
461
    
462
    XMPPJID *jid = [XMPPJID jidWithString:self.account.username resource:self.account.resource];
463
    
464
    if (![jid.domain isEqualToString:self.xmppStream.myJID.domain]) {
465
        [self.xmppStream disconnect];
466
    }
467
    self.xmppStream.myJID = jid;
468
	if (![self.xmppStream isDisconnected]) {
469
        [self authenticateWithStream:self.xmppStream];
470
		return YES;
471
	}
472
    
473
	//
474
	// If you don't want to use the Settings view to set the JID, 
475
	// uncomment the section below to hard code a JID and password.
476
	//
477
	// Replace me with the proper JID and password:
478
	//	myJID = @"user@gmail.com/xmppframework";
479
	//	myPassword = @"";
480
    
481
	
482
    
483
    
484
    NSError * error = nil;
485
    NSString * domainString = [self accountDomainWithError:error];
486
    if (error) {
487
        [self failedToConnect:error];
488
        return NO;
489
    }
490
    if ([domainString length]) {
491
        [self.xmppStream setHostName:domainString];
492
    }
493
    
494
    [self.xmppStream setHostPort:self.account.port];
495
	
496
    
497
	error = nil;
498
	if (![self.xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&error])
499
	{
500
		[self failedToConnect:error];
501
        
502
		DDLogError(@"Error connecting: %@", error);
503
        
504
		return NO;
505
	}
506
    
507
	return YES;
508
}
509

    
510
- (void) disconnectSocketOnly:(BOOL)socketOnly {
511
    DDLogVerbose(@"%@: %@ %d", THIS_FILE, THIS_METHOD, socketOnly);
512
    if (socketOnly) {
513
        if (self.connectionStatus == OTRProtocolConnectionStatusConnecting || self.connectionStatus == OTRProtocolConnectionStatusConnected) {
514
            self.connectionStatus = OTRProtocolConnectionStatusDisconnecting;
515
            [self goAway];
516
        }
517
        return;
518
    }
519
    
520
    [self goOffline];
521
    [self.xmppStream disconnectAfterSending];
522

    
523
    if([OTRSettingsManager boolForOTRSettingKey:kOTRSettingKeyDeleteOnDisconnect])
524
    {
525
        [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
526
            [OTRBaseMessage deleteAllMessagesForAccountId:self.account.uniqueId transaction:transaction];
527
        }];
528
    }
529
}
530

    
531
- (void)disconnect
532
{
533
    [self disconnectSocketOnly:NO];
534
}
535

    
536
- (BOOL)startRegisteringNewAccount
537
{
538
    self.isRegisteringNewAccount = YES;
539
    if (self.xmppStream.isConnected) {
540
        [self.xmppStream disconnect];
541
        return NO;
542
    }
543
    
544
    return [self startConnection];
545
}
546

    
547
- (BOOL)continueRegisteringNewAccount
548
{
549
    NSError * error = nil;
550
    if ([self.xmppStream supportsInBandRegistration]) {
551
        [self.xmppStream registerWithPassword:self.account.password error:&error];
552
        if (error) {
553
            [self failedToRegisterNewAccount:error];
554
            return NO;
555
        }
556
    } else {
557
        error = [NSError errorWithDomain:OTRXMPPErrorDomain code:OTRXMPPErrorCodeUnsupportedAction userInfo:nil];
558
        [self failedToRegisterNewAccount:error];
559
        return NO;
560
    }
561
    return YES;
562
}
563

    
564
#pragma mark Public Methods
565

    
566
/** Will send a probe to fetch last seen */
567
- (void) sendPresenceProbeForBuddy:(OTRXMPPBuddy*)buddy {
568
    NSParameterAssert(buddy);
569
    if (!buddy) { return; }
570
    XMPPJID *jid = buddy.bareJID;
571
    if (!jid) { return; }
572
    
573
    // We can't probe presence if we are still pending approval, so resend the request.
574
    if (buddy.pendingApproval) {
575
        [self.xmppRoster subscribePresenceToUser:jid];
576
        return;
577
    }
578
    
579
    // https://xmpp.org/extensions/xep-0318.html
580
    // <presence from='juliet@capulet.com/balcony' to='romeo@montague.com' type='probe' />
581
    XMPPPresence *probe = [XMPPPresence presenceWithType:@"probe" to:jid];
582
    if (!probe) { return; }
583
    [self.xmppStream sendElement:probe];
584
}
585

    
586

    
587
/** Enqueues a message to be sent by message queue */
588
- (void) enqueueMessage:(id<OTRMessageProtocol>)message {
589
    NSParameterAssert(message);
590
    if (!message) { return; }
591
    [self enqueueMessages:@[message]];
592
}
593

    
594
/** Enqueues an array of messages to be sent by message queue */
595
- (void) enqueueMessages:(NSArray<id<OTRMessageProtocol>>*)messages {
596
    NSParameterAssert(messages);
597
    if (!messages.count) {
598
        return;
599
    }
600
    [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
601
        [messages enumerateObjectsUsingBlock:^(id<OTRMessageProtocol> _Nonnull message, NSUInteger idx, BOOL * _Nonnull stop) {
602
            if (message.isMessageIncoming) {
603
                // We cannot send incoming messages
604
                DDLogError(@"Cannot send incoming message: %@", message);
605
                return;
606
            }
607
            //2. Create send message task
608
            OTRYapMessageSendAction *sendingAction = [OTRYapMessageSendAction sendActionForMessage:message date:message.messageDate];
609
            //3. save both to database
610
            [message saveWithTransaction:transaction];
611
            [sendingAction saveWithTransaction:transaction];
612
            //Update thread
613
            id<OTRThreadOwner> thread = [message threadOwnerWithTransaction:transaction];
614
            if (!thread) {
615
                return;
616
            }
617
            thread.currentMessageText = nil;
618
            thread.lastMessageIdentifier = message.uniqueId;
619
            [thread saveWithTransaction:transaction];
620
        }];
621
    }];
622
}
623

    
624
- (void)setAvatar:(UIImage *)avatarImage completion:(void (^)(BOOL success))completion
625
{
626
    if (!avatarImage) {
627
        completion(NO);
628
        return;
629
    }
630
    
631
    __block UIImage *newImage = avatarImage;
632
    
633
    
634
    dispatch_async(self.workQueue, ^{
635
        
636
        //Square crop & Resize image
637
        newImage = [UIImage otr_prepareForAvatarUpload:newImage maxSize:120.0];
638
        //jpeg compression
639
        NSData *data = UIImageJPEGRepresentation(newImage, 0.6);
640
        
641
        //Save new avatar right away to update UI
642
        
643
        [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
644
            OTRXMPPAccount *account = [[OTRXMPPAccount fetchObjectWithUniqueID:self.account.uniqueId transaction:transaction] copy];
645
            account.avatarData = data;
646
            [account saveWithTransaction:transaction];
647
        }];
648
        
649
        self.changeAvatar = [[OTRXMPPChangeAvatar alloc] initWithPhotoData:data
650
                                                       xmppvCardTempModule:self.xmppvCardTempModule];
651
        
652
        __weak typeof(self) weakSelf = self;
653
        [self.changeAvatar updatePhoto:^(BOOL success) {
654
            typeof(weakSelf) strongSelf = weakSelf;
655
            if (completion) {
656
                completion(success);
657
            }
658
            strongSelf.changeAvatar = nil;
659
        }];
660
    });
661
    
662
}
663

    
664
// Currently the only way to force a vCard update is to update the avatar. We want to do this in a non-destructive way, so that this method can be used many times without the avatar being distorted. The idea is to manipulate the bottom rightmost pixel value, setting it to the neighboring pixel value with the blue component alternating between +-1.
665
- (void)forcevCardUpdateWithCompletion:(void (^)(BOOL success))completion {
666
    UIImage *image = [self.account avatarImage];
667
    if (image != nil) {
668
        CGFloat width = image.size.width;
669
        CGFloat height = image.size.height;
670
        if (width <= 0 || height <= 0) {
671
            return;
672
        }
673
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
674
        
675
        size_t bitsPerComponent = 8;
676
        size_t bytesPerPixel    = 4;
677
        size_t bytesPerRow      = (width * bitsPerComponent * bytesPerPixel + 7) / 8;
678
        size_t dataSize         = bytesPerRow * height;
679
        
680
        unsigned char *data = malloc(dataSize);
681
        memset(data, 0, dataSize);
682
        
683
        CGContextRef context = CGBitmapContextCreate(data, width, height,
684
                                                     bitsPerComponent,
685
                                                     bytesPerRow, colorSpace,
686
                                                     kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big);
687
        
688
        
689
        CGColorSpaceRelease(colorSpace);
690
        
691
        CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
692
        
693
        // Get pixel color for neighboring pixel
694
        int offset = (width - 2) * bytesPerRow + (height - 1) * bytesPerPixel;
695
        int alpha =  data[offset];
696
        int red = data[offset+1];
697
        int green = data[offset+2];
698
        int blue = data[offset+3];
699

    
700
        // Manipulate the bottom right pixel
701
        offset = (width - 1) * bytesPerRow + (height - 1) * bytesPerPixel;
702
        int currentBlue = data[offset+3];
703

    
704
        data[offset] = alpha;
705
        data[offset+1] = red;
706
        data[offset+2] = green;
707
        if (currentBlue != blue) {
708
            // Not equal, just use the blue value for the neighboring pixel
709
            data[offset+3] = blue;
710
        } else {
711
            if (blue == 255) {
712
                data[offset+3] = blue - 1;
713
            } else {
714
                data[offset+3] = blue + 1;
715
            }
716
        }
717
        
718
        CGImageRef imgRef = CGBitmapContextCreateImage(context);
719
        UIImage* img = [UIImage imageWithCGImage:imgRef];
720
        CGImageRelease(imgRef);
721
        
722
        // When finished, release the context
723
        CGContextRelease(context);
724
        if (data) { free(data); }
725
        
726
        [self setAvatar:img completion:completion];
727
    }
728
}
729

    
730
- (void)changePassword:(NSString *)newPassword completion:(void (^)(BOOL,NSError*))completion {
731
    if (!completion) {
732
        return;
733
    }
734
    
735
    if (!self.xmppStream.isAuthenticated || [newPassword length] == 0) {
736
        completion(NO,nil);
737
    }
738
    
739
    self.changePasswordManager = [[OTRXMPPChangePasswordManager alloc] initWithNewPassword:newPassword xmppStream:self.xmppStream completion:^(BOOL success, NSError * _Nullable error) {
740
        
741
        if (success) {
742
            self.account.password = newPassword;
743
        }
744
        self.changePasswordManager = nil;
745
        completion(success,error);
746
    }];
747
#pragma clang diagnostic push
748
#pragma clang diagnostic ignored "-Wunused-value"
749
    [self.changePasswordManager changePassword];
750
#pragma clang diagnostic pop
751
}
752

    
753
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
754
#pragma mark XMPPStream Delegate
755
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
756

    
757
- (XMPPMessage *)xmppStream:(XMPPStream *)sender willSendMessage:(XMPPMessage *)message {
758
    NSString *body = [message body];
759
    if (body.length && ![body containsString:@" "]) {
760
        NSURL *url = [NSURL URLWithString:body];
761
        NSString *extension = url.pathExtension;
762
        NSString *scheme = url.scheme;
763
        if (scheme.length &&
764
            extension.length &&
765
            ([scheme isEqualToString:@"https"])) {
766
            // this means we're probably sending a file so mark it as OOB data
767
            /*
768
             <x xmlns="jabber:x:oob">
769
               <url>https://xmpp.example.com/upload/6c44c22c-70c6-4375-8b2d-1992a0f9dc18/8CleEoqhTjiMcny0PqoZyg.jpg</url>
770
             </x>
771
             */
772
            NSXMLElement *oob = [NSXMLElement elementWithName:@"x" xmlns:@"jabber:x:oob"];
773
            NSXMLElement *urlElement = [NSXMLElement elementWithName:@"url" stringValue:body];
774
            [oob addChild:urlElement];
775
            [message addChild:oob];
776
        }
777
    }
778
    return message;
779
}
780

    
781
- (void)xmppStreamDidChangeMyJID:(XMPPStream *)stream
782
{
783
    if (![[stream.myJID bare] isEqualToString:self.account.username] || ![[stream.myJID resource] isEqualToString:self.account.resource])
784
    {
785
        self.account.username = [stream.myJID bare];
786
        self.account.resource = [stream.myJID resource];
787
        [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
788
            [self.account saveWithTransaction:transaction];
789
        }];
790
    }
791
}
792

    
793
- (void)xmppStream:(XMPPStream *)sender socketDidConnect:(GCDAsyncSocket *)socket 
794
{
795
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
796
    [self changeLoginStatus:OTRLoginStatusConnected error:nil];
797
}
798

    
799
- (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings
800
{
801
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
802
    
803
    settings[GCDAsyncSocketSSLProtocolVersionMin] = @(kTLSProtocol1);
804
    settings[GCDAsyncSocketSSLCipherSuites] = [OTRUtilities cipherSuites];
805
    settings[GCDAsyncSocketManuallyEvaluateTrust] = @(YES);
806
    
807
    [self changeLoginStatus:OTRLoginStatusSecuring error:nil];
808
}
809

    
810
- (void)xmppStreamDidSecure:(XMPPStream *)sender
811
{
812
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
813
    
814
    [self changeLoginStatus:OTRLoginStatusSecured error:nil];
815
}
816

    
817
- (void)xmppStreamDidConnect:(XMPPStream *)sender
818
{
819
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
820
    
821
    if (self.isRegisteringNewAccount) {
822
        [self continueRegisteringNewAccount];
823
    }
824
    else{
825
        [self authenticateWithStream:sender];
826
    }
827
    
828
    [self changeLoginStatus:OTRLoginStatusAuthenticating error:nil];
829
}
830

    
831
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
832
{
833
    DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, error);
834
    
835
    self.connectionStatus = OTRProtocolConnectionStatusDisconnected;
836
    
837
    [self changeLoginStatus:OTRLoginStatusDisconnected error:error];
838
    
839
    if (self.loginStatus == OTRLoginStatusDisconnected)
840
    {
841
        DDLogError(@"Unable to connect to server. Check xmppStream.hostName");
842
        
843
        [self failedToConnect:error];
844
    }
845
    
846
    //Reset buddy info to offline
847
    __block NSArray<OTRXMPPBuddy*> *allBuddies = nil;
848
    [self.databaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction) {
849
        allBuddies = [self.account allBuddiesWithTransaction:transaction];
850
    } completionBlock:^{
851
        // We don't need to save in here because we're using OTRBuddyCache in memory storage
852
        if (!self.streamManagementDelegate.streamManagementEnabled) {
853
            [OTRBuddyCache.shared purgeAllPropertiesForBuddies:allBuddies];
854
        }
855
    }];
856
}
857

    
858
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
859
{
860
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
861
    if ([sender supportsStreamManagement] && ![self.streamManagement didResume]) {
862
        [self.streamManagement enableStreamManagementWithResumption:YES maxTimeout:0];
863
    }
864
    
865
    self.connectionStatus = OTRProtocolConnectionStatusConnected;
866
    NSString *accountKey = self.account.uniqueId;
867
    NSString *accountCollection = [[self.account class] collection];
868
    NSDictionary *userInfo = nil;
869
    if(accountKey && accountCollection) {
870
        userInfo = @{kOTRNotificationAccountUniqueIdKey:accountKey,kOTRNotificationAccountCollectionKey:accountCollection};
871
    }
872
    [[NSNotificationCenter defaultCenter]
873
     postNotificationName:kOTRProtocolLoginSuccess
874
     object:self userInfo:userInfo];
875
    
876
    [self changeLoginStatus:OTRLoginStatusAuthenticated error:nil];
877
    
878
    [self goOnline];
879
    
880
    // Fetch latest vCard from server so we can update nickname
881
    //[self.xmppvCardTempModule fetchvCardTempForJID:self.JID ignoreStorage:YES];
882
    
883
    // [self.messageStorage.archiving retrieveMessageArchiveWithFields:nil withResultSet:nil];
884
}
885

    
886
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(NSXMLElement *)error
887
{
888
	DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
889
    self.connectionStatus = OTRProtocolConnectionStatusDisconnected;
890
    NSError *err = [OTRXMPPError errorForXMLElement:error];
891
    [self failedToConnect:err];
892
    
893
    [self changeLoginStatus:OTRLoginStatusSecured error:err];
894
}
895

    
896
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
897
{
898
	DDLogVerbose(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, iq);
899
	return NO;
900
}
901

    
902
- (void)xmppStreamDidRegister:(XMPPStream *)sender {
903
    [self didRegisterNewAccountWithStream:sender];
904
}
905

    
906
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(NSXMLElement *)xmlError {
907
    
908
    self.isRegisteringNewAccount = NO;
909
    NSError * error = [OTRXMPPError errorForXMLElement:xmlError];
910
    [self failedToRegisterNewAccount:error];
911
    
912
    [self changeLoginStatus:OTRLoginStatusSecured error:error];
913
}
914

    
915
-(nullable OTRXMPPBuddy *)buddyWithMessage:(XMPPMessage *)message transaction:(YapDatabaseReadTransaction *)transaction
916
{
917
    XMPPJID *from = message.from;
918
    if (!from) { return nil; }
919
    OTRXMPPBuddy *buddy = [OTRXMPPBuddy fetchBuddyWithJid:from accountUniqueId:self.account.uniqueId transaction:transaction];
920
    return buddy;
921
}
922

    
923

    
924
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)xmppMessage
925
{
926
	DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, xmppMessage);
927
}
928

    
929
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
930
{
931
	DDLogVerbose(@"%@: %@\n%@", THIS_FILE, THIS_METHOD, presence.prettyXMLString);
932
    
933
    
934
}
935

    
936
- (void)xmppStream:(XMPPStream *)sender didReceiveError:(id)error
937
{
938
	DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, error);
939
}
940

    
941
- (void)xmppStream:(XMPPStream *)sender didFailToSendIQ:(XMPPIQ *)iq error:(NSError *)error
942
{
943
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, iq, error);
944
}
945

    
946
- (void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error
947
{
948
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, message, error);
949
    [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
950
        [transaction enumerateMessagesWithElementId:message.elementID originId:message.originId stanzaId:nil block:^(id<OTRMessageProtocol> _Nonnull databaseMessage, BOOL * _Null_unspecified stop) {
951
            if ([databaseMessage isKindOfClass:[OTRBaseMessage class]]) {
952
                ((OTRBaseMessage *)databaseMessage).error = error;
953
                [(OTRBaseMessage *)databaseMessage saveWithTransaction:transaction];
954
                *stop = YES;
955
            }
956
        }];
957
    }];
958
}
959
- (void)xmppStream:(XMPPStream *)sender didFailToSendPresence:(XMPPPresence *)presence error:(NSError *)error
960
{
961
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, presence, error);
962
    [self finishDisconnectingIfNeeded];
963
}
964

    
965
- (void)xmppStream:(XMPPStream *)sender didSendPresence:(XMPPPresence *)presence
966
{
967
    [self finishDisconnectingIfNeeded];
968
}
969

    
970
- (void)finishDisconnectingIfNeeded {
971
    if (self.connectionStatus == OTRProtocolConnectionStatusDisconnecting) {
972
        [self.xmppStream disconnect];
973
        self.connectionStatus =  OTRProtocolConnectionStatusDisconnected;
974
    }
975
}
976

    
977
#pragma mark XMPPvCardTempModuleDelegate
978

    
979
- (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule
980
        didReceivevCardTemp:(XMPPvCardTemp *)vCardTemp
981
                     forJID:(XMPPJID *)jid {
982
    DDLogVerbose(@"%@: %@ %@ %@ %@", THIS_FILE, THIS_METHOD, vCardTempModule, vCardTemp, jid);
983
    
984
    // update my vCard to local nickname setting
985
    // currently this will clobber whatever you have on the server
986
    if ([self.xmppStream.myJID isEqualToJID:jid options:XMPPJIDCompareBare]) {
987
        if (self.account.displayName.length &&
988
            vCardTemp.nickname.length &&
989
            ![vCardTemp.nickname isEqualToString:self.account.displayName]) {
990
            vCardTemp.nickname = self.account.displayName;
991
            [vCardTempModule updateMyvCardTemp:vCardTemp];
992
        } else if (vCardTemp.nickname.length) {
993
            [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
994
                NSString *collection = [self.account.class collection];
995
                NSString *key = self.account.uniqueId;
996
                OTRXMPPAccount *account = [[transaction objectForKey:key inCollection:collection] copy];
997
                account.displayName = vCardTemp.nickname;
998
                [transaction setObject:account forKey:key inCollection:collection];
999
            }];
1000
        }
1001
    } else {
1002
        // this is someone elses vCard
1003
        DDLogVerbose(@"%@: other's vCard %@ %@ %@ %@", THIS_FILE, THIS_METHOD, vCardTempModule, vCardTemp, jid);
1004
    }
1005
}
1006

    
1007
- (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule
1008
   failedToFetchvCardForJID:(XMPPJID *)jid
1009
                      error:(NSXMLElement*)error {
1010
    DDLogVerbose(@"%@: %@ %@ %@ %@", THIS_FILE, THIS_METHOD, vCardTempModule, jid, error);
1011
    
1012
    // update my vCard to local nickname setting
1013
    if ([self.xmppStream.myJID isEqualToJID:jid options:XMPPJIDCompareBare] &&
1014
        self.account.displayName.length) {
1015
        XMPPvCardTemp *vCardTemp = [XMPPvCardTemp vCardTemp];
1016
        vCardTemp.nickname = self.account.displayName;
1017
        [vCardTempModule updateMyvCardTemp:vCardTemp];
1018
    }
1019
}
1020

    
1021
- (void)xmppvCardTempModuleDidUpdateMyvCard:(XMPPvCardTempModule *)vCardTempModule {
1022
    DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, vCardTempModule);
1023
}
1024

    
1025
- (void)xmppvCardTempModule:(XMPPvCardTempModule *)vCardTempModule failedToUpdateMyvCard:(NSXMLElement *)error {
1026
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, vCardTempModule, error);
1027
}
1028

    
1029

    
1030
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1031
#pragma mark XMPPRosterDelegate
1032
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1033

    
1034
- (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(NSXMLElement *)item {
1035
    DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, item);
1036

    
1037
    // Because XMPP sucks, there's no way to know if a vCard has changed without fetching all of them again
1038
    // To preserve user mobile data, just fetch each vCard once, only if it's never been fetched
1039
    // Otherwise you'll only receive vCard updates if someone updates their avatar
1040
    NSString *jidStr = [item attributeStringValueForName:@"jid"];
1041
    XMPPJID *jid = [[XMPPJID jidWithString:jidStr] bareJID];
1042
    __block OTRXMPPBuddy *buddy = nil;
1043
    [[OTRDatabaseManager sharedInstance].readOnlyDatabaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
1044
        buddy = [OTRXMPPBuddy fetchBuddyWithJid:jid accountUniqueId:self.account.uniqueId transaction:transaction];
1045
    } completionQueue:self.workQueue completionBlock:^{
1046
        if (!buddy) { return; }
1047
        XMPPvCardTemp *vCard = [self.xmppvCardTempModule vCardTempForJID:jid shouldFetch:YES];
1048
        if (!vCard) { return; }
1049
        [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
1050
            buddy = [buddy refetchWithTransaction:transaction];
1051
            if (!buddy) { return; }
1052
            buddy.vCardTemp = vCard;
1053
            [buddy saveWithTransaction:transaction];
1054
        }];
1055
    }];
1056
}
1057

    
1058
-(void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence
1059
{
1060
    DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, presence);
1061
    
1062
	NSString *jidStrBare = [presence fromStr];
1063
    
1064
    [self.databaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
1065
        OTRXMPPPresenceSubscriptionRequest *request = [OTRXMPPPresenceSubscriptionRequest fetchPresenceSubscriptionRequestWithJID:jidStrBare accontUniqueId:self.account.uniqueId transaction:transaction];
1066
        if (!request) {
1067
            request = [[OTRXMPPPresenceSubscriptionRequest alloc] init];
1068
            [[UIApplication sharedApplication] showLocalNotificationForSubscriptionRequestFrom:jidStrBare];
1069
        }
1070
        
1071
        request.jid = jidStrBare;
1072
        request.accountUniqueId = self.account.uniqueId;
1073
        
1074
        [request saveWithTransaction:transaction];
1075
    }];
1076
}
1077

    
1078
- (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterPush:(XMPPIQ *)iq
1079
{
1080
    DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, iq);
1081
    //verry unclear what this delegate call is supposed to do with jabber.ccc.de it seems to have all the subscription=both,none and jid
1082
    /*
1083
    if ([iq isSetIQ] && [[[[[[iq elementsForName:@"query"] firstObject] elementsForName:@"item"] firstObject] attributeStringValueForName:@"subscription"] isEqualToString:@"from"]) {
1084
        NSString *jidString = [[[[[iq elementsForName:@"query"] firstObject] elementsForName:@"item"] firstObject] attributeStringValueForName:@"jid"];
1085
        
1086
        [self.databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
1087
            OTRXMPPPresenceSubscriptionRequest *request = [OTRXMPPPresenceSubscriptionRequest fetchPresenceSubscriptionRequestWithJID:jidString accontUniqueId:self.account.uniqueId transaction:transaction];
1088
            if (!request) {
1089
                request = [[OTRXMPPPresenceSubscriptionRequest alloc] init];
1090
            }
1091
            
1092
            request.jid = jidString;
1093
            request.accountUniqueId = self.account.uniqueId;
1094
            
1095
            [transaction setObject:request forKey:request.uniqueId inCollection:[OTRXMPPPresenceSubscriptionRequest collection]];
1096
        }];
1097
    }
1098
    else if ([iq isSetIQ] && [[[[[[iq elementsForName:@"query"] firstObject] elementsForName:@"item"] firstObject] attributeStringValueForName:@"subscription"] isEqualToString:@"none"])
1099
    {
1100
        [self.databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
1101
            NSString *jidString = [[[[[iq elementsForName:@"query"] firstObject] elementsForName:@"item"] firstObject] attributeStringValueForName:@"jid"];
1102
            
1103
            OTRXMPPBuddy *buddy = [[OTRXMPPBuddy fetchBuddyWithUsername:jidString withAccountUniqueId:self.account.uniqueId transaction:transaction] copy];
1104
            buddy.pendingApproval = YES;
1105
            [buddy saveWithTransaction:transaction];
1106
        }];
1107
    }
1108
    
1109
    */
1110
    
1111
    
1112
}
1113

    
1114
#pragma mark XMPPPushDelegate
1115

    
1116
- (void) pushAccountChanged:(NSNotification*)notif {
1117
    __block OTRXMPPAccount *account = nil;
1118
    [self.databaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
1119
        NSString *collection = [self.account.class collection];
1120
        NSString *key = self.account.uniqueId;
1121
        account = [transaction objectForKey:key inCollection:collection];
1122
    }];
1123
    if (!account.pushPubsubEndpoint) { return; }
1124
    XMPPJID *serverJID = [XMPPJID jidWithUser:nil domain:account.pushPubsubEndpoint resource:nil];
1125
    if (!serverJID) { return; }
1126
    XMPPPushStatus status = [self.xmppPushModule registrationStatusForServerJID:serverJID];
1127
    if (status != XMPPPushStatusRegistered &&
1128
        status != XMPPPushStatusRegistering) {
1129
        [self.xmppPushModule refresh];
1130
    }
1131
}
1132

    
1133
- (void)pushModule:(XMPPPushModule*)module readyWithCapabilities:(NSXMLElement *)caps jid:(XMPPJID *)jid {
1134
    // Enable XEP-0357 push bridge if server supports it
1135
    // ..but don't register for Tor accounts
1136
    if (self.account.accountType == OTRAccountTypeXMPPTor) {
1137
        return;
1138
    }
1139
    BOOL hasPushAccount = [[OTRProtocolManager sharedInstance].pushController.pushStorage hasPushAccount];
1140
    if (!hasPushAccount) {
1141
        return;
1142
    }
1143
    [[OTRProtocolManager sharedInstance].pushController getPubsubEndpoint:^(NSString * _Nullable endpoint, NSError * _Nullable error) {
1144
        if (endpoint) {
1145
            [[OTRProtocolManager sharedInstance].pushController getNewPushToken:nil completion:^(TokenContainer * _Nullable token, NSError * _Nullable error) {
1146
                if (token) {
1147
                    [self enablePushWithToken:token endpoint:endpoint];
1148
                } else if (error) {
1149
                    DDLogError(@"fetch token error: %@", error);
1150
                }
1151
            }];
1152
        } else if (error) {
1153
            DDLogError(@"357 pubsub Error: %@", error);
1154
        }
1155
    }];
1156
}
1157

    
1158
- (void) enablePushWithToken:(TokenContainer*)token endpoint:(NSString*)endpoint {
1159
    __block OTRXMPPAccount *account = nil;
1160
    [self.databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
1161
        NSString *collection = [self.account.class collection];
1162
        NSString *key = self.account.uniqueId;
1163
        account = [[transaction objectForKey:key inCollection:collection] copy];
1164
        account.pushPubsubEndpoint = endpoint;
1165
        if (!account.pushPubsubNode.length) {
1166
            account.pushPubsubNode = [[NSUUID UUID] UUIDString];
1167
        }
1168
        [transaction setObject:account forKey:key inCollection:collection];
1169
    }];
1170
    XMPPJID *nodeJID = [XMPPJID jidWithString:endpoint];
1171
    NSString *tokenString = token.pushToken.tokenString;
1172
    if (tokenString.length > 0) {
1173
        NSString *pushEndpointURLString = [[OTRProtocolManager sharedInstance].pushController getMessagesEndpoint].absoluteString;
1174
        NSMutableDictionary *options = [NSMutableDictionary dictionary];
1175
        [options setObject:tokenString forKey:@"token"];
1176
        if (pushEndpointURLString) {
1177
            [options setObject:pushEndpointURLString forKey:@"endpoint"];
1178
        }
1179
        XMPPPushOptions *pushOptions = [[XMPPPushOptions alloc] initWithServerJID:nodeJID node:account.pushPubsubNode formOptions:options];
1180
        [self.xmppPushModule registerForPushWithOptions:pushOptions elementId:nil];
1181
    }
1182
}
1183

    
1184
- (void)pushModule:(XMPPPushModule*)module
1185
didRegisterWithResponseIq:(XMPPIQ*)responseIq
1186
        outgoingIq:(XMPPIQ*)outgoingIq {
1187
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, responseIq, outgoingIq);
1188
}
1189

    
1190
- (void)pushModule:(XMPPPushModule*)module
1191
failedToRegisterWithErrorIq:(nullable XMPPIQ*)errorIq
1192
        outgoingIq:(XMPPIQ*)outgoingIq {
1193
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, errorIq, outgoingIq);
1194
}
1195

    
1196
- (void)pushModule:(XMPPPushModule*)module
1197
disabledPushForServerJID:(XMPPJID*)serverJID
1198
              node:(nullable NSString*)node
1199
        responseIq:(XMPPIQ*)responseIq
1200
        outgoingIq:(XMPPIQ*)outgoingIq {
1201
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, responseIq, outgoingIq);
1202
}
1203

    
1204
- (void)pushModule:(XMPPPushModule*)module
1205
failedToDisablePushWithErrorIq:(nullable XMPPIQ*)errorIq
1206
         serverJID:(XMPPJID*)serverJID
1207
              node:(nullable NSString*)node
1208
        outgoingIq:(XMPPIQ*)outgoingIq {
1209
    DDLogVerbose(@"%@: %@ %@ %@", THIS_FILE, THIS_METHOD, errorIq, outgoingIq);
1210
}
1211

    
1212

    
1213
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1214
#pragma mark OTRProtocol
1215
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1216

    
1217
- (void) sendMessage:(OTROutgoingMessage*)message
1218
{
1219
    NSParameterAssert(message);
1220
    NSString *text = message.text;
1221
    if (!text.length) {
1222
        return;
1223
    }
1224
    __block OTRXMPPBuddy *buddy = nil;
1225
    [[OTRDatabaseManager sharedInstance].readOnlyDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
1226
        buddy = (OTRXMPPBuddy *)[message threadOwnerWithTransaction:transaction];
1227
    }];
1228
    if (!buddy || ![buddy isKindOfClass:[OTRXMPPBuddy class]]) {
1229
        return;
1230
    }
1231
    [self invalidatePausedChatStateTimerForBuddyUniqueId:buddy.uniqueId];
1232
    
1233
    NSString * messageID = message.messageId;
1234
    XMPPMessage * xmppMessage = [XMPPMessage messageWithType:@"chat" to:buddy.bareJID elementID:messageID];
1235
    [xmppMessage addBody:text];
1236
    
1237
    [xmppMessage addActiveChatState];
1238
    
1239
    if ([OTRKit stringStartsWithOTRPrefix:text]) {
1240
        [xmppMessage addPrivateMessageCarbons];
1241
        [xmppMessage addStorageHint:XMPPMessageStorageNoCopy];
1242
        [xmppMessage addStorageHint:XMPPMessageStorageNoPermanentStore];
1243
    }
1244
    
1245
    [self.xmppStream sendElement:xmppMessage];
1246
}
1247

    
1248
- (NSString*) type {
1249
    return kOTRProtocolTypeXMPP;
1250
}
1251

    
1252
- (void) connectUserInitiated:(BOOL)userInitiated
1253
{
1254
    self.userInitiatedConnection = userInitiated;
1255
    // Don't issue a reconnect if we're already connected and authenticated
1256
    if ([self.xmppStream isConnected] && [self.xmppStream isAuthenticated]) {
1257
        [self goOnline];
1258
        return;
1259
    }
1260
    [self startConnection];
1261
    if (self.userInitiatedConnection) {
1262
        [[OTRNotificationController sharedInstance] showAccountConnectingNotificationWithAccountName:self.account.username];
1263
    }
1264
}
1265

    
1266
-(void)connect
1267
{
1268
    [self connectUserInitiated:NO];
1269
}
1270

    
1271
-(void)sendChatState:(OTRChatState)chatState withBuddyID:(NSString *)buddyUniqueId
1272
{
1273
    dispatch_async(self.workQueue, ^{
1274
        
1275
        __block OTRXMPPBuddy *buddy = nil;
1276
        [[OTRDatabaseManager sharedInstance].readOnlyDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
1277
            buddy = [OTRXMPPBuddy fetchObjectWithUniqueID:buddyUniqueId transaction:transaction];
1278
        }];
1279
        if (!buddy) { return; }
1280
        
1281
        if (buddy.lastSentChatState == chatState) {
1282
            return;
1283
        }
1284
        
1285
        XMPPMessage * xMessage = [[XMPPMessage alloc] initWithType:@"chat" to:[XMPPJID jidWithString:buddy.username]];
1286
        BOOL shouldSend = YES;
1287
        
1288
        if (chatState == OTRChatStateActive) {
1289
            //Timers
1290
            dispatch_async(dispatch_get_main_queue(), ^{
1291
                [[self pausedChatStateTimerForBuddyObjectID:buddyUniqueId] invalidate];
1292
                [self restartInactiveChatStateTimerForBuddyObjectID:buddyUniqueId];
1293
            });
1294
            
1295
            [xMessage addActiveChatState];
1296
        }
1297
        else if (chatState == OTRChatStateComposing)
1298
        {
1299
            if(buddy.lastSentChatState !=OTRChatStateComposing)
1300
                [xMessage addComposingChatState];
1301
            else
1302
                shouldSend = NO;
1303
            
1304
            //Timers
1305
            dispatch_async(dispatch_get_main_queue(), ^{
1306
                [self restartPausedChatStateTimerForBuddyObjectID:buddy.uniqueId];
1307
                [[self inactiveChatStateTimerForBuddyObjectID:buddy.uniqueId] invalidate];
1308
            });
1309
        }
1310
        else if(chatState == OTRChatStateInactive)
1311
        {
1312
            if(buddy.lastSentChatState != OTRChatStateInactive)
1313
                [xMessage addInactiveChatState];
1314
            else
1315
                shouldSend = NO;
1316
        }
1317
        else if (chatState == OTRChatStatePaused)
1318
        {
1319
            [xMessage addPausedChatState];
1320
        }
1321
        else if (chatState == OTRChatStateGone)
1322
        {
1323
            [xMessage addGoneChatState];
1324
        }
1325
        else
1326
        {
1327
            shouldSend = NO;
1328
        }
1329
        
1330
        if(shouldSend)
1331
        {
1332
            [OTRBuddyCache.shared setLastSentChatState:chatState forBuddy:buddy];
1333
            [self.xmppStream sendElement:xMessage];
1334
        }
1335
    });
1336
}
1337

    
1338
- (void) addBuddies:(NSArray<OTRXMPPBuddy*> *)buddies {
1339
    NSParameterAssert(buddies != nil);
1340
    if (!buddies.count) { return; }
1341
    [[OTRDatabaseManager sharedInstance].readWriteDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
1342
        [buddies enumerateObjectsUsingBlock:^(OTRXMPPBuddy * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
1343
            [obj saveWithTransaction:transaction];
1344
            OTRYapAddBuddyAction *addBuddyAction = [[OTRYapAddBuddyAction alloc] init];
1345
            addBuddyAction.buddyKey = obj.uniqueId;
1346
            [addBuddyAction saveWithTransaction:transaction];
1347
        }];
1348
    }];
1349
}
1350

    
1351
- (void) addBuddy:(OTRXMPPBuddy *)newBuddy
1352
{
1353
    NSParameterAssert(newBuddy != nil);
1354
    if (!newBuddy) { return; }
1355
    [self addBuddies:@[newBuddy]];
1356
}
1357

    
1358
- (void) setDisplayName:(NSString *) newDisplayName forBuddy:(OTRXMPPBuddy *)buddy
1359
{
1360
    XMPPJID * jid = [XMPPJID jidWithString:buddy.username];
1361
    [self.xmppRoster setNickname:newDisplayName forUser:jid];
1362
    
1363
}
1364
-(void)removeBuddies:(NSArray *)buddies
1365
{
1366
    [[OTRDatabaseManager sharedInstance].readWriteDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
1367
        
1368
        // Add actions to the queue
1369
        for (OTRXMPPBuddy *buddy in buddies){
1370
            OTRYapRemoveBuddyAction *removeBuddyAction = [[OTRYapRemoveBuddyAction alloc] init];
1371
            removeBuddyAction.buddyKey = buddy.uniqueId;
1372
            removeBuddyAction.buddyJid = buddy.username;
1373
            removeBuddyAction.accountKey = buddy.accountUniqueId;
1374
            [removeBuddyAction saveWithTransaction:transaction];
1375
        }
1376
        
1377
        [transaction removeObjectsForKeys:[buddies valueForKey:NSStringFromSelector(@selector(uniqueId))] inCollection:[OTRXMPPBuddy collection]];
1378
    }];
1379

    
1380

    
1381

    
1382
}
1383
-(void)blockBuddies:(NSArray *)buddies
1384
{
1385
    for (OTRXMPPBuddy *buddy in buddies){
1386
        XMPPJID * jid = [XMPPJID jidWithString:buddy.username];
1387
        [self.xmppRoster revokePresencePermissionFromUser:jid];
1388
    }
1389
}
1390

    
1391
//Chat State
1392

    
1393
-(OTRXMPPBuddyTimers *)buddyTimersForBuddyObjectID:(NSString *)
1394
managedBuddyObjectID
1395
{
1396
    OTRXMPPBuddyTimers * timers = [self.buddyTimers objectForKey:managedBuddyObjectID];
1397
    return timers;
1398
}
1399

    
1400
-(NSTimer *)inactiveChatStateTimerForBuddyObjectID:(NSString *)
1401
managedBuddyObjectID
1402
{
1403
   return [self buddyTimersForBuddyObjectID:managedBuddyObjectID].inactiveChatStateTimer;
1404
    
1405
}
1406
-(NSTimer *)pausedChatStateTimerForBuddyObjectID:(NSString *)
1407
managedBuddyObjectID
1408
{
1409
    return [self buddyTimersForBuddyObjectID:managedBuddyObjectID].pausedChatStateTimer;
1410
}
1411

    
1412
-(void)restartPausedChatStateTimerForBuddyObjectID:(NSString *)managedBuddyObjectID
1413
{
1414
    dispatch_async(dispatch_get_main_queue(), ^{
1415
        OTRXMPPBuddyTimers * timer = [self.buddyTimers objectForKey:managedBuddyObjectID];
1416
        if(!timer)
1417
        {
1418
            timer = [[OTRXMPPBuddyTimers alloc] init];
1419
        }
1420
        [timer.pausedChatStateTimer invalidate];
1421
        timer.pausedChatStateTimer = [NSTimer scheduledTimerWithTimeInterval:kOTRChatStatePausedTimeout target:self selector:@selector(sendPausedChatState:) userInfo:managedBuddyObjectID repeats:NO];
1422
        [self.buddyTimers setObject:timer forKey:managedBuddyObjectID];
1423
    });
1424
    
1425
}
1426
-(void)restartInactiveChatStateTimerForBuddyObjectID:(NSString *)managedBuddyObjectID
1427
{
1428
    dispatch_async(dispatch_get_main_queue(), ^{
1429
        OTRXMPPBuddyTimers * timer = [self.buddyTimers objectForKey:managedBuddyObjectID];
1430
        if(!timer)
1431
        {
1432
            timer = [[OTRXMPPBuddyTimers alloc] init];
1433
        }
1434
        [timer.inactiveChatStateTimer invalidate];
1435
        timer.inactiveChatStateTimer = [NSTimer scheduledTimerWithTimeInterval:kOTRChatStateInactiveTimeout target:self selector:@selector(sendInactiveChatState:) userInfo:managedBuddyObjectID repeats:NO];
1436
        [self.buddyTimers setObject:timer forKey:managedBuddyObjectID];
1437
    });
1438
}
1439
-(void)sendPausedChatState:(NSTimer *)timer
1440
{
1441
    NSString * managedBuddyObjectID= (NSString *)timer.userInfo;
1442
    dispatch_async(dispatch_get_main_queue(), ^{
1443
        [timer invalidate];
1444
    });
1445
    [self sendChatState:OTRChatStatePaused withBuddyID:managedBuddyObjectID];
1446
}
1447
-(void)sendInactiveChatState:(NSTimer *)timer
1448
{
1449
    NSString *managedBuddyObjectID= (NSString *)timer.userInfo;
1450
    dispatch_async(dispatch_get_main_queue(), ^{
1451
        [timer invalidate];
1452
    });
1453
    
1454
    [self sendChatState:OTRChatStateInactive withBuddyID:managedBuddyObjectID];
1455
}
1456

    
1457
- (void)invalidatePausedChatStateTimerForBuddyUniqueId:(NSString *)buddyUniqueId
1458
{
1459
    [[self pausedChatStateTimerForBuddyObjectID:buddyUniqueId] invalidate];
1460
}
1461

    
1462
- (void)failedToConnect:(NSError *)error
1463
{
1464
    __weak typeof(self)weakSelf = self;
1465
    dispatch_async(dispatch_get_main_queue(), ^{
1466
        __strong typeof(weakSelf)strongSelf = weakSelf;
1467
        strongSelf->_lastConnectionError = error;
1468
        
1469
        NSMutableDictionary *userInfo = [@{kOTRProtocolLoginUserInitiated : @(self.userInitiatedConnection)} mutableCopy];
1470
        if (error) {
1471
            [userInfo setObject:error forKey:kOTRNotificationErrorKey];
1472
        }
1473
        
1474
        [[NSNotificationCenter defaultCenter] postNotificationName:kOTRProtocolLoginFail object:self userInfo:userInfo];
1475
        //Only user initiated on the first time any subsequent attempts will not be from user
1476
        strongSelf.userInitiatedConnection = NO;
1477
    });
1478
}
1479

    
1480
- (OTRCertificatePinning *)certificatePinningModule
1481
{
1482
    if(!_certificatePinningModule){
1483
        _certificatePinningModule = [[OTRCertificatePinning alloc] init];
1484
        _certificatePinningModule.delegate = self;
1485
    }
1486
    return _certificatePinningModule;
1487
}
1488

    
1489
- (void)newTrust:(SecTrustRef)trust withHostName:(NSString *)hostname systemTrustResult:(SecTrustResultType)trustResultType
1490
{
1491
    NSData * certifcateData = [OTRCertificatePinning dataForCertificate:[OTRCertificatePinning certForTrust:trust]];
1492
    DDLogVerbose(@"New trustResultType: %d certLength: %d", (int)trustResultType, (int)certifcateData.length);
1493
    NSError *error = [OTRXMPPError errorForTrustResult:trustResultType withCertData:certifcateData hostname:hostname];
1494
    dispatch_async(dispatch_get_main_queue(), ^{
1495
        [self failedToConnect:error];
1496
    });
1497
    
1498
    [self changeLoginStatus:OTRLoginStatusDisconnected error:error];
1499
}
1500

    
1501
- (void)changeLoginStatus:(OTRLoginStatus)status error:(NSError *)error
1502
{
1503
    OTRLoginStatus oldStatus = self.loginStatus;
1504
    OTRLoginStatus newStatus = status;
1505
    self.loginStatus = status;
1506
    
1507
    NSMutableDictionary *userInfo = [@{OTRXMPPOldLoginStatusKey: @(oldStatus), OTRXMPPNewLoginStatusKey: @(newStatus)} mutableCopy];
1508
    
1509
    if (error) {
1510
        userInfo[OTRXMPPLoginErrorKey] = error;
1511
    }
1512
    
1513
    dispatch_async(dispatch_get_main_queue(), ^{
1514
        [[NSNotificationCenter defaultCenter] postNotificationName:OTRXMPPLoginStatusNotificationName object:self userInfo:userInfo];
1515
    });
1516
}
1517

    
1518
// Delivery receipts
1519
- (void) sendDeliveryReceiptForMessage:(OTRIncomingMessage*)message {
1520
    [[OTRDatabaseManager sharedInstance].readOnlyDatabaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
1521
        OTRBuddy *buddy = [OTRBuddy fetchObjectWithUniqueID:message.buddyUniqueId transaction:transaction];        
1522
        XMPPMessage *tempMessage = [XMPPMessage messageWithType:@"chat" elementID:message.messageId];
1523
        [tempMessage addAttributeWithName:@"from" stringValue:buddy.username];
1524
        XMPPMessage *receiptMessage = [tempMessage generateReceiptResponse];
1525
        [self.xmppStream sendElement:receiptMessage];
1526
    }];
1527
}
1528

    
1529
// A new buddy has approved us, show a local notification
1530
- (void) buddyPendingApprovalStateChanged:(NSNotification*)notif {
1531
    if (notif != nil && notif.userInfo != nil) {
1532
        OTRBuddy *buddy = [notif.userInfo objectForKey:@"buddy"];
1533
        if (buddy != nil) {
1534
            [[UIApplication sharedApplication] showLocalNotificationForApprovedBuddy:buddy];
1535
        }
1536
    }
1537
}
1538

    
1539
@end