Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  ServerConnector.m   Sprache: Unknown

 

Spracherkennung für: .m vermutete Sprache: Lisp {Lisp[51] Masm[51] ABAP[44]} [Methode: maximale Elemente, drei Dimensionen]

#include <CommonCrypto/CommonDigest.h>

#import "ThreemaFramework/ThreemaFramework-Swift.h"

#import "ServerConnector.h"
#import "NaClCrypto.h"
#import "ChatTcpSocket.h"
#import "NSString+Hex.h"
#import "BoxedMessage.h"
#import "MyIdentityStore.h"
#import "ProtocolDefines.h"
#import "ThreemaUtilityObjC.h"
#import "ContactStore.h"
#import "UserSettings.h"
#import "BundleUtil.h"
#import "AppGroup.h"
#import "LicenseStore.h"
@import Keychain;

#ifdef DEBUG
  static const DDLogLevel ddLogLevel = DDLogLevelAll;
#else
  static const DDLogLevel ddLogLevel = DDLogLevelNotice;
#endif
#define LOG_KEY_INFO 0

static const int MAX_BYTES_TO_DECRYPT_NO_LIMIT = 0;
static const int MAX_BYTES_TO_DECRYPT_NOTIFICATION_EXTENSION = 500000;

@implementation ServerConnector {
    NSData *clientTempKeyPub;
    NSData *clientTempKeySec;
    NSData *clientCookie;
    
    NSData *serverCookie;
    NSData *serverTempKeyPub;
    
    NSData *chosenServerKeyPub;
    NSData *serverKeyPub;
    NSData *serverAltKeyPub;
    NSData *tempKeyHash;
    
    dispatch_queue_t sendPushTokenQueue;
    dispatch_queue_t removeVoIPPushTokenQueue;
    BOOL isRemovedVoIPPushToken;
    
    uint64_t serverNonce;
    uint64_t clientNonce;
    dispatch_queue_t socketQueue;
    dispatch_queue_t sendMessageQueue;
    dispatch_source_t keepalive_timer;
    NSCondition *disconnectCondition;
    id<SocketProtocol> socket;
    int reconnectAttempts;

    NSMutableArray *connectionInitiators;

    ProcessCoordinator *processCoordinator;
    ServerConnectorConnectionState *serverConnectorConnectionState;

    BOOL autoReconnect;
    CFTimeInterval lastRead;
    NSDate *lastErrorDisplay;

    CFTimeInterval lastEchoSendTime;
    uint64_t lastSentEchoSeq;
    uint64_t lastRcvdEchoSeq;

    ReachabilityWrapper *reachabilityWrapper;
    
    NSMutableSet *displayedServerAlerts;
    int anotherConnectionCount;
    BOOL chatServerInInitialQueueSend;
    BOOL mediatorServerInInitialQueueSend;
    BOOL doUnblockIncomingMessages;
    BOOL isWaitingForReconnect;

    dispatch_queue_t queueConnectionStateDelegate;
    NSMutableSet *clientConnectionStateDelegates;

    dispatch_queue_t queueMessageListenerDelegate;
    NSMutableSet *clientMessageListenerDelegates;

    dispatch_queue_t queueMessageProcessorDelegate;
    id<MessageProcessorDelegate> clientMessageProcessorDelegate;
    
    dispatch_queue_t queueTaskExecutionTransactionDelegate;
    id<TaskExecutionTransactionDelegate> clientTaskExecutionTransactionDelegate;
}

@synthesize backgroundEntityManagerForMessageProcessing;
@synthesize lastRtt;
@synthesize deviceGroupKeys;
@synthesize deviceID;
@synthesize maximumNumberOfDeviceSlots;
@synthesize isAppInBackground;
@synthesize isRolePromotedToLeader;

#pragma pack(push, 1)
#pragma pack(1)

struct pktClientHello {
    unsigned char client_tempkey_pub[kNaClCryptoPubKeySize];
    unsigned char client_cookie[kCookieLen];
};

struct pktServerHelloBox {
    unsigned char server_tempkey_pub[kNaClCryptoPubKeySize];
    unsigned char client_cookie[kCookieLen];
};

struct pktServerHello {
    unsigned char server_cookie[kCookieLen];
    char box[sizeof(struct pktServerHelloBox) + kNaClBoxOverhead];
};

struct pktLogin {
    char identity[kIdentityLen];
    char client_version[kClientVersionLen];
    unsigned char server_cookie[kCookieLen];
    unsigned char reserved1[24]; // all-zero
    unsigned char vouch[kVouchLen];
    char reserved2[16]; // all-zero
};

struct pktLoginAck {
    char reserved[kLoginAckReservedLen];
};

struct pktPayload {
    uint8_t type;
    uint8_t reserved[3];
    char data[];
};

struct pktExtension {
    uint8_t type;
    uint16_t length;
    char data[];
};

#pragma pack(pop)

#define TAG_CLIENT_HELLO_SENT 1
#define TAG_SERVER_HELLO_READ 2
#define TAG_LOGIN_SENT 3
#define TAG_LOGIN_ACK_READ 4
#define TAG_PAYLOAD_SENT 5
#define TAG_PAYLOAD_LENGTH_READ 6
#define TAG_PAYLOAD_READ 7
#define TAG_PAYLOAD_MEDIATOR_TRIGGERED 8

#define EXTENSION_TYPE_CLIENT_INFO 0x00
#define EXTENSION_TYPE_DEVICE_ID 0x01
#define EXTENSION_TYPE_MESSAGE_PAYLOAD_VERSION 0x02
#define EXTENSION_TYPE_DEVICE_COOKIE 0x03

#define DUPLICATE_CONNECTION_RECONNECT_ALLOWED @"duplicateConnectionReconnectAllowed"

+ (ServerConnector*)sharedServerConnector {
    static ServerConnector *instance;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [ServerConnector new];
    });

    return instance;
}

- (id)init
{
    self = [super init];
    if (self) {
        sendPushTokenQueue = dispatch_queue_create("ch.threema.ServerConnector.sendPushTokenQueue", NULL);
        removeVoIPPushTokenQueue = dispatch_queue_create("ch.threema.ServerConnector.removeVoIPPushTokenQueue", NULL);
        isRemovedVoIPPushToken = NO;
        
        connectionInitiators = [[NSMutableArray alloc] init];

        socketQueue = dispatch_queue_create("ch.threema.ServerConnector.socketQueue", NULL);
        sendMessageQueue = dispatch_queue_create("ch.threema.ServerConnector.sendMessageQueue", NULL);
        disconnectCondition = [[NSCondition alloc] init];

        serverConnectorConnectionState = [[ServerConnectorConnectionState alloc]  initWithUserSettings:[UserSettings sharedUserSettings] connectionStateDelegate:self];
        reconnectAttempts = 0;
        lastSentEchoSeq = 0;
        lastRcvdEchoSeq = 0;

        displayedServerAlerts = [NSMutableSet set];
        
        /* register with reachability API */
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkStatusDidChange:) name:@"reachabilityChanged" object:nil];
        
        reachabilityWrapper = [[ReachabilityWrapper alloc] init];

        doUnblockIncomingMessages = YES;
        isWaitingForReconnect = NO;
        [self setIsRolePromotedToLeader:NO];

        // Listen for identity changes
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(identityDestroyed:) name:kNotificationDestroyedIdentity object:nil];

        queueConnectionStateDelegate = dispatch_queue_create("ch.threema.ServerConnector.queueConnectionStateDelegate", NULL);
        queueMessageListenerDelegate = dispatch_queue_create("ch.threema.ServerConnector.queueMessageListenerDelegate", NULL);
        queueMessageProcessorDelegate = dispatch_queue_create("ch.threema.ServerConnector.queueMessageProcessorDelegate", NULL);
        queueTaskExecutionTransactionDelegate = dispatch_queue_create("ch.threema.ServerConnector.queueTaskExecutionTransactionDelegate", NULL);

        if ([[UserSettings sharedUserSettings] ipcCommunicationEnabled]) {
            processCoordinator = [[ProcessCoordinator alloc] initWithServerConnector:self userSettings:[UserSettings sharedUserSettings]];
        }
    }
    return self;
}

#pragma mark - Register/unregister delegates for Message Listener, Message Processor and Task Manager transaction

- (void)registerConnectionStateDelegate:(id<ConnectionStateDelegate>)delegate {
    dispatch_sync(queueConnectionStateDelegate, ^{
        if (clientConnectionStateDelegates == nil) {
            clientConnectionStateDelegates = [NSMutableSet new];
        }
        if ([clientConnectionStateDelegates containsObject:delegate] == NO) {
            [clientConnectionStateDelegates addObject:delegate];
        }
    });
}

- (void)unregisterConnectionStateDelegate:(id<ConnectionStateDelegate>)delegate {
    dispatch_sync(queueConnectionStateDelegate, ^{
        if (clientConnectionStateDelegates != nil && [clientConnectionStateDelegates containsObject:delegate] == YES) {
            [clientConnectionStateDelegates removeObject:delegate];
        }
    });
}

- (void)registerMessageListenerDelegate:(id<MessageListenerDelegate>)delegate {
    dispatch_sync(queueMessageListenerDelegate, ^{
        if (clientMessageListenerDelegates == nil) {
            clientMessageListenerDelegates = [NSMutableSet new];
        }
        if ([clientMessageListenerDelegates containsObject:delegate] == NO) {
            [clientMessageListenerDelegates addObject:delegate];
        }
    });
}

- (void)unregisterMessageListenerDelegate:(id<MessageListenerDelegate>)delegate {
    dispatch_sync(queueMessageListenerDelegate, ^{
        if (clientMessageListenerDelegates != nil && [clientMessageListenerDelegates containsObject:delegate] == YES) {
            [clientMessageListenerDelegates removeObject:delegate];
        }
    });
}

- (void)registerMessageProcessorDelegate:(id<MessageProcessorDelegate>)delegate {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        if (delegate != nil) {
            clientMessageProcessorDelegate = delegate;
        }
    });
}

- (void)unregisterMessageProcessorDelegate:(id<MessageProcessorDelegate>)delegate {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        if ([delegate isEqual:clientMessageProcessorDelegate]) {
            clientMessageProcessorDelegate = nil;
        }
    });
}

- (void)registerTaskExecutionTransactionDelegate:(id<TaskExecutionTransactionDelegate>)delegate {
    dispatch_sync(queueTaskExecutionTransactionDelegate, ^{
        if (delegate != nil) {
            clientTaskExecutionTransactionDelegate = delegate;
        }
    });
}

- (void)unregisterTaskExecutionTransactionDelegate:(id<TaskExecutionTransactionDelegate>)delegate {
    dispatch_sync(queueTaskExecutionTransactionDelegate, ^{
        if ([delegate isEqual:clientTaskExecutionTransactionDelegate]) {
            clientTaskExecutionTransactionDelegate = nil;
        }
    });
}

