186 lines
7.0 KiB
Objective-C
186 lines
7.0 KiB
Objective-C
#import "ZrtpManager.h"
|
|
#import "ThreadManager.h"
|
|
#import "ZrtpInitiator.h"
|
|
#import "ZrtpResponder.h"
|
|
#import "ConfirmAckPacket.h"
|
|
|
|
#define MAX_RETRANSMIT_COUNT 45
|
|
#define MIN_RETRANSMIT_INTERVAL_SECONDS 0.15
|
|
#define RETRANSMIT_INTERVAL_GROW_FACTOR 2.0
|
|
#define MAX_RETRANSMIT_INTERVAL_SECONDS 1.5
|
|
#define MAX_WAIT_FOR_RESPONDER_HELLO_SECONDS 60.0
|
|
|
|
@implementation ZrtpManager
|
|
|
|
+(TOCFuture*) asyncPerformHandshakeOver:(RtpSocket*)rtpSocket
|
|
andCallController:(CallController*)callController {
|
|
|
|
require(rtpSocket != nil);
|
|
require(callController != nil);
|
|
|
|
ZrtpHandshakeSocket* handshakeChannel = [ZrtpHandshakeSocket zrtpHandshakeSocketOverRtp:rtpSocket];
|
|
|
|
id<ZrtpRole> role = callController.isInitiator
|
|
? [ZrtpInitiator zrtpInitiatorWithCallController:callController]
|
|
: [ZrtpResponder zrtpResponderWithCallController:callController];
|
|
|
|
ZrtpManager* manager = [ZrtpManager zrtpManagerWithHandshakeSocket:handshakeChannel
|
|
andRtpSocketToSecure:rtpSocket
|
|
andZrtpRole:role
|
|
andCallController:callController];
|
|
|
|
return [manager asyncPerformHandshake];
|
|
}
|
|
|
|
+(ZrtpManager*) zrtpManagerWithHandshakeSocket:(ZrtpHandshakeSocket*)handshakeSocket
|
|
andRtpSocketToSecure:(RtpSocket*)rtpSocket
|
|
andZrtpRole:(id<ZrtpRole>)zrtpRole
|
|
andCallController:(CallController*)callController {
|
|
|
|
require(handshakeSocket != nil);
|
|
require(rtpSocket != nil);
|
|
require(callController != nil);
|
|
require(zrtpRole != nil);
|
|
|
|
ZrtpManager* manager = [ZrtpManager new];
|
|
|
|
manager->callController = callController;
|
|
manager->zrtpRole = zrtpRole;
|
|
manager->futureHandshakeResultSource = [TOCFutureSource new];
|
|
manager->rtpSocketToSecure = rtpSocket;
|
|
manager->handshakeSocket = handshakeSocket;
|
|
manager->cancelTokenSource = [TOCCancelTokenSource new];
|
|
[[callController untilCancelledToken] whenCancelledTerminate:manager];
|
|
|
|
[manager->futureHandshakeResultSource.future catchDo:^(id error) {
|
|
[callController terminateWithReason:CallTerminationType_HandshakeFailed
|
|
withFailureInfo:error
|
|
andRelatedInfo:nil];
|
|
}];
|
|
|
|
return manager;
|
|
}
|
|
|
|
-(TOCFuture*) asyncPerformHandshake {
|
|
PacketHandlerBlock packetHandler = ^(id packet) {
|
|
require(packet != nil);
|
|
require([packet isKindOfClass:HandshakePacket.class]);
|
|
[self handleHandshakePacket:(HandshakePacket*)packet];
|
|
};
|
|
|
|
ErrorHandlerBlock errorHandler = ^(id error, id relatedInfo, bool causedTermination) {
|
|
if (causedTermination) {
|
|
[self terminate];
|
|
[futureHandshakeResultSource trySetFailure:error];
|
|
return;
|
|
}
|
|
|
|
// was Conf2Ack lost, and we're receiving encrypted audio data instead of handshake packets?
|
|
// (the RFC says to treat this as implying a Conf2Ack)
|
|
if ([zrtpRole isAuthenticatedAudioDataImplyingConf2Ack:relatedInfo]) {
|
|
// low-priority todo: Can we cache this bit of audio data, so that when the srtp socket is started the data comes out?
|
|
[self handleHandshakePacket:[[ConfirmAckPacket confirmAckPacket] embeddedIntoHandshakePacket]];
|
|
}
|
|
};
|
|
|
|
[handshakeSocket startWithHandler:[PacketHandler packetHandler:packetHandler withErrorHandler:errorHandler]
|
|
untilCancelled:cancelTokenSource.token];
|
|
|
|
HandshakePacket* initialPacket = [zrtpRole initialPacket];
|
|
if (initialPacket == nil) {
|
|
[self scheduleTimeoutIfNoHello];
|
|
} else {
|
|
[self setAndSendPacketToTransmit:initialPacket];
|
|
}
|
|
|
|
return futureHandshakeResultSource.future;
|
|
}
|
|
|
|
-(void) setAndSendPacketToTransmit:(HandshakePacket*)packet {
|
|
currentPacketTransmitCount = 0;
|
|
currentPacketToRetransmit = packet;
|
|
[self transmitCurrentHandshakePacket];
|
|
}
|
|
|
|
-(void) transmitCurrentHandshakePacket {
|
|
if (done) return;
|
|
|
|
requireState(currentPacketToRetransmit != nil);
|
|
[handshakeSocket send:currentPacketToRetransmit];
|
|
|
|
[self scheduleRetransmit];
|
|
}
|
|
-(void) scheduleRetransmit {
|
|
double falloffFactor = pow(RETRANSMIT_INTERVAL_GROW_FACTOR, currentPacketTransmitCount);
|
|
NSTimeInterval delay = MIN(MIN_RETRANSMIT_INTERVAL_SECONDS * falloffFactor, MAX_RETRANSMIT_INTERVAL_SECONDS);
|
|
currentPacketTransmitCount += 1;
|
|
|
|
[currentRetransmit cancel];
|
|
currentRetransmit = [TOCCancelTokenSource new];
|
|
|
|
[TimeUtil scheduleRun:^{[self handleRetransmit];}
|
|
afterDelay:delay
|
|
onRunLoop:[ThreadManager lowLatencyThreadRunLoop]
|
|
unlessCancelled:currentRetransmit.token];
|
|
}
|
|
|
|
-(void) handleRetransmit {
|
|
if (done) return;
|
|
currentPacketTransmitCount = 0;
|
|
if (currentPacketTransmitCount > MAX_RETRANSMIT_COUNT) {
|
|
done = true;
|
|
if (currentPacketToRetransmit == nil) {
|
|
[callController terminateWithReason:CallTerminationType_RecipientUnavailable
|
|
withFailureInfo:nil
|
|
andRelatedInfo:@"retransmit threshold exceeded"];
|
|
} else {
|
|
[futureHandshakeResultSource trySetFailure:[NegotiationFailed negotiationFailedWithReason:@"retransmit threshold exceeded"]];
|
|
}
|
|
return;
|
|
}
|
|
|
|
[self transmitCurrentHandshakePacket];
|
|
}
|
|
-(void) scheduleTimeoutIfNoHello {
|
|
void (^timeoutFail)(void) = ^{
|
|
[callController terminateWithReason:CallTerminationType_RecipientUnavailable
|
|
withFailureInfo:nil
|
|
andRelatedInfo:nil];
|
|
[futureHandshakeResultSource trySetFailure:[RecipientUnavailable recipientUnavailable]];
|
|
};
|
|
|
|
currentRetransmit = [TOCCancelTokenSource new];
|
|
[TimeUtil scheduleRun:timeoutFail
|
|
afterDelay:MAX_WAIT_FOR_RESPONDER_HELLO_SECONDS
|
|
onRunLoop:[ThreadManager lowLatencyThreadRunLoop]
|
|
unlessCancelled:currentRetransmit.token];
|
|
}
|
|
|
|
-(void) terminate {
|
|
done = true;
|
|
[cancelTokenSource cancel];
|
|
[currentRetransmit cancel];
|
|
}
|
|
|
|
-(void) handleHandshakePacket:(HandshakePacket*)packet {
|
|
require(packet != nil);
|
|
if (done) return;
|
|
|
|
HandshakePacket* response = [zrtpRole handlePacket:packet];
|
|
if (response != nil) {
|
|
[self setAndSendPacketToTransmit:response];
|
|
}
|
|
if (zrtpRole.hasHandshakeFinishedSuccessfully) {
|
|
done = true;
|
|
handshakeCompletedSuccesfully = true;
|
|
|
|
SrtpSocket* secureChannel = [zrtpRole useKeysToSecureRtpSocket:rtpSocketToSecure];
|
|
MasterSecret* masterSecret = zrtpRole.getMasterSecret;
|
|
|
|
ZrtpHandshakeResult* result = [ZrtpHandshakeResult zrtpHandshakeResultWithSecureChannel:secureChannel andMasterSecret:masterSecret];
|
|
|
|
[futureHandshakeResultSource trySetResult:result];
|
|
}
|
|
}
|
|
@end
|