Skip to content

Commit

Permalink
6.4.9 (#1360)
Browse files Browse the repository at this point in the history
- Fix rare crashes on app startup 
MACOS_ONLY - Don't lock up app on close
- Improve internal logging
- Fixed background crash
- Revert christmas/winter special
- Fix crash on incoming non-spec-conformant LMC
- Make xmpp gateway detection more robust (thanks lissine)
- Add new action to directly block users on incoming subscription
requests rather than just denying the request
- Check if we are still joined when opening a channel/group and rejoin
if not
  • Loading branch information
tmolitor-stud-tu authored Jan 11, 2025
2 parents 503f09d + d39c644 commit 35b7a15
Show file tree
Hide file tree
Showing 25 changed files with 639 additions and 240 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/create-stable-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ jobs:
body: `${{ steps.get_commits.outputs.description }}`,
});
console.log(`Created pull request #${pullRequest.data.number}`);
//update pr after creation to trigger our pr-semver-title workflow
pullRequest = await github.rest.pulls.update({
owner,
repo,
pull_number: pullRequest.data.number,
title: `${{ steps.get_commits.outputs.buildVersion }}`,
body: `${{ steps.get_commits.outputs.description }}`,
});
console.log(`Updated pull request #${pullRequest.data.number}`);
}
return pullRequest.data.number;
- name: Add Label to Pull Request
Expand Down
5 changes: 3 additions & 2 deletions Monal/Classes/ContactEntry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
//