#pragma mark - Chat (Mediator) Server connection handling

/// If another process is active then the timeout will be set to 4900 ms otherwise to 700 ms.
///
/// @return The timeout in milliseconds
- (int)processCoordinatorPollingTimeout {
    if (AppGroup.areOthersActive) {
        return 4900;
    }
    return 700;
}

/// Check of duplicate connection error of the share or notification extension.
/// Show duplicate connection alert is it called by the app.
///
/// @return Is reconnection allowed, is always true is it called by the extension
- (BOOL)checkDuplicateConnectionInExtension {
    BOOL isReconnectAllowed = YES;

    if (AppGroup.getCurrentType != AppGroupTypeApp) {
        return isReconnectAllowed;
    }

    NSUserDefaults *userDefaults = AppGroup.userDefaults;
    id duplicateConnectionReconnectAllowed = [userDefaults valueForKey:DUPLICATE_CONNECTION_RECONNECT_ALLOWED];
    if (duplicateConnectionReconnectAllowed) {
        [userDefaults removeObjectForKey:DUPLICATE_CONNECTION_RECONNECT_ALLOWED];

        isReconnectAllowed = ((NSNumber *)duplicateConnectionReconnectAllowed).boolValue;
        [self showAnotherConnectionAlertWithReconnectAllowed:isReconnectAllowed];
    }

    return isReconnectAllowed;
}

- (void)connect:(ConnectionInitiator)initiator onCompletion:(void(^ _Nullable)(BOOL))onCompletion {
    if (![self checkDuplicateConnectionInExtension]) {
        return;
    }

    if ([[UserSettings sharedUserSettings] ipcCommunicationEnabled]) {
        int timeout = [self processCoordinatorPollingTimeout];

        DDLogNotice(@"[Darwin] Connect (timeout: %i)", timeout);
        [processCoordinator requestAccessWithPollingTimeout:timeout completionHandler:^(enum AccessState state) {
            switch (state) {
                case AccessStateUnused:
                    if ([AppGroup getCurrentType] == AppGroupTypeApp) {
                        DDLogNotice(@"[Darwin] Resource is unused (App), request access again");
                        [self connect:initiator onCompletion:onCompletion];
                    }
                    else {
                        DDLogNotice(@"[Darwin] Resource is unused (NOT App), disconnecting from server");
                        [self _disconnect];
                        if (onCompletion) {
                            onCompletion(NO);
                        }
                    }
                    break;
                case AccessStateRequested:
                    DDLogNotice(@"[Darwin] Resource is requested, request access again");
                    [self connect:initiator onCompletion:onCompletion];
                    break;
                case AccessStateUsing:
                    DDLogNotice(@"[Darwin] Resource is using, connecting to server");

                    [self _connectWithConnectionInitiator:initiator];
                    if (onCompletion) {
                        onCompletion(YES);
                    }
                    break;
                case AccessStateWillRelease:
                    DDLogNotice(@"[Darwin] Resource will release, disconnecting from server");
                    if (AppGroup.getCurrentType == AppGroupTypeApp) {
                        // This is happen when the Share Extension and the App is active at the same time,
                        // e.g. when sharing Debug_log via Share Extension.
                        // In this case disconnect immediately and wait a little time to reconnect.
                        [self _disconnectAndReconnectAfterDelay];
                    }
                    else {
                        [self _disconnect];
                    }
                    if (onCompletion) {
                        onCompletion(NO);
                    }
                    break;
                default:
                    DDLogError(@"[Darwin] Unknown access state %@, do not connect", [ProcessCoordinator nameForAccessState:state]);
                    if (onCompletion) {
                        onCompletion(NO);
                    }
                    break;
            }
        }];
    }
    else {
        [self _connectWithConnectionInitiator:initiator];
        if (onCompletion) {
            onCompletion(YES);
        }
    }
}

- (void)_connectWithConnectionInitiator:(ConnectionInitiator)initiator {
    if (!doUnblockIncomingMessages) {
        DDLogNotice(@"Cannot connect - incoming messages are blocked");
        return;
    }
    
    dispatch_async(socketQueue, ^{
        // TODO: Remove comment IOS-3558
        DDLogNotice(@"Entered socket queue in connect.");
        [self connectBy:initiator];
        // TODO: Remove comment IOS-3558
        DDLogNotice(@"ConnectBy finished.");

        lastErrorDisplay = nil;
        [self _connect];
        // TODO: Remove comment IOS-3558
        DDLogNotice(@"Left socket queue in connect.");
    });
}

- (void)connectWait:(ConnectionInitiator)initiator {
    if (![self checkDuplicateConnectionInExtension]) {
        return;
    }

    if ([[UserSettings sharedUserSettings] ipcCommunicationEnabled]) {
        int timeout = [self processCoordinatorPollingTimeout];

        DDLogNotice(@"[Darwin] Connect wait (timeout: %i)", timeout);
        [processCoordinator requestAccessWithPollingTimeout:timeout completionHandler:^(enum AccessState state) {
            switch (state) {
                case AccessStateUnused:
                    if ([AppGroup getCurrentType] == AppGroupTypeApp) {
                        DDLogNotice(@"[Darwin] Resource is unused (App), request access again");
                        [self connectWait:initiator];
                    }
                    else {
                        DDLogNotice(@"[Darwin] Resource is unused (NOT App), disconnecting from server");
                        [self _disconnect];
                    }
                    break;
                case AccessStateRequested:
                    DDLogNotice(@"[Darwin] Resource is requested, request access again");
                    [self connectWait:initiator];
                    break;
                case AccessStateUsing:
                    DDLogNotice(@"[Darwin] Resource is using, connecting to server");

                    [self _connectWaitWithConnectionInitiator:initiator];
                    break;
                case AccessStateWillRelease:
                    DDLogNotice(@"[Darwin] Resource will release, disconnecting from server");
                    if (AppGroup.getCurrentType == AppGroupTypeApp) {
                        // This is happen when the Share Extension and the App is active at the same time,
                        // e.g. when sharing Debug_log via Share Extension.
                        // In this case disconnect immediately and wait a little time to reconnect.
                        [self _disconnectAndReconnectAfterDelay];
                    }
                    else {
                        [self _disconnect];
                    }
                    break;
                default:
                    DDLogError(@"[Darwin] Unknown access state %@, do not connect", [ProcessCoordinator nameForAccessState:state]);
                    break;
            }
        }];
    }
    else {
        [self _connectWaitWithConnectionInitiator:initiator];
    }
}

- (void)_connectWaitWithConnectionInitiator:(ConnectionInitiator)initiator {
    if (!doUnblockIncomingMessages) {
        DDLogNotice(@"Cannot connect - incoming messages are blocked");
        return;
    }
    
    dispatch_sync(socketQueue, ^{
        // TODO: Remove comment IOS-3558
        DDLogNotice(@"Entered socket queue in connectWait.");
        [self connectBy:initiator];
        // TODO: Remove comment IOS-3558
        DDLogNotice(@"ConnectBy finished.");

        lastErrorDisplay = nil;
        [self _connect];
        // TODO: Remove comment IOS-3558
        DDLogNotice(@"Left socket queue in connectWait.");
    });
}

- (void)connectWaitDoNotUnblockIncomingMessages:(ConnectionInitiator)initiator {
    doUnblockIncomingMessages = NO;

    dispatch_sync(socketQueue, ^{
        [self connectBy:initiator];

        lastErrorDisplay = nil;
        [self _connect];
    });
}

- (void)unblockIncomingMessages {
    BOOL blockedBefore = (doUnblockIncomingMessages == NO);
    doUnblockIncomingMessages = YES;
    
    dispatch_sync(socketQueue, ^{
        if (blockedBefore && self.connectionState == ConnectionStateLoggedIn) {
            [self _unblockIncomingMessagesIfAllowed];
        }
    });
}

- (void)connectWithCurrentAppGroupType {
    ConnectionInitiator initiator;

    switch ([AppGroup getCurrentType]) {
        case AppGroupTypeApp:
        case AppGroupTypeNone:
            initiator = ConnectionInitiatorApp;
            break;
        case AppGroupTypeShareExtension:
            initiator = ConnectionInitiatorShareExtension;
            break;
        case AppGroupTypeNotificationExtension:
            initiator = ConnectionInitiatorNotificationExtension;
            break;
    }

    [self connect:initiator onCompletion:nil];
}

