From c21138bd39330d8b3bc6c821b615568bcdf6baa6 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 15 Oct 2024 13:19:49 +0200 Subject: [PATCH 01/18] Show timestamp of last received push in notification debug menu --- Monal/Classes/NotificationDebugging.swift | 14 +++++++++++++- Monal/NotificationService/NotificationService.m | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/NotificationDebugging.swift b/Monal/Classes/NotificationDebugging.swift index 3dc7396466..ebe6bdcbfa 100644 --- a/Monal/Classes/NotificationDebugging.swift +++ b/Monal/Classes/NotificationDebugging.swift @@ -8,6 +8,11 @@ import OrderedCollections +class NotificationDebuggingDefaultsDB: ObservableObject { + @defaultsDB("lastAppexStart") + var lastAppexStart: Date? +} + struct NotificationDebugging: View { private let applePushEnabled: Bool private let applePushToken: String @@ -19,18 +24,25 @@ struct NotificationDebugging: View { @State private var showPushToken = false @State private var selectedPushServer: String + + @ObservedObject var notificationDebuggingDefaultsDB = NotificationDebuggingDefaultsDB() var body: some View { Form { Group { Section(header: Text("Status").font(.title3)) { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing:10) { buildNotificationStateLabel(Text("Apple Push Service"), isWorking: self.applePushEnabled); Divider() Text("Apple push service should always be on. If it is off, your device can not talk to Apple's server.").foregroundColor(Color(UIColor.secondaryLabel)).font(.footnote) if !self.applePushEnabled, let apnsError = MLXMPPManager.sharedInstance().apnsError { Text("Error: \(String(describing:apnsError))").foregroundColor(.red).font(.footnote) } + if let lastAppexStart = notificationDebuggingDefaultsDB.lastAppexStart { + Text("Last incoming push: \(String(describing:lastAppexStart))").foregroundColor(.gray).font(.footnote) + } else { + Text("Last incoming push: unknown").foregroundColor(.gray).font(.footnote) + } }.onTapGesture(count: 2, perform: { showPushToken = true }).alert(isPresented: $showPushToken) { diff --git a/Monal/NotificationService/NotificationService.m b/Monal/NotificationService/NotificationService.m index 6bb79c6e57..c25f3052ca 100644 --- a/Monal/NotificationService/NotificationService.m +++ b/Monal/NotificationService/NotificationService.m @@ -441,6 +441,8 @@ +(void) initialize if(warnUnclean) DDLogError(@"detected unclean appex shutdown!"); + [[HelperTools defaultsDB] setObject:[NSDate now] forKey:@"lastAppexStart"]; + //mark this appex as unclean (will be cleared directly before calling exit(0)) [NotificationService setAppexCleanShutdownStatus:NO]; } From 42df817e18812a0e45ef346b776ab0901ab76ea1 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 15 Oct 2024 14:11:44 +0200 Subject: [PATCH 02/18] Improve bare muc handling --- Monal/Classes/MLMessageProcessor.m | 48 +++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index b7059261ed..c2ca3858cf 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -124,7 +124,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag } //ignore messages from our own device, see this github issue: https://github.com/monal-im/Monal/issues/941 - if(![messageNode check:@"/"] && !isMLhistory && [messageNode.from isEqualToString:account.connectionProperties.identity.fullJid] && [messageNode.toUser isEqualToString:account.connectionProperties.identity.jid]) + if(!isMLhistory && [messageNode.from isEqualToString:account.connectionProperties.identity.fullJid] && [messageNode.toUser isEqualToString:account.connectionProperties.identity.jid]) return nil; //handle incoming jmi calls (TODO: add entry to local history, once the UI for this is implemented) @@ -207,8 +207,10 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag //ignore muc PMs (after discussion with holger we don't want to support that) if( - ![messageNode check:@"/"] && [messageNode check:@"{http://jabber.org/protocol/muc#user}x"] && - ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"] && [messageNode check:@"body#"] + ![messageNode check:@"/"] && + ([messageNode check:@"{http://jabber.org/protocol/muc#user}x"] || messageNode.fromResource != nil) && + ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"] && + [messageNode check:@"body#"] ) { DDLogWarn(@"Ignoring muc pm marked as such..."); @@ -242,7 +244,8 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag DDLogVerbose(@"Not a carbon copy of a muc pm for contact: %@", carbonTestContact); } - if(([messageNode check:@"/"] || [messageNode check:@"{http://jabber.org/protocol/muc#user}x"]) && ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"]) + + if((possiblyUnknownContact.isMuc || [messageNode check:@"{http://jabber.org/protocol/muc#user}x"]) && ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"]) { // Ignore all group chat msgs from unkown groups if(![[[DataLayer sharedInstance] listMucsForAccount:account.accountNo] containsObject:messageNode.fromUser]) @@ -272,9 +275,9 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag //check stanza-id @by according to the rules outlined in XEP-0359 if(!stanzaid) { - if(![messageNode check:@"/"] && [messageNode check:@"{urn:xmpp:sid:0}stanza-id", account.connectionProperties.identity.jid]) + if(!possiblyUnknownContact.isMuc && [messageNode check:@"{urn:xmpp:sid:0}stanza-id", account.connectionProperties.identity.jid]) stanzaid = [messageNode findFirst:@"{urn:xmpp:sid:0}stanza-id@id", account.connectionProperties.identity.jid]; - else if([messageNode check:@"/"] && [messageNode check:@"{urn:xmpp:sid:0}stanza-id", messageNode.fromUser] && [[account.mucProcessor getRoomFeaturesForMuc:messageNode.fromUser] containsObject:@"urn:xmpp:sid:0"]) + else if(possiblyUnknownContact.isMuc && [messageNode check:@"{urn:xmpp:sid:0}stanza-id", messageNode.fromUser] && [[account.mucProcessor getRoomFeaturesForMuc:messageNode.fromUser] containsObject:@"urn:xmpp:sid:0"]) stanzaid = [messageNode findFirst:@"{urn:xmpp:sid:0}stanza-id@id", messageNode.fromUser]; } @@ -313,15 +316,35 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag NSString* actualFrom = messageNode.fromUser; NSString* participantJid = nil; NSString* occupantId = nil; - if([messageNode check:@"/"] && messageNode.fromResource) + if(possiblyUnknownContact.isMuc) { - ownNick = [[DataLayer sharedInstance] ownNickNameforMuc:messageNode.fromUser forAccount:account.accountNo]; - actualFrom = messageNode.fromResource; + actualFrom = messageNode.fromResource ?: @""; + + ownNick = [[DataLayer sharedInstance] ownNickNameforMuc:messageNode.fromUser forAccount:account.accountID]; + ownOccupantId = [[DataLayer sharedInstance] getOwnOccupantIdForMuc:messageNode.fromUser onAccountID:account.accountID]; + + //occupant ids are widely supported now and allow us to have a stable identifier of every muc participant, + //even if it is a semi-anonymous channel + if([[account.mucProcessor getRoomFeaturesForMuc:messageNode.fromUser] containsObject:@"urn:xmpp:occupant-id:0"] && [messageNode check:@"{urn:xmpp:occupant-id:0}occupant-id@id"]) + { + occupantId = [messageNode findFirst:@"{urn:xmpp:occupant-id:0}occupant-id@id"]; + NSDictionary* mucParticipant = [[DataLayer sharedInstance] getParticipantForOccupant:occupantId inRoom:messageNode.fromUser forAccountID:account.accountID]; + //we will be able to get to know the real jid, if this is a group or we are the channel admin + participantJid = mucParticipant ? mucParticipant[@"participant_jid"] : nil; + } + //mam catchups will contain a muc#user item listing the jid of the participant //this can't be reconstructed from *current* participant lists because someone new could have taken the same nick //we don't accept this in non-mam context to make sure this can't be spoofed somehow - participantJid = [messageNode findFirst:@"//{http://jabber.org/protocol/muc#user}x/item@jid"]; - if(![outerMessageNode check:@"{urn:xmpp:mam:2}result"] || participantJid == nil) + //we also don't do that, if this was a message from the bare muc jid + //NOTE: this will override the participantJid extracted using the occupantId above, + //NOTE: but those should ALWAYS be the same (that's the exact purpose of occupant ids) + if([outerMessageNode check:@"{urn:xmpp:mam:2}result"] && ![@"" isEqualToString:actualFrom]) + participantJid = [messageNode findFirst:@"{http://jabber.org/protocol/muc#user}x/item@jid"]; + + //try to get the jid of the current participant if the occupant-id based approach above did not work + //but don't do so, if this was a message from the bare muc jid + if(![outerMessageNode check:@"{urn:xmpp:mam:2}result"] && occupantId == nil && participantJid == nil && ![@"" isEqualToString:actualFrom]) { if([[account.mucProcessor getRoomFeaturesForMuc:messageNode.fromUser] containsObject:@"urn:xmpp:occupant-id:0"] && [messageNode check:@"{urn:xmpp:occupant-id:0}occupant-id@id"]) { @@ -567,6 +590,9 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag else if([lowercaseBody hasPrefix:@"https://"]) messageType = kMessageTypeUrl; } + //messages from the bare muc jid are classified as status messages + if(possiblyUnknownContact.isMuc && [@"" isEqualToString:actualFrom]) + messageType = kMessageTypeStatus; DDLogInfo(@"Got message of type: %@", messageType); if(body) From 1081da1acad67fb960b3436fe612bf55018be000 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 16 Oct 2024 19:08:53 +0200 Subject: [PATCH 03/18] Use dedicated resource string for quicksy users --- Monal/Classes/HelperTools.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Monal/Classes/HelperTools.m b/Monal/Classes/HelperTools.m index 6c1fcb886f..aac8558eb3 100644 --- a/Monal/Classes/HelperTools.m +++ b/Monal/Classes/HelperTools.m @@ -2352,8 +2352,12 @@ +(NSString*) encodeRandomResource u_int32_t i=arc4random(); #if TARGET_OS_MACCATALYST NSString* resource = [NSString stringWithFormat:@"Monal-macOS.%@", [self hexadecimalString:[NSData dataWithBytes: &i length: sizeof(i)]]]; +#else +#if IS_QUICKSY + NSString* resource = [NSString stringWithFormat:@"Quicksy-iOS.%@", [self hexadecimalString:[NSData dataWithBytes: &i length: sizeof(i)]]]; #else NSString* resource = [NSString stringWithFormat:@"Monal-iOS.%@", [self hexadecimalString:[NSData dataWithBytes: &i length: sizeof(i)]]]; +#endif #endif return resource; } From 047cd72fb18bc1c0af3be97a024ca038f655b65c Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 19 Oct 2024 18:09:39 +0200 Subject: [PATCH 04/18] Fix pm bug in eb5da94afff8a27546a630988a4a92be2c6da0d6 --- Monal/Classes/MLMessageProcessor.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index c2ca3858cf..ead09d4415 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -208,7 +208,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag //ignore muc PMs (after discussion with holger we don't want to support that) if( ![messageNode check:@"/"] && - ([messageNode check:@"{http://jabber.org/protocol/muc#user}x"] || messageNode.fromResource != nil) && + [messageNode check:@"{http://jabber.org/protocol/muc#user}x"] && ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"] && [messageNode check:@"body#"] ) @@ -245,7 +245,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag } - if((possiblyUnknownContact.isMuc || [messageNode check:@"{http://jabber.org/protocol/muc#user}x"]) && ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"]) + if(([messageNode check:@"/"] || [messageNode check:@"{http://jabber.org/protocol/muc#user}x"]) && ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"]) { // Ignore all group chat msgs from unkown groups if(![[[DataLayer sharedInstance] listMucsForAccount:account.accountNo] containsObject:messageNode.fromUser]) From e688d9effa918c9ae74334bf57ba95bf55f147a0 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 20 Oct 2024 19:36:19 +0200 Subject: [PATCH 05/18] Fix unsolicited settings close --- Monal/Classes/ActiveChatsViewController.h | 2 +- Monal/Classes/ActiveChatsViewController.m | 19 +++++++++++++++---- Monal/Classes/MonalAppDelegate.m | 8 ++++---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Monal/Classes/ActiveChatsViewController.h b/Monal/Classes/ActiveChatsViewController.h index 0cab6291e6..1f24caf553 100644 --- a/Monal/Classes/ActiveChatsViewController.h +++ b/Monal/Classes/ActiveChatsViewController.h @@ -24,8 +24,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) IBOutlet UIBarButtonItem* settingsButton; @property (weak, nonatomic) IBOutlet UIBarButtonItem* spinnerButton; @property (nonatomic, weak) IBOutlet UIBarButtonItem* composeButton; -@property (nonatomic, strong) chatViewController* currentChatViewController; @property (nonatomic, strong) UIActivityIndicatorView* spinner; +@property (atomic, readonly) chatViewController* _Nullable currentChatView; -(void) showCallContactNotFoundAlert:(NSString*) jid; -(void) callContact:(MLContact*) contact withUIKitSender:(_Nullable id) sender; diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index b9628b7f1a..31bb7ec756 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -361,13 +361,13 @@ -(void) refreshDisplay dispatch_async(dispatch_get_main_queue(), ^{ //make sure we don't display a chat view for a disabled account - if(self.currentChatViewController != nil && self.currentChatViewController.contact != nil) + if([MLNotificationManager sharedInstance].currentContact != nil) { BOOL found = NO; for(NSDictionary* accountDict in [[DataLayer sharedInstance] enabledAccountList]) { - NSNumber* accountNo = accountDict[kAccountID]; - if(self.currentChatViewController.contact.accountId.intValue == accountNo.intValue) + NSNumber* accountID = accountDict[kAccountID]; + if([MLNotificationManager sharedInstance].currentContact.accountID.intValue == accountID.intValue) found = YES; } if(!found) @@ -879,6 +879,7 @@ -(void) presentSplitPlaceholder UIViewController* detailsViewController = [[SwiftuiInterface new] makeViewWithName:@"ChatPlaceholder"]; [self showDetailViewController:detailsViewController sender:self]; } + [MLNotificationManager sharedInstance].currentContact = nil; } -(void) showNotificationSettings @@ -1094,7 +1095,6 @@ -(void) prepareForSegue:(UIStoryboardSegue*) segue sender:(id) sender UIBarButtonItem* barButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; self.navigationItem.backBarButtonItem = barButtonItem; [chatVC setupWithContact:sender]; - self.currentChatViewController = chatVC; } else if([segue.identifier isEqualToString:@"showDetails"]) { @@ -1513,6 +1513,17 @@ -(void) dismissRecursorWithViewControllers:(NSMutableArray*) viewControllers ani } } +-(chatViewController* _Nullable) currentChatView +{ + NSArray* controllers = ((UINavigationController*)self.splitViewController.viewControllers[0]).viewControllers; + chatViewController* chatView = nil; + if(controllers.count > 1) + chatView = [((UINavigationController*)controllers[1]).viewControllers firstObject]; + if(![chatView isKindOfClass:NSClassFromString(@"chatViewController")]) + chatView = nil; + return chatView; +} + -(void) scrollToContact:(MLContact*) contact { __block NSIndexPath* indexPath = nil; diff --git a/Monal/Classes/MonalAppDelegate.m b/Monal/Classes/MonalAppDelegate.m index a32ddbb052..7769e3ac82 100644 --- a/Monal/Classes/MonalAppDelegate.m +++ b/Monal/Classes/MonalAppDelegate.m @@ -1972,10 +1972,10 @@ -(void) sendAllOutboxes monal_id_block_t cleanup = ^(NSDictionary* payload) { [[DataLayer sharedInstance] deleteShareSheetPayloadWithId:payload[@"id"]]; [[MLNotificationQueue currentQueue] postNotificationName:kMonalRefresh object:nil userInfo:nil]; - if(self.activeChats.currentChatViewController != nil) + if(self.activeChats.currentChatView != nil) { - [self.activeChats.currentChatViewController scrollToBottomAnimated:NO]; - [self.activeChats.currentChatViewController hideUploadHUD]; + [self.activeChats.currentChatView scrollToBottomAnimated:NO]; + [self.activeChats.currentChatView hideUploadHUD]; } //send next item (if there is one left) [self sendAllOutboxes]; @@ -2007,7 +2007,7 @@ -(void) sendAllOutboxes else if([payload[@"type"] isEqualToString:@"image"] || [payload[@"type"] isEqualToString:@"file"] || [payload[@"type"] isEqualToString:@"contact"] || [payload[@"type"] isEqualToString:@"audiovisual"]) { DDLogInfo(@"Got %@ upload: %@", payload[@"type"], payload[@"data"]); - [self.activeChats.currentChatViewController showUploadHUD]; + [self.activeChats.currentChatView showUploadHUD]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ $call(payload[@"data"], $ID(account), $BOOL(encrypted), $ID(completion, (^(NSString* url, NSString* mimeType, NSNumber* size, NSError* error) { dispatch_async(dispatch_get_main_queue(), ^{ From 967cf602961e59459fab91b2c7685cdd841ffe80 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 2 Nov 2024 09:11:40 +0100 Subject: [PATCH 06/18] Fix bug in MLStream on slow connections --- Monal/Classes/MLStream.m | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Monal/Classes/MLStream.m b/Monal/Classes/MLStream.m index 1070be770b..b38cce82a9 100644 --- a/Monal/Classes/MLStream.m +++ b/Monal/Classes/MLStream.m @@ -50,7 +50,7 @@ @interface MLInputStream() //(mutexes can not be unlocked in a thread different from the one it got locked in and NSLock internally uses mutext --> both can not be used) dispatch_semaphore_t _read_sem; } -@property (atomic, readonly) void (^incoming_data_handler)(NSData* _Nullable, BOOL, NSError* _Nullable, BOOL allow_next_read); +@property (atomic, readonly) void (^incoming_data_handler)(NSData* _Nullable, BOOL, NSError* _Nullable); @end @interface MLOutputStream() @@ -89,7 +89,7 @@ -(instancetype) initWithSharedState:(MLSharedStreamState*) shared //this handler will be called by the schedule_read method //since the framer swallows all data, nw_connection_receive() and the framer cannot race against each other and deliver reordered data weakify(self); - _incoming_data_handler = ^(NSData* _Nullable content, BOOL is_complete, NSError* _Nullable st_error, BOOL allow_next_read) { + _incoming_data_handler = ^(NSData* _Nullable content, BOOL is_complete, NSError* _Nullable st_error) { strongify(self); if(self == nil) return; @@ -142,7 +142,7 @@ -(instancetype) initWithSharedState:(MLSharedStreamState*) shared [self generateEvent:NSStreamEventEndEncountered]; //try to read again - if(!is_complete && !generate_bytes_available_event && allow_next_read) + if(!is_complete && !generate_bytes_available_event) [self schedule_read]; }; return self; @@ -235,8 +235,7 @@ -(void) schedule_read DDLogDebug(@"now calling nw_framer_parse_input inside framer queue"); nw_framer_parse_input(self.shared_state.framer, 1, BUFFER_SIZE, nil, ^size_t(uint8_t* buffer, size_t buffer_length, bool is_complete) { DDLogDebug(@"nw_framer_parse_input got callback with is_complete:%@, length=%zu", bool2str(is_complete), (unsigned long)buffer_length); - //we only want to allow new calls to schedule_read if we received some data --> set last arg accordingly - self.incoming_data_handler([NSData dataWithBytes:buffer length:buffer_length], is_complete, nil, buffer_length > 0); + self.incoming_data_handler([NSData dataWithBytes:buffer length:buffer_length], is_complete, nil); return buffer_length; }); }); @@ -249,8 +248,7 @@ -(void) schedule_read NSError* st_error = nil; if(receive_error) st_error = (NSError*)CFBridgingRelease(nw_error_copy_cf_error(receive_error)); - //we always want to allow new calls to schedule_read --> set last arg to YES - self.incoming_data_handler((NSData*)content, is_complete, st_error, YES); + self.incoming_data_handler((NSData*)content, is_complete, st_error); }); } } From 530f34d88a9564f2a84dc0b6da9c82828324f5f0 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Fri, 8 Nov 2024 04:41:37 +0100 Subject: [PATCH 07/18] Fix bogus assertion (possibly some race condition) --- Monal/Classes/chatViewController.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/chatViewController.m b/Monal/Classes/chatViewController.m index 8a689dbe58..b9ce268932 100644 --- a/Monal/Classes/chatViewController.m +++ b/Monal/Classes/chatViewController.m @@ -3560,7 +3560,8 @@ -(NSInteger)collectionView:(nonnull UICollectionView*) collectionView numberOfIt -(void) notifyUploadQueueRemoval:(NSUInteger) index { - MLAssert(index < self.uploadQueue.count, @"index is only allowed to be smaller than uploadQueue.count"); + if(index >= self.uploadQueue.count) + return; [self.uploadMenuView performBatchUpdates:^{ [self deleteQueueItemAtIndex:index]; } completion:^(BOOL finished) { From daf055a320f48de16c4913832e94d18950c92044 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 9 Nov 2024 03:45:38 +0100 Subject: [PATCH 08/18] Fix crash in MLDelayableTimer when calling invalidate Calling the invalidate method from some other thread than the thread which added the timer to our runloop, will crash. --> Add and remove the timer to/from the runloop by executing a block in exactly this runloop. --- Monal/Classes/MLDelayableTimer.m | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MLDelayableTimer.m b/Monal/Classes/MLDelayableTimer.m index bf3ed5e691..ad7dceefca 100644 --- a/Monal/Classes/MLDelayableTimer.m +++ b/Monal/Classes/MLDelayableTimer.m @@ -52,7 +52,10 @@ -(void) start return; } DDLogDebug(@"Starting timer: %@", self); - [[HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierTimer] addTimer:_wrappedTimer forMode:NSRunLoopCommonModes]; + //scheduling and unscheduling of a timer must be done from the same thread --> use our runloop + [self scheduleBlockInRunLoop:^{ + [[HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierTimer] addTimer:self->_wrappedTimer forMode:NSRunLoopCommonModes]; + }]; } } @@ -131,8 +134,28 @@ -(void) invalidate return; } //DDLogVerbose(@"Invalidating timer: %@", self); - [_wrappedTimer invalidate]; + //scheduling and unscheduling of a timer must be done from the same thread --> use our runloop + [self scheduleBlockInRunLoop:^{ + [self->_wrappedTimer invalidate]; + }]; } } +-(void) scheduleBlockInRunLoop:(monal_void_block_t) block +{ + NSRunLoop* runLoop = [HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierTimer]; +// NSCondition* condition = [NSCondition new]; +// [condition lock]; + CFRunLoopPerformBlock([runLoop getCFRunLoop], (__bridge CFStringRef)NSDefaultRunLoopMode, ^{ + block(); +// [condition lock]; +// [condition signal]; +// [condition unlock]; + }); + CFRunLoopWakeUp([runLoop getCFRunLoop]); //trigger wakeup of runloop to execute the block as soon as possible +// //wait for our block to finish executing +// [condition wait]; +// [condition unlock]; +} + @end From 25350649bba61ce1f0e29c7fd7fbad451c00db99 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 16 Oct 2024 19:08:53 +0200 Subject: [PATCH 09/18] Use dedicated resource string for quicksy users --- Monal/Classes/HelperTools.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Monal/Classes/HelperTools.m b/Monal/Classes/HelperTools.m index 6c1fcb886f..aac8558eb3 100644 --- a/Monal/Classes/HelperTools.m +++ b/Monal/Classes/HelperTools.m @@ -2352,8 +2352,12 @@ +(NSString*) encodeRandomResource u_int32_t i=arc4random(); #if TARGET_OS_MACCATALYST NSString* resource = [NSString stringWithFormat:@"Monal-macOS.%@", [self hexadecimalString:[NSData dataWithBytes: &i length: sizeof(i)]]]; +#else +#if IS_QUICKSY + NSString* resource = [NSString stringWithFormat:@"Quicksy-iOS.%@", [self hexadecimalString:[NSData dataWithBytes: &i length: sizeof(i)]]]; #else NSString* resource = [NSString stringWithFormat:@"Monal-iOS.%@", [self hexadecimalString:[NSData dataWithBytes: &i length: sizeof(i)]]]; +#endif #endif return resource; } From 55ae57a25e4adef28e20609c8f2b05046253cd60 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 20 Oct 2024 19:36:19 +0200 Subject: [PATCH 10/18] Fix unsolicited settings close --- Monal/Classes/ActiveChatsViewController.h | 2 +- Monal/Classes/ActiveChatsViewController.m | 19 +++++++++++++++---- Monal/Classes/MonalAppDelegate.m | 8 ++++---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Monal/Classes/ActiveChatsViewController.h b/Monal/Classes/ActiveChatsViewController.h index 0cab6291e6..1f24caf553 100644 --- a/Monal/Classes/ActiveChatsViewController.h +++ b/Monal/Classes/ActiveChatsViewController.h @@ -24,8 +24,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) IBOutlet UIBarButtonItem* settingsButton; @property (weak, nonatomic) IBOutlet UIBarButtonItem* spinnerButton; @property (nonatomic, weak) IBOutlet UIBarButtonItem* composeButton; -@property (nonatomic, strong) chatViewController* currentChatViewController; @property (nonatomic, strong) UIActivityIndicatorView* spinner; +@property (atomic, readonly) chatViewController* _Nullable currentChatView; -(void) showCallContactNotFoundAlert:(NSString*) jid; -(void) callContact:(MLContact*) contact withUIKitSender:(_Nullable id) sender; diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index b9628b7f1a..31bb7ec756 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -361,13 +361,13 @@ -(void) refreshDisplay dispatch_async(dispatch_get_main_queue(), ^{ //make sure we don't display a chat view for a disabled account - if(self.currentChatViewController != nil && self.currentChatViewController.contact != nil) + if([MLNotificationManager sharedInstance].currentContact != nil) { BOOL found = NO; for(NSDictionary* accountDict in [[DataLayer sharedInstance] enabledAccountList]) { - NSNumber* accountNo = accountDict[kAccountID]; - if(self.currentChatViewController.contact.accountId.intValue == accountNo.intValue) + NSNumber* accountID = accountDict[kAccountID]; + if([MLNotificationManager sharedInstance].currentContact.accountID.intValue == accountID.intValue) found = YES; } if(!found) @@ -879,6 +879,7 @@ -(void) presentSplitPlaceholder UIViewController* detailsViewController = [[SwiftuiInterface new] makeViewWithName:@"ChatPlaceholder"]; [self showDetailViewController:detailsViewController sender:self]; } + [MLNotificationManager sharedInstance].currentContact = nil; } -(void) showNotificationSettings @@ -1094,7 +1095,6 @@ -(void) prepareForSegue:(UIStoryboardSegue*) segue sender:(id) sender UIBarButtonItem* barButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; self.navigationItem.backBarButtonItem = barButtonItem; [chatVC setupWithContact:sender]; - self.currentChatViewController = chatVC; } else if([segue.identifier isEqualToString:@"showDetails"]) { @@ -1513,6 +1513,17 @@ -(void) dismissRecursorWithViewControllers:(NSMutableArray*) viewControllers ani } } +-(chatViewController* _Nullable) currentChatView +{ + NSArray* controllers = ((UINavigationController*)self.splitViewController.viewControllers[0]).viewControllers; + chatViewController* chatView = nil; + if(controllers.count > 1) + chatView = [((UINavigationController*)controllers[1]).viewControllers firstObject]; + if(![chatView isKindOfClass:NSClassFromString(@"chatViewController")]) + chatView = nil; + return chatView; +} + -(void) scrollToContact:(MLContact*) contact { __block NSIndexPath* indexPath = nil; diff --git a/Monal/Classes/MonalAppDelegate.m b/Monal/Classes/MonalAppDelegate.m index a32ddbb052..7769e3ac82 100644 --- a/Monal/Classes/MonalAppDelegate.m +++ b/Monal/Classes/MonalAppDelegate.m @@ -1972,10 +1972,10 @@ -(void) sendAllOutboxes monal_id_block_t cleanup = ^(NSDictionary* payload) { [[DataLayer sharedInstance] deleteShareSheetPayloadWithId:payload[@"id"]]; [[MLNotificationQueue currentQueue] postNotificationName:kMonalRefresh object:nil userInfo:nil]; - if(self.activeChats.currentChatViewController != nil) + if(self.activeChats.currentChatView != nil) { - [self.activeChats.currentChatViewController scrollToBottomAnimated:NO]; - [self.activeChats.currentChatViewController hideUploadHUD]; + [self.activeChats.currentChatView scrollToBottomAnimated:NO]; + [self.activeChats.currentChatView hideUploadHUD]; } //send next item (if there is one left) [self sendAllOutboxes]; @@ -2007,7 +2007,7 @@ -(void) sendAllOutboxes else if([payload[@"type"] isEqualToString:@"image"] || [payload[@"type"] isEqualToString:@"file"] || [payload[@"type"] isEqualToString:@"contact"] || [payload[@"type"] isEqualToString:@"audiovisual"]) { DDLogInfo(@"Got %@ upload: %@", payload[@"type"], payload[@"data"]); - [self.activeChats.currentChatViewController showUploadHUD]; + [self.activeChats.currentChatView showUploadHUD]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ $call(payload[@"data"], $ID(account), $BOOL(encrypted), $ID(completion, (^(NSString* url, NSString* mimeType, NSNumber* size, NSError* error) { dispatch_async(dispatch_get_main_queue(), ^{ From 22ee93ad4dcae3ce0975bdb50787fb4f89781fc2 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 2 Nov 2024 09:11:40 +0100 Subject: [PATCH 11/18] Fix bug in MLStream on slow connections --- Monal/Classes/MLStream.m | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Monal/Classes/MLStream.m b/Monal/Classes/MLStream.m index 1070be770b..b38cce82a9 100644 --- a/Monal/Classes/MLStream.m +++ b/Monal/Classes/MLStream.m @@ -50,7 +50,7 @@ @interface MLInputStream() //(mutexes can not be unlocked in a thread different from the one it got locked in and NSLock internally uses mutext --> both can not be used) dispatch_semaphore_t _read_sem; } -@property (atomic, readonly) void (^incoming_data_handler)(NSData* _Nullable, BOOL, NSError* _Nullable, BOOL allow_next_read); +@property (atomic, readonly) void (^incoming_data_handler)(NSData* _Nullable, BOOL, NSError* _Nullable); @end @interface MLOutputStream() @@ -89,7 +89,7 @@ -(instancetype) initWithSharedState:(MLSharedStreamState*) shared //this handler will be called by the schedule_read method //since the framer swallows all data, nw_connection_receive() and the framer cannot race against each other and deliver reordered data weakify(self); - _incoming_data_handler = ^(NSData* _Nullable content, BOOL is_complete, NSError* _Nullable st_error, BOOL allow_next_read) { + _incoming_data_handler = ^(NSData* _Nullable content, BOOL is_complete, NSError* _Nullable st_error) { strongify(self); if(self == nil) return; @@ -142,7 +142,7 @@ -(instancetype) initWithSharedState:(MLSharedStreamState*) shared [self generateEvent:NSStreamEventEndEncountered]; //try to read again - if(!is_complete && !generate_bytes_available_event && allow_next_read) + if(!is_complete && !generate_bytes_available_event) [self schedule_read]; }; return self; @@ -235,8 +235,7 @@ -(void) schedule_read DDLogDebug(@"now calling nw_framer_parse_input inside framer queue"); nw_framer_parse_input(self.shared_state.framer, 1, BUFFER_SIZE, nil, ^size_t(uint8_t* buffer, size_t buffer_length, bool is_complete) { DDLogDebug(@"nw_framer_parse_input got callback with is_complete:%@, length=%zu", bool2str(is_complete), (unsigned long)buffer_length); - //we only want to allow new calls to schedule_read if we received some data --> set last arg accordingly - self.incoming_data_handler([NSData dataWithBytes:buffer length:buffer_length], is_complete, nil, buffer_length > 0); + self.incoming_data_handler([NSData dataWithBytes:buffer length:buffer_length], is_complete, nil); return buffer_length; }); }); @@ -249,8 +248,7 @@ -(void) schedule_read NSError* st_error = nil; if(receive_error) st_error = (NSError*)CFBridgingRelease(nw_error_copy_cf_error(receive_error)); - //we always want to allow new calls to schedule_read --> set last arg to YES - self.incoming_data_handler((NSData*)content, is_complete, st_error, YES); + self.incoming_data_handler((NSData*)content, is_complete, st_error); }); } } From 05e87e68a64b40e96712945b135a2ec7fe0a58b9 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Fri, 8 Nov 2024 04:41:37 +0100 Subject: [PATCH 12/18] Fix bogus assertion (possibly some race condition) --- Monal/Classes/chatViewController.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/chatViewController.m b/Monal/Classes/chatViewController.m index 8a689dbe58..b9ce268932 100644 --- a/Monal/Classes/chatViewController.m +++ b/Monal/Classes/chatViewController.m @@ -3560,7 +3560,8 @@ -(NSInteger)collectionView:(nonnull UICollectionView*) collectionView numberOfIt -(void) notifyUploadQueueRemoval:(NSUInteger) index { - MLAssert(index < self.uploadQueue.count, @"index is only allowed to be smaller than uploadQueue.count"); + if(index >= self.uploadQueue.count) + return; [self.uploadMenuView performBatchUpdates:^{ [self deleteQueueItemAtIndex:index]; } completion:^(BOOL finished) { From 306a44fc2af171cd627dddd8400268a7b974601b Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 9 Nov 2024 03:45:38 +0100 Subject: [PATCH 13/18] Fix crash in MLDelayableTimer when calling invalidate Calling the invalidate method from some other thread than the thread which added the timer to our runloop, will crash. --> Add and remove the timer to/from the runloop by executing a block in exactly this runloop. --- Monal/Classes/MLDelayableTimer.m | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MLDelayableTimer.m b/Monal/Classes/MLDelayableTimer.m index bf3ed5e691..ad7dceefca 100644 --- a/Monal/Classes/MLDelayableTimer.m +++ b/Monal/Classes/MLDelayableTimer.m @@ -52,7 +52,10 @@ -(void) start return; } DDLogDebug(@"Starting timer: %@", self); - [[HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierTimer] addTimer:_wrappedTimer forMode:NSRunLoopCommonModes]; + //scheduling and unscheduling of a timer must be done from the same thread --> use our runloop + [self scheduleBlockInRunLoop:^{ + [[HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierTimer] addTimer:self->_wrappedTimer forMode:NSRunLoopCommonModes]; + }]; } } @@ -131,8 +134,28 @@ -(void) invalidate return; } //DDLogVerbose(@"Invalidating timer: %@", self); - [_wrappedTimer invalidate]; + //scheduling and unscheduling of a timer must be done from the same thread --> use our runloop + [self scheduleBlockInRunLoop:^{ + [self->_wrappedTimer invalidate]; + }]; } } +-(void) scheduleBlockInRunLoop:(monal_void_block_t) block +{ + NSRunLoop* runLoop = [HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierTimer]; +// NSCondition* condition = [NSCondition new]; +// [condition lock]; + CFRunLoopPerformBlock([runLoop getCFRunLoop], (__bridge CFStringRef)NSDefaultRunLoopMode, ^{ + block(); +// [condition lock]; +// [condition signal]; +// [condition unlock]; + }); + CFRunLoopWakeUp([runLoop getCFRunLoop]); //trigger wakeup of runloop to execute the block as soon as possible +// //wait for our block to finish executing +// [condition wait]; +// [condition unlock]; +} + @end From 68d30293ff43942113176078809aeed08813c7b3 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 9 Nov 2024 04:40:19 +0100 Subject: [PATCH 14/18] Revert "6.4.6-rc1" --- Monal/Classes/ActiveChatsViewController.h | 2 +- Monal/Classes/ActiveChatsViewController.m | 19 ++------ Monal/Classes/HelperTools.m | 4 -- Monal/Classes/MLDelayableTimer.m | 27 +---------- Monal/Classes/MLMessageProcessor.m | 46 ++++--------------- Monal/Classes/MLStream.m | 12 +++-- Monal/Classes/MonalAppDelegate.m | 8 ++-- Monal/Classes/NotificationDebugging.swift | 14 +----- Monal/Classes/chatViewController.m | 3 +- .../NotificationService/NotificationService.m | 2 - 10 files changed, 30 insertions(+), 107 deletions(-) diff --git a/Monal/Classes/ActiveChatsViewController.h b/Monal/Classes/ActiveChatsViewController.h index 1f24caf553..0cab6291e6 100644 --- a/Monal/Classes/ActiveChatsViewController.h +++ b/Monal/Classes/ActiveChatsViewController.h @@ -24,8 +24,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) IBOutlet UIBarButtonItem* settingsButton; @property (weak, nonatomic) IBOutlet UIBarButtonItem* spinnerButton; @property (nonatomic, weak) IBOutlet UIBarButtonItem* composeButton; +@property (nonatomic, strong) chatViewController* currentChatViewController; @property (nonatomic, strong) UIActivityIndicatorView* spinner; -@property (atomic, readonly) chatViewController* _Nullable currentChatView; -(void) showCallContactNotFoundAlert:(NSString*) jid; -(void) callContact:(MLContact*) contact withUIKitSender:(_Nullable id) sender; diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index 31bb7ec756..b9628b7f1a 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -361,13 +361,13 @@ -(void) refreshDisplay dispatch_async(dispatch_get_main_queue(), ^{ //make sure we don't display a chat view for a disabled account - if([MLNotificationManager sharedInstance].currentContact != nil) + if(self.currentChatViewController != nil && self.currentChatViewController.contact != nil) { BOOL found = NO; for(NSDictionary* accountDict in [[DataLayer sharedInstance] enabledAccountList]) { - NSNumber* accountID = accountDict[kAccountID]; - if([MLNotificationManager sharedInstance].currentContact.accountID.intValue == accountID.intValue) + NSNumber* accountNo = accountDict[kAccountID]; + if(self.currentChatViewController.contact.accountId.intValue == accountNo.intValue) found = YES; } if(!found) @@ -879,7 +879,6 @@ -(void) presentSplitPlaceholder UIViewController* detailsViewController = [[SwiftuiInterface new] makeViewWithName:@"ChatPlaceholder"]; [self showDetailViewController:detailsViewController sender:self]; } - [MLNotificationManager sharedInstance].currentContact = nil; } -(void) showNotificationSettings @@ -1095,6 +1094,7 @@ -(void) prepareForSegue:(UIStoryboardSegue*) segue sender:(id) sender UIBarButtonItem* barButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; self.navigationItem.backBarButtonItem = barButtonItem; [chatVC setupWithContact:sender]; + self.currentChatViewController = chatVC; } else if([segue.identifier isEqualToString:@"showDetails"]) { @@ -1513,17 +1513,6 @@ -(void) dismissRecursorWithViewControllers:(NSMutableArray*) viewControllers ani } } --(chatViewController* _Nullable) currentChatView -{ - NSArray* controllers = ((UINavigationController*)self.splitViewController.viewControllers[0]).viewControllers; - chatViewController* chatView = nil; - if(controllers.count > 1) - chatView = [((UINavigationController*)controllers[1]).viewControllers firstObject]; - if(![chatView isKindOfClass:NSClassFromString(@"chatViewController")]) - chatView = nil; - return chatView; -} - -(void) scrollToContact:(MLContact*) contact { __block NSIndexPath* indexPath = nil; diff --git a/Monal/Classes/HelperTools.m b/Monal/Classes/HelperTools.m index aac8558eb3..6c1fcb886f 100644 --- a/Monal/Classes/HelperTools.m +++ b/Monal/Classes/HelperTools.m @@ -2352,12 +2352,8 @@ +(NSString*) encodeRandomResource u_int32_t i=arc4random(); #if TARGET_OS_MACCATALYST NSString* resource = [NSString stringWithFormat:@"Monal-macOS.%@", [self hexadecimalString:[NSData dataWithBytes: &i length: sizeof(i)]]]; -#else -#if IS_QUICKSY - NSString* resource = [NSString stringWithFormat:@"Quicksy-iOS.%@", [self hexadecimalString:[NSData dataWithBytes: &i length: sizeof(i)]]]; #else NSString* resource = [NSString stringWithFormat:@"Monal-iOS.%@", [self hexadecimalString:[NSData dataWithBytes: &i length: sizeof(i)]]]; -#endif #endif return resource; } diff --git a/Monal/Classes/MLDelayableTimer.m b/Monal/Classes/MLDelayableTimer.m index ad7dceefca..bf3ed5e691 100644 --- a/Monal/Classes/MLDelayableTimer.m +++ b/Monal/Classes/MLDelayableTimer.m @@ -52,10 +52,7 @@ -(void) start return; } DDLogDebug(@"Starting timer: %@", self); - //scheduling and unscheduling of a timer must be done from the same thread --> use our runloop - [self scheduleBlockInRunLoop:^{ - [[HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierTimer] addTimer:self->_wrappedTimer forMode:NSRunLoopCommonModes]; - }]; + [[HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierTimer] addTimer:_wrappedTimer forMode:NSRunLoopCommonModes]; } } @@ -134,28 +131,8 @@ -(void) invalidate return; } //DDLogVerbose(@"Invalidating timer: %@", self); - //scheduling and unscheduling of a timer must be done from the same thread --> use our runloop - [self scheduleBlockInRunLoop:^{ - [self->_wrappedTimer invalidate]; - }]; + [_wrappedTimer invalidate]; } } --(void) scheduleBlockInRunLoop:(monal_void_block_t) block -{ - NSRunLoop* runLoop = [HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierTimer]; -// NSCondition* condition = [NSCondition new]; -// [condition lock]; - CFRunLoopPerformBlock([runLoop getCFRunLoop], (__bridge CFStringRef)NSDefaultRunLoopMode, ^{ - block(); -// [condition lock]; -// [condition signal]; -// [condition unlock]; - }); - CFRunLoopWakeUp([runLoop getCFRunLoop]); //trigger wakeup of runloop to execute the block as soon as possible -// //wait for our block to finish executing -// [condition wait]; -// [condition unlock]; -} - @end diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index ead09d4415..b7059261ed 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -124,7 +124,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag } //ignore messages from our own device, see this github issue: https://github.com/monal-im/Monal/issues/941 - if(!isMLhistory && [messageNode.from isEqualToString:account.connectionProperties.identity.fullJid] && [messageNode.toUser isEqualToString:account.connectionProperties.identity.jid]) + if(![messageNode check:@"/"] && !isMLhistory && [messageNode.from isEqualToString:account.connectionProperties.identity.fullJid] && [messageNode.toUser isEqualToString:account.connectionProperties.identity.jid]) return nil; //handle incoming jmi calls (TODO: add entry to local history, once the UI for this is implemented) @@ -207,10 +207,8 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag //ignore muc PMs (after discussion with holger we don't want to support that) if( - ![messageNode check:@"/"] && - [messageNode check:@"{http://jabber.org/protocol/muc#user}x"] && - ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"] && - [messageNode check:@"body#"] + ![messageNode check:@"/"] && [messageNode check:@"{http://jabber.org/protocol/muc#user}x"] && + ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"] && [messageNode check:@"body#"] ) { DDLogWarn(@"Ignoring muc pm marked as such..."); @@ -244,7 +242,6 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag DDLogVerbose(@"Not a carbon copy of a muc pm for contact: %@", carbonTestContact); } - if(([messageNode check:@"/"] || [messageNode check:@"{http://jabber.org/protocol/muc#user}x"]) && ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"]) { // Ignore all group chat msgs from unkown groups @@ -275,9 +272,9 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag //check stanza-id @by according to the rules outlined in XEP-0359 if(!stanzaid) { - if(!possiblyUnknownContact.isMuc && [messageNode check:@"{urn:xmpp:sid:0}stanza-id", account.connectionProperties.identity.jid]) + if(![messageNode check:@"/"] && [messageNode check:@"{urn:xmpp:sid:0}stanza-id", account.connectionProperties.identity.jid]) stanzaid = [messageNode findFirst:@"{urn:xmpp:sid:0}stanza-id@id", account.connectionProperties.identity.jid]; - else if(possiblyUnknownContact.isMuc && [messageNode check:@"{urn:xmpp:sid:0}stanza-id", messageNode.fromUser] && [[account.mucProcessor getRoomFeaturesForMuc:messageNode.fromUser] containsObject:@"urn:xmpp:sid:0"]) + else if([messageNode check:@"/"] && [messageNode check:@"{urn:xmpp:sid:0}stanza-id", messageNode.fromUser] && [[account.mucProcessor getRoomFeaturesForMuc:messageNode.fromUser] containsObject:@"urn:xmpp:sid:0"]) stanzaid = [messageNode findFirst:@"{urn:xmpp:sid:0}stanza-id@id", messageNode.fromUser]; } @@ -316,35 +313,15 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag NSString* actualFrom = messageNode.fromUser; NSString* participantJid = nil; NSString* occupantId = nil; - if(possiblyUnknownContact.isMuc) + if([messageNode check:@"/"] && messageNode.fromResource) { - actualFrom = messageNode.fromResource ?: @""; - - ownNick = [[DataLayer sharedInstance] ownNickNameforMuc:messageNode.fromUser forAccount:account.accountID]; - ownOccupantId = [[DataLayer sharedInstance] getOwnOccupantIdForMuc:messageNode.fromUser onAccountID:account.accountID]; - - //occupant ids are widely supported now and allow us to have a stable identifier of every muc participant, - //even if it is a semi-anonymous channel - if([[account.mucProcessor getRoomFeaturesForMuc:messageNode.fromUser] containsObject:@"urn:xmpp:occupant-id:0"] && [messageNode check:@"{urn:xmpp:occupant-id:0}occupant-id@id"]) - { - occupantId = [messageNode findFirst:@"{urn:xmpp:occupant-id:0}occupant-id@id"]; - NSDictionary* mucParticipant = [[DataLayer sharedInstance] getParticipantForOccupant:occupantId inRoom:messageNode.fromUser forAccountID:account.accountID]; - //we will be able to get to know the real jid, if this is a group or we are the channel admin - participantJid = mucParticipant ? mucParticipant[@"participant_jid"] : nil; - } - + ownNick = [[DataLayer sharedInstance] ownNickNameforMuc:messageNode.fromUser forAccount:account.accountNo]; + actualFrom = messageNode.fromResource; //mam catchups will contain a muc#user item listing the jid of the participant //this can't be reconstructed from *current* participant lists because someone new could have taken the same nick //we don't accept this in non-mam context to make sure this can't be spoofed somehow - //we also don't do that, if this was a message from the bare muc jid - //NOTE: this will override the participantJid extracted using the occupantId above, - //NOTE: but those should ALWAYS be the same (that's the exact purpose of occupant ids) - if([outerMessageNode check:@"{urn:xmpp:mam:2}result"] && ![@"" isEqualToString:actualFrom]) - participantJid = [messageNode findFirst:@"{http://jabber.org/protocol/muc#user}x/item@jid"]; - - //try to get the jid of the current participant if the occupant-id based approach above did not work - //but don't do so, if this was a message from the bare muc jid - if(![outerMessageNode check:@"{urn:xmpp:mam:2}result"] && occupantId == nil && participantJid == nil && ![@"" isEqualToString:actualFrom]) + participantJid = [messageNode findFirst:@"//{http://jabber.org/protocol/muc#user}x/item@jid"]; + if(![outerMessageNode check:@"{urn:xmpp:mam:2}result"] || participantJid == nil) { if([[account.mucProcessor getRoomFeaturesForMuc:messageNode.fromUser] containsObject:@"urn:xmpp:occupant-id:0"] && [messageNode check:@"{urn:xmpp:occupant-id:0}occupant-id@id"]) { @@ -590,9 +567,6 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag else if([lowercaseBody hasPrefix:@"https://"]) messageType = kMessageTypeUrl; } - //messages from the bare muc jid are classified as status messages - if(possiblyUnknownContact.isMuc && [@"" isEqualToString:actualFrom]) - messageType = kMessageTypeStatus; DDLogInfo(@"Got message of type: %@", messageType); if(body) diff --git a/Monal/Classes/MLStream.m b/Monal/Classes/MLStream.m index b38cce82a9..1070be770b 100644 --- a/Monal/Classes/MLStream.m +++ b/Monal/Classes/MLStream.m @@ -50,7 +50,7 @@ @interface MLInputStream() //(mutexes can not be unlocked in a thread different from the one it got locked in and NSLock internally uses mutext --> both can not be used) dispatch_semaphore_t _read_sem; } -@property (atomic, readonly) void (^incoming_data_handler)(NSData* _Nullable, BOOL, NSError* _Nullable); +@property (atomic, readonly) void (^incoming_data_handler)(NSData* _Nullable, BOOL, NSError* _Nullable, BOOL allow_next_read); @end @interface MLOutputStream() @@ -89,7 +89,7 @@ -(instancetype) initWithSharedState:(MLSharedStreamState*) shared //this handler will be called by the schedule_read method //since the framer swallows all data, nw_connection_receive() and the framer cannot race against each other and deliver reordered data weakify(self); - _incoming_data_handler = ^(NSData* _Nullable content, BOOL is_complete, NSError* _Nullable st_error) { + _incoming_data_handler = ^(NSData* _Nullable content, BOOL is_complete, NSError* _Nullable st_error, BOOL allow_next_read) { strongify(self); if(self == nil) return; @@ -142,7 +142,7 @@ -(instancetype) initWithSharedState:(MLSharedStreamState*) shared [self generateEvent:NSStreamEventEndEncountered]; //try to read again - if(!is_complete && !generate_bytes_available_event) + if(!is_complete && !generate_bytes_available_event && allow_next_read) [self schedule_read]; }; return self; @@ -235,7 +235,8 @@ -(void) schedule_read DDLogDebug(@"now calling nw_framer_parse_input inside framer queue"); nw_framer_parse_input(self.shared_state.framer, 1, BUFFER_SIZE, nil, ^size_t(uint8_t* buffer, size_t buffer_length, bool is_complete) { DDLogDebug(@"nw_framer_parse_input got callback with is_complete:%@, length=%zu", bool2str(is_complete), (unsigned long)buffer_length); - self.incoming_data_handler([NSData dataWithBytes:buffer length:buffer_length], is_complete, nil); + //we only want to allow new calls to schedule_read if we received some data --> set last arg accordingly + self.incoming_data_handler([NSData dataWithBytes:buffer length:buffer_length], is_complete, nil, buffer_length > 0); return buffer_length; }); }); @@ -248,7 +249,8 @@ -(void) schedule_read NSError* st_error = nil; if(receive_error) st_error = (NSError*)CFBridgingRelease(nw_error_copy_cf_error(receive_error)); - self.incoming_data_handler((NSData*)content, is_complete, st_error); + //we always want to allow new calls to schedule_read --> set last arg to YES + self.incoming_data_handler((NSData*)content, is_complete, st_error, YES); }); } } diff --git a/Monal/Classes/MonalAppDelegate.m b/Monal/Classes/MonalAppDelegate.m index 7769e3ac82..a32ddbb052 100644 --- a/Monal/Classes/MonalAppDelegate.m +++ b/Monal/Classes/MonalAppDelegate.m @@ -1972,10 +1972,10 @@ -(void) sendAllOutboxes monal_id_block_t cleanup = ^(NSDictionary* payload) { [[DataLayer sharedInstance] deleteShareSheetPayloadWithId:payload[@"id"]]; [[MLNotificationQueue currentQueue] postNotificationName:kMonalRefresh object:nil userInfo:nil]; - if(self.activeChats.currentChatView != nil) + if(self.activeChats.currentChatViewController != nil) { - [self.activeChats.currentChatView scrollToBottomAnimated:NO]; - [self.activeChats.currentChatView hideUploadHUD]; + [self.activeChats.currentChatViewController scrollToBottomAnimated:NO]; + [self.activeChats.currentChatViewController hideUploadHUD]; } //send next item (if there is one left) [self sendAllOutboxes]; @@ -2007,7 +2007,7 @@ -(void) sendAllOutboxes else if([payload[@"type"] isEqualToString:@"image"] || [payload[@"type"] isEqualToString:@"file"] || [payload[@"type"] isEqualToString:@"contact"] || [payload[@"type"] isEqualToString:@"audiovisual"]) { DDLogInfo(@"Got %@ upload: %@", payload[@"type"], payload[@"data"]); - [self.activeChats.currentChatView showUploadHUD]; + [self.activeChats.currentChatViewController showUploadHUD]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ $call(payload[@"data"], $ID(account), $BOOL(encrypted), $ID(completion, (^(NSString* url, NSString* mimeType, NSNumber* size, NSError* error) { dispatch_async(dispatch_get_main_queue(), ^{ diff --git a/Monal/Classes/NotificationDebugging.swift b/Monal/Classes/NotificationDebugging.swift index ebe6bdcbfa..3dc7396466 100644 --- a/Monal/Classes/NotificationDebugging.swift +++ b/Monal/Classes/NotificationDebugging.swift @@ -8,11 +8,6 @@ import OrderedCollections -class NotificationDebuggingDefaultsDB: ObservableObject { - @defaultsDB("lastAppexStart") - var lastAppexStart: Date? -} - struct NotificationDebugging: View { private let applePushEnabled: Bool private let applePushToken: String @@ -24,25 +19,18 @@ struct NotificationDebugging: View { @State private var showPushToken = false @State private var selectedPushServer: String - - @ObservedObject var notificationDebuggingDefaultsDB = NotificationDebuggingDefaultsDB() var body: some View { Form { Group { Section(header: Text("Status").font(.title3)) { - VStack(alignment: .leading, spacing:10) { + VStack(alignment: .leading) { buildNotificationStateLabel(Text("Apple Push Service"), isWorking: self.applePushEnabled); Divider() Text("Apple push service should always be on. If it is off, your device can not talk to Apple's server.").foregroundColor(Color(UIColor.secondaryLabel)).font(.footnote) if !self.applePushEnabled, let apnsError = MLXMPPManager.sharedInstance().apnsError { Text("Error: \(String(describing:apnsError))").foregroundColor(.red).font(.footnote) } - if let lastAppexStart = notificationDebuggingDefaultsDB.lastAppexStart { - Text("Last incoming push: \(String(describing:lastAppexStart))").foregroundColor(.gray).font(.footnote) - } else { - Text("Last incoming push: unknown").foregroundColor(.gray).font(.footnote) - } }.onTapGesture(count: 2, perform: { showPushToken = true }).alert(isPresented: $showPushToken) { diff --git a/Monal/Classes/chatViewController.m b/Monal/Classes/chatViewController.m index b9ce268932..8a689dbe58 100644 --- a/Monal/Classes/chatViewController.m +++ b/Monal/Classes/chatViewController.m @@ -3560,8 +3560,7 @@ -(NSInteger)collectionView:(nonnull UICollectionView*) collectionView numberOfIt -(void) notifyUploadQueueRemoval:(NSUInteger) index { - if(index >= self.uploadQueue.count) - return; + MLAssert(index < self.uploadQueue.count, @"index is only allowed to be smaller than uploadQueue.count"); [self.uploadMenuView performBatchUpdates:^{ [self deleteQueueItemAtIndex:index]; } completion:^(BOOL finished) { diff --git a/Monal/NotificationService/NotificationService.m b/Monal/NotificationService/NotificationService.m index c25f3052ca..6bb79c6e57 100644 --- a/Monal/NotificationService/NotificationService.m +++ b/Monal/NotificationService/NotificationService.m @@ -441,8 +441,6 @@ +(void) initialize if(warnUnclean) DDLogError(@"detected unclean appex shutdown!"); - [[HelperTools defaultsDB] setObject:[NSDate now] forKey:@"lastAppexStart"]; - //mark this appex as unclean (will be cleared directly before calling exit(0)) [NotificationService setAppexCleanShutdownStatus:NO]; } From b7b4a79633e8a58ef04b7de54c4073406eaa9ebe Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 9 Nov 2024 04:48:42 +0100 Subject: [PATCH 15/18] Fix backported code --- Monal/Classes/ActiveChatsViewController.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index 31bb7ec756..f63f3d0012 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -366,8 +366,8 @@ -(void) refreshDisplay BOOL found = NO; for(NSDictionary* accountDict in [[DataLayer sharedInstance] enabledAccountList]) { - NSNumber* accountID = accountDict[kAccountID]; - if([MLNotificationManager sharedInstance].currentContact.accountID.intValue == accountID.intValue) + NSNumber* accountNo = accountDict[kAccountID]; + if([MLNotificationManager sharedInstance].currentContact.accountNo.intValue == accountNo.intValue) found = YES; } if(!found) From e64a13bd68678aa0ea86e1889b2e98cab1df4fe5 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 9 Nov 2024 04:48:42 +0100 Subject: [PATCH 16/18] Fix backported code --- Monal/Classes/ActiveChatsViewController.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index 31bb7ec756..0eb0ada153 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -366,8 +366,8 @@ -(void) refreshDisplay BOOL found = NO; for(NSDictionary* accountDict in [[DataLayer sharedInstance] enabledAccountList]) { - NSNumber* accountID = accountDict[kAccountID]; - if([MLNotificationManager sharedInstance].currentContact.accountID.intValue == accountID.intValue) + NSNumber* accountId = accountDict[kAccountID]; + if([MLNotificationManager sharedInstance].currentContact.accountId.intValue == accountId.intValue) found = YES; } if(!found) From 490e41d6ca5b61415d8adc1ca70cc32578e82c4c Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 12 Nov 2024 03:18:23 +0100 Subject: [PATCH 17/18] Fix double-disconnect assertion when creating unusable account When creating a new account that can not be connected to, a timeout error is triggered after 30 seconds. If the app was put into background, a disconnect and receiveQueue freeze could happen before the timeout handler tries to disconnect again. Prviosuly this triggered an assertion, now we try to handle that case more gracefully while not introducing new race conditions. --- Monal/Classes/xmpp.m | 133 +++++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 61 deletions(-) diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index fa6e985ce2..c3a3863f26 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -829,19 +829,21 @@ -(void) unfreeze //this operation has highest priority to make sure it will be executed first once unfrozen NSBlockOperation* unfreezeOperation = [NSBlockOperation blockOperationWithBlock:^{ //this has to be the very first thing even before unfreezing the parse or send queues - if(self.accountState < kStateReconnecting) - { - DDLogInfo(@"Reloading UNfrozen account %@", self.accountNo); - //(re)read persisted state (could be changed by appex) - [self readState]; + @synchronized(self->_stateLockObject) { + if(self.accountState < kStateReconnecting) + { + DDLogInfo(@"Reloading UNfrozen account %@", self.accountNo); + //(re)read persisted state (could be changed by appex) + [self readState]; + } + else + DDLogInfo(@"Not reloading UNfrozen account %@, already connected", self.accountNo); + + //this must be inside the dispatch async, because it will dispatch *SYNC* to the receive queue and potentially block or even deadlock the system + [self unfreezeParseQueue]; + + [self unfreezeSendQueue]; } - else - DDLogInfo(@"Not reloading UNfrozen account %@, already connected", self.accountNo); - - //this must be inside the dispatch async, because it will dispatch *SYNC* to the receive queue and potentially block or even deadlock the system - [self unfreezeParseQueue]; - - [self unfreezeSendQueue]; }]; unfreezeOperation.queuePriority = NSOperationQueuePriorityVeryHigh; //make sure this will become the first operation executed once unfrozen [self->_receiveQueue addOperations: @[unfreezeOperation] waitUntilFinished:NO]; @@ -962,11 +964,67 @@ -(void) disconnectWithStreamError:(MLXMLNode* _Nullable) streamError -(void) disconnectWithStreamError:(MLXMLNode* _Nullable) streamError andExplicitLogout:(BOOL) explicitLogout { DDLogInfo(@"disconnect called..."); + //commonly used by shortcut outside of receive queue and called from inside the receive queue, too + monal_void_block_t doExplicitLogout = ^{ + @synchronized(self->_stateLockObject) { + DDLogVerbose(@"explicitLogout == YES --> clearing state"); + + //preserve unAckedStanzas even on explicitLogout and resend them on next connect + //if we don't do this, messages could get lost when logging out directly after sending them + //and: sending messages twice is less intrusive than silently loosing them + NSMutableArray* stanzas = self.unAckedStanzas; + + //reset smacks state to sane values (this can be done even if smacks is not supported) + [self initSM3]; + self.unAckedStanzas = stanzas; + + //inform all old iq handlers of invalidation and clear _iqHandlers dictionary afterwards + @synchronized(self->_iqHandlers) { + for(NSString* iqid in [self->_iqHandlers allKeys]) + { + DDLogWarn(@"Invalidating iq handler for iq id '%@'", iqid); + if(self->_iqHandlers[iqid][@"handler"] != nil) + $invalidate(self->_iqHandlers[iqid][@"handler"], $ID(account, self), $ID(reason, @"disconnect")); + else if(self->_iqHandlers[iqid][@"errorHandler"]) + ((monal_iq_handler_t)self->_iqHandlers[iqid][@"errorHandler"])(nil); + } + self->_iqHandlers = [NSMutableDictionary new]; + } + + //invalidate pubsub queue (*after* iq handlers that also might invalidate a result handler of the queued operation) + [self.pubsub invalidateQueue]; + + //clear pipeline cache + self->_pipeliningState = kPipelinedNothing; + self->_cachedStreamFeaturesBeforeAuth = nil; + self->_cachedStreamFeaturesAfterAuth = nil; + + //clear all reconnection handlers + @synchronized(self->_reconnectionHandlers) { + [self->_reconnectionHandlers removeAllObjects]; + } + + //persist these changes + [self persistState]; + } + + [[DataLayer sharedInstance] resetContactsForAccount:self.accountNo]; + + //trigger view updates to make sure enabled/disabled account state propagates to all ui elements + [[MLNotificationQueue currentQueue] postNotificationName:kMonalRefresh object:nil userInfo:nil]; + }; //short-circuit common case without dispatching to receive queue //this allows calling a noop disconnect while the receive queue is frozen - if(self->_accountState an unfreeze can not happen half way through this explicit logout and therefore can't corrupt any state + //--> an unfreeze is needed to dispatch to the receive queue which is used by our connect method + if(self->_accountState_stateLockObject) { - DDLogVerbose(@"explicitLogout == YES --> clearing state"); - - //preserve unAckedStanzas even on explicitLogout and resend them on next connect - //if we don't do this, messages could get lost when logging out directly after sending them - //and: sending messages twice is less intrusive than silently loosing them - NSMutableArray* stanzas = self.unAckedStanzas; - - //reset smacks state to sane values (this can be done even if smacks is not supported) - [self initSM3]; - self.unAckedStanzas = stanzas; - - //inform all old iq handlers of invalidation and clear _iqHandlers dictionary afterwards - @synchronized(self->_iqHandlers) { - for(NSString* iqid in [self->_iqHandlers allKeys]) - { - DDLogWarn(@"Invalidating iq handler for iq id '%@'", iqid); - if(self->_iqHandlers[iqid][@"handler"] != nil) - $invalidate(self->_iqHandlers[iqid][@"handler"], $ID(account, self), $ID(reason, @"disconnect")); - else if(self->_iqHandlers[iqid][@"errorHandler"]) - ((monal_iq_handler_t)self->_iqHandlers[iqid][@"errorHandler"])(nil); - } - self->_iqHandlers = [NSMutableDictionary new]; - } - - //invalidate pubsub queue (*after* iq handlers that also might invalidate a result handler of the queued operation) - [self.pubsub invalidateQueue]; - - //clear pipeline cache - self->_pipeliningState = kPipelinedNothing; - self->_cachedStreamFeaturesBeforeAuth = nil; - self->_cachedStreamFeaturesAfterAuth = nil; - - //clear all reconnection handlers - @synchronized(self->_reconnectionHandlers) { - [self->_reconnectionHandlers removeAllObjects]; - } - - //persist these changes - [self persistState]; - } - - [[DataLayer sharedInstance] resetContactsForAccount:self.accountNo]; - - //trigger view updates to make sure enabled/disabled account state propagates to all ui elements - [[MLNotificationQueue currentQueue] postNotificationName:kMonalRefresh object:nil userInfo:nil]; - } + doExplicitLogout(); return; } DDLogInfo(@"disconnecting"); From f09b4a67baa9767462bfed31a5d24cb624963c5f Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 12 Nov 2024 03:32:46 +0100 Subject: [PATCH 18/18] Fix regression when doing starttls --- Monal/Classes/MLStream.m | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Monal/Classes/MLStream.m b/Monal/Classes/MLStream.m index b38cce82a9..2112a087f6 100644 --- a/Monal/Classes/MLStream.m +++ b/Monal/Classes/MLStream.m @@ -50,7 +50,7 @@ @interface MLInputStream() //(mutexes can not be unlocked in a thread different from the one it got locked in and NSLock internally uses mutext --> both can not be used) dispatch_semaphore_t _read_sem; } -@property (atomic, readonly) void (^incoming_data_handler)(NSData* _Nullable, BOOL, NSError* _Nullable); +@property (atomic, readonly) void (^incoming_data_handler)(NSData* _Nullable, BOOL, NSError* _Nullable, BOOL); @end @interface MLOutputStream() @@ -89,7 +89,7 @@ -(instancetype) initWithSharedState:(MLSharedStreamState*) shared //this handler will be called by the schedule_read method //since the framer swallows all data, nw_connection_receive() and the framer cannot race against each other and deliver reordered data weakify(self); - _incoming_data_handler = ^(NSData* _Nullable content, BOOL is_complete, NSError* _Nullable st_error) { + _incoming_data_handler = ^(NSData* _Nullable content, BOOL is_complete, NSError* _Nullable st_error, BOOL polling_active) { strongify(self); if(self == nil) return; @@ -142,7 +142,7 @@ -(instancetype) initWithSharedState:(MLSharedStreamState*) shared [self generateEvent:NSStreamEventEndEncountered]; //try to read again - if(!is_complete && !generate_bytes_available_event) + if(!is_complete && !generate_error_event && !generate_bytes_available_event && polling_active) [self schedule_read]; }; return self; @@ -235,7 +235,8 @@ -(void) schedule_read DDLogDebug(@"now calling nw_framer_parse_input inside framer queue"); nw_framer_parse_input(self.shared_state.framer, 1, BUFFER_SIZE, nil, ^size_t(uint8_t* buffer, size_t buffer_length, bool is_complete) { DDLogDebug(@"nw_framer_parse_input got callback with is_complete:%@, length=%zu", bool2str(is_complete), (unsigned long)buffer_length); - self.incoming_data_handler([NSData dataWithBytes:buffer length:buffer_length], is_complete, nil); + //we don't want to do "polling" here, our next nw_framer_parse_input will be triggered by the nw_framer_set_input_handler block + self.incoming_data_handler([NSData dataWithBytes:buffer length:buffer_length], is_complete, nil, NO); return buffer_length; }); }); @@ -248,7 +249,8 @@ -(void) schedule_read NSError* st_error = nil; if(receive_error) st_error = (NSError*)CFBridgingRelease(nw_error_copy_cf_error(receive_error)); - self.incoming_data_handler((NSData*)content, is_complete, st_error); + //we want to do "polling" here (e.g. start our next blocking nw_connection_receive call if we did not receive new data nor any error) + self.incoming_data_handler((NSData*)content, is_complete, st_error, YES); }); } }