Statistics
| Branch: | Tag: | Revision:

chatsecureios / ChatSecure / Classes / Controllers / XMPP / OTRXMPPManager.m @ a4bb25f6

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 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 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
    // Testing MAM
884
    [self.messageStorage.archiving retrieveMessageArchiveWithFields:nil withResultSet:nil];
885
}
886

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

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

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

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

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

    
924

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

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

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

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

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

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

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

    
978
#pragma mark XMPPvCardTempModuleDelegate
979

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

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

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

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

    
1030

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

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

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

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

    
1079
- (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterPush:(XMPPIQ *)iq
1080
{
1081
    DDLogVerbose(@"%@: %@ %@", THIS_FILE, THIS_METHOD, iq);
1082
    //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
1083
    /*
1084
    if ([iq isSetIQ] && [[[[[[iq elementsForName:@"query"] firstObject] elementsForName:@"item"] firstObject] attributeStringValueForName:@"subscription"] isEqualToString:@"from"]) {
1085
        NSString *jidString = [[[[[iq elementsForName:@"query"] firstObject] elementsForName:@"item"] firstObject] attributeStringValueForName:@"jid"];
1086
        
1087
        [self.databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
1088
            OTRXMPPPresenceSubscriptionRequest *request = [OTRXMPPPresenceSubscriptionRequest fetchPresenceSubscriptionRequestWithJID:jidString accontUniqueId:self.account.uniqueId transaction:transaction];
1089
            if (!request) {
1090
                request = [[OTRXMPPPresenceSubscriptionRequest alloc] init];
1091
            }
1092
            
1093
            request.jid = jidString;
1094
            request.accountUniqueId = self.account.uniqueId;
1095
            
1096
            [transaction setObject:request forKey:request.uniqueId inCollection:[OTRXMPPPresenceSubscriptionRequest collection]];
1097
        }];
1098
    }
1099
    else if ([iq isSetIQ] && [[[[[[iq elementsForName:@"query"] firstObject] elementsForName:@"item"] firstObject] attributeStringValueForName:@"subscription"] isEqualToString:@"none"])
1100
    {
1101
        [self.databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
1102
            NSString *jidString = [[[[[iq elementsForName:@"query"] firstObject] elementsForName:@"item"] firstObject] attributeStringValueForName:@"jid"];
1103
            
1104
            OTRXMPPBuddy *buddy = [[OTRXMPPBuddy fetchBuddyWithUsername:jidString withAccountUniqueId:self.account.uniqueId transaction:transaction] copy];
1105
            buddy.pendingApproval = YES;
1106
            [buddy saveWithTransaction:transaction];
1107
        }];
1108
    }
1109
    
1110
    */
1111
    
1112
    
1113
}
1114

    
1115
#pragma mark XMPPPushDelegate
1116

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

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

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

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

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

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

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

    
1213

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

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

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

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

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

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

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

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

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

    
1381

    
1382

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

    
1392
//Chat State
1393

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

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

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

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

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

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

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

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

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

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

    
1540
@end