- (void)_connect {
    // TODO: Remove comment IOS-3558
    DDLogNotice(@"Connect began.");
    if (ProcessInfoHelper.isRunningForScreenshots)  {
        return;
    }

    if (!AppSetup.isIdentityProvisioned) {
        DDLogNotice(@"Cannot connect - identity not provisioned");
        return;
    }

    if (self.connectionState == ConnectionStateDisconnecting) {
        // The socket does not always report its disconnect and the state then remains on `disconnecting` forever.
        // Assume the callback is lost when the state is expired and connect anyway.
        if ([serverConnectorConnectionState isStateExpired]) {
            DDLogWarn(@"Connect: No disconnect reported, assume the socket is disconnected");
        }
        else {
            DDLogNotice(@"Connect: State was disconnecting.");

            if ([AppGroup getCurrentType] != AppGroupTypeNotificationExtension) {
                // The socketDidDisconnect callback has not been called yet; ensure that we reconnect
                // as soon as the previous disconnect has finished.
                autoReconnect = YES;
                [self reconnectAfterDelay];
                return;
            }
        }
    } else if (self.connectionState != ConnectionStateDisconnected) {
        if (![serverConnectorConnectionState isStateExpired]) {
            DDLogNotice(@"Abort connect, because connection state is %@", [self nameForConnectionState:self.connectionState]);
            return;
        }

        DDLogWarn(@"Connection state is %@ for too long, force disconnect", [self nameForConnectionState:self.connectionState]);

        // The state `disconnected` is set after the successful disconnect, otherwise another process will connect
        // too early and it could end in a duplicate connection.
        [serverConnectorConnectionState disconnecting];
        [socket disconnect];

        if ([AppGroup getCurrentType] != AppGroupTypeNotificationExtension) {
            // Reconnect as soon as the disconnect has finished
            autoReconnect = YES;
            [self reconnectAfterDelay];
            return;
        }
    }

    if ([[UserSettings sharedUserSettings] ipcCommunicationEnabled] == NO && [AppGroup amIActive] == NO) {
        if ([AppGroup areOthersActive] == NO) {
            DDLogNotice(@"Not active -> there are no other processes active, set me as active");
            [AppGroup setMeActive];
        }
        else {
            if ([AppGroup getCurrentType] != AppGroupTypeNotificationExtension) {
                DDLogNotice(@"Not active -> don't connect now, retry later");
                // keep delay at constant rate to avoid too long waits when becoming active again
                reconnectAttempts = 1;
                autoReconnect = YES;
                [self reconnectAfterDelay];
            }
            else {
                DDLogNotice(@"Not active -> disconnect now");
                [self _disconnect];
            }
            return;
        }
    }

    LicenseStore *licenseStore = [LicenseStore sharedLicenseStore];
    if ([licenseStore isValid] == NO) {
        // TODO: Remove comment IOS-3558
        DDLogNotice(@"LicenseStore was invalid.");
        [licenseStore performLicenseCheckWithCompletion:^(BOOL success) {
            // TODO: Remove comment IOS-3558
            DDLogNotice(@"LicenseStore perform check.");
            if (success) {
                [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationLicenseCheckSuccess object:nil];
                [self _connect];
            } else {
                // don't show license warning for connection errors
                DDLogNotice(@"License check failed: %@", licenseStore.error);
                if ([licenseStore.error.domain hasPrefix:@"NSURL"] == NO && licenseStore.error.code != 256) {                    
                    // License check failed permanently; need to inform user and ask for new license username/password
                    [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationLicenseMissing object:nil];
                }
                else if ([licenseStore.error.domain hasPrefix:@"NSURL"] == YES && licenseStore.error.code == -1009 && ![[LicenseStore sharedLicenseStore] isWithinOfflineInterval]) {
                    // License check failed because we don't have network connection. WithinCheckInterval for license failed, show license screen
                    [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationLicenseMissing object:nil];
                }
                else {
                    // License check failed due to connection error – try again later
                    // TODO: Remove comment IOS-3558
                    DDLogNotice(@"LicenseStore failed, reconnecting after delay.");
                    autoReconnect = YES;
                    [self reconnectAfterDelay];
                }
            }
        }];
        
        return;
    }

    [licenseStore performUpdateWorkInfo];

    [serverConnectorConnectionState connecting];
    autoReconnect = YES;
    self.lastRtt = -1;
    lastRead = CACurrentMediaTime();
    chatServerInInitialQueueSend = YES;
    mediatorServerInInitialQueueSend = YES;
    
    /* Reset nonces for new connection */
    clientNonce = 1;
    serverNonce = 1;
    
    /* Generate a new ephemeral key pair for the server connection. */
    NSData *publicKey, *secretKey;
    [[NaClCrypto sharedCrypto] generateKeyPairPublicKey:&publicKey secretKey:&secretKey];
    clientTempKeyPub = publicKey;
    clientTempKeySec = secretKey;
#if LOG_KEY_INFO
    DDLogVerbose(@"Client tempkey_pub = %@, tempkey_sec = %@", clientTempKeyPub, clientTempKeySec);
#endif

    DeviceGroupKeyManager *deviceGroupKeyManager = [DeviceGroupKeyManager new];
    NSData *dgk = deviceGroupKeyManager.dgk;
    if (!dgk) {
        DDLogNotice(@"Connect direct.");
        // TODO: Remove comment IOS-3558
        [self _connectDirect];
    } else {
        DDLogNotice(@"Connect mediator.");
        // TODO: Remove comment IOS-3558
        [self _connectViaMediator:dgk];
    }
}

- (void)_connectDirect {
    // TODO: Remove comment IOS-3558
    DDLogNotice(@"Connect direct started.");
    // Multi device is not activated, reset device group keys and device ID
    @synchronized (deviceGroupKeys) {
        deviceGroupKeys = nil;
        deviceID = nil;
    };

    // Obtain chat server host/ports/keys from ServerInfoProvider
    [[ServerInfoProviderFactory makeServerInfoProvider] chatServerWithIpv6:[UserSettings sharedUserSettings].enableIPv6 completionHandler:^(ChatServerInfo * _Nullable chatServerInfo, NSError *error) {
        // TODO: Remove comment IOS-3558
        DDLogNotice(@"ChatServerWithIpv6 started.");
        dispatch_async(socketQueue, ^{
            // TODO: Remove comment IOS-3558
            DDLogNotice(@"Connect direct socket queue entered.");
            if (chatServerInfo == nil) {
                // Could not get the info at the moment; try again later
                [serverConnectorConnectionState disconnected];
                [self reconnectAfterDelay];
                return;
            }
            
            NSString *serverHost;
            if (chatServerInfo.useServerGroups) {
                serverHost = [NSString stringWithFormat:@"%@%@%@", chatServerInfo.serverNamePrefix, [MyIdentityStore sharedMyIdentityStore].serverGroup, chatServerInfo.serverNameSuffix];
            } else {
                serverHost = chatServerInfo.serverNameSuffix;
            }
            
            serverKeyPub = chatServerInfo.publicKey;
            serverAltKeyPub = chatServerInfo.publicKeyAlt;
            
            UserSettings *settings = [UserSettings sharedUserSettings];

            NSError *socketError;
            socket = [[ChatTcpSocket alloc] initWithServer:serverHost ports:chatServerInfo.serverPorts preferIPv6:settings.enableIPv6 delegate:self queue:socketQueue error:&socketError]='color:green'>;
            
            if (socketError != nil || ![socket connect]) {
                // TODO: Remove comment IOS-3558
                DDLogNotice(@"Connect direct error.");
                [serverConnectorConnectionState disconnected];
                [self reconnectAfterDelay];
                return;
            }
            // TODO: Remove comment IOS-3558
            DDLogNotice(@"Connect direct socket queue left.");
        });
    }];
}

- (void)_connectViaMediator:(nonnull NSData *)dgk {
    // Derive multi device keys
    NSError *deriveKeyError;
    DeviceGroupDerivedKey *deviceGroupDerivedKey = [[DeviceGroupDerivedKey alloc] initWithDgk:dgk error:&deriveKeyError];

    if (deriveKeyError) {
        DDLogError(@"Device Group Keys could not be derived");
        [serverConnectorConnectionState disconnected];
        [self reconnectAfterDelay];
        return;
    }

    // Multi device is activated, check device ID
    NSData *deviceGroupID;

    @synchronized (deviceGroupKeys) {
        deviceGroupID = [[NaClCrypto sharedCrypto] derivePublicKeyFromSecretKey:deviceGroupDerivedKey.dgpk];

        const unsigned char *deviceGroupIDBytes = [deviceGroupID bytes];
        NSString *deviceGroupIDFirstByteHex = [NSString stringWithFormat:@"%02lx", (unsigned long)deviceGroupIDBytes[0]];

        NSLog(@"%@", [NSString stringWithHexData:deviceGroupID]);
        deviceGroupKeys = [[DeviceGroupKeys alloc] initWithDgpk:deviceGroupDerivedKey.dgpk dgrk:deviceGroupDerivedKey.dgrk dgdik:deviceGroupDerivedKey.dgdik dgsddk:deviceGroupDerivedKey.dgsddk dgtsk:deviceGroupDerivedKey.dgtsk deviceGroupIDFirstByteHex:deviceGroupIDFirstByteHex];

        KeychainManager *keychainManager = [[BusinessInjector ui] keychainManagerObjC];
        deviceID = [keychainManager loadMultiDeviceIDObjC];

        if (deviceID == nil || [deviceID length] != kDeviceIdLen) {
            deviceID = [NSData dataWithBytes:[[NaClCrypto sharedCrypto] randomBytes:kDeviceIdLen].bytes length:kDeviceIdLen];

            NSError *error = nil;
            [keychainManager storeMultiDeviceIDWithId:deviceID error:&error];
            if (error) {
                NSString *errorMessage = [NSString stringWithFormat:@"Couldn't store device ID in Keychain: %@", error.localizedDescription];
                DDLogError(@"%@", errorMessage);
                [NSException raise:error.domain.description format:@"%@", errorMessage];
            }
        }
    };

    NSAssert([deviceID length] == kDeviceIdLen, @"Device ID has wrong length");

    NSString *clientUrlInfo = [MediatorMessageProtocol encodeClientURLInfoWithDgpkPublicKey:deviceGroupID serverGroup:[MyIdentityStore sharedMyIdentityStore].serverGroup];

    id<ServerInfoProvider> serverInfoProvider = [ServerInfoProviderFactory makeServerInfoProvider];
    [serverInfoProvider mediatorServerWithDeviceGroupIDFirstByteHex: deviceGroupKeys.deviceGroupIDFirstByteHex completionHandler:^(MediatorServerInfo * _Nullable mediatorServerInfo, NSError * _Nullable mediatorServerError) {
        // Obtain chat server info too (for public keys)
        [serverInfoProvider chatServerWithIpv6:[UserSettings sharedUserSettings].enableIPv6 completionHandler:^(ChatServerInfo * _Nullable chatServerInfo, NSError * _Nullable chatServerError) {
            if (mediatorServerInfo == nil || chatServerInfo == nil) {
                // Could not get the info at the moment; try again later
                [serverConnectorConnectionState disconnected];
                [self reconnectAfterDelay];
                return;
            }
            
            NSString *server = [NSString stringWithFormat:@"%@/%@", mediatorServerInfo.url, clientUrlInfo];
            serverKeyPub = chatServerInfo.publicKey;
            serverAltKeyPub = chatServerInfo.publicKeyAlt;

            UserSettings *settings = [UserSettings sharedUserSettings];
            NSError *socketError;
            socket = [[MediatorWebSocket alloc] initWithServer:server ports:@[] preferIPv6:settings.enableIPv6 delegate:self queue:socketQueue error:&socketError];
            
            if (socketError != nil || ![socket connect]) {
                [serverConnectorConnectionState disconnected];
                [self reconnectAfterDelay];
                return;
            }
        }];
    }];
}

- (void)_disconnect {
    if ([serverConnectorConnectionState connectionState] == ConnectionStateDisconnected) {
        return;
    }
    
    /* disconnect socket and make sure we don't reconnect */
    autoReconnect = NO;
    [serverConnectorConnectionState disconnecting];
    [socket disconnect];
}

/**
 Disconnect anyway and reconnect after a little delay
 */

- (void)_disconnectAndReconnectAfterDelay {
    [self _disconnect];

    // Keep delay at constant rate to avoid too long waits when becoming active again
    reconnectAttempts = 1;
    autoReconnect = YES;
    [self reconnectAfterDelay];
}

- (void)disconnect:(ConnectionInitiator)initiator {
    [self disconnect:initiator force:NO];
}

- (void)disconnect:(ConnectionInitiator)initiator force:(BOOL)force {
    dispatch_async(socketQueue, ^{
        if ([self isConnectionStillRequestedAfterDisconnectBy:initiator] == NO || force == YES) {
            [self _disconnect];
        }
    });
}

