199 lines
7.2 KiB
Objective-C
199 lines
7.2 KiB
Objective-C
#import "Constraints.h"
|
|
#import "ThreadManager.h"
|
|
#import "UdpSocket.h"
|
|
#import "Util.h"
|
|
|
|
@implementation UdpSocket
|
|
|
|
+(UdpSocket*) udpSocketToFirstSenderOnLocalPort:(in_port_t)localPort {
|
|
require(localPort > 0);
|
|
UdpSocket* p = [UdpSocket new];
|
|
p->specifiedLocalPort = localPort;
|
|
p->specifiedRemoteEndPoint = nil;
|
|
return p;
|
|
}
|
|
|
|
+(UdpSocket*) udpSocketFromLocalPort:(in_port_t)localPort
|
|
toRemoteEndPoint:(IpEndPoint*)remoteEndPoint {
|
|
require(remoteEndPoint != nil);
|
|
require([remoteEndPoint port] > 0);
|
|
require(localPort > 0);
|
|
|
|
UdpSocket* p = [UdpSocket new];
|
|
p->specifiedLocalPort = localPort;
|
|
p->specifiedRemoteEndPoint = remoteEndPoint;
|
|
return p;
|
|
}
|
|
+(UdpSocket*) udpSocketTo:(IpEndPoint*)remoteEndPoint {
|
|
require(remoteEndPoint != nil);
|
|
require([remoteEndPoint port] > 0);
|
|
|
|
UdpSocket* p = [UdpSocket new];
|
|
p->specifiedLocalPort = 0; // passing port 0 to CFSocketAddress means 'pick one for me'
|
|
p->specifiedRemoteEndPoint = remoteEndPoint;
|
|
return p;
|
|
}
|
|
|
|
-(void) dealloc {
|
|
if (socket != nil) {
|
|
CFSocketInvalidate(socket);
|
|
CFRelease(socket);
|
|
}
|
|
}
|
|
|
|
-(void) send:(NSData*)packet {
|
|
@synchronized(self) {
|
|
require(packet != nil);
|
|
requireState(socket != nil);
|
|
requireState([self isRemoteEndPointKnown]);
|
|
|
|
hasSentData = true;
|
|
CFTimeInterval t = 2.0;
|
|
CFSocketError result = CFSocketSendData(socket, NULL, (__bridge CFDataRef)packet, t);
|
|
|
|
if (result != kCFSocketSuccess) {
|
|
[currentHandler handleError:[NSString stringWithFormat:@"Send failed with error code: %ld", result]
|
|
relatedInfo:packet
|
|
causedTermination:false];
|
|
}
|
|
}
|
|
}
|
|
|
|
-(bool) isRemoteEndPointKnown {
|
|
@synchronized(self) {
|
|
return specifiedRemoteEndPoint != nil || clientConnectedFromRemoteEndPoint != nil;
|
|
}
|
|
}
|
|
|
|
-(IpEndPoint *)remoteEndPoint {
|
|
requireState([self isRemoteEndPointKnown]);
|
|
if (specifiedRemoteEndPoint != nil) return specifiedRemoteEndPoint;
|
|
return clientConnectedFromRemoteEndPoint;
|
|
}
|
|
|
|
|
|
-(bool) isLocalPortKnown {
|
|
@synchronized(self) {
|
|
return specifiedLocalPort != 0 || measuredLocalPort != 0;
|
|
}
|
|
}
|
|
|
|
-(in_port_t) localPort {
|
|
requireState([self isLocalPortKnown]);
|
|
if (specifiedLocalPort != 0) return specifiedLocalPort;
|
|
return measuredLocalPort;
|
|
}
|
|
|
|
void onReceivedData(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info);
|
|
void onReceivedData(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
|
|
UdpSocket* udp = (__bridge UdpSocket*)info;
|
|
NSData* copyOfPacketData = [NSData dataWithBytes:CFDataGetBytePtr(data)
|
|
length:(NSUInteger)CFDataGetLength((CFDataRef)data)];
|
|
[udp onReceivedData:copyOfPacketData
|
|
withEventType:type
|
|
from:address];
|
|
}
|
|
-(void) onReceivedData:(NSData*)data
|
|
withEventType:(CFSocketCallBackType)type
|
|
from:(CFDataRef)addressData {
|
|
|
|
@synchronized(self) {
|
|
@try {
|
|
checkOperation(type == kCFSocketDataCallBack);
|
|
|
|
bool waitingForClient = ![self isRemoteEndPointKnown];
|
|
bool packetHasContent = data.length > 0;
|
|
bool haveNotSentPacketToBeBounced = !hasSentData;
|
|
checkOperationDescribe(packetHasContent || waitingForClient || haveNotSentPacketToBeBounced,
|
|
@"Received empty UDP packet. Probably indicates destination is unreachable.");
|
|
|
|
if (waitingForClient) {
|
|
[self onConnectFrom:addressData];
|
|
}
|
|
|
|
[currentHandler handlePacket:data];
|
|
} @catch (OperationFailed* ex) {
|
|
[currentHandler handleError:ex
|
|
relatedInfo:nil
|
|
causedTermination:true];
|
|
CFSocketInvalidate(socket);
|
|
}
|
|
}
|
|
}
|
|
-(void) onConnectFrom:(CFDataRef)addressData {
|
|
CFSocketError connectResult = CFSocketConnectToAddress(socket,
|
|
addressData,
|
|
-1);
|
|
|
|
checkOperationDescribe(connectResult == 0,
|
|
([NSString stringWithFormat:@"CFSocketConnectToAddress failed with error code: %ld", connectResult]));
|
|
|
|
clientConnectedFromRemoteEndPoint = [IpEndPoint ipEndPointFromSockaddrData:(__bridge NSData*)addressData];
|
|
}
|
|
|
|
-(void) setupLocalEndPoint {
|
|
IpEndPoint* specifiedLocalEndPoint = [IpEndPoint ipEndPointAtUnspecifiedAddressOnPort:specifiedLocalPort];
|
|
|
|
CFSocketError setAddressResult = CFSocketSetAddress(socket, (__bridge CFDataRef)[specifiedLocalEndPoint sockaddrData]);
|
|
checkOperationDescribe(setAddressResult == kCFSocketSuccess,
|
|
([NSString stringWithFormat:@"CFSocketSetAddress failed with error code: %ld", setAddressResult]));
|
|
|
|
IpEndPoint* measuredLocalEndPoint = [IpEndPoint ipEndPointFromSockaddrData:(__bridge_transfer NSData*)CFSocketCopyAddress(socket)];
|
|
measuredLocalPort = [measuredLocalEndPoint port];
|
|
}
|
|
-(void) setupRemoteEndPoint {
|
|
if (specifiedRemoteEndPoint == nil) return;
|
|
|
|
CFSocketError connectResult = CFSocketConnectToAddress(socket,
|
|
(__bridge CFDataRef)[specifiedRemoteEndPoint sockaddrData],
|
|
-1);
|
|
|
|
checkOperationDescribe(connectResult == kCFSocketSuccess,
|
|
([NSString stringWithFormat:@"CFSocketConnectToAddress failed with error code: %ld", connectResult]));
|
|
|
|
}
|
|
|
|
-(void) startWithHandler:(PacketHandler*)handler
|
|
untilCancelled:(id<CancelToken>)untilCancelledToken {
|
|
|
|
require(handler != nil);
|
|
|
|
@synchronized(self) {
|
|
bool isFirstTime = currentHandler == nil;
|
|
currentHandler = handler;
|
|
if (!isFirstTime) return;
|
|
}
|
|
|
|
@try {
|
|
CFSocketContext socketContext = { 0, (__bridge void *)self, CFRetain, CFRelease, CFCopyDescription };
|
|
|
|
socket = CFSocketCreate(kCFAllocatorDefault,
|
|
PF_INET,
|
|
SOCK_DGRAM,
|
|
IPPROTO_UDP,
|
|
kCFSocketDataCallBack,
|
|
onReceivedData,
|
|
&socketContext);
|
|
checkOperationDescribe(socket != nil,
|
|
@"Failed to create socket.");
|
|
|
|
[self setupLocalEndPoint];
|
|
[self setupRemoteEndPoint];
|
|
|
|
NSRunLoop* runLoop = [ThreadManager lowLatencyThreadRunLoop];
|
|
CFRunLoopAddSource([runLoop getCFRunLoop], CFSocketCreateRunLoopSource(NULL, socket, 0), kCFRunLoopCommonModes);
|
|
|
|
[untilCancelledToken whenCancelled:^{
|
|
@synchronized(self) {
|
|
currentHandler = nil;
|
|
CFSocketInvalidate(socket);
|
|
}
|
|
}];
|
|
} @catch (OperationFailed* ex) {
|
|
[handler handleError:ex relatedInfo:nil causedTermination:true];
|
|
CFSocketInvalidate(socket);
|
|
}
|
|
}
|
|
|
|
@end
|