SOSPeerOTRTimer.m   [plain text]

//
// SOSPeerOTRTimer.m
//

#import <Foundation/Foundation.h>
#include "keychain/SecureObjectSync/SOSPeer.h"
#include "keychain/SecureObjectSync/SOSPeerCoder.h"
#include "keychain/SecureObjectSync/SOSTransportMessage.h"
#include "keychain/SecureObjectSync/SOSAccount.h"
#include "keychain/SecureObjectSync/SOSCoder.h"
#include "keychain/SecureObjectSync/SOSEngine.h"
#include "keychain/SecureObjectSync/SOSDataSource.h"
#include "keychain/SecureObjectSync/SOSAccountTransaction.h"
#include "keychain/SecureObjectSync/SOSKVSKeys.h"
#include "keychain/SecureObjectSync/SOSPeerOTRTimer.h"
#include "keychain/SecureObjectSync/CKBridge/SOSCloudKeychainClient.h"

#include <utilities/debugging.h>
#include <utilities/SecCFWrappers.h>

#include <AssertMacros.h>
#include "keychain/SecureObjectSync/SOSInternal.h"

static int maxRetryCount = 7; //max number of times to attempt restarting OTR negotiation

bool SOSPeerOTRTimerHaveReachedMaxRetryAllowance(SOSAccount* account, NSString* peerid){
bool reachedMax = false;
NSMutableDictionary* attemptsPerPeer = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountRenegotiationRetryCount, NULL);
if(!attemptsPerPeer){
attemptsPerPeer = [NSMutableDictionary dictionary];
}
NSNumber* attempt = [attemptsPerPeer objectForKey:peerid];
if(attempt && [attempt intValue] >= maxRetryCount)
{
reachedMax = true;
}
return reachedMax;
}

//used when evaluating whether or not securityd should start a timer for negotiation
bool SOSPeerOTRTimerHaveAnRTTAvailable(SOSAccount* account, NSString* peerid)
{
CFErrorRef error = NULL;
CFNumberRef timeout = NULL;
bool doesRTTExist = false;

CFMutableDictionaryRef timeouts = (CFMutableDictionaryRef)asDictionary(SOSAccountGetValue(account, kSOSAccountPeerNegotiationTimeouts, &error), NULL);
require_action_quiet(timeouts, exit, secnotice("otrtimer", "do not have an rtt yet"));

timeout = CFDictionaryGetValue(timeouts, (__bridge CFStringRef)peerid);
require_action_quiet(timeout, exit, secnotice("otrtimer", "do not have an rtt yet"));

doesRTTExist = true;
exit:
return doesRTTExist;
}

//call when a timer has fired, remove the current rtt entry as the existing one isn't working
void SOSPeerOTRTimerRemoveRTTTimeoutForPeer(SOSAccount* account, NSString* peerid)
{
CFNumberRef timeout = NULL;
CFErrorRef error = NULL;

CFMutableDictionaryRef timeouts = (CFMutableDictionaryRef)asDictionary(SOSAccountGetValue(account, kSOSAccountPeerNegotiationTimeouts, &error), NULL);
require_action_quiet(timeouts && (error == NULL), exit, secnotice("otrtimer","timeout dictionary doesn't exist"));

timeout = CFDictionaryGetValue(timeouts, (__bridge CFStringRef)peerid);
require_action_quiet(timeout, exit, secnotice("otrtimer","timeout for peerid: %@, doesn't exist", (__bridge CFStringRef)peerid));

CFDictionaryRemoveValue(timeouts, (__bridge CFStringRef)peerid);
SOSAccountSetValue(account, kSOSAccountPeerNegotiationTimeouts, timeouts, &error);
if(error){
secnotice("otrtimer","SOSAccountSetValue threw an error for key kSOSAccountPeerNegotiationTimeouts: %@", error);
}
exit:
CFReleaseNull(error);
}