- (BOOL)disconnectWait:(ConnectionInitiator)initiator {
    __block BOOL isDisconnected = NO;

    dispatch_sync(socketQueue, ^{
        if ([self isConnectionStillRequestedAfterDisconnectBy:initiator] == NO) {
            [self _disconnect];
            isDisconnected = YES;
        }
    });

    if (isDisconnected) {
        [serverConnectorConnectionState waitForStateDisconnected];
    }

    return isDisconnected;
}

- (void)reconnect {
    dispatch_async(socketQueue, ^{
        if ([serverConnectorConnectionState connectionState] == ConnectionStateDisconnected) {
            [self connectWithCurrentAppGroupType];
        } else if ([serverConnectorConnectionState connectionState] == ConnectionStateConnecting) {
            DDLogVerbose(@"Connection already in progress, not reconnecting");
        } else {
            autoReconnect = YES;
            [serverConnectorConnectionState disconnecting];
            [socket disconnect];
        }
    });
}

- (ConnectionState)connectionState {
    return [serverConnectorConnectionState connectionState];
}

#pragma mark - Chat (Mediator) Server connection initiator handling

- (void)connectBy:(ConnectionInitiator)initiator {
    if (![connectionInitiators containsObject:[NSNumber numberWithInteger:initiator]]) {
        [connectionInitiators addObject:[NSNumber numberWithInteger:initiator]];
    }

    DDLogNotice(@"[ConnectionInitiator] Connect requested by %@, connection is requested by: %@", [self nameForConnectionInitiator:initiator], [self namesForConnectionInitiators]);
}

- (BOOL)isConnectionStillRequestedAfterDisconnectBy:(ConnectionInitiator)initiator {
    [connectionInitiators removeObject:[NSNumber numberWithInteger:initiator]];

    if ([connectionInitiators count] != 0) {
        DDLogNotice(@"[ConnectionInitiator] Disconnect requested by %@, but connection is still requested by: %@", [self nameForConnectionInitiator:initiator], [self namesForConnectionInitiators]);
        return YES;
    }

    DDLogNotice(@"[ConnectionInitiator] Disconnect requested by %@, connection is requested by nobody anymore, disconnect now", [self nameForConnectionInitiator:initiator]);
    return NO;
}

- (NSString *)namesForConnectionInitiators {
    NSMutableString *names = [NSMutableString new];

    for (NSNumber *item in connectionInitiators) {
        if ([names length] > 0) {
            [names appendString:@", "];
        }
        [names appendString:[self nameForConnectionInitiator:(ConnectionInitiator)[item intValue]]];
    }

    return names;
}

- (NSString *)nameForConnectionInitiator:(ConnectionInitiator)initiator {
    switch (initiator) {
        case ConnectionInitiatorApp:
            return @"App";
        case ConnectionInitiatorNotificationExtension:
            return @"NotificationExtension";
        case ConnectionInitiatorNotificationHandler:
            return @"NotificationHandler";
        case ConnectionInitiatorShareExtension:
            return @"ShareExtension";
        case ConnectionInitiatorThreemaCall:
            return @"ThreemaCall";
        case ConnectionInitiatorThreemaWeb:
            return @"ThreemaWeb";
        default:
            return nil;
    }
}

#pragma mark - Processing incoming payloads

- (void)processPayload:(struct pktPayload*)pl datalen:(int)datalen {
    
    switch (pl->type) {
        case PLTYPE_ECHO_RESPONSE: {
            self.lastRtt = CACurrentMediaTime() - lastEchoSendTime;
            if (datalen == sizeof(lastRcvdEchoSeq)) {
                memcpy(&lastRcvdEchoSeq, pl->data, sizeof(lastRcvdEchoSeq));
            } else {
                DDLogError(@"Bad echo reply datalen %d", datalen);
                [socket disconnect];
                break;
            }
            DDLogInfo(@"Received echo reply (seq %llu, RTT %.1f ms)", lastRcvdEchoSeq, self.lastRtt * 1000);
            break;
        }
        case PLTYPE_ERROR: {
            if (datalen < sizeof(struct plError)) {
                DDLogError(@"Bad error payload datalen %d", datalen);
                [socket disconnect];
                break;
            }
            struct plError *plerr = (struct plError*)pl->data;
            NSData *errorMessageData = [NSData dataWithBytes:plerr->err_message length:datalen - sizeof(struct plError)];
            NSString *errorMessage = [[NSString alloc] initWithData:errorMessageData encoding:NSUTF8StringEncoding];
            DDLogError(@"Received error message from server: %@", errorMessage);

            if ([errorMessage rangeOfString:@"Another connection"].location != NSNotFound) {
                [self showAnotherConnectionAlertWithReconnectAllowed:plerr->reconnect_allowed ? YES : NO];
            }
            else {
                if (!plerr->reconnect_allowed) {
                    autoReconnect = NO;
                }

                NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys: errorMessage, kKeyMessage, nil];
                [self showConnectionAlertWithInfo:info];
            }

            break;
        }
        case PLTYPE_ALERT: {
            NSData *alertData = [NSData dataWithBytes:pl->data length:datalen];
            NSString *alertText = [[NSString alloc] initWithData:alertData encoding:NSUTF8StringEncoding];
            [self displayServerAlert:alertText];
            break;
        }
        case PLTYPE_OUTGOING_MESSAGE_ACK: {
            if (datalen != sizeof(struct plMessageAck)) {
                DDLogError(@"Bad ACK payload datalen %d", datalen);
                [socket disconnect];
                break;
            }
            
            struct plOutgoingMessageAck *ack = (struct plOutgoingMessageAck*)pl->data;
            NSData *messageId = [NSData dataWithBytes:ack->message_id length:kMessageIdLen];
            NSString *toIdentity = [[NSString alloc] initWithData:[NSData dataWithBytes:ack->to_identity length:kIdentityLen] encoding:NSASCIIStringEncoding];
            [[NSNotificationCenter defaultCenter] postNotificationName:[TaskManager chatMessageAckObserverNameWithMessageID:messageId toIdentity:toIdentity] object:nil];
            break;
        }
        case PLTYPE_INCOMING_MESSAGE: {
            if (datalen <= sizeof(struct plMessage)) {
                DDLogError(@"Bad message payload datalen %d", datalen);
                [socket disconnect];
                break;
            }
            
            if (((![[UserSettings sharedUserSettings] ipcCommunicationEnabled] && [AppGroup amIActive])
                  || [[UserSettings sharedUserSettings] ipcCommunicationEnabled])
                 && [AppGroup getCurrentType] != AppGroupTypeShareExtension) {
                struct plMessage *plmsg = (struct plMessage*)pl->data;
                int minlen = (sizeof(struct plMessage) + kNonceLen + plmsg->metadata_len + kNaClBoxOverhead + 1);
                if (datalen <= minlen || (plmsg->metadata_len > 0 && plmsg->metadata_len <= kNaClBoxOverhead)) {
                    DDLogError(@"Bad message payload datalen %d, metadata_len %d", datalen, plmsg->metadata_len);
                    [socket disconnect];
                    break;
                }
                
                BoxedMessage *boxmsg = [[BoxedMessage alloc] init];
                boxmsg.fromIdentity = [[NSString alloc] initWithData:[NSData dataWithBytes:plmsg->from_identity length:kIdentityLen] encoding:NSASCIIStringEncoding];
                boxmsg.toIdentity = [[NSString alloc] initWithData:[NSData dataWithBytes:plmsg->to_identity length:kIdentityLen] encoding:NSASCIIStringEncoding];
                boxmsg.messageId = [NSData dataWithBytes:plmsg->message_id length:kMessageIdLen];
                boxmsg.date = [NSDate dateWithTimeIntervalSince1970:plmsg->date];
                boxmsg.flags = plmsg->flags;
                char pushFromNameT[kPushFromNameLen+1];
                memcpy(pushFromNameT, plmsg->push_from_name, kPushFromNameLen);
                pushFromNameT[kPushFromNameLen] = 0;
                boxmsg.pushFromName = [NSString stringWithCString:pushFromNameT encoding:NSUTF8StringEncoding];
                if (plmsg->metadata_len > 0) {
                    boxmsg.metadataBox = [NSData dataWithBytes:&plmsg->metadata_nonce_box length:plmsg->metadata_len];
                }
                boxmsg.nonce = [NSData dataWithBytes:&plmsg->metadata_nonce_box[plmsg->metadata_len] length:kNonceLen];
                boxmsg.box = [NSData dataWithBytes:&plmsg->metadata_nonce_box[plmsg->metadata_len + kNonceLen] length:(datalen - sizeof(struct plMessage) - kNonceLen - plmsg->metadata_len)];

                // Set time out for downloading thumbnail to 5s, if the app in background or notification extension is running
                int timeoutDownloadThumbnail = isAppInBackground || [AppGroup getCurrentType] == AppGroupTypeNotificationExtension ? 5 : 0;

                TaskDefinitionReceiveMessage *task = [[TaskDefinitionReceiveMessage alloc] initWithMessage:boxmsg receivedAfterInitialQueueSend:!chatServerInInitialQueueSend maxBytesToDecrypt:[AppGroup getCurrentType] != AppGroupTypeNotificationExtension ? MAX_BYTES_TO_DECRYPT_NO_LIMIT : MAX_BYTES_TO_DECRYPT_NOTIFICATION_EXTENSION timeoutDownloadThumbnail:timeoutDownloadThumbnail];

                // Use `[self backgroundEntityManagerForMessageProcessing]` if is not nil (properly setted from Notification Extension), otherwise nil (means will be created within TaskManager) for in App processing
                TaskManager *tm = [[TaskManager alloc] initWithBackgroundEntityManager:[self backgroundEntityManagerForMessageProcessing] serverConnector:self];
                [tm addObjcWithTaskDefinition:task];
            }
            break;
        }
        case PLTYPE_QUEUE_SEND_COMPLETE:
            DDLogInfo(@"Queue send complete");
            chatServerInInitialQueueSend = NO;
            
            [self chatQueueDry];
            break;
        case PLTYPE_DEVICE_COOKIE_CHANGE_INDICATION: {
            DDLogWarn(@"Got device cookie change indication");
            if ([DeviceCookieManager changeIndicationReceived]) {
                [self clearDeviceCookieChangedIndicator];
            }
            break;
        }
        default:
            DDLogWarn(@"Unsupported payload type %d", pl->type);
            break;
    }
}

- (void)completedProcessingAbstractMessage:(AbstractMessage *)msg {
    if (!(msg.flags.intValue & MESSAGE_FLAG_DONT_ACK)) {
        /* send ACK to server */
        [self ackMessage:msg.messageId fromIdentity:msg.fromIdentity];
    }
}

- (BOOL)completedProcessingMessage:(BoxedMessage *)boxmsg {
    if (!(boxmsg.flags & MESSAGE_FLAG_DONT_ACK)) {
        /* send ACK to server */
        return [self ackMessage:boxmsg.messageId fromIdentity:boxmsg.fromIdentity];
    }
    return YES;
}

