1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213 |
- // _____ _
- // |_ _| |_ _ _ ___ ___ _ __ __ _
- // | | | ' \| '_/ -_) -_) ' \/ _` |_
- // |_| |_||_|_| \___\___|_|_|_\__,_(_)
- //
- // Threema iOS Client
- // Copyright (c) 2012-2020 Threema GmbH
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License, version 3,
- // as published by the Free Software Foundation.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
- #include <CommonCrypto/CommonDigest.h>
- #import "ServerConnector.h"
- #import "NSString+Hex.h"
- #import "BoxedMessage.h"
- #import "MyIdentityStore.h"
- #import "ProtocolDefines.h"
- #import "Reachability.h"
- #import "MessageQueue.h"
- #import "MessageProcessorProxy.h"
- #import "Utils.h"
- #import "ContactStore.h"
- #import "UserSettings.h"
- #import "BundleUtil.h"
- #import "AppGroup.h"
- #import "LicenseStore.h"
- #import "PushPayloadDecryptor.h"
- #import "ValidationLogger.h"
- #import "GCDAsyncSocketFactory.h"
- #ifdef DEBUG
- static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
- #else
- static const DDLogLevel ddLogLevel = DDLogLevelWarning;
- #endif
- #define LOG_KEY_INFO 0
- @implementation ServerConnector {
- NSData *clientTempKeyPub;
- NSData *clientTempKeySec;
- time_t clientTempKeyGenTime;
- NSData *clientCookie;
-
- NSData *serverCookie;
- NSData *serverTempKeyPub;
-
- NSString *serverNamePrefix;
- NSString *serverNamePrefixv6;
- NSString *serverNameSuffix;
- NSArray *serverPorts;
- int curServerPortIndex;
- NSData *chosenServerKeyPub;
- NSData *serverKeyPub;
- NSData *serverAltKeyPub;
-
- uint64_t serverNonce;
- uint64_t clientNonce;
- dispatch_queue_t queue;
- dispatch_source_t keepalive_timer;
- NSCondition *disconnectCondition;
- GCDAsyncSocket *socket;
- int reconnectAttempts;
-
- enum ConnectionState connectionState;
- BOOL autoReconnect;
- CFTimeInterval lastRead;
- NSDate *lastErrorDisplay;
- CFTimeInterval lastEchoSendTime;
- uint64_t lastSentEchoSeq;
- uint64_t lastRcvdEchoSeq;
- Reachability *internetReachability;
- NetworkStatus lastInternetStatus;
-
- NSMutableSet *displayedServerAlerts;
- int anotherConnectionCount;
- BOOL serverInInitialQueueSend;
- BOOL isWaitingForReconnect;
- }
- @synthesize connectionState;
- @synthesize lastRtt;
- #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 pktVouch {
- unsigned char client_tempkey_pub[kNaClCryptoPubKeySize];
- };
- struct pktLogin {
- char identity[kIdentityLen];
- char client_version[kClientVersionLen];
- unsigned char server_cookie[kCookieLen];
- unsigned char vouch_nonce[kNaClCryptoNonceSize];
- char vouch_box[sizeof(struct pktVouch) + kNaClBoxOverhead];
- };
- struct pktLoginAck {
- char reserved[kLoginAckReservedLen];
- };
- struct pktPayload {
- uint8_t type;
- uint8_t reserved[3];
- 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
- + (ServerConnector*)sharedServerConnector {
- static ServerConnector *instance;
-
- @synchronized (self) {
- if (!instance)
- instance = [[ServerConnector alloc] init];
- }
-
- return instance;
- }
- - (id)init
- {
- self = [super init];
- if (self) {
- /* Read server info */
- if ([LicenseStore requiresLicenseKey]) {
- serverNamePrefix = [BundleUtil objectForInfoDictionaryKey:@"ThreemaWorkServerNamePrefix"];
- serverNamePrefixv6 = [BundleUtil objectForInfoDictionaryKey:@"ThreemaWorkServerNamePrefixv6"];
- } else {
- serverNamePrefix = [BundleUtil objectForInfoDictionaryKey:@"ThreemaServerNamePrefix"];
- serverNamePrefixv6 = [BundleUtil objectForInfoDictionaryKey:@"ThreemaServerNamePrefixv6"];
- }
- serverNameSuffix = [BundleUtil objectForInfoDictionaryKey:@"ThreemaServerNameSuffix"];
- serverPorts = [BundleUtil objectForInfoDictionaryKey:@"ThreemaServerPorts"];
- curServerPortIndex = 0;
- serverKeyPub = [BundleUtil objectForInfoDictionaryKey:@"ThreemaServerPublicKey"];
- serverAltKeyPub = [BundleUtil objectForInfoDictionaryKey:@"ThreemaServerAltPublicKey"];
-
- queue = dispatch_queue_create("ch.threema.SocketQueue", NULL);
- disconnectCondition = [[NSCondition alloc] init];
-
- reconnectAttempts = 0;
- lastSentEchoSeq = 0;
- lastRcvdEchoSeq = 0;
- self.connectionState = ConnectionStateDisconnected;
-
- displayedServerAlerts = [NSMutableSet set];
-
- /* register with reachability API */
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkStatusDidChange:) name:kReachabilityChangedNotification object:nil];
-
- internetReachability = [Reachability reachabilityForInternetConnection];
- lastInternetStatus = [internetReachability currentReachabilityStatus];
- [internetReachability startNotifier];
-
- isWaitingForReconnect = false;
-
- /* listen for identity changes */
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(identityCreated:) name:kNotificationCreatedIdentity object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(identityDestroyed:) name:kNotificationDestroyedIdentity object:nil];
- }
- return self;
- }
- - (void)connect {
- dispatch_async(queue, ^{
- lastErrorDisplay = nil;
- [self _connect];
- });
- }
- - (void)connectWait {
- dispatch_sync(queue, ^{
- lastErrorDisplay = nil;
- [self _connect];
- });
- }
- - (void)_connect {
- if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FASTLANE_SNAPSHOT"]) {
- return;
- }
-
- if (![[MyIdentityStore sharedMyIdentityStore] isProvisioned]) {
- DDLogInfo(@"Cannot connect - missing identity or key");
- [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Cannot connect - missing identity or key"]];
- return;
- }
-
- if (self.connectionState == ConnectionStateDisconnecting) {
- // The socketDidDisconnect callback has not been called yet; ensure that we reconnect
- // as soon as the previous disconnect has finished.
- reconnectAttempts = 1;
- autoReconnect = YES;
- return;
- } else if (self.connectionState != ConnectionStateDisconnected) {
- if (self.connectionState == ConnectionStateLoggedIn) {
- return;
- }
- NSString *error = [NSString stringWithFormat:@"Cannot connect - invalid connection state (actual state: %u)", self.connectionState];
- DDLogInfo(@"%@", error);
- [[ValidationLogger sharedValidationLogger] logString:error];
- autoReconnect = YES;
- [self reconnectAfterDelay];
- return;
- }
-
- if ([AppGroup amIActive] == NO) {
- DDLogInfo(@"Not active -> don't connect now, retry later");
- [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"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];
- return;
- }
- LicenseStore *licenseStore = [LicenseStore sharedLicenseStore];
- if ([licenseStore isValid] == NO) {
- [licenseStore performLicenseCheckWithCompletion:^(BOOL success) {
- if (success) {
- [self connect];
- } else {
- // don't show license warning for connection errors
- [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"License check failed: %@", licenseStore.error]];
- if ([licenseStore.error.domain hasPrefix:@"NSURL"] == NO) {
- // License check failed permanently; need to inform user and ask for new license username/password
- [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationLicenseMissing object:nil];
- } else {
- // License check failed due to connection error – try again later
- autoReconnect = YES;
- [self reconnectAfterDelay];
- }
- }
- }];
-
- return;
- }
- self.connectionState = ConnectionStateConnecting;
- autoReconnect = YES;
- self.lastRtt = -1;
- lastRead = CACurrentMediaTime();
- serverInInitialQueueSend = YES;
-
- /* Generate a new key pair for the server connection. */
- time_t uptime = [Utils systemUptime];
- DDLogVerbose(@"System uptime is %ld", uptime);
- if (clientTempKeyPub == nil || clientTempKeySec == nil || uptime <= 0 || (uptime - clientTempKeyGenTime) > kClientTempKeyMaxAge) {
- NSData *publicKey, *secretKey;
- [[NaClCrypto sharedCrypto] generateKeyPairPublicKey:&publicKey secretKey:&secretKey];
- clientTempKeyPub = publicKey;
- clientTempKeySec = secretKey;
- clientTempKeyGenTime = uptime;
- #if LOG_KEY_INFO
- DDLogVerbose(@"Client tempkey_pub = %@, tempkey_sec = %@", clientTempKeyPub, clientTempKeySec);
- #endif
- }
-
- /* Determine server host name */
- NSString *serverHost;
- NSTimeInterval timeout = kConnectTimeout;
- if ([UserSettings sharedUserSettings].enableIPv6) {
- serverHost = [NSString stringWithFormat:@"%@%@%@", serverNamePrefixv6, [MyIdentityStore sharedMyIdentityStore].serverGroup, serverNameSuffix];
- } else {
- serverHost = [NSString stringWithFormat:@"%@%@%@", serverNamePrefix, [MyIdentityStore sharedMyIdentityStore].serverGroup, serverNameSuffix];
- }
-
- NSNumber* serverPort = [serverPorts objectAtIndex:curServerPortIndex];
-
- socket = [GCDAsyncSocketFactory proxyAwareAsyncSocketForHost:serverHost port:serverPort delegate:self delegateQueue:queue];
- if ([UserSettings sharedUserSettings].enableIPv6) {
- [socket setIPv4PreferredOverIPv6:NO];
- } else {
- [socket setIPv4PreferredOverIPv6:YES];
- }
-
- DDLogInfo(@"Connecting to %@:%@...", serverHost, serverPort);
- [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Connecting to %@:%@...", serverHost, serverPort]];
-
- NSError *error;
- if (![socket connectToHost:serverHost onPort:[serverPort intValue] withTimeout:timeout error:&error]) {
- DDLogWarn(@"Connect failed: %@", error);
- [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Connect failed: %@", error]];
- self.connectionState = ConnectionStateDisconnected;
- [self reconnectAfterDelay];
- return;
- }
-
- /* Reset nonces for new connection */
- clientNonce = 1;
- serverNonce = 1;
- }
- - (void)_disconnect {
- if (connectionState == ConnectionStateDisconnected) {
- return;
- }
-
- /* disconnect socket and make sure we don't reconnect */
- autoReconnect = NO;
- self.connectionState = ConnectionStateDisconnecting;
- [self _disconnectSocketWithTimeout];
- }
- - (void)_disconnectSocketWithTimeout {
- // Give the socket time for pending writes, but force disconnect if it takes too long for them to complete
- [socket disconnectAfterWriting];
-
- GCDAsyncSocket *socketToDisconnect = socket;
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDisconnectTimeout * NSEC_PER_SEC)), queue, ^{
- if (socket == socketToDisconnect) {
- DDLogInfo(@"Socket still not disconnected - forcing disconnect now");
- [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Socket still not disconnected - forcing disconnect now"]];
- [socket disconnect];
- }
- });
- }
- - (void)disconnect {
- dispatch_async(queue, ^{
- [self _disconnect];
- });
- }
- - (void)disconnectWait {
- dispatch_sync(queue, ^{
- [self _disconnect];
- });
-
- [disconnectCondition lock];
- if (connectionState != ConnectionStateDisconnected) {
- [disconnectCondition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:kDisconnectTimeout]];
- }
- // Note: it's not guaranteed that the state is actually disconnected at this point, but it's good enough for our purposes
- [disconnectCondition unlock];
- }
- - (void)reconnect {
- dispatch_async(queue, ^{
- if (connectionState == ConnectionStateDisconnected) {
- [self _connect];
- } else if (connectionState == ConnectionStateConnecting) {
- DDLogVerbose(@"Connection already in progress, not reconnecting");
- } else {
- autoReconnect = YES;
- self.connectionState = ConnectionStateDisconnecting;
- [self _disconnectSocketWithTimeout];
- }
- });
- }
- - (void)setConnectionState:(enum ConnectionState)newConnectionState {
- [disconnectCondition lock];
- connectionState = newConnectionState;
- if (connectionState == ConnectionStateDisconnected) {
- [disconnectCondition broadcast];
- }
- [disconnectCondition unlock];
- }
- - (void)processPayload:(struct pktPayload*)pl datalen:(int)datalen {
-
- switch (pl->type) {
- case PLTYPE_ECHO_REPLY: {
- 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) {
- // extension took over connection
- if ([AppGroup amIActive] == NO) {
- break;
- }
-
- // ignore first few occurrences of "Another connection" messages to gracefully handle network switches
- if (anotherConnectionCount < 5) {
- anotherConnectionCount++;
- break;
- }
- }
-
- if (!plerr->reconnect_allowed) {
- autoReconnect = NO;
- }
-
- if (lastErrorDisplay == nil || ((-[lastErrorDisplay timeIntervalSinceNow]) > kErrorDisplayInterval)) {
- lastErrorDisplay = [NSDate date];
-
-
- NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
- errorMessage, kKeyMessage,
- nil];
-
- [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationErrorConnectionFailed object:nil userInfo: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 plMessageAck *ack = (struct plMessageAck*)pl->data;
- /* ignore from identity, as it must be ours */
- NSData *messageId = [NSData dataWithBytes:ack->message_id length:kMessageIdLen];
- [[MessageQueue sharedMessageQueue] processAck:messageId];
- break;
- }
- case PLTYPE_INCOMING_MESSAGE: {
- if (datalen <= sizeof(struct plMessage)) {
- DDLogError(@"Bad message payload datalen %d", datalen);
- [socket disconnect];
- break;
- }
- struct plMessage *plmsg = (struct plMessage*)pl->data;
-
- 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];
- boxmsg.nonce = [NSData dataWithBytes:plmsg->nonce length:kNonceLen];
- boxmsg.box = [NSData dataWithBytes:plmsg->box length:(datalen - sizeof(struct plMessage))];
-
- [MessageProcessorProxy processIncomingMessage:boxmsg receivedAfterInitialQueueSend:!serverInInitialQueueSend onCompletion:^{
- [self completedProcessingMessage:boxmsg];
- } onError:^(NSError *err) {
- [self failedProcessingMessage:boxmsg error:err];
- }];
-
- break;
- }
- case PLTYPE_QUEUE_SEND_COMPLETE:
- DDLogInfo(@"Queue send complete");
- serverInInitialQueueSend = NO;
- [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationQueueSendComplete object:nil userInfo:nil];
- break;
- default:
- DDLogWarn(@"Unsupported payload type %d", pl->type);
- break;
- }
- }
- - (void)completedProcessingMessage:(BoxedMessage *)boxmsg {
- if (!(boxmsg.flags & MESSAGE_FLAG_NOACK)) {
- /* send ACK to server */
- [self ackMessage:boxmsg.messageId fromIdentity:boxmsg.fromIdentity];
- }
- }
- - (void)completedProcessingAbstractMessage:(AbstractGroupMessage *)abstractGroupMsg {
- uint8_t flags = abstractGroupMsg.flags.unsignedCharValue;
- if (!(flags & MESSAGE_FLAG_NOACK)) {
- /* send ACK to server */
- [self ackMessage:abstractGroupMsg.messageId fromIdentity:abstractGroupMsg.fromIdentity];
- }
- }
- - (void)failedProcessingMessage:(BoxedMessage *)boxmsg error:(NSError *)err {
- if (err.code == kBlockUnknownContactErrorCode) {
- DDLogVerbose(@"Message processing error due to block contacts - acking anyway");
- [self ackMessage:boxmsg.messageId fromIdentity:boxmsg.fromIdentity];
- } else if (err.code == kBadMessageErrorCode) {
- DDLogVerbose(@"Message processing error due to bad message format or decryption failure - acking anyway");
- [self ackMessage:boxmsg.messageId fromIdentity:boxmsg.fromIdentity];
- } else if (err.code == kMessageProcessingErrorCode) {
- DDLogError(@"Message processing error due to being unable to handle message: %@", err);
- } else {
- DDLogInfo(@"Could not process incoming message: %@", err);
- }
- }
- - (void)reconnectAfterDelay {
- if (!autoReconnect) {
- return;
- }
-
- /* calculate delay using bound exponential backoff */
- float reconnectDelay = powf(kReconnectBaseInterval, MIN(reconnectAttempts - 1, 10));
- if (reconnectDelay > kReconnectMaxInterval) {
- reconnectDelay = kReconnectMaxInterval;
- }
-
- if (!isWaitingForReconnect) {
- isWaitingForReconnect = true;
- [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Waiting %f seconds before reconnecting", reconnectDelay]];
-
- reconnectAttempts++;
- DDLogInfo(@"Waiting %f seconds before reconnecting", reconnectDelay);
- dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, reconnectDelay * NSEC_PER_SEC);
- dispatch_after(popTime, queue, ^(void){
- isWaitingForReconnect = false;
- [self _connect];
- });
- }
- }
- - (void)sendPayloadWithType:(uint8_t)type data:(NSData*)data {
- if (connectionState != ConnectionStateLoggedIn) {
- DDLogVerbose(@"Cannot send payload - not logged in");
- return;
- }
-
- dispatch_async(queue, ^{
- [self sendPayloadAsyncWithType:type data:data];
- });
- }
- - (void)sendPayloadAsyncWithType:(uint8_t)type data:(NSData*)data {
- /* Make encrypted box */
- unsigned long pllen = sizeof(struct pktPayload) + data.length;
- struct pktPayload *pl = malloc(pllen);
- if (!pl) {
- return;
- }
-
- bzero(pl, pllen);
-
- pl->type = type;
- memcpy(pl->data, data.bytes, data.length);
-
- NSData *plData = [NSData dataWithBytesNoCopy:pl length:pllen];
- NSData *nextClientNonce = [self nextClientNonce];
- NSData *plBox = [[NaClCrypto sharedCrypto] encryptData:plData withPublicKey:serverTempKeyPub signKey:clientTempKeySec nonce:nextClientNonce];
- if (plBox == nil) {
- DDLogError(@"Payload encryption failed!");
- return;
- }
-
- /* 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);
- return;
- }
-
- NSMutableData *sendData = [NSMutableData dataWithCapacity:plBox.length + sizeof(uint16_t)];
- [sendData appendBytes:&pktlen length:sizeof(uint16_t)];
- [sendData appendData:plBox];
-
- [socket writeData:sendData withTimeout:kWriteTimeout tag:TAG_PAYLOAD_SENT];
- return;
- }
- - (void)sendMessage:(BoxedMessage*)message {
- unsigned long msglen = sizeof(struct plMessage) + message.box.length;
- struct plMessage *plmsg = malloc(msglen);
- if (!plmsg) {
- return;
- }
-
- DDLogInfo(@"Sending message from %@ to %@ (ID %@), box length %lu", message.fromIdentity,
- message.toIdentity, message.messageId, (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);
- plmsg->date = [message.date timeIntervalSince1970];
- plmsg->flags = message.flags;
- plmsg->reserved[0] = 0; plmsg->reserved[1] = 0; plmsg->reserved[2] = 0;
- bzero(plmsg->push_from_name, kPushFromNameLen);
- if (message.pushFromName != nil) {
- NSData *encodedPushFromName = [Utils truncatedUTF8String:message.pushFromName maxLength:kPushFromNameLen];
- strncpy(plmsg->push_from_name, encodedPushFromName.bytes, encodedPushFromName.length);
- }
- memcpy(plmsg->nonce, message.nonce.bytes, kNaClCryptoNonceSize);
- memcpy(plmsg->box, message.box.bytes, message.box.length);
-
- [self sendPayloadWithType:PLTYPE_OUTGOING_MESSAGE data:[NSData dataWithBytesNoCopy:plmsg length:msglen]];
- }
- - (void)ackMessage:(NSData*)messageId fromIdentity:(NSString*)fromIdentity {
- int msglen = sizeof(struct plMessageAck);
- struct plMessageAck *plmsgack = malloc(msglen);
- if (!plmsgack)
- return;
-
- 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);
-
- [self sendPayloadWithType:PLTYPE_INCOMING_MESSAGE_ACK data:[NSData dataWithBytesNoCopy:plmsgack length:msglen]];
- }
- - (void)ping {
- dispatch_async(queue, ^{
- [self sendEchoRequest];
- });
- }
- - (void)sendEchoRequest {
- if (connectionState != ConnectionStateLoggedIn)
- return;
-
- lastSentEchoSeq++;
- DDLogInfo(@"Sending echo request (seq %llu)", lastSentEchoSeq);
-
- lastEchoSendTime = CACurrentMediaTime();
- [self sendPayloadAsyncWithType:PLTYPE_ECHO_REQUEST data:[NSData dataWithBytes:&lastSentEchoSeq length:sizeof(lastSentEchoSeq)]];
-
- GCDAsyncSocket *curSocket = socket;
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kReadTimeout * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void){
- if (curSocket == socket && lastRcvdEchoSeq < lastSentEchoSeq) {
- DDLogInfo(@"No reply to echo payload; disconnecting");
- [socket disconnect];
- }
- });
- }
- - (void)cleanPushToken {
- if ([[AppGroup userDefaults] objectForKey:kPushNotificationDeviceToken] != nil) {
- [[AppGroup userDefaults] setObject:nil forKey:kPushNotificationDeviceToken];
- [[AppGroup userDefaults] synchronize];
- }
- [self sendPushToken];
- [self sendPushAllowedIdentities];
- [self sendPushSound];
- }
- - (void)setVoIPPushToken:(NSData *)voIPPushToken {
- [[AppGroup userDefaults] setObject:voIPPushToken forKey:kVoIPPushNotificationDeviceToken];
- [[AppGroup userDefaults] synchronize];
- [self sendVoIPPushToken];
- }
- - (void)sendPushToken {
- if ([self shouldRegisterPush] == NO) {
- return;
- }
-
- DDLogInfo(@"Sending push notification token");
-
- uint8_t pushTokenType = PUSHTOKEN_TYPE_NONE;
- NSMutableData *payloadData = [NSMutableData dataWithBytes:&pushTokenType length:1];
-
- [self sendPayloadWithType:PLTYPE_PUSH_NOTIFICATION_TOKEN data:payloadData];
- }
- - (void)sendVoIPPushToken {
- if ([self shouldRegisterVoIP] == NO) {
- return;
- }
-
- NSData *voIPPushToken = [[AppGroup userDefaults] objectForKey:kVoIPPushNotificationDeviceToken];
-
- DDLogInfo(@"Sending VoIP push notification token");
-
- uint8_t voIPPushTokenType;
- #ifdef DEBUG
- voIPPushTokenType = PUSHTOKEN_TYPE_APPLE_SANDBOX;
- #else
- voIPPushTokenType = PUSHTOKEN_TYPE_APPLE_PROD;
- #endif
-
- NSMutableData *payloadData = [NSMutableData dataWithBytes:&voIPPushTokenType length:1];
- [payloadData appendData:voIPPushToken];
- [payloadData appendData:[@"|" dataUsingEncoding:NSUTF8StringEncoding]];
- [payloadData appendData:[[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding:NSASCIIStringEncoding]];
- [payloadData appendData:[@"|" dataUsingEncoding:NSUTF8StringEncoding]];
- [payloadData appendData:[PushPayloadDecryptor pushEncryptionKey]];
- [self sendPayloadWithType:PLTYPE_VOIP_PUSH_NOTIFICATION_TOKEN data:payloadData];
- }
- - (void)sendPushAllowedIdentities {
- if ([self shouldRegisterPush] == NO) {
- return;
- }
-
- // Disable filter by allowing all IDs; we filter pushes in our own logic now
- DDLogInfo(@"Sending push allowed identities");
- dispatch_async(dispatch_get_main_queue(), ^{
- NSData *iddata = [NSData dataWithBytes:"\0" length:1];
-
- DDLogVerbose(@"Sending allowed identities: %@", iddata);
- [self sendPayloadWithType:PLTYPE_PUSH_ALLOWED_IDENTITIES data:iddata];
- });
- }
- - (BOOL)shouldRegisterPush {
- if (connectionState != ConnectionStateLoggedIn) {
- return NO;
- }
-
- if ([AppGroup getCurrentType] != AppGroupTypeApp) {
- // only register within main app for pushes
- return NO;
- }
-
- return YES;
- }
- - (BOOL)shouldRegisterVoIP {
- if (connectionState != ConnectionStateLoggedIn) {
- return NO;
- }
-
- if ([[AppGroup userDefaults] objectForKey:kVoIPPushNotificationDeviceToken] == nil) {
- return NO;
- }
-
- if ([AppGroup getCurrentType] != AppGroupTypeApp) {
- // only register within main app for pushes
- return NO;
- }
-
- return YES;
- }
- - (void)sendPushSound{
- if ([self shouldRegisterPush] == NO) {
- return;
- }
-
- NSString *pushSound = @"";
- DDLogInfo(@"Sending push sound: %@", pushSound);
- [self sendPayloadWithType:PLTYPE_PUSH_SOUND data:[pushSound dataUsingEncoding:NSASCIIStringEncoding]];
- }
- - (void)sendPushGroupSound {
- if ([self shouldRegisterPush] == NO) {
- return;
- }
-
- NSString *pushGroupSound = @"";
-
- DDLogInfo(@"Sending push group sound: %@", pushGroupSound);
- [self sendPayloadWithType:PLTYPE_PUSH_GROUP_SOUND data:[pushGroupSound dataUsingEncoding:NSASCIIStringEncoding]];
- }
- - (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];
- }
- - (NSString*)nameForConnectionState:(enum ConnectionState)_connectionState {
- switch (_connectionState) {
- case ConnectionStateDisconnected:
- return @"disconnected";
- case ConnectionStateConnecting:
- return @"connecting";
- case ConnectionStateConnected:
- return @"connected";
- case ConnectionStateLoggedIn:
- return @"loggedin";
- case ConnectionStateDisconnecting:
- return @"disconnecting";
- }
- return nil;
- }
- - (BOOL)isIPv6Connection {
- return [socket isIPv6];
- }
- - (BOOL)isProxyConnection {
- return (socket != nil && ![socket isMemberOfClass:[GCDAsyncSocket class]]);
- }
- - (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)networkStatusDidChange:(NSNotification *)notice
- {
- NetworkStatus internetStatus = [internetReachability currentReachabilityStatus];
- switch (internetStatus) {
- case NotReachable:
- DDLogInfo(@"Internet is not reachable");
- [[ValidationLogger sharedValidationLogger] logString:@"Internet is not reachable"];
- break;
- case ReachableViaWiFi:
- DDLogInfo(@"Internet is reachable via WiFi");
- [[ValidationLogger sharedValidationLogger] logString:@"Internet is reachable via WiFi"];
- break;
- case ReachableViaWWAN:
- DDLogInfo(@"Internet is reachable via WWAN");
- [[ValidationLogger sharedValidationLogger] logString:@"Internet is reachable via WWAN"];
- break;
- }
-
- if (internetStatus != lastInternetStatus) {
- DDLogInfo(@"Internet status changed - forcing reconnect");
- [[ValidationLogger sharedValidationLogger] logString:@"Internet status changed - forcing reconnect"];
- curServerPortIndex = 0;
- [self reconnect];
- lastInternetStatus = internetStatus;
- }
- }
- - (void)setServerPorts:(NSArray *)ports {
- serverPorts = ports;
- }
- - (void)sendPushOverrideTimeout {
- DDLogInfo(@"Sending set push override timeout");
- NSUserDefaults *defaults = [AppGroup userDefaults];
- NSDate *lastSendDate = [defaults objectForKey:kLastPushOverrideSendDate];
-
- if (lastSendDate == nil) {
- [self setPushOverrideTimeout];
- } else {
- NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitMinute fromDate:[NSDate date] toDate:lastSendDate options:0];
- NSInteger minutes = [components minute];
-
- if (minutes > 60 || lastSendDate == nil) {
- [self setPushOverrideTimeout];
- }
- }
- }
- - (void)setPushOverrideTimeout {
- NSUserDefaults *defaults = [AppGroup userDefaults];
- NSTimeInterval secondsInEightHours = 8 * 60 * 60;
- NSDate *pushOverrideEndDate = [[NSDate date] dateByAddingTimeInterval:secondsInEightHours];
- uint64_t timestamp = [pushOverrideEndDate timeIntervalSince1970];
- NSData *payloadData = [NSData dataWithBytes:×tamp length:sizeof(timestamp)];
- [self sendPayloadWithType:PLTYPE_PUSH_OVERRIDE_TIMEOUT data:payloadData];
-
- [defaults setObject:[NSDate date] forKey:kLastPushOverrideSendDate];
- [defaults synchronize];
- }
- - (void)resetPushOverrideTimeout {
- DDLogInfo(@"Reset push override timeout");
- NSUserDefaults *defaults = [AppGroup userDefaults];
-
- uint64_t timestamp = 0;
- NSData *data = [NSData dataWithBytes:×tamp length:sizeof(timestamp)];
- [self sendPayloadWithType:PLTYPE_PUSH_OVERRIDE_TIMEOUT data:data];
- [defaults setObject:nil forKey:kLastPushOverrideSendDate];
- [defaults synchronize];
- }
- #pragma mark - GCDAsyncSocketDelegate
- - (void)socket:(GCDAsyncSocket *)sender didConnectToHost:(NSString *)host port:(UInt16)port {
- if (sender != socket) {
- DDLogWarn(@"didConnectToHost from old socket");
- return;
- }
-
- DDLogInfo(@"Connected to %@:%d", host, port);
- [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Connected to %@:%d", host, port]];
- self.connectionState = ConnectionStateConnected;
-
- /* 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 writeData:[NSData dataWithBytes:&clientHello length:sizeof(clientHello)] withTimeout:kWriteTimeout tag:TAG_CLIENT_HELLO_SENT];
-
- /* Prepare to receive server hello packet */
- [socket readDataToLength:sizeof(struct pktServerHello) withTimeout:kReadTimeout tag:TAG_SERVER_HELLO_READ];
- }
- - (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag {
- //DDLogVerbose(@"Read data (%d bytes) with tag: %ld", data.length, tag);
-
- if (sender != socket) {
- DDLogWarn(@"didReadData from old socket");
- return;
- }
-
- 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);
-
- /* now prepare login packet */
- NSData *vouchNonce = [[NaClCrypto sharedCrypto] randomBytes:kNaClCryptoNonceSize];
- struct pktLogin login;
- memcpy(login.identity, [[MyIdentityStore sharedMyIdentityStore].identity dataUsingEncoding:NSASCIIStringEncoding].bytes, kIdentityLen);
- bzero(login.client_version, kClientVersionLen);
- NSData *clientVersion = [[Utils getClientVersion] dataUsingEncoding:NSASCIIStringEncoding];
- memcpy(login.client_version, clientVersion.bytes, MIN(clientVersion.length, kClientVersionLen));
- memcpy(login.server_cookie, serverCookie.bytes, kCookieLen);
- memcpy(login.vouch_nonce, vouchNonce.bytes, kNaClCryptoNonceSize);
-
- /* vouch subpacket */
- struct pktVouch vouch;
- memcpy(vouch.client_tempkey_pub, clientTempKeyPub.bytes, kNaClCryptoPubKeySize);
- NSData *vouchBox = [[MyIdentityStore sharedMyIdentityStore] encryptData:[NSData dataWithBytes:&vouch length:sizeof(vouch)] withNonce:vouchNonce publicKey:chosenServerKeyPub];
- memcpy(login.vouch_box, vouchBox.bytes, sizeof(login.vouch_box));
-
- /* encrypt login packet */
- NSData *loginBox = [[NaClCrypto sharedCrypto] encryptData:[NSData dataWithBytes:&login length:sizeof(login)] withPublicKey:serverTempKeyPub signKey:clientTempKeySec nonce:[self nextClientNonce]];
-
- /* send it! */
- [socket writeData:loginBox withTimeout:kWriteTimeout tag:TAG_LOGIN_SENT];
-
- /* Prepare to receive login ack packet */
- [socket readDataToLength:sizeof(struct pktLoginAck) + kNaClBoxOverhead withTimeout:kReadTimeout tag:TAG_LOGIN_ACK_READ];
-
- break;
- }
-
- case TAG_LOGIN_ACK_READ: {
- DDLogInfo(@"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;
- self.connectionState = ConnectionStateLoggedIn;
-
- /* Clean and send nil push token info */
- [self cleanPushToken];
-
- /* Send voIP push token info */
- [self sendVoIPPushToken];
-
- /* Schedule task for keepalive */
- keepalive_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
- dispatch_source_set_event_handler(keepalive_timer, ^{
- [self sendEchoRequest];
- });
- dispatch_source_set_timer(keepalive_timer, dispatch_time(DISPATCH_TIME_NOW, kKeepAliveInterval * NSEC_PER_SEC),
- kKeepAliveInterval * NSEC_PER_SEC, NSEC_PER_SEC);
- dispatch_resume(keepalive_timer);
-
- /* Receive next payload header */
- [socket readDataToLength:sizeof(uint16_t) withTimeout:-1 tag:TAG_PAYLOAD_LENGTH_READ];
-
- break;
- }
-
- case TAG_PAYLOAD_LENGTH_READ: {
- uint16_t msglen = *((uint16_t*)data.bytes);
- [socket readDataToLength:msglen withTimeout:-1 tag:TAG_PAYLOAD_READ];
- break;
- }
-
- case TAG_PAYLOAD_READ: {
- DDLogVerbose(@"Payload (%lu bytes) received", (unsigned long)data.length);
-
- lastRead = CACurrentMediaTime();
-
- dispatch_source_set_timer(keepalive_timer, dispatch_time(DISPATCH_TIME_NOW, kKeepAliveInterval * NSEC_PER_SEC),
- kKeepAliveInterval * 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 readDataToLength:sizeof(uint16_t) withTimeout:-1 tag:TAG_PAYLOAD_LENGTH_READ];
-
- break;
- }
- }
- }
- - (void)socketDidDisconnect:(GCDAsyncSocket *)sender withError:(NSError *)error {
- [[ValidationLogger sharedValidationLogger] logString:@"socketDidDisconnect called"];
- if (sender != socket) {
- DDLogWarn(@"socketDidDisconnect from old socket");
- [[ValidationLogger sharedValidationLogger] logString:@"socketDidDisconnect from old socket"];
- return;
- }
-
- if (error != nil) {
- DDLogWarn(@"Socket disconnected, error = %@", error);
- [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Socket disconnected, error = %@", error]];
-
- /* try next port */
- curServerPortIndex++;
- if (curServerPortIndex >= serverPorts.count)
- curServerPortIndex = 0;
- }
-
- self.connectionState = ConnectionStateDisconnected;
- if (keepalive_timer != nil) {
- dispatch_source_cancel(keepalive_timer);
- keepalive_timer = nil;
- }
- socket = nil;
- [self reconnectAfterDelay];
- }
- - (NSTimeInterval)socket:(GCDAsyncSocket *)sender shouldTimeoutReadWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length {
- if (sender != socket) {
- DDLogWarn(@"shouldTimeoutReadWithTag from old socket");
- return 0;
- }
-
- DDLogInfo(@"Read timeout, tag = %ld", tag);
- [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Read timeout"]];
- [socket disconnect];
- return 0;
- }
- - (NSTimeInterval)socket:(GCDAsyncSocket *)sender shouldTimeoutWriteWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length {
- if (sender != socket) {
- DDLogWarn(@"shouldTimeoutWriteWithTag from old socket");
- return 0;
- }
-
- DDLogInfo(@"Write timeout, tag = %ld", tag);
- [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Write timeout"]];
- [socket disconnect];
- return 0;
- }
- #pragma mark - Notifications
- - (void)identityCreated:(NSNotification*)notification {
- /* when the identity is created, we should connect */
- [self connect];
- }
- - (void)identityDestroyed:(NSNotification*)notification {
- /* when the identity is destroyed, we must disconnect */
- if (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];
- }
-
- /* destroy temporary keys, as we cannot reuse them for the new identity */
- dispatch_async(queue, ^{
- clientTempKeyPub = nil;
- clientTempKeySec = nil;
- });
-
- /* also flush the queue so that messages stuck in it don't later cause problems
- because they have the wrong from identity */
- [[MessageQueue sharedMessageQueue] flush];
- }
- @end
|