diff --git a/.gitignore b/.gitignore index 893939c..36f8695 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .svn *.xcuserdatad *.mode1v3 -*.pbxuser \ No newline at end of file +*.pbxuser +build diff --git a/CocoaOSC.xcodeproj/project.pbxproj b/CocoaOSC.xcodeproj/project.pbxproj index 63324f9..262ab78 100644 --- a/CocoaOSC.xcodeproj/project.pbxproj +++ b/CocoaOSC.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 039D2DC916A9FE9000BDF0CE /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 039D2DC516A9FE9000BDF0CE /* GCDAsyncSocket.h */; }; + 039D2DCA16A9FE9000BDF0CE /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 039D2DC616A9FE9000BDF0CE /* GCDAsyncSocket.m */; }; + 039D2DCB16A9FE9000BDF0CE /* GCDAsyncUdpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 039D2DC716A9FE9000BDF0CE /* GCDAsyncUdpSocket.h */; }; + 039D2DCC16A9FE9000BDF0CE /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 039D2DC816A9FE9000BDF0CE /* GCDAsyncUdpSocket.m */; }; 0B4C46861460E8B9007B5E06 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B4C46851460E8B9007B5E06 /* Cocoa.framework */; }; 0B4C46901460E8B9007B5E06 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0B4C468E1460E8B9007B5E06 /* InfoPlist.strings */; }; 0B4C46C81460EAAF007B5E06 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B4C46C71460EAAF007B5E06 /* UIKit.framework */; }; @@ -55,15 +59,7 @@ 0B5068E71460E46500BD27ED /* OSCPacket.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B5068DD1460E46500BD27ED /* OSCPacket.h */; }; 0B5068E81460E46500BD27ED /* OSCPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B5068DE1460E46500BD27ED /* OSCPacket.m */; }; 0B50693E1460E4BC00BD27ED /* RegexKitLite.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B50691D1460E4BC00BD27ED /* RegexKitLite.h */; }; - 0B50693F1460E4BC00BD27ED /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B50691F1460E4BC00BD27ED /* RegexKitLite.m */; }; - 0BFFAAD215F42EC50051E00F /* AsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BFFAACE15F42EC50051E00F /* AsyncSocket.h */; }; - 0BFFAAD315F42EC50051E00F /* AsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BFFAACF15F42EC50051E00F /* AsyncSocket.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; - 0BFFAAD415F42EC50051E00F /* AsyncUdpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BFFAAD015F42EC50051E00F /* AsyncUdpSocket.h */; }; - 0BFFAAD515F42EC50051E00F /* AsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BFFAAD115F42EC50051E00F /* AsyncUdpSocket.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; - 0BFFAAD615F42EEE0051E00F /* AsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BFFAACE15F42EC50051E00F /* AsyncSocket.h */; }; - 0BFFAAD715F42EF20051E00F /* AsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BFFAACF15F42EC50051E00F /* AsyncSocket.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; - 0BFFAAD815F42EF40051E00F /* AsyncUdpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BFFAAD015F42EC50051E00F /* AsyncUdpSocket.h */; }; - 0BFFAAD915F42EF70051E00F /* AsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BFFAAD115F42EC50051E00F /* AsyncUdpSocket.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 0B50693F1460E4BC00BD27ED /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B50691F1460E4BC00BD27ED /* RegexKitLite.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -97,6 +93,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 039D2DC516A9FE9000BDF0CE /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GCDAsyncSocket.h; path = CocoaAsyncSocket/GCD/GCDAsyncSocket.h; sourceTree = ""; }; + 039D2DC616A9FE9000BDF0CE /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GCDAsyncSocket.m; path = CocoaAsyncSocket/GCD/GCDAsyncSocket.m; sourceTree = ""; }; + 039D2DC716A9FE9000BDF0CE /* GCDAsyncUdpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GCDAsyncUdpSocket.h; path = CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.h; sourceTree = ""; }; + 039D2DC816A9FE9000BDF0CE /* GCDAsyncUdpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GCDAsyncUdpSocket.m; path = CocoaAsyncSocket/GCD/GCDAsyncUdpSocket.m; sourceTree = ""; }; 0B4C46841460E8B9007B5E06 /* CocoaOSC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CocoaOSC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0B4C46851460E8B9007B5E06 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; 0B4C46881460E8B9007B5E06 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -149,10 +149,6 @@ 0B50691D1460E4BC00BD27ED /* RegexKitLite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegexKitLite.h; sourceTree = ""; }; 0B50691E1460E4BC00BD27ED /* RegexKitLite.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = RegexKitLite.html; sourceTree = ""; }; 0B50691F1460E4BC00BD27ED /* RegexKitLite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegexKitLite.m; sourceTree = ""; }; - 0BFFAACE15F42EC50051E00F /* AsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AsyncSocket.h; path = CocoaAsyncSocket/RunLoop/AsyncSocket.h; sourceTree = ""; }; - 0BFFAACF15F42EC50051E00F /* AsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AsyncSocket.m; path = CocoaAsyncSocket/RunLoop/AsyncSocket.m; sourceTree = ""; }; - 0BFFAAD015F42EC50051E00F /* AsyncUdpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AsyncUdpSocket.h; path = CocoaAsyncSocket/RunLoop/AsyncUdpSocket.h; sourceTree = ""; }; - 0BFFAAD115F42EC50051E00F /* AsyncUdpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AsyncUdpSocket.m; path = CocoaAsyncSocket/RunLoop/AsyncUdpSocket.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -340,10 +336,10 @@ 0BFFAACD15F42EB70051E00F /* CocoaAsyncSocket */ = { isa = PBXGroup; children = ( - 0BFFAACE15F42EC50051E00F /* AsyncSocket.h */, - 0BFFAACF15F42EC50051E00F /* AsyncSocket.m */, - 0BFFAAD015F42EC50051E00F /* AsyncUdpSocket.h */, - 0BFFAAD115F42EC50051E00F /* AsyncUdpSocket.m */, + 039D2DC516A9FE9000BDF0CE /* GCDAsyncSocket.h */, + 039D2DC616A9FE9000BDF0CE /* GCDAsyncSocket.m */, + 039D2DC716A9FE9000BDF0CE /* GCDAsyncUdpSocket.h */, + 039D2DC816A9FE9000BDF0CE /* GCDAsyncUdpSocket.m */, ); name = CocoaAsyncSocket; sourceTree = ""; @@ -361,8 +357,6 @@ 0B4C47131460F4B8007B5E06 /* OSCDispatcher.h in Headers */, 0B4C47151460F4B8007B5E06 /* OSCPacket.h in Headers */, 0B4C471B1460F565007B5E06 /* RegexKitLite.h in Headers */, - 0BFFAAD615F42EEE0051E00F /* AsyncSocket.h in Headers */, - 0BFFAAD815F42EF40051E00F /* AsyncUdpSocket.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -377,8 +371,8 @@ 0B5068E51460E46500BD27ED /* OSCDispatcher.h in Headers */, 0B5068E71460E46500BD27ED /* OSCPacket.h in Headers */, 0B50693E1460E4BC00BD27ED /* RegexKitLite.h in Headers */, - 0BFFAAD215F42EC50051E00F /* AsyncSocket.h in Headers */, - 0BFFAAD415F42EC50051E00F /* AsyncUdpSocket.h in Headers */, + 039D2DC916A9FE9000BDF0CE /* GCDAsyncSocket.h in Headers */, + 039D2DCB16A9FE9000BDF0CE /* GCDAsyncUdpSocket.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -527,8 +521,6 @@ 0B4C47141460F4B8007B5E06 /* OSCDispatcher.m in Sources */, 0B4C47161460F4B8007B5E06 /* OSCPacket.m in Sources */, 0B4C471C1460F565007B5E06 /* RegexKitLite.m in Sources */, - 0BFFAAD715F42EF20051E00F /* AsyncSocket.m in Sources */, - 0BFFAAD915F42EF70051E00F /* AsyncUdpSocket.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -562,8 +554,8 @@ 0B5068E61460E46500BD27ED /* OSCDispatcher.m in Sources */, 0B5068E81460E46500BD27ED /* OSCPacket.m in Sources */, 0B50693F1460E4BC00BD27ED /* RegexKitLite.m in Sources */, - 0BFFAAD315F42EC50051E00F /* AsyncSocket.m in Sources */, - 0BFFAAD515F42EC50051E00F /* AsyncUdpSocket.m in Sources */, + 039D2DCA16A9FE9000BDF0CE /* GCDAsyncSocket.m in Sources */, + 039D2DCC16A9FE9000BDF0CE /* GCDAsyncUdpSocket.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -764,6 +756,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CLANG_ENABLE_OBJC_ARC = YES; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -787,6 +780,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + CLANG_ENABLE_OBJC_ARC = YES; COPY_PHASE_STRIP = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; @@ -814,10 +808,7 @@ 0B5068D41460E41500BD27ED /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = ( - armv6, - armv7, - ); + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; DSTROOT = /tmp/CocoaOSC.dst; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "CocoaOSC/CocoaOSC-Prefix.pch"; diff --git a/CocoaOSC/NS+OSCAdditions.m b/CocoaOSC/NS+OSCAdditions.m index 14e5bbe..e341332 100644 --- a/CocoaOSC/NS+OSCAdditions.m +++ b/CocoaOSC/NS+OSCAdditions.m @@ -22,8 +22,6 @@ + (NSDate *)ntpReferenceDate [components setDay:1]; NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; date = [[calendar dateFromComponents:components] copy]; - [components release]; - [calendar release]; } return date; } @@ -55,7 +53,7 @@ @implementation NSString (OSCAdditions) - (NSData *)oscStringData { - NSMutableData *data = [[[self dataUsingEncoding:NSASCIIStringEncoding] mutableCopy] autorelease]; + NSMutableData *data = [[self dataUsingEncoding:NSASCIIStringEncoding] mutableCopy]; // Add terminating NULL. [data setLength:[data length]+1]; diff --git a/CocoaOSC/OSCConnection.h b/CocoaOSC/OSCConnection.h index 9c94b53..275ab74 100644 --- a/CocoaOSC/OSCConnection.h +++ b/CocoaOSC/OSCConnection.h @@ -9,9 +9,9 @@ #import #import "OSCConnectionDelegate.h" +#import "GCDAsyncSocket.h" +#import "GCDAsyncUdpSocket.h" -@class AsyncSocket; -@class AsyncUdpSocket; @class OSCPacket; @class OSCDispatcher; @@ -24,14 +24,17 @@ typedef enum { -@interface OSCConnection : NSObject +@interface OSCConnection : NSObject { - id delegate; + __unsafe_unretained id delegate; OSCDispatcher *dispatcher; - AsyncSocket *tcpListenSocket; - AsyncSocket *tcpSocket; - AsyncUdpSocket *udpSocket; + GCDAsyncSocket *tcpListenSocket; + GCDAsyncSocket *tcpSocket; + GCDAsyncUdpSocket *udpSocket; + + /// Used to serialize accesses to pendingPacketsByTag + dispatch_queue_t pendingPacketsQueue; OSCConnectionProtocol protocol; @@ -51,6 +54,11 @@ typedef enum { @property (nonatomic, readonly) UInt16 localPort; @property (nonatomic, readonly) OSCConnectionProtocol protocol; +/** + @param dispatcher can be nil + */ +- (id)initWithDispatcher:(OSCDispatcher *)dispatcher; + // Connect and either accept or bind are mutually exclusive. Don't call one after calling the other. - (BOOL)connectToHost:(NSString *)host port:(UInt16)port protocol:(OSCConnectionProtocol)protocol error:(NSError **)errPtr; - (void)disconnect; diff --git a/CocoaOSC/OSCConnection.m b/CocoaOSC/OSCConnection.m index f85e12c..cbec9d9 100644 --- a/CocoaOSC/OSCConnection.m +++ b/CocoaOSC/OSCConnection.m @@ -9,8 +9,8 @@ #import "OSCConnection.h" #import "OSCPacket.h" #import "OSCDispatcher.h" -#import "AsyncSocket.h" -#import "AsyncUdpSocket.h" +#import "GCDAsyncSocket.h" +#import "GCDAsyncUdpSocket.h" #define MAX_PACKET_LENGTH 1048576 @@ -28,6 +28,7 @@ - (void)dispatchPacketData:(NSData *)data fromHost:(NSString *)host port:(UInt16 - (void)notifyDelegateOfSentPacketWithTag:(long)tag; - (void)disconnectAndNotifyDelegate:(BOOL)notify; @property (nonatomic, readonly) id socket; // TCP or UDP socket or nil. +@property (readonly) dispatch_queue_t delegateQueue; // Queue on which to call delegate methods @end @@ -35,13 +36,21 @@ - (void)disconnectAndNotifyDelegate:(BOOL)notify; @implementation OSCConnection @synthesize protocol, delegate, dispatcher, continuouslyReceivePackets; +@dynamic delegateQueue; - (id)init +{ + return [self initWithDispatcher:[[OSCDispatcher alloc] init]]; +} + +- (id)initWithDispatcher:(OSCDispatcher *)_dispatcher { if (self = [super init]) { - dispatcher = [[OSCDispatcher alloc] init]; + dispatcher = _dispatcher; pendingPacketsByTag = [[NSMutableDictionary alloc] init]; + pendingPacketsQueue = dispatch_queue_create("com.github.cocoaosc.pending-packets", + DISPATCH_QUEUE_SERIAL); } return self; } @@ -49,9 +58,6 @@ - (id)init - (void)dealloc { [self disconnect]; - [dispatcher release]; - [pendingPacketsByTag release]; - [super dealloc]; } @@ -64,19 +70,20 @@ - (void)disconnectAndNotifyDelegate:(BOOL)notify { [tcpListenSocket setDelegate:nil]; [tcpListenSocket disconnect]; - [tcpListenSocket release]; tcpListenSocket = nil; [tcpSocket setDelegate:nil]; [tcpSocket disconnect]; - [tcpSocket release]; tcpSocket = nil; [udpSocket setDelegate:nil]; - [udpSocket release]; + [udpSocket close]; udpSocket = nil; - - [pendingPacketsByTag removeAllObjects]; + + dispatch_sync(pendingPacketsQueue, ^{ + [pendingPacketsByTag removeAllObjects]; + }); + if (notify && [delegate respondsToSelector:@selector(oscConnectionDidDisconnect:)]) { @@ -87,7 +94,7 @@ - (void)disconnectAndNotifyDelegate:(BOOL)notify - (BOOL)isConnected { - return [self.socket isConnected]; + return ([self.socket isConnected]); } @@ -96,6 +103,14 @@ - (id)socket return (tcpSocket ? (id)tcpSocket : (id)udpSocket); } +- (dispatch_queue_t)delegateQueue +{ + if([delegate respondsToSelector:@selector(queue)]) { + return [delegate queue] ?: dispatch_get_main_queue(); + } + + return dispatch_get_main_queue(); +} - (NSString *)connectedHost { @@ -132,7 +147,7 @@ - (BOOL)connectToHost:(NSString *)host port:(UInt16)port protocol:(OSCConnection if (protocol == OSCConnectionTCP_Int32Header || protocol == OSCConnectionTCP_RFC1055) { - tcpSocket = [[AsyncSocket alloc] initWithDelegate:self]; + tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.delegateQueue]; if (![tcpSocket connectToHost:host onPort:port error:errPtr]) { goto onError; @@ -140,7 +155,8 @@ - (BOOL)connectToHost:(NSString *)host port:(UInt16)port protocol:(OSCConnection } else { - udpSocket = [[AsyncUdpSocket alloc] initWithDelegate:self]; + udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:self.delegateQueue]; + udpSocket.maxReceiveIPv4BufferSize = 65535; // Max UDP datagram size if (![udpSocket connectToHost:host onPort:port error:errPtr]) { goto onError; @@ -169,7 +185,8 @@ - (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port protocol:(OSCC @"Can only accept connections on TCP sockets!"); [self disconnectAndNotifyDelegate:self.connected]; protocol = proto; - tcpListenSocket = [[AsyncSocket alloc] initWithDelegate:self]; + tcpListenSocket = [[GCDAsyncSocket alloc] initWithDelegate:self + delegateQueue:self.delegateQueue]; return [tcpListenSocket acceptOnInterface:interface port:port error:errPtr]; } @@ -178,10 +195,22 @@ - (BOOL)bindToAddress:(NSString *)localAddr port:(UInt16)port error:(NSError **) { [self disconnectAndNotifyDelegate:self.connected]; protocol = OSCConnectionUDP; - udpSocket = [[AsyncUdpSocket alloc] initWithDelegate:self]; - return [udpSocket bindToAddress:localAddr port:port error:errPtr]; -} + udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self + delegateQueue:self.delegateQueue]; + udpSocket.maxReceiveIPv4BufferSize = 65535; // Max UDP datagram size + BOOL bound = [udpSocket bindToPort:port interface:localAddr error:errPtr]; + + if(!bound) { + return NO; + } + + if(continuouslyReceivePackets) { + return [udpSocket beginReceiving:errPtr]; + } + + return bound; +} - (void)sendPacket:(OSCPacket *)packet { @@ -198,7 +227,10 @@ - (void)sendPacket:(OSCPacket *)packet } lastSendTag++; - [pendingPacketsByTag setObject:packet forKey:[NSNumber numberWithLong:lastSendTag]]; + + dispatch_async(pendingPacketsQueue, ^{ + [pendingPacketsByTag setObject:packet forKey:[NSNumber numberWithLong:lastSendTag]]; + }); NSData *packetData = [packet encode]; if (protocol == OSCConnectionUDP) @@ -222,13 +254,17 @@ - (void)sendPacket:(OSCPacket *)packet - (void)sendPacket:(OSCPacket *)packet toHost:(NSString *)host port:(UInt16)port { - NSAssert(protocol == OSCConnectionUDP && - udpSocket && - ![udpSocket isConnected], - @"-[OSCConnection sendPacket:toHost:port] can only be called on a UDP connection that has been binded."); + if(!(protocol == OSCConnectionUDP && udpSocket && ![udpSocket isConnected])) { + NSLog(@"-[OSCConnection sendPacket:toHost:port] can only be called on a UDP connection that has been binded."); + return; + } + lastSendTag++; - [pendingPacketsByTag setObject:packet forKey:[NSNumber numberWithLong:lastSendTag]]; - [udpSocket sendData:[packet encode] toHost:host port:port withTimeout:-1 tag:lastSendTag]; + + dispatch_async(pendingPacketsQueue, ^{ + [pendingPacketsByTag setObject:packet forKey:[NSNumber numberWithLong:lastSendTag]]; + [udpSocket sendData:[packet encode] toHost:host port:port withTimeout:-1 tag:lastSendTag]; + }); } @@ -236,7 +272,9 @@ - (void)receivePacket { if (protocol == OSCConnectionUDP) { - [udpSocket receiveWithTimeout:-1 tag:0]; + // TODO: Are we still doing receiveOnce? +// [udpSocket receiveWithTimeout:-1 tag:0]; + } else if (protocol == OSCConnectionTCP_Int32Header) { @@ -253,6 +291,12 @@ - (void)receivePacket - (void)dispatchPacketData:(NSData *)data fromHost:(NSString *)host port:(UInt16)port { + if([delegate respondsToSelector:@selector(oscConnection:shouldReceivePacketWithData:fromHost:port:)]) { + if([delegate oscConnection:self shouldReceivePacketWithData:data fromHost:host port:port] == NO) { + return; + } + } + OSCPacket *packet = [[OSCPacket alloc] initWithData:data]; if (!packet) { @@ -274,32 +318,37 @@ - (void)dispatchPacketData:(NSData *)data fromHost:(NSString *)host port:(UInt16 { [delegate oscConnection:self didReceivePacket:packet]; } - [packet release]; } - (void)notifyDelegateOfSentPacketWithTag:(long)tag { - NSNumber *key = [NSNumber numberWithLong:tag]; - if ([delegate respondsToSelector:@selector(oscConnection:didSendPacket:)]) - { - OSCPacket *packet = [pendingPacketsByTag objectForKey:key]; - [delegate oscConnection:self didSendPacket:packet]; - } - [pendingPacketsByTag removeObjectForKey:key]; + dispatch_async(pendingPacketsQueue, ^{ + NSNumber *key = [NSNumber numberWithLong:tag]; + if ([delegate respondsToSelector:@selector(oscConnection:didSendPacket:)]) + { + OSCPacket *packet = [pendingPacketsByTag objectForKey:key]; + + dispatch_async(self.delegateQueue, ^{ + [delegate oscConnection:self didSendPacket:packet]; + }); + } + + [pendingPacketsByTag removeObjectForKey:key]; + }); } #pragma mark TCP Delegate Methods -- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket +- (void)onSocket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket { [self disconnectAndNotifyDelegate:NO]; - tcpSocket = [newSocket retain]; + tcpSocket = newSocket; } -- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port +- (void)onSocket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port { if ([delegate respondsToSelector:@selector(oscConnectionDidConnect:)]) { @@ -308,13 +357,13 @@ - (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UIn } -- (void)onSocketDidDisconnect:(AsyncSocket *)sock +- (void)onSocketDidDisconnect:(GCDAsyncSocket *)sock { [self disconnectAndNotifyDelegate:YES]; } -- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag +- (void)onSocket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { if (tag == kPacketHeaderTag) { @@ -345,7 +394,7 @@ - (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)ta } -- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag +- (void)onSocket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag { if (tag != kPacketHeaderTag) { @@ -356,19 +405,26 @@ - (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag #pragma mark UDP Delegate Methods -- (BOOL)onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data + fromAddress:(NSData *)address +withFilterContext:(id)filterContext { + NSString *host; + UInt16 port; + + [GCDAsyncUdpSocket getHost:&host port:&port fromAddress:address]; + [self dispatchPacketData:data fromHost:host port:port]; - if (self.continuouslyReceivePackets) - { - [self receivePacket]; - } - return YES; +// if (self.continuouslyReceivePackets) +// { +// [self receivePacket]; +// } } -- (void)onUdpSocket:(AsyncUdpSocket *)sock didSendDataWithTag:(long)tag +- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag { - [self notifyDelegateOfSentPacketWithTag:tag]; + [self notifyDelegateOfSentPacketWithTag:tag]; } + @end diff --git a/CocoaOSC/OSCConnectionDelegate.h b/CocoaOSC/OSCConnectionDelegate.h index 11ed7dd..f3f30ad 100644 --- a/CocoaOSC/OSCConnectionDelegate.h +++ b/CocoaOSC/OSCConnectionDelegate.h @@ -22,8 +22,17 @@ - (void)oscConnection:(OSCConnection *)connection willSendPacket:(OSCPacket *)packet; - (void)oscConnection:(OSCConnection *)connection didSendPacket:(OSCPacket *)packet; +/** Provide an opportunity for delegates to reject a packet before parsing + + Parsing is an expensive operation, and there may be a situation where a packet will + eventually be discarded without being used. Returning NO from this method will + cause the data to be discarded before it's parsed. + */ +- (BOOL)oscConnection:(OSCConnection *)connection shouldReceivePacketWithData:(NSData *)data fromHost:(NSString *)host port:(UInt16)port; + - (void)oscConnection:(OSCConnection *)connection didReceivePacket:(OSCPacket *)packet; - (void)oscConnection:(OSCConnection *)connection didReceivePacket:(OSCPacket *)packet fromHost:(NSString *)host port:(UInt16)port; - (void)oscConnection:(OSCConnection *)connection failedToReceivePacketWithError:(NSError *)error; +- (dispatch_queue_t)queue; @end \ No newline at end of file diff --git a/CocoaOSC/OSCDispatcher.m b/CocoaOSC/OSCDispatcher.m index a52d6cc..efaa7e4 100644 --- a/CocoaOSC/OSCDispatcher.m +++ b/CocoaOSC/OSCDispatcher.m @@ -18,11 +18,8 @@ @interface OSCAddressNode : NSObject { - OSCAddressNode *parent; NSString *name; NSMutableDictionary *children; - id target; - SEL action; } @property (nonatomic, assign) OSCAddressNode *parent; @@ -47,19 +44,11 @@ @implementation OSCAddressNode @synthesize parent, name, target, action; -- (void)dealloc -{ - [children release]; - [name release]; - [super dealloc]; -} - - (NSString *)description { NSMutableString *string = [NSMutableString stringWithFormat:@"%@ -> %@ (%@)", self.address, self.target, NSStringFromSelector(self.action)]; NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; NSArray *allChildren = [[self.children allValues] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]]; - [sortDescriptor release]; for (OSCAddressNode *child in allChildren) { [string appendFormat:@"\n%@", child]; @@ -105,7 +94,6 @@ - (void)addMethodAtSubAddress:(NSArray *)address target:(id)t action:(SEL)a child.name = childName; child.parent = self; [self.children setObject:child forKey:childName]; - [child release]; } [child addMethodAtSubAddress:[address cdr] target:t action:a]; } @@ -185,9 +173,6 @@ - (id)init - (void)dealloc { [self cancelQueuedBundles]; - [queuedBundles release]; - [rootNode release]; - [super dealloc]; } diff --git a/CocoaOSC/OSCPacket.h b/CocoaOSC/OSCPacket.h index 4e0e84f..918a25e 100644 --- a/CocoaOSC/OSCPacket.h +++ b/CocoaOSC/OSCPacket.h @@ -8,6 +8,19 @@ #import +typedef enum : char { + OSCValueTypeNone = 0, + + // Standard arguments + OSCValueTypeInteger = 'i', // Int32 + OSCValueTypeFloat = 'f', // float + OSCValueTypeString = 's', // char* + OSCValueTypeBlob = 'b', + + // Nonstandard arguments + OSCValueTypeLong = 'h', // Int64 + OSCValueTypeTimetag = 't' // Int64 +} OSCValueType; /** OSCPacket is a class cluster. The initializer initWithData: returns one of the two subclasses depending on whether the data describes an OSC message or bundle. This is used for receiving and parsing OSC packets. To construct a new packet for sending, using the init initializer of one of the subclasses directly, then call encode to get the network data. @@ -58,6 +71,51 @@ @end +/** Immutable lazily-parsed message + */ +@interface OSCMessage : OSCPacket +{ + /** Preprocessed packet data + + All arguments have been converted to their native endianness after initWithData: + */ + __strong NSMutableData *packetData; +} + +/// System time at which the packet was received (set in initWithData:) +@property (readonly) CFAbsoluteTime timestamp; + +/** Initialize with raw packet buffer + + @param data raw packet buffer + + The data will be converted in-place where byte orders differ from the host CPU + */ +- (id)initWithData:(NSData *)data; + + +- (NSUInteger)countOfArguments; + + + +/** Pointer to data inside of packet buffer + + This method exposes the raw packet data + @param index Must be < countOfArguments + + @warning This method will return NULL for certain types + */ +- (void *)pointerToArgumentAtIndex:(NSUInteger)index; + +/** Length of consumable data + + This method does not take 4-byte alignment into consideration + */ +- (NSUInteger)lengthOfArgumentAtIndex:(NSUInteger)index; +- (OSCValueType)typeOfArgumentAtIndex:(NSUInteger)index; + +@end + @interface OSCMutableBundle : OSCPacket { NSDate *timetag; diff --git a/CocoaOSC/OSCPacket.m b/CocoaOSC/OSCPacket.m index 1ac0652..90c56c5 100644 --- a/CocoaOSC/OSCPacket.m +++ b/CocoaOSC/OSCPacket.m @@ -9,6 +9,8 @@ #import "OSCPacket.h" #import "NS+OSCAdditions.h" +/// Pad length to 4-byte alignment +static NSUInteger padLength(NSUInteger length); static id parseOSCObject(char typetag, const void *bytes, NSUInteger *ioIndex, NSUInteger length); @@ -67,6 +69,7 @@ - (NSString *)description @implementation OSCPacket +@dynamic address, arguments, timetag, childPackets, bundle; // Following accessors overridden by concrete subclasses. @@ -106,25 +109,21 @@ - (id)initWithData:(NSData *)data { if ([data length] == 0) { - [self release]; return nil; } unsigned char firstByte[1]; [data getBytes:firstByte length:1]; if (firstByte[0] == '/') { - [self release]; - self = [[OSCMutableMessage alloc] initWithData:data]; + self = [[OSCMessage alloc] initWithData:data]; } else if (firstByte[0] == '#') { - [self release]; self = [[OSCMutableBundle alloc] initWithData:data]; } else { NSLog(@"Unrecognized first byte for OSC message: %@", data); - [self release]; return nil; } } @@ -133,7 +132,6 @@ - (id)initWithData:(NSData *)data - (id)initWithCoder:(NSCoder *)aDecoder { - [self release]; self = nil; NSData *data = [aDecoder decodeObjectForKey:@"data"]; if (data) { @@ -229,6 +227,12 @@ - (NSData *)encode return nil; } + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@ (%@) %@>", self.class, self.address, self.arguments]; +} + @end @@ -238,12 +242,6 @@ @implementation OSCMutableMessage @synthesize arguments; @synthesize address; -- (void)dealloc -{ - [arguments release]; - [address release]; - [super dealloc]; -} - (id)init { @@ -421,13 +419,236 @@ - (NSData *)encode } -- (NSString *)description + +@end + +@interface OSCMessage () + +- (const char *)_address; +- (const NSUInteger)packetSize; +- (const char *)typetag; + +@end + +@implementation OSCMessage + +@synthesize timestamp; + +- (id)initWithData:(NSData *)data { - return [NSString stringWithFormat:@"", address, arguments]; + self = [super init]; + if(!self) return self; + + if(data.length % 4 != 0) { + NSLog(@"Invalid OSC packet received"); + return nil; + } + + packetData = [NSMutableData dataWithData:data]; + timestamp = CFAbsoluteTimeGetCurrent(); + +#if __LITTLE_ENDIAN__ + + // Convert endianness to native for each argument + for(int i = 0 ; i < [self countOfArguments] ; i++) { + void *argumentBytes = [self pointerToArgumentAtIndex:i]; + + OSCValueType type = [self typeOfArgumentAtIndex:i]; + switch (type) { + case OSCValueTypeFloat: { + Float32 hostFloat = CFConvertFloat32SwappedToHost(*(CFSwappedFloat32 *)argumentBytes); + *((Float32 *)argumentBytes) = hostFloat; + } + break; + + case OSCValueTypeBlob: + case OSCValueTypeInteger: { + int32_t hostInt = CFSwapInt32BigToHost(*((int32_t *)argumentBytes)); + *((int32_t *)argumentBytes) = hostInt; + } + break; + + case OSCValueTypeLong: + case OSCValueTypeTimetag: { + int64_t hostInt = CFSwapInt64BigToHost(*(uint64_t *)argumentBytes); + *((int64_t *)argumentBytes) = hostInt; + } + break; + + default: + break; + } + } + +#endif + + return self; } -@end +#pragma mark Parsing + +- (const NSUInteger)packetSize +{ + return *((NSUInteger *)packetData.bytes); +} + +- (const char *)_address +{ + return packetData.bytes; +} + +/// Padded address length +- (size_t)addressLength +{ + return padLength(strnlen([self _address], packetData.length) + 1); +} + +- (NSString *)address +{ + size_t length = strnlen([self _address], [self addressLength]); + return [[NSString alloc] initWithBytes:(void *)[self _address] length:length + encoding:NSASCIIStringEncoding]; +} + +/// Padded typetag length +- (size_t)typetagLength +{ + return padLength(strnlen([self typetag], packetData.length - [self addressLength])); +} + +- (const char *)typetag +{ + return (packetData.bytes + [self addressLength]); +} + + +#pragma mark Arguments + +/// Slow copy of all arguments wrapped in NSObjects +- (NSArray *)arguments +{ + int count = [self countOfArguments]; + NSMutableArray *args = [NSMutableArray arrayWithCapacity:count]; + + for(int i = 0 ; i < count ; i++) { + switch([self typeOfArgumentAtIndex:i]) { + case OSCValueTypeFloat: + [args addObject:[NSNumber numberWithFloat: + *((float *)[self pointerToArgumentAtIndex:i])]]; + break; + + case OSCValueTypeInteger: + [args addObject:[NSNumber numberWithInt: + *((int32_t *)[self pointerToArgumentAtIndex:i])]]; + break; + + case OSCValueTypeLong: + [args addObject:[NSNumber numberWithLong: + *((int64_t *)[self pointerToArgumentAtIndex:i])]]; + break; + + case OSCValueTypeString: { + char *str = [self pointerToArgumentAtIndex:i]; + int len = strnlen(str, [self lengthOfArgumentAtIndex:i]); + + [args addObject:[[NSString alloc] initWithBytes:str length:len + encoding:NSASCIIStringEncoding]]; + } + break; + + case OSCValueTypeBlob: + [args addObject:[NSData dataWithBytesNoCopy:[self pointerToArgumentAtIndex:i] + length:[self lengthOfArgumentAtIndex:i] + freeWhenDone:NO]]; + break; + + case OSCValueTypeTimetag: + [args addObject:[NSDate dateWithNTPTimestamp: + *((int64_t *)[self pointerToArgumentAtIndex:i])]]; + break; + + case OSCValueTypeNone: + default: + [args addObject:[NSNull null]]; + break; + } + } + return [NSArray arrayWithArray:args]; +} + +- (NSUInteger)countOfArguments +{ + // TODO: Use strnlen for safety + return strlen([self typetag]) - 1; +} + +- (void *)pointerToArgumentAtIndex:(NSUInteger)index +{ + if(index >= self.countOfArguments) { + return NULL; + } + + void *arg = ((void *)[self typetag] + [self typetagLength]); + int i = 0; + + // Iterate until we get to the desired argument + while(i != index) { + arg += [self lengthOfArgumentAtIndex:i]; + i++; + } + + return arg; +} + +- (NSUInteger)lengthOfArgumentAtIndex:(NSUInteger)index +{ + if(index >= self.countOfArguments) { + return 0; + } + + switch([self typeOfArgumentAtIndex:index]) { + case OSCValueTypeInteger: + case OSCValueTypeFloat: + return 4; + + case OSCValueTypeLong: + return 8; + + case OSCValueTypeBlob: + return padLength(*((UInt32 *)[self pointerToArgumentAtIndex:index])) + 4; + + case OSCValueTypeString: + // TODO: Use strnlen for safety + return padLength(strlen([self pointerToArgumentAtIndex:index]) + 1); + + case OSCValueTypeNone: + default: + return 0; + } +} + +- (OSCValueType)typeOfArgumentAtIndex:(NSUInteger)index +{ + if(index >= self.countOfArguments) { + return OSCValueTypeNone; + } + + return (OSCValueType) self.typetag[index + 1]; // comma is first char +} + +#pragma mark - + +- (BOOL)isEqual:(id)object +{ + if([object isKindOfClass:[OSCMessage class]]) { + return [packetData isEqualToData:((OSCMessage *)object)->packetData]; + } + + return [super isEqual:object]; +} + +@end @implementation OSCMutableBundle @@ -435,12 +656,6 @@ @implementation OSCMutableBundle @synthesize childPackets; @synthesize timetag; -- (void)dealloc -{ - [childPackets release]; - [timetag release]; - [super dealloc]; -} - (id)init { @@ -467,7 +682,6 @@ - (id)initWithData:(NSData *)data if (![bundleMarker isEqualToString:@"#bundle"]) { NSLog(@"Malformed bundle marker: %@", bundleMarker); - [self release]; return nil; } @@ -478,10 +692,13 @@ - (id)initWithData:(NSData *)data while (index < length) { NSUInteger size = [parseOSCObject('i', bytes, &index, length) unsignedIntegerValue]; + if(index + size > length) { + NSLog(@"Invalid packet size '%ld' in %@", (unsigned long)size, self); + return self; + } NSData *subData = [data subdataWithRange:NSMakeRange(index, size)]; OSCPacket *childPacket = [[OSCPacket alloc] initWithData:subData]; [self addChildPacket:childPacket]; - [childPacket release]; index += size; } } @@ -519,6 +736,10 @@ - (NSString *)description @end +static NSUInteger padLength(NSUInteger length) +{ + return length - ((length % 4) ?: 4) + 4; +} static id parseOSCObject(char typetag, const void *bytes, NSUInteger *ioIndex, NSUInteger length) { @@ -535,7 +756,7 @@ static id parseOSCObject(char typetag, const void *bytes, NSUInteger *ioIndex, N if (buffer[bufferSize-1] == '\0') { NSUInteger strLength = strlen(buffer); - returnValue = [[[NSString alloc] initWithBytesNoCopy:buffer length:strLength encoding:NSASCIIStringEncoding freeWhenDone:YES] autorelease]; + returnValue = [[NSString alloc] initWithBytesNoCopy:buffer length:strLength encoding:NSASCIIStringEncoding freeWhenDone:YES]; *ioIndex += strLength+1; } else diff --git a/lib/CocoaAsyncSocket b/lib/CocoaAsyncSocket index 9616a40..5cf7bac 160000 --- a/lib/CocoaAsyncSocket +++ b/lib/CocoaAsyncSocket @@ -1 +1 @@ -Subproject commit 9616a404dbda03c29dc85ab9acd1c698d04f2745 +Subproject commit 5cf7bac4d0bc18d257e989ea922fd2c4ca9255c4