- (void)reconnectAfterDelay {
    // Never reconnect for the notification extension
    if (!autoReconnect || [AppGroup getCurrentType] == AppGroupTypeNotificationExtension) {
        return;
    }
    
    /* calculate delay using bound exponential backoff */
    float reconnectDelay = powf(kReconnectBaseInterval, MIN(reconnectAttempts - 1, 10));
    if (reconnectDelay > kReconnectMaxInterval) {
        reconnectDelay = kReconnectMaxInterval;
    }
    
    if (!isWaitingForReconnect) {
        isWaitingForReconnect = true;
        reconnectAttempts++;
        DDLogNotice(@"Waiting %f seconds before reconnecting", reconnectDelay);
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, reconnectDelay * NSEC_PER_SEC);
        dispatch_after(popTime, socketQueue, ^(void){
            isWaitingForReconnect = false;
            [self connectWithCurrentAppGroupType];
        });
    }
}

- (BOOL)sendPayloadWithType:(uint8_t)type data:(NSData*)data {
    if ([serverConnectorConnectionState connectionState] != ConnectionStateLoggedIn) {
        DDLogVerbose(@"Cannot send payload - not logged in");
        return NO;
    }
    
    /* Make encrypted box */
    unsigned long pllen = sizeof(struct pktPayload) + data.length;
    struct pktPayload *pl = malloc(pllen);
    if (!pl) {
        return NO;
    }
    
    memset(pl, 0, pllen);
    
    pl->type = type;
    memcpy(pl->data, data.bytes, data.length);
    
    NSData *plData = [NSData dataWithBytesNoCopy:pl length:pllen];
    
    __block BOOL isSent = NO;
    
    // Gets next client nonce not before the message was sent
    dispatch_barrier_sync(sendMessageQueue, ^{
        NSData *nextClientNonce = [self nextClientNonce];
        NSData *plBox = [[NaClCrypto sharedCrypto] encryptData:plData withPublicKey:serverTempKeyPub signKey:clientTempKeySec nonce:nextClientNonce];
        if (plBox == nil) {
            DDLogError(@"Payload encryption failed!");
            isSent = NO;
        }
        else {
            /* prepend length - make one NSData object to pass to socket to ensure it is sent
               in a single TCP segment */

            uint16_t pktlen = plBox.length;
            
            if (pktlen > kMaxPktLen) {
                DDLogError(@"Packet is too big (%d) - cannot send", pktlen);
                isSent = NO;
            }
            else {
                NSMutableData *sendData = [NSMutableData dataWithCapacity:plBox.length + sizeof(uint16_t)];
                [sendData appendBytes:&pktlen length:sizeof(uint16_t)];
                [sendData appendData:plBox];
                
                [socket writeWithData:sendData tag:TAG_PAYLOAD_SENT];
                
                isSent = YES;
            }
        }
    });
    
    return isSent;
}

- (BOOL)sendMessage:(BoxedMessage*)message {
    unsigned long msglen = sizeof(struct plMessage) + message.metadataBox.length + message.nonce.length + message.box.length;
    struct plMessage *plmsg = malloc(msglen);
    if (!plmsg) {
        return NO;
    }
    
    DDLogInfo(@"Sending message from %@ to %@ (ID %@), metadata box length %lu, box length %lu", message.fromIdentity,
          message.toIdentity, message.messageId, (unsigned long)message.metadataBox.length, (unsigned long)message.box.length);
    
    memcpy(plmsg->from_identity, [message.fromIdentity dataUsingEncoding:NSASCIIStringEncoding].bytes, kIdentityLen);
    memcpy(plmsg->to_identity, [message.toIdentity dataUsingEncoding:NSASCIIStringEncoding].bytes, kIdentityLen);
    memcpy(plmsg->message_id, message.messageId.bytes, kMessageIdLen);
    // Timestamp is now in encrypted metadata, so we always send 0. This will then be set by the server.
    plmsg->date = 0;
    plmsg->flags = message.flags;
    plmsg->reserved = 0;
    plmsg->metadata_len = message.metadataBox.length;
    memset(plmsg->push_from_name, 0, kPushFromNameLen);
    if (message.pushFromName != nil) {
        NSData *encodedPushFromName = [ThreemaUtilityObjC truncatedUTF8String:message.pushFromName maxLength:kPushFromNameLen];
        strncpy(plmsg->push_from_name, encodedPushFromName.bytes, encodedPushFromName.length);
    }
    
    size_t offset = 0;
    if (message.metadataBox != nil) {
        memcpy(&plmsg->metadata_nonce_box[offset], message.metadataBox.bytes, message.metadataBox.length);
        offset += message.metadataBox.length;
    }
    memcpy(&plmsg->metadata_nonce_box[offset], message.nonce.bytes, message.nonce.length);
    offset += message.nonce.length;
    memcpy(&plmsg->metadata_nonce_box[offset], message.box.bytes, message.box.length);
    
    return [self sendPayloadWithType:PLTYPE_OUTGOING_MESSAGE data:[NSData dataWithBytesNoCopy:plmsg length:msglen]];
}

- (NSError * _Nullable)reflectMessage:(NSData *)message {

    if (message == nil) {
        return [ThreemaError threemaError:[NSString stringWithFormat:@"Bad message"] withCode:ThreemaProtocolErrorBadMessage];
    }
    
    if ([(NSObject*)socket isKindOfClass:[MediatorWebSocket class]] == NO) {
        return [ThreemaError threemaError:[NSString stringWithFormat:@"Not connected to mediator"] withCode:ThreemaProtocolErrorNotConnectedToMediator];
    }

    if ([serverConnectorConnectionState connectionState] != ConnectionStateLoggedIn) {
        return [ThreemaError threemaError:[NSString stringWithFormat:@"Not logged in"] withCode:ThreemaProtocolErrorNotLoggedIn];
    }

    [socket writeWithData:message];
    return nil;
}

- (BOOL)ackMessage:(NSData*)messageId fromIdentity:(NSString*)fromIdentity {
    int msglen = sizeof(struct plMessageAck);
    struct plMessageAck *plmsgack = malloc(msglen);
    if (!plmsgack)
        return NO;
    
    DDLogInfo(@"Sending ack for message ID %@ from %@", messageId, fromIdentity);
    
    memcpy(plmsgack->from_identity, [fromIdentity dataUsingEncoding:NSASCIIStringEncoding].bytes, kIdentityLen);
    memcpy(plmsgack->message_id, messageId.bytes, kMessageIdLen);
    
    return [self sendPayloadWithType:PLTYPE_INCOMING_MESSAGE_ACK data:[NSData dataWithBytesNoCopy:plmsgack length:msglen]];
}

- (void)ping {
    dispatch_async(socketQueue, ^{
        [self sendEchoRequest];
    });
}

- (void)sendEchoRequest {
    [self sendEchoRequestWithTimeout:kEchoRequestTimeout];
}

- (void)sendEchoRequestWithTimeout:(NSTimeInterval)timeout {
    if ([serverConnectorConnectionState connectionState] != ConnectionStateLoggedIn)
        return;
    
    lastSentEchoSeq++;
    DDLogInfo(@"Sending echo request (seq %llu)", lastSentEchoSeq);
    
    lastEchoSendTime = CACurrentMediaTime();
    [self sendPayloadWithType:PLTYPE_ECHO_REQUEST data:[NSData dataWithBytes:&lastSentEchoSeq length:sizeof(lastSentEchoSeq)]];
    
    id<SocketProtocol> curSocket = socket;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){
        if (curSocket == socket && lastRcvdEchoSeq < lastSentEchoSeq) {
            DDLogInfo(@"No reply to echo payload; disconnecting");
            [socket disconnect];
        }
    });
}

- (void)sendConnectionIdleTimeout {
    if ([serverConnectorConnectionState connectionState] != ConnectionStateLoggedIn)
        return;
    
    uint16_t connectionIdleTimeout = [UserSettings sharedUserSettings].enableMultiDevice ? kConnectionIdleMDTimeout : kConnectionIdleTimeout;
    if (connectionIdleTimeout < 30) {
        connectionIdleTimeout = 30;
        DDLogError(@"[ServerConnector] Set connection idle timeout can be min 30 seconds!");
    }
    else if (connectionIdleTimeout > 600) {
        connectionIdleTimeout = 600;
        DDLogError(@"[ServerConnector] Set connection idle timeout can be max 600 seconds!");
    }
    
    DDLogInfo(@"Sending connection idle timeout");
    [self sendPayloadWithType:PLTYPE_SET_CONNECTION_IDLE_TIMEOUT data: [NSData dataWithBytes:&connectionIdleTimeout length:sizeof(UInt16)]];
}

#pragma mark - Unblocking

- (void)_unblockIncomingMessagesIfAllowed {
    /* Unblock incoming messages if not running multi-device or already promoted to leader */
    if (doUnblockIncomingMessages && (deviceID == nil || [deviceID length] != kDeviceIdLen || isRolePromotedToLeader)) {
        [self sendPayloadWithType:PLTYPE_UNBLOCK_INCOMING_MESSAGES data:[NSData data]];
    }
}

#pragma mark - Multi-Device

- (BOOL)isMultiDeviceActivated {
    @synchronized (deviceGroupKeys) {
        return deviceGroupKeys != nil && deviceID != nil;
    }
}

- (void)deactivateMultiDevice {
    @synchronized (deviceGroupKeys) {
        [DeviceGroupKeyManager destroy];
        [UserSettings sharedUserSettings].enableMultiDevice = NO;
        // Ensure that the change is observed by SettingStores
        [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationSettingStoreSynchronization object:nil];
        deviceGroupKeys = nil;
        deviceID = nil;
        doUnblockIncomingMessages = YES;
    };
}

- (void)setIsRolePromotedToLeader:(BOOL)newIsRolePromotedToLeader {
    if (isRolePromotedToLeader != newIsRolePromotedToLeader) {
        isRolePromotedToLeader = newIsRolePromotedToLeader;
    }

    if ([[UserSettings sharedUserSettings] enableMultiDevice] == YES) {
        DDLogNotice(@"[Config] Multi-Device: Am I promoted to leader? %@", isRolePromotedToLeader ? @"YES" : @"NO");
    }
}

#pragma mark - Push Notification

- (BOOL)shouldRegisterPush {
    return [serverConnectorConnectionState connectionState] == ConnectionStateLoggedIn;
}

- (void)setPushToken:(NSData *)pushToken {
    [[AppGroup userDefaults] setObject:pushToken forKey:kPushNotificationDeviceToken];
    [[AppGroup userDefaults] synchronize];
    [self sendPushToken];
}