void SOSPeerOTRTimerRemoveLastSentMessageTimestamp(SOSAccount* account, NSString* peerid)
{
NSMutableDictionary *peerToTimeLastSentDict = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);

if(peerToTimeLastSentDict){
NSDate* storedDate = [peerToTimeLastSentDict objectForKey:peerid];
if(storedDate){
[peerToTimeLastSentDict removeObjectForKey:peerid];
SOSAccountSetValue(account, kSOSAccountPeerLastSentTimestamp, (__bridge CFMutableDictionaryRef)peerToTimeLastSentDict, NULL);
}
}
}

void SOSPeerOTRTimerIncreaseOTRNegotiationRetryCount(SOSAccount* account, NSString* peerid)
{
NSMutableDictionary* attemptsPerPeer = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountRenegotiationRetryCount, NULL);
if(!attemptsPerPeer){
attemptsPerPeer = [NSMutableDictionary dictionary];
}
NSNumber* attempt = [attemptsPerPeer objectForKey:peerid];
if(!attempt){
attempt = [[NSNumber alloc] initWithInt:1];
[attemptsPerPeer setObject:attempt forKey:peerid];
}
else{
NSNumber* newCount = [[NSNumber alloc]initWithInt:([attempt intValue]+1)];
[attemptsPerPeer setObject:newCount forKey:peerid];
secnotice("otr","OTR negotiation retry count: %d", [newCount intValue]);
}
SOSAccountSetValue(account, kSOSAccountRenegotiationRetryCount, (__bridge CFMutableDictionaryRef)attemptsPerPeer, NULL);
}

int SOSPeerOTRTimerTimeoutValue(SOSAccount* account, SOSPeerRef peer)
{
CFErrorRef error = NULL;
int timeoutIntValue = 0;

CFMutableDictionaryRef timeouts = (CFMutableDictionaryRef)asDictionary(SOSAccountGetValue(account, kSOSAccountPeerNegotiationTimeouts, &error), NULL);
require_action_quiet(timeouts, xit, secnotice("otrtimer","deadline value not available yet"));

CFNumberRef timeout = CFDictionaryGetValue(timeouts, SOSPeerGetID(peer));
require_action_quiet(timeout, xit, secnotice("otrtimer","deadline value not available yet"));

secnotice("otrtimer", "decided to wait %d before restarting negotiation", [(__bridge NSNumber*)timeout intValue]);
timeoutIntValue = [(__bridge NSNumber*)timeout intValue];

xit:
return timeoutIntValue;
}

void SOSPeerOTRTimerSetupAwaitingTimer(SOSAccount* account, SOSPeerRef peer, SOSEngineRef engine, SOSCoderRef coder)
{
//check which timeout value to use
int timeoutValue = SOSPeerOTRTimerTimeoutValue(account, peer);
CFStringRef peerid = CFRetainSafe(SOSPeerGetID(peer));

secnotice("otrtimer", "setting timer for peer: %@", peer);
__block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, timeoutValue * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0);
dispatch_source_set_event_handler(timer, ^{
secnotice("otrtimer","otrTimerFired fired");
SOSCCResetOTRNegotiation_Server(peerid);

});

dispatch_source_set_cancel_handler(timer, ^{
CFReleaseSafe(peerid);
});

dispatch_resume(timer);

SOSPeerSetOTRTimer(peer, timer);
}

//clear the max retry counter in the account object
void SOSPeerOTRTimerClearMaxRetryCount(SOSAccount* account, NSString* peerid)
{
secnotice("otrtimer", "negotiation finished! clearing max retry counter");
NSMutableDictionary* attemptsPerPeer = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountRenegotiationRetryCount, NULL);
if(!attemptsPerPeer){
attemptsPerPeer = [NSMutableDictionary dictionary];
}
[attemptsPerPeer removeObjectForKey:peerid];
SOSAccountSetValue(account, kSOSAccountRenegotiationRetryCount, (__bridge CFMutableDictionaryRef)attemptsPerPeer, NULL);
}