struct ContactEntry<AdditionalContent: View>: View {
let contact: ObservableKVOWrapper<MLContact>
let selfnotesPrefix: Bool
let fallback: String?
@ViewBuilder let additionalContent: () -> AdditionalContent

@StateObject var contact: ObservableKVOWrapper<MLContact>

init(contact:ObservableKVOWrapper<MLContact>, selfnotesPrefix: Bool = true, fallback: String? = nil) where AdditionalContent == EmptyView {
self.init(contact:contact, selfnotesPrefix:selfnotesPrefix, fallback:fallback, additionalContent:{ EmptyView() })
}
Expand All @@ -33,7 +34,7 @@ struct ContactEntry<AdditionalContent: View>: View {
}

init(contact:ObservableKVOWrapper<MLContact>, selfnotesPrefix: Bool, fallback: String?, @ViewBuilder additionalContent: @escaping () -> AdditionalContent) {
self.contact = contact
_contact = StateObject(wrappedValue: contact)
self.selfnotesPrefix = selfnotesPrefix
self.fallback = fallback
self.additionalContent = additionalContent
Expand Down
11 changes: 11 additions & 0 deletions Monal/Classes/ContactRequestsMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ struct ContactRequestsMenuEntry: View {
//see https://www.hackingwithswift.com/forums/swiftui/tap-button-in-hstack-activates-all-button-actions-ios-14-swiftui-2/2952
.buttonStyle(BorderlessButtonStyle())

Button {
// deny request
MLXMPPManager.sharedInstance().remove(contact)
MLXMPPManager.sharedInstance().block(true, contact:contact)
} label: {
Image(systemName: "xmark.circle")
.accentColor(.red)
}
//see https://www.hackingwithswift.com/forums/swiftui/tap-button-in-hstack-activates-all-button-actions-ios-14-swiftui-2/2952
.buttonStyle(BorderlessButtonStyle())

Button {
// deny request
MLXMPPManager.sharedInstance().remove(contact)
Expand Down
6 changes: 6 additions & 0 deletions Monal/Classes/HelperTools.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ void swizzle(Class c, SEL orig, SEL new);
-(id) initWithObj:(id) obj;
@end

@interface DDLogMessage(TaggedMessage)
@property (nonatomic) BOOL ml_isDirect;
@end

@interface HelperTools : NSObject

@property (class, nonatomic, strong, nullable) DDFileLogger* fileLogger;
Expand All @@ -78,8 +82,10 @@ void swizzle(Class c, SEL orig, SEL new);
+(void) installExceptionHandler;
+(int) pendingCrashreportCount;
+(void) flushLogsWithTimeout:(double) timeout;
+(BOOL) isAppSuspended;
+(void) signalSuspension;
+(void) signalResumption;
+(void) activateTerminationLogging;
+(void) __attribute__((noreturn)) MLAssertWithText:(NSString*) text andUserData:(id _Nullable) additionalData andFile:(const char* const) file andLine:(int) line andFunc:(const char* const) func;
+(void) __attribute__((noreturn)) handleRustPanicWithText:(NSString*) text andBacktrace:(NSString*) backtrace;
+(void) __attribute__((noreturn)) throwExceptionWithName:(NSString*) name reason:(NSString*) reason userInfo:(NSDictionary* _Nullable) userInfo;
Expand Down
120 changes: 91 additions & 29 deletions Monal/Classes/HelperTools.m
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ -(void) swizzled_queueLogMessage:(DDLogMessage*) logMessage asynchronously:(BOOL
#pragma pack()


void exitLogging(void)
{
DDLogInfo(@"exit() was called...");
//make sure to unfreeze logging before flushing everything and terminating
[HelperTools activateTerminationLogging];
[HelperTools flushLogsWithTimeout:0.025];
return;
}

// see: https://developer.apple.com/library/archive/qa/qa1361/_index.html
// Returns true if the current process is being debugged (either
// running under the debugger or has a debugger attached post facto).
Expand Down Expand Up @@ -302,14 +311,45 @@ -(id) initWithObj:(id) obj
}
@end

@implementation DDLogMessage(TaggedMessage)
@dynamic ml_isDirect;
-(void) setMl_isDirect:(BOOL) value
{
objc_setAssociatedObject(self, @selector(ml_isDirect), @(value), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL) ml_isDirect
{
return ((NSNumber*)objc_getAssociatedObject(self, @selector(ml_isDirect))).boolValue;
}
@end

@implementation DDLog (AllowQueueFreeze)

-(void) swizzled_queueLogMessage:(DDLogMessage*) logMessage asynchronously:(BOOL) asyncFlag
{
//don't do sync logging for any message (usually ERROR), while the global logging queue is suspended
@synchronized(_suspensionHandling_lock) {
return [self swizzled_queueLogMessage:logMessage asynchronously:_suspensionHandling_isSuspended ? YES : asyncFlag];
//make sure this method remains performant even when checking for udp logging presence
static BOOL udpLoggerEnabled = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
udpLoggerEnabled = [[HelperTools defaultsDB] boolForKey:@"udpLoggerEnabled"];
});

//use udp logger to log all messages, even if the loggging queue is in suspended state
//this hopefully enables us to catch strange bugs sometimes hanging and then watchdog-killing the app when resuming from resumption
if(udpLoggerEnabled && _suspensionHandling_isSuspended)
{
//this marks a message as already directly logged to allow the udp logger to later ignore the queued log request for the same message
logMessage.ml_isDirect = YES;
//make sure all udp log messages are still logged chronologically and prevent race conditions with our static counter var
dispatch_async([MLUDPLogger getCurrentInstance].loggerQueue, ^{
[MLUDPLogger directlyWriteLogMessage:logMessage];
});
}

//don't do sync logging for any message (usually ERROR), while the global logging queue is suspended
//don't use _suspensionHandling_lock here because that can introduce deadlocks
//(for example if we have log statements in our MLLogFileManager code rotating the logfile and creating a new one)
return [self swizzled_queueLogMessage:logMessage asynchronously:_suspensionHandling_isSuspended ? YES : asyncFlag];
}

//see https://stackoverflow.com/a/13326633 and https://fek.io/blog/method-swizzling-in-obj-c-and-swift/
Expand Down Expand Up @@ -391,10 +431,8 @@ +(void) __attribute__((noreturn)) handleRustPanicWithText:(NSString*) text andBa
_crash_info.backtrace = backtrace.UTF8String;

//log error and flush all logs
[DDLog flushLog];
DDLogError(@"*****************\n%@\n%@", abort_msg, backtrace);
[DDLog flushLog];
[HelperTools flushLogsWithTimeout:0.250];
[HelperTools flushLogsWithTimeout:0.025];

//now abort everything
abort();
Expand Down Expand Up @@ -1867,32 +1905,37 @@ +(NSData* _Nullable) convertLogmessageToJsonData:(DDLogMessage*) logMessage coun

//construct json dictionary
(*counter)++;
NSDictionary* representedObject = @{
@"queueThreadLabel": [self getQueueThreadLabelFor:logMessage],
NSDictionary* tag = @{
@"queueThreadLabel": nilWrapper([self getQueueThreadLabelFor:logMessage]),
@"processType": [self isAppExtension] ? @"appex" : @"mainapp",
@"processName": [[[NSBundle mainBundle] executablePath] lastPathComponent],
@"counter": [NSNumber numberWithUnsignedLongLong:*counter],
@"processID": _processID,
@"qosName": qos2name(logMessage.qos),
@"representedObject": logMessage.representedObject ? logMessage.representedObject : [NSNull null],
@"processName": nilWrapper([[[NSBundle mainBundle] executablePath] lastPathComponent]),
@"counter": @(*counter),
@"processID": nilWrapper(_processID),
@"qosName": nilWrapper(qos2name(logMessage.qos)),
@"loggingQueueSuspended": @(_suspensionHandling_isSuspended),
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@"tag": nilWrapper(logMessage.tag),
#pragma clang diagnostic pop
};
NSDictionary* msgDict = @{
@"messageFormat": logMessage.messageFormat,
@"message": logMessage.message,
@"level": [NSNumber numberWithInteger:logMessage.level],
@"flag": [NSNumber numberWithInteger:logMessage.flag],
@"context": [NSNumber numberWithInteger:logMessage.context],
@"file": logMessage.file,
@"fileName": logMessage.fileName,
@"function": logMessage.function,
@"line": [NSNumber numberWithInteger:logMessage.line],
@"tag": representedObject,
@"options": [NSNumber numberWithInteger:logMessage.options],
@"messageFormat": nilWrapper(logMessage.messageFormat),
@"message": nilWrapper(logMessage.message),
@"level": @(logMessage.level),
@"flag": @(logMessage.flag),
@"context": @(logMessage.context),
@"file": nilWrapper(logMessage.file),
@"fileName": nilWrapper(logMessage.fileName),
@"function": nilWrapper(logMessage.function),
@"line": @(logMessage.line),
@"representedObject": nilWrapper(logMessage.representedObject),
@"tag": nilWrapper(tag),
@"options": @(logMessage.options),
@"timestamp": [dateFormatter stringFromDate:logMessage.timestamp],
@"threadID": logMessage.threadID,
@"threadName": logMessage.threadName,
@"queueLabel": logMessage.queueLabel,
@"qos": [NSNumber numberWithInteger:logMessage.qos],
@"threadID": nilWrapper(logMessage.threadID),
@"threadName": nilWrapper(logMessage.threadName),
@"queueLabel": nilWrapper(logMessage.queueLabel),
@"qos": @(logMessage.qos),
};

//encode json into NSData
Expand All @@ -1915,6 +1958,13 @@ +(void) flushLogsWithTimeout:(double) timeout
[MLUDPLogger flushWithTimeout:timeout];
}

+(BOOL) isAppSuspended
{
@synchronized(_suspensionHandling_lock) {
return _suspensionHandling_isSuspended;
}
}

+(void) signalSuspension
{
@synchronized(_suspensionHandling_lock) {
Expand Down Expand Up @@ -1946,6 +1996,18 @@ +(void) signalResumption
}
}

+(void) activateTerminationLogging
{
@synchronized(_suspensionHandling_lock) {
if(_suspensionHandling_isSuspended)
{
DDLogVerbose(@"Activating logging for app termination...");
dispatch_resume([DDLog loggingQueue]);
_suspensionHandling_isSuspended = NO;
}
}
}

+(void) configureXcodeLogging
{
//only start console logger
Expand Down Expand Up @@ -2074,7 +2136,7 @@ +(void) installCrashHandler
handler.maxReportCount = 4;
handler.deadlockWatchdogInterval = 0; // no main thread watchdog
handler.userInfo = @{
@"isAppex": @([self isAppExtension]),
@"isAppex": bool2str([self isAppExtension]),
@"processName": [[[NSBundle mainBundle] executablePath] lastPathComponent],
@"bundleName": nilWrapper([[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]),
@"appVersion": [self appBuildVersionInfoFor:MLVersionTypeLog],
Expand Down
25 changes: 15 additions & 10 deletions Monal/Classes/MLMessageProcessor.m
Original file line number Diff line number Diff line change
Expand Up @@ -579,18 +579,23 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag
{
NSString* messageIdToReplace = [messageNode findFirst:@"{urn:xmpp:message-correct:0}replace@id"];
DDLogVerbose(@"Message id to LMC-replace: %@", messageIdToReplace);
//this checks if this message is from the same jid as the message it tries to do the LMC for (e.g. inbound can only correct inbound and outbound only outbound)
historyId = [[DataLayer sharedInstance] getLMCHistoryIDForMessageId:messageIdToReplace from:messageNode.fromUser occupantId:occupantId participantJid:participantJid andAccount:account.accountNo];
DDLogVerbose(@"History id to LMC-replace: %@", historyId);
//now check if the LMC is allowed (we use historyIdToUse for MLhistory mam queries to only check LMC for the 3 messages coming before this ID in this converastion)
//historyIdToUse will be nil, for messages going forward in time which means (check for the newest 3 messages in this conversation)
if(historyId != nil && [[DataLayer sharedInstance] checkLMCEligible:historyId encrypted:encrypted historyBaseID:historyIdToUse])
if(messageIdToReplace == nil)
DDLogWarn(@"Ignoring LMC message not carrying a replacement id, spec vialoation!");
else
{
[[DataLayer sharedInstance] updateMessageHistory:historyId withText:body];
LMCReplaced = YES;
//this checks if this message is from the same jid as the message it tries to do the LMC for (e.g. inbound can only correct inbound and outbound only outbound)
historyId = [[DataLayer sharedInstance] getLMCHistoryIDForMessageId:messageIdToReplace from:messageNode.fromUser occupantId:occupantId participantJid:participantJid andAccount:account.accountNo];
DDLogVerbose(@"History id to LMC-replace: %@", historyId);
//now check if the LMC is allowed (we use historyIdToUse for MLhistory mam queries to only check LMC for the 3 messages coming before this ID in this converastion)
//historyIdToUse will be nil, for messages going forward in time which means (check for the newest 3 messages in this conversation)
if(historyId != nil && [[DataLayer sharedInstance] checkLMCEligible:historyId encrypted:encrypted historyBaseID:historyIdToUse])
{
[[DataLayer sharedInstance] updateMessageHistory:historyId withText:body];
LMCReplaced = YES;
}
else
historyId = nil;
}
else
historyId = nil;
}

//handle normal messages or LMC messages that can not be found
Expand Down
5 changes: 4 additions & 1 deletion Monal/Classes/MLMucProcessor.m
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,10 @@ -(NSString* _Nullable) generateMucJid
NSString* mucServer = nil;
for(NSString* jid in _account.connectionProperties.conferenceServers)
{
if([_account.connectionProperties.conferenceServers[jid] check:@"identity<type=text>"])
// Do not use gateways
if(![_account.connectionProperties.conferenceServers[jid] check:@"identity<category=gateway>"]
&& [_account.connectionProperties.conferenceServers[jid] check:@"identity<category=conference>"]
&& [_account.connectionProperties.conferenceServers[jid] check:@"identity<type=text>"])
{
mucServer = jid;
break;
Expand Down
65 changes: 27 additions & 38 deletions Monal/Classes/MLStream.m
Original file line number Diff line number Diff line change
Expand Up @@ -470,59 +470,48 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host
return YES;
});

/*
//some weird apple stuff creates the framer twice: once directly when starting the tcp handshake
//and again later after the tcp connection was established successfully --> ignore the first one
//and once a few milliseconds later, presumably after the tcp connection was established successfully
//--> ignore all but the first one
if(framerId < 1)
{
nw_framer_set_input_handler(framer, ^size_t(nw_framer_t framer) {
nw_framer_parse_input(framer, 1, BUFFER_SIZE, nil, ^size_t(uint8_t* buffer, size_t buffer_length, bool is_complete) {
MLAssert(NO, @"Unexpected incoming bytes in first framer!", (@{
@"logtag": nilWrapper(logtag),
@"framer": framer,
@"buffer": [NSData dataWithBytes:buffer length:buffer_length],
@"buffer_length": @(buffer_length),
@"is_complete": bool2str(is_complete),
}));
return buffer_length;
DDLogVerbose(@"Framer is the first one, using it...");
//we have to simulate nw_connection_state_ready because the connection state will not reflect that while our framer is active
//--> use framer start as "connection active" signal
//first framer start is allowed to directly send data which will be used as tcp early data
if(!wasOpenOnce)
{
wasOpenOnce = YES;
@synchronized(shared_state) {
shared_state.open = YES;
}
//make sure to not do this inside the framer thread to not cause any deadlocks
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[input generateEvent:NSStreamEventOpenCompleted];
[output generateEvent:NSStreamEventOpenCompleted];
});
return 0; //why that?
}

nw_framer_set_input_handler(framer, ^size_t(nw_framer_t framer) {
DDLogDebug(@"Got new input for framer: %@", framer);
[input schedule_read];
return 0; //why that??
});
nw_framer_set_output_handler(framer, ^(nw_framer_t framer, nw_framer_message_t message, size_t message_length, bool is_complete) {
MLAssert(NO, @"Unexpected outgoing bytes in first framer!", (@{
MLAssert(NO, @"Unexpected outgoing bytes in framer!", (@{
@"logtag": nilWrapper(logtag),
@"framer": framer,
@"message": message,
@"message_length": @(message_length),
@"is_complete": bool2str(is_complete),
}));
});
return nw_framer_start_result_will_mark_ready;
}
*/

//we have to simulate nw_connection_state_ready because the connection state will not reflect that while our framer is active
//--> use framer start as "connection active" signal
//first framer start is allowed to directly send data which will be used as tcp early data
if(!wasOpenOnce)
{
wasOpenOnce = YES;
@synchronized(shared_state) {
shared_state.open = YES;
}
//make sure to not do this inside the framer thread to not cause any deadlocks
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[input generateEvent:NSStreamEventOpenCompleted];
[output generateEvent:NSStreamEventOpenCompleted];
});

shared_state.framer = framer;
}
else
DDLogVerbose(@"Ignoring subsequent framer...");

nw_framer_set_input_handler(framer, ^size_t(nw_framer_t framer) {
[input schedule_read];
return 0; //why that??
});

shared_state.framer = framer;
return nw_framer_start_result_will_mark_ready;
});
DDLogInfo(@"%@: Not doing direct TLS: appending framer to protocol stack...", logtag);
Expand Down
Loading

0 comments on commit 35b7a15

Please sign in to comment.