- (void)sendPushToken {
    dispatch_sync(sendPushTokenQueue, ^{
        NSData *pushToken = [[AppGroup userDefaults] objectForKey:kPushNotificationDeviceToken];
        
        if ([self shouldRegisterPush] == NO || pushToken == nil) {
            return;
        }

        NSData *pushEncryptionKey = [KeychainManager loadPushNotificationEncryptionKeyObjC];
        if (pushEncryptionKey == nil) {
            DDLogError(@"Cannot send push notification token (apple mc), because the push encryption key is missing");
            return;
        }

        DDLogInfo(@"Sending push notification token (apple mc)");
        
#ifdef DEBUG
        uint8_t pushTokenType = PUSHTOKEN_TYPE_APPLE_SANDBOX_MC;
#else
        uint8_t pushTokenType = PUSHTOKEN_TYPE_APPLE_PROD_MC;
#endif
        
        NSMutableData *payloadData = [NSMutableData dataWithBytes:&pushTokenType length:1];
        [payloadData appendData:pushToken];
        [payloadData appendData:[@"|" dataUsingEncoding:NSUTF8StringEncoding]];
        [payloadData appendData:[[BundleUtil threemaAppIdentifier] dataUsingEncoding:NSASCIIStringEncoding]];
        [payloadData appendData:[@"|" dataUsingEncoding:NSUTF8StringEncoding]];
        [payloadData appendData:pushEncryptionKey];
        [self sendPayloadWithType:PLTYPE_PUSH_NOTIFICATION_TOKEN data:payloadData];
    });
}

- (void)removePushToken {
    dispatch_sync(sendPushTokenQueue, ^{
        if ([[AppGroup userDefaults] objectForKey:kPushNotificationDeviceToken] != nil) {
            [[AppGroup userDefaults] setObject:nil forKey:kPushNotificationDeviceToken];
            [[AppGroup userDefaults] synchronize];
        }
        else {
            DDLogInfo(@"Already removed push notification token (apple mc)");
            return;
        }
        
        if ([self shouldRegisterPush] == NO) {
            return;
        }

        DDLogInfo(@"Clearing push notification token (apple mc)");

        uint8_t pushTokenType = PUSHTOKEN_TYPE_NONE;
        NSData *payloadData = [NSData dataWithBytes:&pushTokenType length:1];
        [self sendPayloadWithType:PLTYPE_PUSH_NOTIFICATION_TOKEN data:payloadData];
    });
}

- (void)removeVoIPPushToken {
    dispatch_sync(removeVoIPPushTokenQueue, ^{
        
        if (isRemovedVoIPPushToken == YES) {
            DDLogInfo(@"Already removed VoIP push token (apple)");
            return;
        }

        if([[AppGroup userDefaults] objectForKey:kVoIPPushNotificationDeviceToken] != nil) {
            [[AppGroup userDefaults] setObject:nil forKey:kVoIPPushNotificationDeviceToken];
            [[AppGroup userDefaults] synchronize];
        }
 
        if ([self shouldRegisterPush] == NO) {
            return;
        }
        
        DDLogInfo(@"Clearing VoIP push token (apple)");
        
        uint8_t voIPPushTokenType = PUSHTOKEN_TYPE_NONE;
        NSData *payloadData = [NSData dataWithBytes:&voIPPushTokenType length:1];
        [self sendPayloadWithType:PLTYPE_VOIP_PUSH_NOTIFICATION_TOKEN data:payloadData];
        
        isRemovedVoIPPushToken = YES;
    });
}

- (void)clearDeviceCookieChangedIndicator {
    [self sendPayloadWithType:PLTYPE_CLEAR_DEVICE_COOKIE_CHANGE_INDICATION data:[NSData data]];
}

#pragma mark - Nonces and encryption

- (NSData*)nextClientNonce {
    char nonce[kNaClCryptoNonceSize];
    memcpy(nonce, clientCookie.bytes, kCookieLen);
    memcpy(&nonce[kCookieLen], &clientNonce, sizeof(clientNonce));
    clientNonce++;
    return [NSData dataWithBytes:nonce length:kNaClCryptoNonceSize];
}

- (NSData*)nextServerNonce {
    char nonce[kNaClCryptoNonceSize];
    memcpy(nonce, serverCookie.bytes, kCookieLen);
    memcpy(&nonce[kCookieLen], &serverNonce, sizeof(serverNonce));
    serverNonce++;
    return [NSData dataWithBytes:nonce length:kNaClCryptoNonceSize];
}

- (NSData *)encryptData:(NSData *)message key:(NSData *)key {
    if (message == nil) {
        return nil;
    }
    
    NSData *nonce = [[NaClCrypto sharedCrypto] randomBytes:kNonceLen];
    NSData *encryptedMessage = [[NaClCrypto sharedCrypto] symmetricEncryptData:message withKey:key nonce:nonce];

    NSMutableData *encryptedData = [[NSMutableData alloc] initWithData:nonce];
    [encryptedData appendData:encryptedMessage];
    
    return encryptedData;
}

#pragma mark - Connection state

- (NSString *)nameForConnectionState:(ConnectionState)state {
    return [serverConnectorConnectionState nameForConnectionState:state];
}

- (BOOL)isIPv6Connection {
    return [socket isIPv6];
}

- (BOOL)isProxyConnection {
    return [socket isProxyConnection];
}

- (void)networkStatusDidChange:(NSNotification *)notice
{
    if (![reachabilityWrapper didLastConnectionTypeChange]) {
        return;
    }

    ConnectionState state = [serverConnectorConnectionState connectionState];
    if (state == ConnectionStateLoggedIn && ![reachabilityWrapper isReachabilityUnavailable]) {
        // The connection may still use the previous network, check it now instead of waiting for the next echo.
        // Slow connections get more time (`lastRtt` is -1 if unknown), at most the regular timeout.
        NSTimeInterval timeout = MIN(MAX(kNetworkChangeEchoRequestTimeout, self.lastRtt * 4), kEchoRequestTimeout);
        DDLogNotice(@"Network type changed - checking connection (timeout %.1f s)", timeout);
        dispatch_async(socketQueue, ^{
            [self sendEchoRequestWithTimeout:timeout];
        });
        return;
    }

    if ([reachabilityWrapper isReachabilityUnavailable] || state == ConnectionStateDisconnected || state == ConnectionStateDisconnecting) {
        if ([AppGroup getCurrentType] != AppGroupTypeNotificationExtension) {
            DDLogNotice(@"Internet status is or was unavailable - forcing reconnect");
            [self reconnect];
        }
        else {
            DDLogNotice(@"Internet status is or was unavailable - disconnect Notification Extension");
            [serverConnectorConnectionState disconnecting];
            [socket disconnect];
        }
    }
}

#pragma mark - Display server / connection alert

- (void)displayServerAlert:(NSString*)alertText {

    if ([displayedServerAlerts containsObject:alertText]) {
        return;
    }

    /* not shown before */
    NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
                          alertText, kKeyMessage,
                          nil];

    [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationServerMessage object:nil userInfo:info];

    [displayedServerAlerts addObject:alertText];
}

- (void)showAnotherConnectionAlertWithReconnectAllowed:(BOOL)isReconnectAllowed {
    // If I am not active than an extension took over the connection
    if ([[UserSettings sharedUserSettings] ipcCommunicationEnabled] == NO && [AppGroup amIActive] == NO) {
        return;
    }

    // Ignore first few occurrences of "Another connection" messages to gracefully handle network switches
    if ([[UserSettings sharedUserSettings] ipcCommunicationEnabled] == NO && anotherConnectionCount < 5) {
        anotherConnectionCount++;
        return;
    }

    if (!isReconnectAllowed) {
        autoReconnect = NO;
    }

    // Do not show the alert if it's the share or notification extension, but set a flag to display it next time in the app
    if (AppGroup.getCurrentType == AppGroupTypeShareExtension || AppGroup.getCurrentType == AppGroupTypeNotificationExtension) {
        NSUserDefaults *userDefaults = AppGroup.userDefaults;
        [userDefaults setValue:[NSNumber numberWithBool:isReconnectAllowed] forKey:DUPLICATE_CONNECTION_RECONNECT_ALLOWED];
        return;
    }

    NSString *localizedErrorMessage = [BundleUtil localizedStringForKey:@"error_other_connection_for_same_identity_message"];
    NSString *errorMessage = [NSString stringWithFormat:localizedErrorMessage, TargetManagerObjC.localizedAppName, TargetManagerObjC.appName, TargetManagerObjC.appName];
    NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys: [BundleUtil localizedStringForKey:@"error_other_connection_for_same_identity_title"], kKeyTitle, errorMessage, kKeyMessage, nil];

    [self showConnectionAlertWithInfo:info];
}

- (void)showConnectionAlertWithInfo:(NSDictionary *)info {
    if (lastErrorDisplay == nil || ((-[lastErrorDisplay timeIntervalSinceNow]) > kErrorDisplayInterval)) {
        lastErrorDisplay = [NSDate date];

        [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationErrorConnectionFailed object:nil userInfo:info];
    }
}

#pragma mark - SocketProtocolDelegate

- (void)didConnect {
    [serverConnectorConnectionState connected];

    /* Send client hello packet with temporary public key and client cookie */
    clientCookie = [[NaClCrypto sharedCrypto] randomBytes:kCookieLen];
    DDLogVerbose(@"Client cookie = %@", clientCookie);
    
    /* Make sure to pass everything in one writeData call, or we will get two separate TCP segments */
    struct pktClientHello clientHello;
    memcpy(clientHello.client_tempkey_pub, clientTempKeyPub.bytes, sizeof(clientHello.client_tempkey_pub));
    memcpy(clientHello.client_cookie, clientCookie.bytes, sizeof(clientHello.client_cookie));
    [socket writeWithData:[NSData dataWithBytes:&clientHello length:sizeof(clientHello)] tag:TAG_CLIENT_HELLO_SENT];
    
    /* Prepare to receive server hello packet */
    [socket readWithLength:sizeof(struct pktServerHello) timeout:kReadTimeout tag:TAG_SERVER_HELLO_READ];
}

- (void)didDisconnectWithErrorCode:(NSInteger)code {
    [serverConnectorConnectionState disconnected];

    DDLogNotice(@"Interrupt queue on Task Manager");
    [TaskManager interrupt];

    [self setIsRolePromotedToLeader:NO];
    doUnblockIncomingMessages = YES;
    maximumNumberOfDeviceSlots = nil;

    if (keepalive_timer != nil) {
        dispatch_source_cancel(keepalive_timer);
        keepalive_timer = nil;
    }

    if (code == ServerConnectionCloseCodeUnsupportedProtocolVersion) {
        [self displayServerAlert:[NSString stringWithFormat:[BundleUtil localizedStringForKey:@"multi_device_unsupported_protocol_version_alert"], TargetManagerObjC.appName, TargetManagerObjC.appName]];
    }
    else if (code == ServerConnectionCloseCodeDeviceSlotStateMismatch) {
        [self displayServerAlert:[BundleUtil localizedStringForKey:@"multi_device_slot_state_mismatch_alert"]];

        // Device slot state mismatch -> this device must relink
        [self deactivateMultiDevice];
    }
    else if (code == ServerConnectionCloseCodeDuplicateConnection) {
        [self showAnotherConnectionAlertWithReconnectAllowed:NO];
    }
    else {
        [self reconnectAfterDelay];
    }
}

- (void)didReadData:(NSData * _Nonnull)data tag:(int16_t)tag {
    switch (tag) {
        case TAG_SERVER_HELLO_READ: {
            DDLogVerbose(@"Got server hello!");
            const struct pktServerHello* serverHello = data.bytes;
            
            serverCookie = [NSData dataWithBytes:serverHello->server_cookie length:sizeof(serverHello->server_cookie)];
            DDLogVerbose(@"Server cookie = %@", serverCookie);
            
            /* decrypt server hello box */
            chosenServerKeyPub = serverKeyPub;
            NSData *serverHelloBox = [NSData dataWithBytes:serverHello->box length:sizeof(serverHello->box)];
            NSData *nonce = [self nextServerNonce];
            NSData *serverHelloBoxOpen = [[NaClCrypto sharedCrypto] decryptData:serverHelloBox withSecretKey:clientTempKeySec signKey:chosenServerKeyPub nonce:nonce];
            if (serverHelloBoxOpen == nil) {
                /* try alternate key */
                chosenServerKeyPub = serverAltKeyPub;
                serverHelloBoxOpen = [[NaClCrypto sharedCrypto] decryptData:serverHelloBox withSecretKey:clientTempKeySec signKey:chosenServerKeyPub nonce:nonce];
                if (serverHelloBoxOpen == nil) {
                    DDLogError(@"Decryption of server hello box failed");
                    [socket disconnect];
                    return;
                } else {
                    DDLogWarn(@"Using alternate server key!");
                }
            }
            
            const struct pktServerHelloBox *serverHelloBoxU = (struct pktServerHelloBox*)serverHelloBoxOpen.bytes;
            
            /* verify client cookie */
            NSData *clientCookieFromServer = [NSData dataWithBytes:serverHelloBoxU->client_cookie length:sizeof(serverHelloBoxU->client_cookie)];
            if (![clientCookieFromServer isEqualToData:clientCookie]) {
                DDLogError(@"Client cookie mismatch (mine: %@, server: %@)", clientCookie, clientCookieFromServer);
                [socket disconnect];
                return;
            }
            
            /* copy temporary server key */
            serverTempKeyPub = [NSData dataWithBytes:serverHelloBoxU->server_tempkey_pub length:sizeof(serverHelloBoxU->server_tempkey_pub)];
            
            DDLogInfo(@"Server hello successful, tempkey_pub = %@", serverTempKeyPub);
            
            /* prepare extension packet */
            NSMutableData *extensionsData = [NSMutableData data];
            
            /* client info (0x00) extension payload */
            NSData *clientVersion = [ThreemaUtility.clientVersion dataUsingEncoding:NSASCIIStringEncoding];
            [extensionsData appendData:[self makeExtensionWithType:EXTENSION_TYPE_CLIENT_INFO data:clientVersion]];
            
            /* message payload version (0x02) extension payload */
            uint8_t plv = 0x01;
            [extensionsData appendData:[self makeExtensionWithType:EXTENSION_TYPE_MESSAGE_PAYLOAD_VERSION data:[NSData dataWithBytes:&plv length:1]]];
            
            // Adding Device ID extension if is Multi Device activated
            if (deviceID != nil && [deviceID length] == kDeviceIdLen) {
                /* CSP device ID (0x01) extension payload */
                [extensionsData appendData:[self makeExtensionWithType:EXTENSION_TYPE_DEVICE_ID data:deviceID]];
            }
            
            /* device cookie (0x03) extension payload */
            NSData *deviceCookie = [DeviceCookieManager obtainDeviceCookie];
            if (deviceCookie == nil) {
                DDLogError(@"Could not obtain device cookie");
                [socket disconnect];
                return;
            }
            [extensionsData appendData:[self makeExtensionWithType:EXTENSION_TYPE_DEVICE_COOKIE data:deviceCookie]];

            NSData *loginNonce = [self nextClientNonce];
            NSData *extensionsNonce = [self nextClientNonce];
            NSData *extensionsBox = [[NaClCrypto sharedCrypto] encryptData:extensionsData withPublicKey:serverTempKeyPub signKey:clientTempKeySec nonce:extensionsNonce];
            
            /* now prepare login packet */
            struct pktLogin login;
            memset(&login, 0, sizeof(struct pktLogin));
            
            memcpy(login.identity, [[MyIdentityStore sharedMyIdentityStore].identity dataUsingEncoding:NSASCIIStringEncoding].bytes, kIdentityLen);
            
            memcpy(login.client_version, "threema-clever-extension-field", 30);
            uint16_t extLen = extensionsBox.length;
            memcpy(&login.client_version[30], &extLen, sizeof(uint16_t));
            
            memcpy(login.server_cookie, serverCookie.bytes, kCookieLen);
            
            /* vouch calculation */
            NSMutableData *sharedSecrets = [NSMutableData dataWithData:[[MyIdentityStore sharedMyIdentityStore] sharedSecretWithPublicKey:chosenServerKeyPub]];
            [sharedSecrets appendData:[[MyIdentityStore sharedMyIdentityStore] sharedSecretWithPublicKey:serverTempKeyPub]];
            NSMutableData *vouchInput = [NSMutableData dataWithData:serverCookie];
            [vouchInput appendData:clientTempKeyPub];
            ThreemaKDF *kdf = [[ThreemaKDF alloc] initWithPersonal:@"3ma-csp"];
            NSData *vouchKey = [kdf deriveKeyWithSalt:@"v2" key:sharedSecrets];
            NSData *vouch = [ThreemaKDF calculateMacWithKey:vouchKey input:vouchInput];
            memcpy(login.vouch, vouch.bytes, kVouchLen);
                        
            /* encrypt login packet */
            NSData *loginBox = [[NaClCrypto sharedCrypto] encryptData:[NSData dataWithBytes:&login length:sizeof(login)] withPublicKey:serverTempKeyPub signKey:clientTempKeySec nonce:loginNonce];
            
            /* send it! */
            [socket writeWithData:loginBox tag:0];
            [socket writeWithData:extensionsBox tag:TAG_LOGIN_SENT];
            
            /* Prepare to receive login ack packet */
            [socket readWithLength:sizeof(struct pktLoginAck) + kNaClBoxOverhead timeout:kReadTimeout tag:TAG_LOGIN_ACK_READ];
            
            break;
        }
            
        case TAG_LOGIN_ACK_READ: {
            DDLogNotice(@"Login ack received");
            lastRead = CACurrentMediaTime();
            
            /* decrypt server hello box */
            NSData *loginAckBox = data;
            loginAckBox = [[NaClCrypto sharedCrypto] decryptData:loginAckBox withSecretKey:clientTempKeySec signKey:serverTempKeyPub nonce:[self nextServerNonce]];
            if (loginAckBox == nil) {
                DDLogError(@"Decryption of login ack failed");
                [socket disconnect];
                return;
            }
            
            /* Don't care about the contents of the login ACK for now; it only needs to decrypt correctly */
            
            reconnectAttempts = 0;
            DDLogNotice(@"Got chat server login ack; client connected");
            [serverConnectorConnectionState loggedInChatServer];

            [self sendPushToken];
            
            // Remove VoIP push token (since min OS version is iOS 15 or above)
            [self removeVoIPPushToken];
            
            /* set connection idle timeout */
            [self sendConnectionIdleTimeout];

            /* Schedule task for keepalive */
            keepalive_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
            dispatch_source_set_event_handler(keepalive_timer, ^{
                [self sendEchoRequest];
            });
            
            int64_t echoRequestInterval = [UserSettings sharedUserSettings].enableMultiDevice ? kEchoRequestMDInterval : kEchoRequestInterval;
            dispatch_source_set_timer(keepalive_timer, dispatch_time(DISPATCH_TIME_NOW, echoRequestInterval * NSEC_PER_SEC),
                                      echoRequestInterval * NSEC_PER_SEC, NSEC_PER_SEC);
            dispatch_resume(keepalive_timer);
            
            [self _unblockIncomingMessagesIfAllowed];
            
            /* Receive next payload header */
            [socket readWithLength:sizeof(uint16_t) timeout:-1 tag:TAG_PAYLOAD_LENGTH_READ];
            
            // Process all tasks.
            //
            // Call the spool function 2s delayed. Because when is running in the Notification Extension (NE)
            // it can happen the task queue is empty, right after the login (because the task for the incoming
            // message is not added at this time) and then the NE will be terminated. But it needs this call,
            // because it can happen also, that is receiving a reflected message before logged in and the
            // task will not be executed (because the connection state is not logged in).
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2000 * NSEC_PER_MSEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                TaskManager *tm = [[TaskManager alloc] init];
                [tm spool];
            });

            break;
        }
            
        case TAG_PAYLOAD_LENGTH_READ: {
            uint16_t msglen = *((uint16_t*)data.bytes);
            [socket readWithLength:msglen timeout:-1 tag:TAG_PAYLOAD_READ];
            break;
        }
            
        case TAG_PAYLOAD_READ: {
            DDLogVerbose(@"Payload (%lu bytes) received", (unsigned long)data.length);
            
            lastRead = CACurrentMediaTime();
            
            int64_t echoRequestInterval = [UserSettings sharedUserSettings].enableMultiDevice ? kEchoRequestMDInterval : kEchoRequestInterval;
            dispatch_source_set_timer(keepalive_timer, dispatch_time(DISPATCH_TIME_NOW, echoRequestInterval * NSEC_PER_SEC),
                                      echoRequestInterval * NSEC_PER_SEC, NSEC_PER_SEC);
            
            /* Decrypt payload */
            NSData *plData = [[NaClCrypto sharedCrypto] decryptData:data withSecretKey:clientTempKeySec signKey:serverTempKeyPub nonce:[self nextServerNonce]];
            if (plData == nil) {
                DDLogError(@"Payload decryption failed");
                [socket disconnect];
                return;
            }
            
            struct pktPayload *pl = (struct pktPayload*)plData.bytes;
            int datalen = (int)plData.length - (int)sizeof(struct pktPayload);
            DDLogInfo(@"Decrypted payload (type %02x, data %@)", pl->type, [NSData dataWithBytes:pl->data length:datalen]);
            
            [self processPayload:pl datalen:datalen];
            
            /* Receive next payload header */
            [socket readWithLength:sizeof(uint16_t) timeout:-1 tag:TAG_PAYLOAD_LENGTH_READ];
            
            break;
        }
            
        case TAG_PAYLOAD_MEDIATOR_TRIGGERED: {
            int timeoutDownloadThumbnail = isAppInBackground || [AppGroup getCurrentType] == AppGroupTypeNotificationExtension ? 20 : 0;

            TaskManager *taskManager = [[TaskManager alloc] init];

            MediatorMessageProcessor *processor = [[MediatorMessageProcessor alloc]
                                                   initWithDeviceGroupKeys:deviceGroupKeys
                                                   deviceID:deviceID
                                                   maxBytesToDecrypt:[AppGroup getCurrentType] != AppGroupTypeNotificationExtension ? MAX_BYTES_TO_DECRYPT_NO_LIMIT : MAX_BYTES_TO_DECRYPT_NOTIFICATION_EXTENSION
                                                   timeoutDownloadThumbnail:timeoutDownloadThumbnail
                                                   mediatorMessageProtocol:[[MediatorMessageProtocol alloc] initWithDeviceGroupKeys:deviceGroupKeys]
                                                   userSettings:[UserSettings sharedUserSettings]
                                                   taskManager:taskManager
                                                   socketProtocolDelegate:self
                                                   messageProcessorDelegate:self];

            uint8_t type;
            NSNumber *maxDeviceSlots;
            NSData *result = [processor processWithMessage:data messageType:&type receivedAfterInitialQueueSend:!mediatorServerInInitialQueueSend maxDeviceSlots:&maxDeviceSlots];

            if (result != nil && (int)type == MediatorMessageProtocol.MEDIATOR_MESSAGE_TYPE_REFLECT_ACK) {
                [[NSNotificationCenter defaultCenter] postNotificationName:[TaskManager mediatorMessageAckObserverNameWithReflectID:result] object:result];
            }
            else if ((int)type == MediatorMessageProtocol.MEDIATOR_MESSAGE_TYPE_LOCK_ACK || (int)type == MediatorMessageProtocol.MEDIATOR_MESSAGE_TYPE_UNLOCK_ACK || (int)type == MediatorMessageProtocol.MEDIATOR_MESSAGE_TYPE_TRANSACTION_REJECT) {
                [self transactionResponse:type reason:result];
            }
            else if ((int)type == MediatorMessageProtocol.MEDIATOR_MESSAGE_TYPE_TRANSACTION_ENDED) {
                [taskManager spool];
            }
            else if (result != nil && (int)type == MediatorMessageProtocol.MEDIATOR_MESSAGE_TYPE_SERVER_HELLO) {
                DDLogInfo(@"Send server hello to mediator");
                [socket writeWithData:result];
            }
            else if ((int)type == MediatorMessageProtocol.MEDIATOR_MESSAGE_TYPE_SERVER_INFO) {
                DDLogNotice(@"Got mediator server info; client connected");
                [serverConnectorConnectionState loggedInMediatorServer];
                maximumNumberOfDeviceSlots = maxDeviceSlots;
            }
            else if ((int)type == MediatorMessageProtocol.MEDIATOR_MESSAGE_TYPE_REFLECTION_QUEUE_DRY) {
                mediatorServerInInitialQueueSend = NO;
            }
            else if ((int)type == MediatorMessageProtocol.MEDIATOR_MESSAGE_TYPE_ROLE_PROMOTED_TO_LEADER) {
                DDLogVerbose(@"Promoted to leader -> unblock incoming chat messages, if allowed");

                [self setIsRolePromotedToLeader:YES];

                if ([serverConnectorConnectionState connectionState] == ConnectionStateLoggedIn) {
                    /* Unblock incoming messages */
                    [self _unblockIncomingMessagesIfAllowed];

                    /* Receive next payload header */
                    [socket readWithLength:sizeof(uint16_t) timeout:-1 tag:TAG_PAYLOAD_LENGTH_READ];
                }
            }
            else if (result != nil) {
                [self messageReceived:self type:type data:result];
            }

            break;
        }
    }
}

- (NSData*)makeExtensionWithType:(uint8_t)type data:(NSData*)data {
    struct pktExtension *extension = malloc(sizeof(struct pktExtension) + data.length);
    if (!extension) {
        return nil;
    }
    extension->type = type;
    extension->length = data.length;
    memcpy(extension->data, data.bytes, data.length);
    return [NSData dataWithBytesNoCopy:extension length:(sizeof(struct pktExtension) + data.length) freeWhenDone:YES];
}

#pragma mark - ConnectionStateDelegate

- (void)connectionStateChanged:(ConnectionState)state {
    if (UserSettings.sharedUserSettings.ipcCommunicationEnabled) {
        if (state == ConnectionStateDisconnected) {
            [AppGroup setMeInactive];
        }
        else if (![AppGroup amIActive]) {
                [AppGroup setMeActive];
        }
    }
    else if (state == ConnectionStateDisconnected && isAppInBackground && [AppGroup getCurrentType] == AppGroupTypeApp && connectionInitiators.count == 0) {
        // Hand over to the extensions, e.g. after a call ended in the background
        [AppGroup setActive:NO forType:AppGroupTypeApp];
    }

    dispatch_sync(queueConnectionStateDelegate, ^{
        if (clientConnectionStateDelegates != nil && [clientConnectionStateDelegates count] > 0) {
            for (id<ConnectionStateDelegate> delegate in clientConnectionStateDelegates) {
                [delegate connectionStateChanged:state];
            }
        }
    });
}

#pragma mark - MessageListenerDelegate

- (void)messageReceived:(_Nonnull id<MessageListenerDelegate>)listener type:(uint8_t)type data:(NSData * _Nonnull)data {
    dispatch_sync(queueMessageListenerDelegate, ^{
        if (clientMessageListenerDelegates != nil && [clientMessageListenerDelegates count] > 0) {
            for (id<MessageListenerDelegate> clientListener in clientMessageListenerDelegates) {
                [clientListener messageReceived:clientListener type:type data:data];
            }
        }
    });
}


#pragma mark - MessageProcessorDelegate

- (void)beforeDecode {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate beforeDecode];
    });
}

- (void)changedManagedObjectID:(NSManagedObjectID *)objectID {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate changedManagedObjectID:objectID];
    });
}

- (void)incomingMessageStarted:(AbstractMessage * _Nonnull)message {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate incomingMessageStarted:message];
    });
}

- (void)incomingMessageChanged:(AbstractMessage * _Nonnull)message baseMessageEntity:(NSObject * _Nonnull)baseMessageEntityObject {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate incomingMessageChanged:message baseMessageEntity:baseMessageEntityObject];
    });
}

- (void)incomingMessageFinished:(AbstractMessage * _Nonnull)message {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate incomingMessageFinished:message];
    });
}

- (void)incomingMessageFailed:(BoxedMessage *)message {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate incomingMessageFailed:message];
    });
}

- (void)incomingAbstractMessageFailed:(AbstractMessage *)message {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate incomingAbstractMessageFailed:message];
    });
}

- (void)incomingForwardSecurityMessageWithNoResultFinished:(AbstractMessage *)message {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate incomingForwardSecurityMessageWithNoResultFinished:message];
    });
}

- (void)readMessage:(NSSet *)inConversations {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate readMessage:inConversations];
    });
}

- (void)taskQueueEmpty {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate taskQueueEmpty];
    });
}

- (void)chatQueueDry {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate chatQueueDry];
    });
}

- (void)reflectionQueueDry {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate reflectionQueueDry];
    });
}

- (void)processTypingIndicator:(TypingIndicatorMessage * _Nonnull)message {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate processTypingIndicator:message];
    });
}

- (void)processVoIPCall:(NSObject * _Nonnull)message identity:(NSString * _Nonnull)identity onCompletion:(void (^_Nonnull)(id<MessageProcessorDelegate> _Nonnull))onCompletion onError:(void(^ _Nonnull)(NSError * _Nonnull, id<MessageProcessorDelegate> _Nonnull))onError {
    dispatch_sync(queueMessageProcessorDelegate, ^{
        [clientMessageProcessorDelegate processVoIPCall:message identity:identity onCompletion:onCompletion onError:onError];
    });
}

#pragma mark - TaskExecutionTransactionDelegate

- (void)transactionResponse:(uint8_t)messageType reason:(NSData * _Nullable)reason {
    dispatch_sync(queueTaskExecutionTransactionDelegate, ^{
        [clientTaskExecutionTransactionDelegate transactionResponse:messageType reason:reason];
    });
}

#pragma mark - Notifications

- (void)identityDestroyed:(NSNotification*)notification {
    /* when the identity is destroyed, we must disconnect */
    if ([serverConnectorConnectionState connectionState] != ConnectionStateDisconnected) {
        DDLogInfo(@"Disconnecting because identity destroyed");
        
        /* Clear push token on server now to reduce occurrence of push messages being
           delivered to devices that don't use that particular identity anymore */

        DDLogInfo(@"Clearing push notification token");
        uint8_t pushTokenType;
#ifdef DEBUG
        pushTokenType = PUSHTOKEN_TYPE_APPLE_SANDBOX_MC;
#else
        pushTokenType = PUSHTOKEN_TYPE_APPLE_PROD_MC;
#endif
        
        NSMutableData *payloadData = [NSMutableData dataWithBytes:&pushTokenType length:1];
        NSData *pushToken = [[NaClCrypto sharedCrypto] zeroBytes:32];
        [payloadData appendData:pushToken];
        [self sendPayloadWithType:PLTYPE_PUSH_NOTIFICATION_TOKEN data:payloadData];
        
        DDLogInfo(@"Sending VoIP push notification token");
        
        uint8_t voIPPushTokenType;
#ifdef DEBUG
        voIPPushTokenType = PUSHTOKEN_TYPE_APPLE_SANDBOX;
#else
        voIPPushTokenType = PUSHTOKEN_TYPE_APPLE_PROD;
#endif
        
        NSMutableData *voipPayloadData = [NSMutableData dataWithBytes:&voIPPushTokenType length:1];
        NSData *voipPushToken = [[NaClCrypto sharedCrypto] zeroBytes:32];
        [voipPayloadData appendData:voipPushToken];
        [self sendPayloadWithType:PLTYPE_VOIP_PUSH_NOTIFICATION_TOKEN data:voipPayloadData];
        
        [self _disconnect];
    }

    /* also clear the queue so that messages stuck in it don't later cause problems
       because they have the wrong from identity */

    DDLogWarn(@"Clear queue on Task Manager");
    [TaskManager removeAllTasks];
}

@end

¤ Dauer der Verarbeitung: 0.18 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104850
#Domains=416975