Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 19e62a54c20de8ce77771d69de6788c5ae87c859 0 parents
Andrew Sliwinski authored
Showing with 58,140 additions and 0 deletions.
  1. +30 −0 Application-Info.plist
  2. +9 −0 Application_Prefix.pch
  3. BIN  Classes/.DS_Store
  4. +15 −0 Classes/ApplicationDelegate.h
  5. +66 −0 Classes/ApplicationDelegate.m
  6. +366 −0 Classes/AsyncUdpSocket.h
  7. +2,343 −0 Classes/AsyncUdpSocket.m
  8. +21 −0 Classes/Game.h
  9. +195 −0 Classes/Game.m
  10. +46 −0 Classes/SXNSDataExtensions.h
  11. +271 −0 Classes/SXNSDataExtensions.m
  12. +156 −0 Classes/SXParticleSystem.h
  13. +461 −0 Classes/SXParticleSystem.m
  14. +397 −0 ILBControl.xcodeproj/project.pbxproj
  15. +7 −0 ILBControl.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  16. +47,461 −0 ILBControl.xcodeproj/project.xcworkspace/xcuserdata/Kael.xcuserdatad/UserInterfaceState.xcuserstate
  17. +10 −0 ILBControl.xcodeproj/project.xcworkspace/xcuserdata/Kael.xcuserdatad/WorkspaceSettings.xcsettings
  18. +6,032 −0 ILBControl.xcodeproj/project.xcworkspace/xcuserdata/asliwinski.xcuserdatad/UserInterfaceState.xcuserstate
  19. +5 −0 ILBControl.xcodeproj/xcuserdata/Kael.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist
  20. +76 −0 ILBControl.xcodeproj/xcuserdata/Kael.xcuserdatad/xcschemes/ILBControl.xcscheme
  21. +22 −0 ILBControl.xcodeproj/xcuserdata/Kael.xcuserdatad/xcschemes/xcschememanagement.plist
  22. +76 −0 ILBControl.xcodeproj/xcuserdata/asliwinski.xcuserdatad/xcschemes/ILBControl.xcscheme
  23. +22 −0 ILBControl.xcodeproj/xcuserdata/asliwinski.xcuserdatad/xcschemes/xcschememanagement.plist
  24. BIN  background.png
  25. BIN  background@2x.png
  26. BIN  control.png
  27. BIN  control@2x.png
  28. +14 −0 main.m
  29. +39 −0 plasma.xml
30 Application-Info.plist
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundleIdentifier</key>
+ <string>com.gamua.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIStatusBarHidden</key>
+ <true/>
+</dict>
+</plist>
9 Application_Prefix.pch
@@ -0,0 +1,9 @@
+//
+// Prefix header for all source files of the 'AppScaffold' target in the 'AppScaffold' project
+//
+
+#ifdef __OBJC__
+ #import <Foundation/Foundation.h>
+ #import <UIKit/UIKit.h>
+ #import "Sparrow.h"
+#endif
BIN  Classes/.DS_Store
Binary file not shown
15 Classes/ApplicationDelegate.h
@@ -0,0 +1,15 @@
+//
+// AppScaffoldAppDelegate.h
+// AppScaffold
+//
+
+#import <UIKit/UIKit.h>
+
+@interface ApplicationDelegate : NSObject <UIApplicationDelegate>
+{
+ @private
+ UIWindow *mWindow;
+ SPView *mSparrowView;
+}
+
+@end
66 Classes/ApplicationDelegate.m
@@ -0,0 +1,66 @@
+//
+// AppScaffoldAppDelegate.m
+// AppScaffold
+//
+
+#import "ApplicationDelegate.h"
+#import "Game.h"
+
+@implementation ApplicationDelegate
+
+- (id)init
+{
+ if ((self = [super init]))
+ {
+ mWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
+ mSparrowView = [[SPView alloc] initWithFrame:mWindow.bounds];
+ [mWindow addSubview:mSparrowView];
+ }
+ return self;
+}
+
+- (void)applicationDidFinishLaunching:(UIApplication *)application
+{
+ SP_CREATE_POOL(pool);
+
+ [SPStage setSupportHighResolutions:YES];
+ [SPAudioEngine start];
+
+ Game *game = [[Game alloc] init];
+ mSparrowView.stage = game;
+ mSparrowView.multipleTouchEnabled = NO;
+ mSparrowView.frameRate = 30.0f;
+ [game release];
+
+ [mWindow makeKeyAndVisible];
+ [mSparrowView start];
+
+ SP_RELEASE_POOL(pool);
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application
+{
+ [mSparrowView stop];
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application
+{
+ [mSparrowView start];
+}
+
+- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
+{
+ [SPPoint purgePool];
+ [SPRectangle purgePool];
+ [SPMatrix purgePool];
+}
+
+- (void)dealloc
+{
+ [SPAudioEngine stop];
+ [mSparrowView release];
+ [mWindow release];
+ [super dealloc];
+}
+
+@end
366 Classes/AsyncUdpSocket.h
@@ -0,0 +1,366 @@
+//
+// AsyncUdpSocket.h
+//
+// This class is in the public domain.
+// Originally created by Robbie Hanson on Wed Oct 01 2008.
+// Updated and maintained by Deusty Designs and the Mac development community.
+//
+// http://code.google.com/p/cocoaasyncsocket/
+//
+
+#import <Foundation/Foundation.h>
+
+@class AsyncSendPacket;
+@class AsyncReceivePacket;
+
+extern NSString *const AsyncUdpSocketException;
+extern NSString *const AsyncUdpSocketErrorDomain;
+
+enum AsyncUdpSocketError
+{
+ AsyncUdpSocketCFSocketError = kCFSocketError, // From CFSocketError enum
+ AsyncUdpSocketNoError = 0, // Never used
+ AsyncUdpSocketBadParameter, // Used if given a bad parameter (such as an improper address)
+ AsyncUdpSocketIPv4Unavailable, // Used if you bind/connect using IPv6 only
+ AsyncUdpSocketIPv6Unavailable, // Used if you bind/connect using IPv4 only (or iPhone)
+ AsyncUdpSocketSendTimeoutError,
+ AsyncUdpSocketReceiveTimeoutError
+};
+typedef enum AsyncUdpSocketError AsyncUdpSocketError;
+
+@interface AsyncUdpSocket : NSObject
+{
+ CFSocketRef theSocket4; // IPv4 socket
+ CFSocketRef theSocket6; // IPv6 socket
+
+ CFRunLoopSourceRef theSource4; // For theSocket4
+ CFRunLoopSourceRef theSource6; // For theSocket6
+ CFRunLoopRef theRunLoop;
+ CFSocketContext theContext;
+ NSArray *theRunLoopModes;
+
+ NSMutableArray *theSendQueue;
+ AsyncSendPacket *theCurrentSend;
+ NSTimer *theSendTimer;
+
+ NSMutableArray *theReceiveQueue;
+ AsyncReceivePacket *theCurrentReceive;
+ NSTimer *theReceiveTimer;
+
+ id theDelegate;
+ UInt16 theFlags;
+
+ long theUserData;
+
+ NSString *cachedLocalHost;
+ UInt16 cachedLocalPort;
+
+ NSString *cachedConnectedHost;
+ UInt16 cachedConnectedPort;
+
+ UInt32 maxReceiveBufferSize;
+}
+
+/**
+ * Creates new instances of AsyncUdpSocket.
+**/
+- (id)init;
+- (id)initWithDelegate:(id)delegate;
+- (id)initWithDelegate:(id)delegate userData:(long)userData;
+
+/**
+ * Creates new instances of AsyncUdpSocket that support only IPv4 or IPv6.
+ * The other init methods will support both, unless specifically binded or connected to one protocol.
+ * If you know you'll only be using one protocol, these init methods may be a bit more efficient.
+**/
+- (id)initIPv4;
+- (id)initIPv6;
+
+- (id)delegate;
+- (void)setDelegate:(id)delegate;
+
+- (long)userData;
+- (void)setUserData:(long)userData;
+
+/**
+ * Returns the local address info for the socket.
+ *
+ * Note: Address info may not be available until after the socket has been bind'ed,
+ * or until after data has been sent.
+**/
+- (NSString *)localHost;
+- (UInt16)localPort;
+
+/**
+ * Returns the remote address info for the socket.
+ *
+ * Note: Since UDP is connectionless by design, connected address info
+ * will not be available unless the socket is explicitly connected to a remote host/port
+**/
+- (NSString *)connectedHost;
+- (UInt16)connectedPort;
+
+/**
+ * Returns whether or not this socket has been connected to a single host.
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ * If connected, the socket will only be able to send/receive data to/from the connected host.
+**/
+- (BOOL)isConnected;
+
+/**
+ * Returns whether or not this socket has been closed.
+ * The only way a socket can be closed is if you explicitly call one of the close methods.
+**/
+- (BOOL)isClosed;
+
+/**
+ * Returns whether or not this socket supports IPv4.
+ * By default this will be true, unless the socket is specifically initialized as IPv6 only,
+ * or is binded or connected to an IPv6 address.
+**/
+- (BOOL)isIPv4;
+
+/**
+ * Returns whether or not this socket supports IPv6.
+ * By default this will be true, unless the socket is specifically initialized as IPv4 only,
+ * or is binded or connected to an IPv4 address.
+ *
+ * This method will also return false on platforms that do not support IPv6.
+ * Note: The iPhone does not currently support IPv6.
+**/
+- (BOOL)isIPv6;
+
+/**
+ * Returns the mtu of the socket.
+ * If unknown, returns zero.
+ *
+ * Sending data larger than this may result in an error.
+ * This is an advanced topic, and one should understand the wide range of mtu's on networks and the internet.
+ * Therefore this method is only for reference and may be of little use in many situations.
+**/
+- (unsigned int)maximumTransmissionUnit;
+
+/**
+ * Binds the UDP socket to the given port and optional address.
+ * Binding should be done for server sockets that receive data prior to sending it.
+ * Client sockets can skip binding,
+ * as the OS will automatically assign the socket an available port when it starts sending data.
+ *
+ * You cannot bind a socket after its been connected.
+ * You can only bind a socket once.
+ * You can still connect a socket (if desired) after binding.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+- (BOOL)bindToPort:(UInt16)port error:(NSError **)errPtr;
+- (BOOL)bindToAddress:(NSString *)localAddr port:(UInt16)port error:(NSError **)errPtr;
+
+/**
+ * Connects the UDP socket to the given host and port.
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ *
+ * Choosing to connect to a specific host/port has the following effect:
+ * - You will only be able to send data to the connected host/port.
+ * - You will only be able to receive data from the connected host/port.
+ * - You will receive ICMP messages that come from the connected host/port, such as "connection refused".
+ *
+ * Connecting a UDP socket does not result in any communication on the socket.
+ * It simply changes the internal state of the socket.
+ *
+ * You cannot bind a socket after its been connected.
+ * You can only connect a socket once.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+- (BOOL)connectToHost:(NSString *)host onPort:(UInt16)port error:(NSError **)errPtr;
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
+
+/**
+ * Join multicast group
+ *
+ * Group should be an IP address (eg @"225.228.0.1")
+**/
+- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr;
+- (BOOL)joinMulticastGroup:(NSString *)group withAddress:(NSString *)interface error:(NSError **)errPtr;
+
+/**
+ * By default, the underlying socket in the OS will not allow you to send broadcast messages.
+ * In order to send broadcast messages, you need to enable this functionality in the socket.
+ *
+ * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is
+ * delivered to every host on the network.
+ * The reason this is generally disabled by default is to prevent
+ * accidental broadcast messages from flooding the network.
+**/
+- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr;
+
+/**
+ * Asynchronously sends the given data, with the given timeout and tag.
+ *
+ * This method may only be used with a connected socket.
+ *
+ * If data is nil or zero-length, this method does nothing and immediately returns NO.
+ * If the socket is not connected, this method does nothing and immediately returns NO.
+**/
+- (BOOL)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Asynchronously sends the given data, with the given timeout and tag, to the given host and port.
+ *
+ * This method cannot be used with a connected socket.
+ *
+ * If data is nil or zero-length, this method does nothing and immediately returns NO.
+ * If the socket is connected, this method does nothing and immediately returns NO.
+ * If unable to resolve host to a valid IPv4 or IPv6 address, this method returns NO.
+**/
+- (BOOL)sendData:(NSData *)data toHost:(NSString *)host port:(UInt16)port withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Asynchronously sends the given data, with the given timeout and tag, to the given address.
+ *
+ * This method cannot be used with a connected socket.
+ *
+ * If data is nil or zero-length, this method does nothing and immediately returns NO.
+ * If the socket is connected, this method does nothing and immediately returns NO.
+**/
+- (BOOL)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Asynchronously receives a single datagram packet.
+ *
+ * If the receive succeeds, the onUdpSocket:didReceiveData:fromHost:port:tag delegate method will be called.
+ * Otherwise, a timeout will occur, and the onUdpSocket:didNotReceiveDataWithTag: delegate method will be called.
+**/
+- (void)receiveWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Closes the socket immediately. Any pending send or receive operations are dropped.
+**/
+- (void)close;
+
+/**
+ * Closes after all pending send operations have completed.
+ * After calling this, the sendData: and receive: methods will do nothing.
+ * In other words, you won't be able to add any more send or receive operations to the queue.
+ * The socket will close even if there are still pending receive operations.
+**/
+- (void)closeAfterSending;
+
+/**
+ * Closes after all pending receive operations have completed.
+ * After calling this, the sendData: and receive: methods will do nothing.
+ * In other words, you won't be able to add any more send or receive operations to the queue.
+ * The socket will close even if there are still pending send operations.
+**/
+- (void)closeAfterReceiving;
+
+/**
+ * Closes after all pending send and receive operations have completed.
+ * After calling this, the sendData: and receive: methods will do nothing.
+ * In other words, you won't be able to add any more send or receive operations to the queue.
+**/
+- (void)closeAfterSendingAndReceiving;
+
+/**
+ * Gets/Sets the maximum size of the buffer that will be allocated for receive operations.
+ * The default size is 9216 bytes.
+ *
+ * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535.
+ * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295.
+ *
+ * In practice, however, the size of UDP packets will be much smaller.
+ * Indeed most protocols will send and receive packets of only a few bytes,
+ * or will set a limit on the size of packets to prevent fragmentation in the IP layer.
+ *
+ * If you set the buffer size too small, the sockets API in the OS will silently discard
+ * any extra data, and you will not be notified of the error.
+**/
+- (UInt32)maxReceiveBufferSize;
+- (void)setMaxReceiveBufferSize:(UInt32)max;
+
+/**
+ * When you create an AsyncUdpSocket, it is added to the runloop of the current thread.
+ * So it is easiest to simply create the socket on the thread you intend to use it.
+ *
+ * If, however, you need to move the socket to a separate thread at a later time, this
+ * method may be used to accomplish the task.
+ *
+ * This method must be called from the thread/runloop the socket is currently running on.
+ *
+ * Note: After calling this method, all further method calls to this object should be done from the given runloop.
+ * Also, all delegate calls will be sent on the given runloop.
+**/
+- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop;
+
+/**
+ * Allows you to configure which run loop modes the socket uses.
+ * The default set of run loop modes is NSDefaultRunLoopMode.
+ *
+ * If you'd like your socket to continue operation during other modes, you may want to add modes such as
+ * NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes.
+ *
+ * Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes.
+**/
+- (BOOL)setRunLoopModes:(NSArray *)runLoopModes;
+
+/**
+ * Returns the current run loop modes the AsyncSocket instance is operating in.
+ * The default set of run loop modes is NSDefaultRunLoopMode.
+**/
+- (NSArray *)runLoopModes;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol AsyncUdpSocketDelegate
+@optional
+
+/**
+ * Called when the datagram with the given tag has been sent.
+**/
+- (void)onUdpSocket:(AsyncUdpSocket *)sock didSendDataWithTag:(long)tag;
+
+/**
+ * Called if an error occurs while trying to send a datagram.
+ * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet.
+**/
+- (void)onUdpSocket:(AsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error;
+
+/**
+ * Called when the socket has received the requested datagram.
+ *
+ * Due to the nature of UDP, you may occasionally receive undesired packets.
+ * These may be rogue UDP packets from unknown hosts,
+ * or they may be delayed packets arriving after retransmissions have already occurred.
+ * It's important these packets are properly ignored, while not interfering with the flow of your implementation.
+ * As an aid, this delegate method has a boolean return value.
+ * If you ever need to ignore a received packet, simply return NO,
+ * and AsyncUdpSocket will continue as if the packet never arrived.
+ * That is, the original receive request will still be queued, and will still timeout as usual if a timeout was set.
+ * For example, say you requested to receive data, and you set a timeout of 500 milliseconds, using a tag of 15.
+ * If rogue data arrives after 250 milliseconds, this delegate method would be invoked, and you could simply return NO.
+ * If the expected data then arrives within the next 250 milliseconds,
+ * this delegate method will be invoked, with a tag of 15, just as if the rogue data never appeared.
+ *
+ * Under normal circumstances, you simply return YES from this method.
+**/
+- (BOOL)onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port;
+
+/**
+ * Called if an error occurs while trying to receive a requested datagram.
+ * This is generally due to a timeout, but could potentially be something else if some kind of OS error occurred.
+**/
+- (void)onUdpSocket:(AsyncUdpSocket *)sock didNotReceiveDataWithTag:(long)tag dueToError:(NSError *)error;
+
+/**
+ * Called when the socket is closed.
+ * A socket is only closed if you explicitly call one of the close methods.
+**/
+- (void)onUdpSocketDidClose:(AsyncUdpSocket *)sock;
+
+@end
2,343 Classes/AsyncUdpSocket.m
@@ -0,0 +1,2343 @@
+//
+// AsyncUdpSocket.m
+//
+// This class is in the public domain.
+// Originally created by Robbie Hanson on Wed Oct 01 2008.
+// Updated and maintained by Deusty Designs and the Mac development community.
+//
+// http://code.google.com/p/cocoaasyncsocket/
+//
+
+#import "AsyncUdpSocket.h"
+#import <sys/socket.h>
+#import <netinet/in.h>
+#import <arpa/inet.h>
+#import <sys/ioctl.h>
+#import <net/if.h>
+#import <netdb.h>
+
+#if TARGET_OS_IPHONE
+// Note: You may need to add the CFNetwork Framework to your project
+#import <CFNetwork/CFNetwork.h>
+#endif
+
+
+#define SENDQUEUE_CAPACITY 5 // Initial capacity
+#define RECEIVEQUEUE_CAPACITY 5 // Initial capacity
+
+#define DEFAULT_MAX_RECEIVE_BUFFER_SIZE 9216
+
+NSString *const AsyncUdpSocketException = @"AsyncUdpSocketException";
+NSString *const AsyncUdpSocketErrorDomain = @"AsyncUdpSocketErrorDomain";
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
+// Mutex lock used by all instances of AsyncUdpSocket, to protect getaddrinfo.
+// Prior to Mac OS X 10.5 this method was not thread-safe.
+static NSString *getaddrinfoLock = @"lock";
+#endif
+
+enum AsyncUdpSocketFlags
+{
+ kDidBind = 1 << 0, // If set, bind has been called.
+ kDidConnect = 1 << 1, // If set, connect has been called.
+ kSock4CanAcceptBytes = 1 << 2, // If set, we know socket4 can accept bytes. If unset, it's unknown.
+ kSock6CanAcceptBytes = 1 << 3, // If set, we know socket6 can accept bytes. If unset, it's unknown.
+ kSock4HasBytesAvailable = 1 << 4, // If set, we know socket4 has bytes available. If unset, it's unknown.
+ kSock6HasBytesAvailable = 1 << 5, // If set, we know socket6 has bytes available. If unset, it's unknown.
+ kForbidSendReceive = 1 << 6, // If set, no new send or receive operations are allowed to be queued.
+ kCloseAfterSends = 1 << 7, // If set, close as soon as no more sends are queued.
+ kCloseAfterReceives = 1 << 8, // If set, close as soon as no more receives are queued.
+ kDidClose = 1 << 9, // If set, the socket has been closed, and should not be used anymore.
+ kDequeueSendScheduled = 1 << 10, // If set, a maybeDequeueSend operation is already scheduled.
+ kDequeueReceiveScheduled = 1 << 11, // If set, a maybeDequeueReceive operation is already scheduled.
+ kFlipFlop = 1 << 12, // Used to alternate between IPv4 and IPv6 sockets.
+};
+
+@interface AsyncUdpSocket (Private)
+
+// Run Loop
+- (void)runLoopAddSource:(CFRunLoopSourceRef)source;
+- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source;
+- (void)runLoopAddTimer:(NSTimer *)timer;
+- (void)runLoopRemoveTimer:(NSTimer *)timer;
+
+// Utilities
+- (NSString *)addressHost4:(struct sockaddr_in *)pSockaddr4;
+- (NSString *)addressHost6:(struct sockaddr_in6 *)pSockaddr6;
+- (NSString *)addressHost:(struct sockaddr *)pSockaddr;
+
+// Disconnect Implementation
+- (void)emptyQueues;
+- (void)closeSocket4;
+- (void)closeSocket6;
+- (void)maybeScheduleClose;
+
+// Errors
+- (NSError *)getErrnoError;
+- (NSError *)getSocketError;
+- (NSError *)getIPv4UnavailableError;
+- (NSError *)getIPv6UnavailableError;
+- (NSError *)getSendTimeoutError;
+- (NSError *)getReceiveTimeoutError;
+
+// Diagnostics
+- (NSString *)connectedHost:(CFSocketRef)socket;
+- (UInt16)connectedPort:(CFSocketRef)socket;
+- (NSString *)localHost:(CFSocketRef)socket;
+- (UInt16)localPort:(CFSocketRef)socket;
+
+// Sending
+- (BOOL)canAcceptBytes:(CFSocketRef)sockRef;
+- (void)scheduleDequeueSend;
+- (void)maybeDequeueSend;
+- (void)doSend:(CFSocketRef)sockRef;
+- (void)completeCurrentSend;
+- (void)failCurrentSend:(NSError *)error;
+- (void)endCurrentSend;
+- (void)doSendTimeout:(NSTimer *)timer;
+
+// Receiving
+- (BOOL)hasBytesAvailable:(CFSocketRef)sockRef;
+- (void)scheduleDequeueReceive;
+- (void)maybeDequeueReceive;
+- (void)doReceive4;
+- (void)doReceive6;
+- (void)doReceive:(CFSocketRef)sockRef;
+- (BOOL)maybeCompleteCurrentReceive;
+- (void)failCurrentReceive:(NSError *)error;
+- (void)endCurrentReceive;
+- (void)doReceiveTimeout:(NSTimer *)timer;
+
+@end
+
+static void MyCFSocketCallback(CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *);
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The AsyncSendPacket encompasses the instructions for a single send/write.
+**/
+@interface AsyncSendPacket : NSObject
+{
+@public
+ NSData *buffer;
+ NSData *address;
+ NSTimeInterval timeout;
+ long tag;
+}
+- (id)initWithData:(NSData *)d address:(NSData *)a timeout:(NSTimeInterval)t tag:(long)i;
+@end
+
+@implementation AsyncSendPacket
+
+- (id)initWithData:(NSData *)d address:(NSData *)a timeout:(NSTimeInterval)t tag:(long)i
+{
+ if((self = [super init]))
+ {
+ buffer = [d retain];
+ address = [a retain];
+ timeout = t;
+ tag = i;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [buffer release];
+ [address release];
+ [super dealloc];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The AsyncReceivePacket encompasses the instructions for a single receive/read.
+**/
+@interface AsyncReceivePacket : NSObject
+{
+@public
+ NSTimeInterval timeout;
+ long tag;
+ NSMutableData *buffer;
+ NSString *host;
+ UInt16 port;
+}
+- (id)initWithTimeout:(NSTimeInterval)t tag:(long)i;
+@end
+
+@implementation AsyncReceivePacket
+
+- (id)initWithTimeout:(NSTimeInterval)t tag:(long)i
+{
+ if((self = [super init]))
+ {
+ timeout = t;
+ tag = i;
+
+ buffer = nil;
+ host = nil;
+ port = 0;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [buffer release];
+ [host release];
+ [super dealloc];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation AsyncUdpSocket
+
+- (id)initWithDelegate:(id)delegate userData:(long)userData enableIPv4:(BOOL)enableIPv4 enableIPv6:(BOOL)enableIPv6
+{
+ if((self = [super init]))
+ {
+ theFlags = 0;
+ theDelegate = delegate;
+ theUserData = userData;
+ maxReceiveBufferSize = DEFAULT_MAX_RECEIVE_BUFFER_SIZE;
+
+ theSendQueue = [[NSMutableArray alloc] initWithCapacity:SENDQUEUE_CAPACITY];
+ theCurrentSend = nil;
+ theSendTimer = nil;
+
+ theReceiveQueue = [[NSMutableArray alloc] initWithCapacity:RECEIVEQUEUE_CAPACITY];
+ theCurrentReceive = nil;
+ theReceiveTimer = nil;
+
+ // Socket context
+ theContext.version = 0;
+ theContext.info = self;
+ theContext.retain = nil;
+ theContext.release = nil;
+ theContext.copyDescription = nil;
+
+ // Create the sockets
+ theSocket4 = NULL;
+ theSocket6 = NULL;
+
+ if(enableIPv4)
+ {
+ theSocket4 = CFSocketCreate(kCFAllocatorDefault,
+ PF_INET,
+ SOCK_DGRAM,
+ IPPROTO_UDP,
+ kCFSocketReadCallBack | kCFSocketWriteCallBack,
+ (CFSocketCallBack)&MyCFSocketCallback,
+ &theContext);
+ }
+ if(enableIPv6)
+ {
+ theSocket6 = CFSocketCreate(kCFAllocatorDefault,
+ PF_INET6,
+ SOCK_DGRAM,
+ IPPROTO_UDP,
+ kCFSocketReadCallBack | kCFSocketWriteCallBack,
+ (CFSocketCallBack)&MyCFSocketCallback,
+ &theContext);
+ }
+
+ // Disable continuous callbacks for read and write.
+ // If we don't do this, the socket(s) will just sit there firing read callbacks
+ // at us hundreds of times a second if we don't immediately read the available data.
+ if(theSocket4)
+ {
+ CFSocketSetSocketFlags(theSocket4, kCFSocketCloseOnInvalidate);
+ }
+ if(theSocket6)
+ {
+ CFSocketSetSocketFlags(theSocket6, kCFSocketCloseOnInvalidate);
+ }
+
+ // Get the CFRunLoop to which the socket should be attached.
+ theRunLoop = CFRunLoopGetCurrent();
+
+ // Set default run loop modes
+ theRunLoopModes = [[NSArray arrayWithObject:NSDefaultRunLoopMode] retain];
+
+ // Attach the sockets to the run loop
+
+ if(theSocket4)
+ {
+ theSource4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, theSocket4, 0);
+ [self runLoopAddSource:theSource4];
+ }
+
+ if(theSocket6)
+ {
+ theSource6 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, theSocket6, 0);
+ [self runLoopAddSource:theSource6];
+ }
+
+ cachedLocalPort = 0;
+ cachedConnectedPort = 0;
+ }
+ return self;
+}
+
+- (id)init
+{
+ return [self initWithDelegate:nil userData:0 enableIPv4:YES enableIPv6:YES];
+}
+
+- (id)initWithDelegate:(id)delegate
+{
+ return [self initWithDelegate:delegate userData:0 enableIPv4:YES enableIPv6:YES];
+}
+
+- (id)initWithDelegate:(id)delegate userData:(long)userData
+{
+ return [self initWithDelegate:delegate userData:userData enableIPv4:YES enableIPv6:YES];
+}
+
+- (id)initIPv4
+{
+ return [self initWithDelegate:nil userData:0 enableIPv4:YES enableIPv6:NO];
+}
+
+- (id)initIPv6
+{
+ return [self initWithDelegate:nil userData:0 enableIPv4:NO enableIPv6:YES];
+}
+
+- (void) dealloc
+{
+ [self close];
+ [theSendQueue release];
+ [theReceiveQueue release];
+ [theRunLoopModes release];
+ [cachedLocalHost release];
+ [cachedConnectedHost release];
+ [NSObject cancelPreviousPerformRequestsWithTarget:theDelegate selector:@selector(onUdpSocketDidClose:) object:self];
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+ [super dealloc];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Accessors
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (id)delegate
+{
+ return theDelegate;
+}
+
+- (void)setDelegate:(id)delegate
+{
+ theDelegate = delegate;
+}
+
+- (long)userData
+{
+ return theUserData;
+}
+
+- (void)setUserData:(long)userData
+{
+ theUserData = userData;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Run Loop
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)runLoopAddSource:(CFRunLoopSourceRef)source
+{
+ NSUInteger i, count = [theRunLoopModes count];
+ for(i = 0; i < count; i++)
+ {
+ CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+ CFRunLoopAddSource(theRunLoop, source, runLoopMode);
+ }
+}
+
+- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source
+{
+ NSUInteger i, count = [theRunLoopModes count];
+ for(i = 0; i < count; i++)
+ {
+ CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+ CFRunLoopRemoveSource(theRunLoop, source, runLoopMode);
+ }
+}
+
+- (void)runLoopAddTimer:(NSTimer *)timer
+{
+ NSUInteger i, count = [theRunLoopModes count];
+ for(i = 0; i < count; i++)
+ {
+ CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+ CFRunLoopAddTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode);
+ }
+}
+
+- (void)runLoopRemoveTimer:(NSTimer *)timer
+{
+ NSUInteger i, count = [theRunLoopModes count];
+ for(i = 0; i < count; i++)
+ {
+ CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+ CFRunLoopRemoveTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (UInt32)maxReceiveBufferSize
+{
+ return maxReceiveBufferSize;
+}
+
+- (void)setMaxReceiveBufferSize:(UInt32)max
+{
+ maxReceiveBufferSize = max;
+}
+
+/**
+ * See the header file for a full explanation of this method.
+**/
+- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop
+{
+ NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()),
+ @"moveToRunLoop must be called from within the current RunLoop!");
+
+ if(runLoop == nil)
+ {
+ return NO;
+ }
+ if(theRunLoop == [runLoop getCFRunLoop])
+ {
+ return YES;
+ }
+
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+ theFlags &= ~kDequeueSendScheduled;
+ theFlags &= ~kDequeueReceiveScheduled;
+
+ if(theSource4) [self runLoopRemoveSource:theSource4];
+ if(theSource6) [self runLoopRemoveSource:theSource6];
+
+ // We do not retain the timers - they get retained by the runloop when we add them as a source.
+ // Since we're about to remove them as a source, we retain now, and release again below.
+ [theSendTimer retain];
+ [theReceiveTimer retain];
+
+ if(theSendTimer) [self runLoopRemoveTimer:theSendTimer];
+ if(theReceiveTimer) [self runLoopRemoveTimer:theReceiveTimer];
+
+ theRunLoop = [runLoop getCFRunLoop];
+
+ if(theSendTimer) [self runLoopAddTimer:theSendTimer];
+ if(theReceiveTimer) [self runLoopAddTimer:theReceiveTimer];
+
+ // Release timers since we retained them above
+ [theSendTimer release];
+ [theReceiveTimer release];
+
+ if(theSource4) [self runLoopAddSource:theSource4];
+ if(theSource6) [self runLoopAddSource:theSource6];
+
+ [runLoop performSelector:@selector(maybeDequeueSend) target:self argument:nil order:0 modes:theRunLoopModes];
+ [runLoop performSelector:@selector(maybeDequeueReceive) target:self argument:nil order:0 modes:theRunLoopModes];
+ [runLoop performSelector:@selector(maybeScheduleClose) target:self argument:nil order:0 modes:theRunLoopModes];
+
+ return YES;
+}
+
+/**
+ * See the header file for a full explanation of this method.
+**/
+- (BOOL)setRunLoopModes:(NSArray *)runLoopModes
+{
+ NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()),
+ @"setRunLoopModes must be called from within the current RunLoop!");
+
+ if([runLoopModes count] == 0)
+ {
+ return NO;
+ }
+ if([theRunLoopModes isEqualToArray:runLoopModes])
+ {
+ return YES;
+ }
+
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+ theFlags &= ~kDequeueSendScheduled;
+ theFlags &= ~kDequeueReceiveScheduled;
+
+ if(theSource4) [self runLoopRemoveSource:theSource4];
+ if(theSource6) [self runLoopRemoveSource:theSource6];
+
+ // We do not retain the timers - they get retained by the runloop when we add them as a source.
+ // Since we're about to remove them as a source, we retain now, and release again below.
+ [theSendTimer retain];
+ [theReceiveTimer retain];
+
+ if(theSendTimer) [self runLoopRemoveTimer:theSendTimer];
+ if(theReceiveTimer) [self runLoopRemoveTimer:theReceiveTimer];
+
+ [theRunLoopModes release];
+ theRunLoopModes = [runLoopModes copy];
+
+ if(theSendTimer) [self runLoopAddTimer:theSendTimer];
+ if(theReceiveTimer) [self runLoopAddTimer:theReceiveTimer];
+
+ // Release timers since we retained them above
+ [theSendTimer release];
+ [theReceiveTimer release];
+
+ if(theSource4) [self runLoopAddSource:theSource4];
+ if(theSource6) [self runLoopAddSource:theSource6];
+
+ [self performSelector:@selector(maybeDequeueSend) withObject:nil afterDelay:0 inModes:theRunLoopModes];
+ [self performSelector:@selector(maybeDequeueReceive) withObject:nil afterDelay:0 inModes:theRunLoopModes];
+ [self performSelector:@selector(maybeScheduleClose) withObject:nil afterDelay:0 inModes:theRunLoopModes];
+
+ return YES;
+}
+
+- (NSArray *)runLoopModes
+{
+ return [[theRunLoopModes retain] autorelease];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Utilities:
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Attempts to convert the given host/port into and IPv4 and/or IPv6 data structure.
+ * The data structure is of type sockaddr_in for IPv4 and sockaddr_in6 for IPv6.
+ *
+ * Returns zero on success, or one of the error codes listed in gai_strerror if an error occurs (as per getaddrinfo).
+**/
+- (int)convertForBindHost:(NSString *)host
+ port:(UInt16)port
+ intoAddress4:(NSData **)address4
+ address6:(NSData **)address6
+{
+ if(host == nil || ([host length] == 0))
+ {
+ // Use ANY address
+ struct sockaddr_in nativeAddr;
+ nativeAddr.sin_len = sizeof(struct sockaddr_in);
+ nativeAddr.sin_family = AF_INET;
+ nativeAddr.sin_port = htons(port);
+ nativeAddr.sin_addr.s_addr = htonl(INADDR_ANY);
+ memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero));
+
+ struct sockaddr_in6 nativeAddr6;
+ nativeAddr6.sin6_len = sizeof(struct sockaddr_in6);
+ nativeAddr6.sin6_family = AF_INET6;
+ nativeAddr6.sin6_port = htons(port);
+ nativeAddr6.sin6_flowinfo = 0;
+ nativeAddr6.sin6_addr = in6addr_any;
+ nativeAddr6.sin6_scope_id = 0;
+
+ // Wrap the native address structures for CFSocketSetAddress.
+ if(address4) *address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)];
+ if(address6) *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+
+ return 0;
+ }
+ else if([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
+ {
+ // Note: getaddrinfo("localhost",...) fails on 10.5.3
+
+ // Use LOOPBACK address
+ struct sockaddr_in nativeAddr;
+ nativeAddr.sin_len = sizeof(struct sockaddr_in);
+ nativeAddr.sin_family = AF_INET;
+ nativeAddr.sin_port = htons(port);
+ nativeAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero));
+
+ struct sockaddr_in6 nativeAddr6;
+ nativeAddr6.sin6_len = sizeof(struct sockaddr_in6);
+ nativeAddr6.sin6_family = AF_INET6;
+ nativeAddr6.sin6_port = htons(port);
+ nativeAddr6.sin6_flowinfo = 0;
+ nativeAddr6.sin6_addr = in6addr_loopback;
+ nativeAddr6.sin6_scope_id = 0;
+
+ // Wrap the native address structures for CFSocketSetAddress.
+ if(address4) *address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)];
+ if(address6) *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+
+ return 0;
+ }
+ else
+ {
+ NSString *portStr = [NSString stringWithFormat:@"%hu", port];
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
+ @synchronized (getaddrinfoLock)
+#endif
+ {
+ struct addrinfo hints, *res, *res0;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ hints.ai_flags = AI_PASSIVE;
+
+ int error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
+
+ if(error) return error;
+
+ for(res = res0; res; res = res->ai_next)
+ {
+ if(address4 && !*address4 && (res->ai_family == AF_INET))
+ {
+ // Found IPv4 address
+ // Wrap the native address structures for CFSocketSetAddress.
+ if(address4) *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+ }
+ else if(address6 && !*address6 && (res->ai_family == AF_INET6))
+ {
+ // Found IPv6 address
+ // Wrap the native address structures for CFSocketSetAddress.
+ if(address6) *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+ }
+ }
+ freeaddrinfo(res0);
+ }
+
+ return 0;
+ }
+}
+
+/**
+ * Attempts to convert the given host/port into and IPv4 and/or IPv6 data structure.
+ * The data structure is of type sockaddr_in for IPv4 and sockaddr_in6 for IPv6.
+ *
+ * Returns zero on success, or one of the error codes listed in gai_strerror if an error occurs (as per getaddrinfo).
+**/
+- (int)convertForSendHost:(NSString *)host
+ port:(UInt16)port
+ intoAddress4:(NSData **)address4
+ address6:(NSData **)address6
+{
+ if(host == nil || ([host length] == 0))
+ {
+ // We're not binding, so what are we supposed to do with this?
+ return EAI_NONAME;
+ }
+ else if([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
+ {
+ // Note: getaddrinfo("localhost",...) fails on 10.5.3
+
+ // Use LOOPBACK address
+ struct sockaddr_in nativeAddr;
+ nativeAddr.sin_len = sizeof(struct sockaddr_in);
+ nativeAddr.sin_family = AF_INET;
+ nativeAddr.sin_port = htons(port);
+ nativeAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero));
+
+ struct sockaddr_in6 nativeAddr6;
+ nativeAddr6.sin6_len = sizeof(struct sockaddr_in6);
+ nativeAddr6.sin6_family = AF_INET6;
+ nativeAddr6.sin6_port = htons(port);
+ nativeAddr6.sin6_flowinfo = 0;
+ nativeAddr6.sin6_addr = in6addr_loopback;
+ nativeAddr6.sin6_scope_id = 0;
+
+ // Wrap the native address structures for CFSocketSetAddress.
+ if(address4) *address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)];
+ if(address6) *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+
+ return 0;
+ }
+ else
+ {
+ NSString *portStr = [NSString stringWithFormat:@"%hu", port];
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
+ @synchronized (getaddrinfoLock)
+#endif
+ {
+ struct addrinfo hints, *res, *res0;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ // No passive flag on a send or connect
+
+ int error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
+
+ if(error) return error;
+
+ for(res = res0; res; res = res->ai_next)
+ {
+ if(address4 && !*address4 && (res->ai_family == AF_INET))
+ {
+ // Found IPv4 address
+ // Wrap the native address structures for CFSocketSetAddress.
+ if(address4) *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+ }
+ else if(address6 && !*address6 && (res->ai_family == AF_INET6))
+ {
+ // Found IPv6 address
+ // Wrap the native address structures for CFSocketSetAddress.
+ if(address6) *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+ }
+ }
+ freeaddrinfo(res0);
+ }
+
+ return 0;
+ }
+}
+
+- (NSString *)addressHost4:(struct sockaddr_in *)pSockaddr4
+{
+ char addrBuf[INET_ADDRSTRLEN];
+
+ if(inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, sizeof(addrBuf)) == NULL)
+ {
+ [NSException raise:NSInternalInconsistencyException format:@"Cannot convert address to string."];
+ }
+
+ return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
+- (NSString *)addressHost6:(struct sockaddr_in6 *)pSockaddr6
+{
+ char addrBuf[INET6_ADDRSTRLEN];
+
+ if(inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, sizeof(addrBuf)) == NULL)
+ {
+ [NSException raise:NSInternalInconsistencyException format:@"Cannot convert address to string."];
+ }
+
+ return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
+- (NSString *)addressHost:(struct sockaddr *)pSockaddr
+{
+ if(pSockaddr->sa_family == AF_INET)
+ {
+ return [self addressHost4:(struct sockaddr_in *)pSockaddr];
+ }
+ else
+ {
+ return [self addressHost6:(struct sockaddr_in6 *)pSockaddr];
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Socket Implementation:
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Binds the underlying socket(s) to the given port.
+ * The socket(s) will be able to receive data on any interface.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+- (BOOL)bindToPort:(UInt16)port error:(NSError **)errPtr
+{
+ return [self bindToAddress:nil port:port error:errPtr];
+}
+
+/**
+ * Binds the underlying socket(s) to the given address and port.
+ * The sockets(s) will be able to receive data only on the given interface.
+ *
+ * To receive data on any interface, pass nil or "".
+ * To receive data only on the loopback interface, pass "localhost" or "loopback".
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+- (BOOL)bindToAddress:(NSString *)host port:(UInt16)port error:(NSError **)errPtr
+{
+ if(theFlags & kDidClose)
+ {
+ [NSException raise:AsyncUdpSocketException
+ format:@"The socket is closed."];
+ }
+ if(theFlags & kDidBind)
+ {
+ [NSException raise:AsyncUdpSocketException
+ format:@"Cannot bind a socket more than once."];
+ }
+ if(theFlags & kDidConnect)
+ {
+ [NSException raise:AsyncUdpSocketException
+ format:@"Cannot bind after connecting. If needed, bind first, then connect."];
+ }
+
+ // Convert the given host/port into native address structures for CFSocketSetAddress
+ NSData *address4 = nil, *address6 = nil;
+
+ int gai_error = [self convertForBindHost:host port:port intoAddress4:&address4 address6:&address6];
+ if(gai_error)
+ {
+ if(errPtr)
+ {
+ NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:info];
+ }
+ return NO;
+ }
+
+ NSAssert((address4 || address6), @"address4 and address6 are nil");
+
+ // Set the SO_REUSEADDR flags
+
+ int reuseOn = 1;
+ if (theSocket4) setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+ if (theSocket6) setsockopt(CFSocketGetNative(theSocket6), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+
+ // Bind the sockets
+
+ if(address4)
+ {
+ if(theSocket4)
+ {
+ CFSocketError error = CFSocketSetAddress(theSocket4, (CFDataRef)address4);
+ if(error != kCFSocketSuccess)
+ {
+ if(errPtr) *errPtr = [self getSocketError];
+ return NO;
+ }
+
+ if(!address6)
+ {
+ // Using IPv4 only
+ [self closeSocket6];
+ }
+ }
+ else if(!address6)
+ {
+ if(errPtr) *errPtr = [self getIPv4UnavailableError];
+ return NO;
+ }
+ }
+
+ if(address6)
+ {
+ // Note: The iPhone doesn't currently support IPv6
+
+ if(theSocket6)
+ {
+ CFSocketError error = CFSocketSetAddress(theSocket6, (CFDataRef)address6);
+ if(error != kCFSocketSuccess)
+ {
+ if(errPtr) *errPtr = [self getSocketError];
+ return NO;
+ }
+
+ if(!address4)
+ {
+ // Using IPv6 only
+ [self closeSocket4];
+ }
+ }
+ else if(!address4)
+ {
+ if(errPtr) *errPtr = [self getIPv6UnavailableError];
+ return NO;
+ }
+ }
+
+ theFlags |= kDidBind;
+ return YES;
+}
+
+/**
+ * Connects the underlying UDP socket to the given host and port.
+ * If an IPv4 address is resolved, the IPv4 socket is connected, and the IPv6 socket is invalidated and released.
+ * If an IPv6 address is resolved, the IPv6 socket is connected, and the IPv4 socket is invalidated and released.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+- (BOOL)connectToHost:(NSString *)host onPort:(UInt16)port error:(NSError **)errPtr
+{
+ if(theFlags & kDidClose)
+ {
+ [NSException raise:AsyncUdpSocketException
+ format:@"The socket is closed."];
+ }
+ if(theFlags & kDidConnect)
+ {
+ [NSException raise:AsyncUdpSocketException
+ format:@"Cannot connect a socket more than once."];
+ }
+
+ // Convert the given host/port into native address structures for CFSocketSetAddress
+ NSData *address4 = nil, *address6 = nil;
+
+ int error = [self convertForSendHost:host port:port intoAddress4:&address4 address6:&address6];
+ if(error)
+ {
+ if(errPtr)
+ {
+ NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding];
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info];
+ }
+ return NO;
+ }
+
+ NSAssert((address4 || address6), @"address4 and address6 are nil");
+
+ // We only want to connect via a single interface.
+ // IPv4 is currently preferred, but this may change in the future.
+
+ if(address4)
+ {
+ if(theSocket4)
+ {
+ CFSocketError sockErr = CFSocketConnectToAddress(theSocket4, (CFDataRef)address4, (CFTimeInterval)0.0);
+ if(sockErr != kCFSocketSuccess)
+ {
+ if(errPtr) *errPtr = [self getSocketError];
+ return NO;
+ }
+ theFlags |= kDidConnect;
+
+ // We're connected to an IPv4 address, so no need for the IPv6 socket
+ [self closeSocket6];
+
+ return YES;
+ }
+ else if(!address6)
+ {
+ if(errPtr) *errPtr = [self getIPv4UnavailableError];
+ return NO;
+ }
+ }
+
+ if(address6)
+ {
+ // Note: The iPhone doesn't currently support IPv6
+
+ if(theSocket6)
+ {
+ CFSocketError sockErr = CFSocketConnectToAddress(theSocket6, (CFDataRef)address6, (CFTimeInterval)0.0);
+ if(sockErr != kCFSocketSuccess)
+ {
+ if(errPtr) *errPtr = [self getSocketError];
+ return NO;
+ }
+ theFlags |= kDidConnect;
+
+ // We're connected to an IPv6 address, so no need for the IPv4 socket
+ [self closeSocket4];
+
+ return YES;
+ }
+ else
+ {
+ if(errPtr) *errPtr = [self getIPv6UnavailableError];
+ return NO;
+ }
+ }
+
+ // It shouldn't be possible to get to this point because either address4 or address6 was non-nil.
+ if(errPtr) *errPtr = nil;
+ return NO;
+}
+
+/**
+ * Connects the underlying UDP socket to the remote address.
+ * If the address is an IPv4 address, the IPv4 socket is connected, and the IPv6 socket is invalidated and released.
+ * If the address is an IPv6 address, the IPv6 socket is connected, and the IPv4 socket is invalidated and released.
+ *
+ * The address is a native address structure, as may be returned from API's such as Bonjour.
+ * An address may be created manually by simply wrapping a sockaddr_in or sockaddr_in6 in an NSData object.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
+{
+ if(theFlags & kDidClose)
+ {
+ [NSException raise:AsyncUdpSocketException
+ format:@"The socket is closed."];
+ }
+ if(theFlags & kDidConnect)
+ {
+ [NSException raise:AsyncUdpSocketException
+ format:@"Cannot connect a socket more than once."];
+ }
+
+ // Is remoteAddr an IPv4 address?
+ if([remoteAddr length] == sizeof(struct sockaddr_in))
+ {
+ if(theSocket4)
+ {
+ CFSocketError error = CFSocketConnectToAddress(theSocket4, (CFDataRef)remoteAddr, (CFTimeInterval)0.0);
+ if(error != kCFSocketSuccess)
+ {
+ if(errPtr) *errPtr = [self getSocketError];
+ return NO;
+ }
+ theFlags |= kDidConnect;
+
+ // We're connected to an IPv4 address, so no need for the IPv6 socket
+ [self closeSocket6];
+
+ return YES;
+ }
+ else
+ {
+ if(errPtr) *errPtr = [self getIPv4UnavailableError];
+ return NO;
+ }
+ }
+
+ // Is remoteAddr an IPv6 address?
+ if([remoteAddr length] == sizeof(struct sockaddr_in6))
+ {
+ if(theSocket6)
+ {
+ CFSocketError error = CFSocketConnectToAddress(theSocket6, (CFDataRef)remoteAddr, (CFTimeInterval)0.0);
+ if(error != kCFSocketSuccess)
+ {
+ if(errPtr) *errPtr = [self getSocketError];
+ return NO;
+ }
+ theFlags |= kDidConnect;
+
+ // We're connected to an IPv6 address, so no need for the IPv4 socket
+ [self closeSocket4];
+
+ return YES;
+ }
+ else
+ {
+ if(errPtr) *errPtr = [self getIPv6UnavailableError];
+ return NO;
+ }
+ }
+
+ // The remoteAddr was invalid
+ if(errPtr)
+ {
+ NSString *errMsg = @"remoteAddr parameter is not a valid address";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ *errPtr = [NSError errorWithDomain:AsyncUdpSocketErrorDomain
+ code:AsyncUdpSocketBadParameter
+ userInfo:info];
+ }
+ return NO;
+}
+
+/**
+ * Join multicast group
+ *
+ * Group should be a multicast IP address (eg. @"239.255.250.250" for IPv4).
+ * Address is local interface for IPv4, but currently defaults under IPv6.
+**/
+- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr
+{
+ return [self joinMulticastGroup:group withAddress:nil error:errPtr];
+}
+
+- (BOOL)joinMulticastGroup:(NSString *)group withAddress:(NSString *)address error:(NSError **)errPtr
+{
+ if(theFlags & kDidClose)
+ {
+ [NSException raise:AsyncUdpSocketException
+ format:@"The socket is closed."];
+ }
+ if(!(theFlags & kDidBind))
+ {
+ [NSException raise:AsyncUdpSocketException
+ format:@"Must bind a socket before joining a multicast group."];
+ }
+ if(theFlags & kDidConnect)
+ {
+ [NSException raise:AsyncUdpSocketException
+ format:@"Cannot join a multicast group if connected."];
+ }
+
+ // Get local interface address
+ // Convert the given host/port into native address structures for CFSocketSetAddress
+ NSData *address4 = nil, *address6 = nil;
+
+ int error = [self convertForBindHost:address port:0 intoAddress4:&address4 address6:&address6];
+ if(error)
+ {
+ if(errPtr)
+ {
+ NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding];
+ NSString *errDsc = [NSString stringWithFormat:@"Invalid parameter 'address': %@", errMsg];
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errDsc forKey:NSLocalizedDescriptionKey];
+
+ *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info];
+ }
+ return NO;
+ }
+
+ NSAssert((address4 || address6), @"address4 and address6 are nil");
+
+ // Get multicast address (group)
+ NSData *group4 = nil, *group6 = nil;
+
+ error = [self convertForBindHost:group port:0 intoAddress4:&group4 address6:&group6];
+ if(error)
+ {
+ if(errPtr)
+ {
+ NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding];
+ NSString *errDsc = [NSString stringWithFormat:@"Invalid parameter 'group': %@", errMsg];
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errDsc forKey:NSLocalizedDescriptionKey];
+
+ *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info];
+ }
+ return NO;
+ }
+
+ NSAssert((group4 || group6), @"group4 and group6 are nil");
+
+ if(theSocket4 && group4 && address4)
+ {
+ const struct sockaddr_in* nativeAddress = [address4 bytes];
+ const struct sockaddr_in* nativeGroup = [group4 bytes];
+
+ struct ip_mreq imreq;
+ imreq.imr_multiaddr = nativeGroup->sin_addr;
+ imreq.imr_interface = nativeAddress->sin_addr;
+
+ // JOIN multicast group on default interface
+ error = setsockopt(CFSocketGetNative(theSocket4), IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ (const void *)&imreq, sizeof(struct ip_mreq));
+ if(error)
+ {
+ if(errPtr)
+ {
+ NSString *errMsg = @"Unable to join IPv4 multicast group";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainPOSIX" code:error userInfo:info];
+ }
+ return NO;
+ }
+
+ // Using IPv4 only
+ [self closeSocket6];
+
+ return YES;
+ }
+
+ if(theSocket6 && group6 && address6)
+ {
+ const struct sockaddr_in6* nativeGroup = [group6 bytes];
+
+ struct ipv6_mreq imreq;
+ imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr;
+ imreq.ipv6mr_interface = 0;
+
+ // JOIN multicast group on default interface
+ error = setsockopt(CFSocketGetNative(theSocket6), IPPROTO_IP, IPV6_JOIN_GROUP,
+ (const void *)&imreq, sizeof(struct ipv6_mreq));
+ if(error)
+ {
+ if(errPtr)
+ {
+ NSString *errMsg = @"Unable to join IPv6 multicast group";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainPOSIX" code:error userInfo:info];
+ }
+ return NO;
+ }
+
+ // Using IPv6 only
+ [self closeSocket4];
+
+ return YES;
+ }
+
+ // The given address and group didn't match the existing socket(s).
+ // This means there were no compatible combination of all IPv4 or IPv6 socket, group and address.
+ if(errPtr)
+ {
+ NSString *errMsg = @"Invalid group and/or address, not matching existing socket(s)";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ *errPtr = [NSError errorWithDomain:AsyncUdpSocketErrorDomain
+ code:AsyncUdpSocketBadParameter
+ userInfo:info];
+ }
+ return NO;
+}
+
+/**
+ * By default, the underlying socket in the OS will not allow you to send broadcast messages.
+ * In order to send broadcast messages, you need to enable this functionality in the socket.
+ *
+ * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is
+ * delivered to every host on the network.
+ * The reason this is generally disabled by default is to prevent
+ * accidental broadcast messages from flooding the network.
+**/
+- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr
+{
+ if (theSocket4)
+ {
+ int value = flag ? 1 : 0;
+ int error = setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_BROADCAST,
+ (const void *)&value, sizeof(value));
+ if(error)
+ {
+ if(errPtr)
+ {
+ NSString *errMsg = @"Unable to enable broadcast message sending";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainPOSIX" code:error userInfo:info];
+ }
+ return NO;
+ }
+ }
+
+ // IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link.
+ // The same effect can be achieved by sending a packet to the link-local all hosts multicast group.
+
+ return YES;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Disconnect Implementation:
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)emptyQueues
+{
+ if (theCurrentSend) [self endCurrentSend];
+ if (theCurrentReceive) [self endCurrentReceive];
+
+ [theSendQueue removeAllObjects];
+ [theReceiveQueue removeAllObjects];
+
+ [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueSend) object:nil];
+ [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueReceive) object:nil];
+
+ theFlags &= ~kDequeueSendScheduled;
+ theFlags &= ~kDequeueReceiveScheduled;
+}
+
+- (void)closeSocket4
+{
+ if (theSocket4 != NULL)
+ {
+ CFSocketInvalidate(theSocket4);
+ CFRelease(theSocket4);
+ theSocket4 = NULL;
+ }
+ if (theSource4 != NULL)
+ {
+ [self runLoopRemoveSource:theSource4];
+ CFRelease(theSource4);
+ theSource4 = NULL;
+ }
+}
+
+- (void)closeSocket6
+{
+ if (theSocket6 != NULL)
+ {
+ CFSocketInvalidate(theSocket6);
+ CFRelease(theSocket6);
+ theSocket6 = NULL;
+ }
+ if (theSource6 != NULL)
+ {
+ [self runLoopRemoveSource:theSource6];
+ CFRelease(theSource6);
+ theSource6 = NULL;
+ }
+}
+
+- (void)close
+{
+ [self emptyQueues];
+ [self closeSocket4];
+ [self closeSocket6];
+
+ theRunLoop = NULL;
+
+ // Delay notification to give user freedom to release without returning here and core-dumping.
+ if ([theDelegate respondsToSelector:@selector(onUdpSocketDidClose:)])
+ {
+ [theDelegate performSelector:@selector(onUdpSocketDidClose:)
+ withObject:self
+ afterDelay:0
+ inModes:theRunLoopModes];
+ }
+
+ theFlags |= kDidClose;
+}
+
+- (void)closeAfterSending
+{
+ if(theFlags & kDidClose) return;
+
+ theFlags |= (kForbidSendReceive | kCloseAfterSends);
+ [self maybeScheduleClose];
+}
+
+- (void)closeAfterReceiving
+{
+ if(theFlags & kDidClose) return;
+
+ theFlags |= (kForbidSendReceive | kCloseAfterReceives);
+ [self maybeScheduleClose];
+}
+
+- (void)closeAfterSendingAndReceiving
+{
+ if(theFlags & kDidClose) return;
+
+ theFlags |= (kForbidSendReceive | kCloseAfterSends | kCloseAfterReceives);
+ [self maybeScheduleClose];
+}
+
+- (void)maybeScheduleClose
+{
+ BOOL shouldDisconnect = NO;
+
+ if(theFlags & kCloseAfterSends)
+ {
+ if(([theSendQueue count] == 0) && (theCurrentSend == nil))
+ {
+ if(theFlags & kCloseAfterReceives)
+ {
+ if(([theReceiveQueue count] == 0) && (theCurrentReceive == nil))
+ {
+ shouldDisconnect = YES;
+ }
+ }
+ else
+ {
+ shouldDisconnect = YES;
+ }
+ }
+ }
+ else if(theFlags & kCloseAfterReceives)
+ {
+ if(([theReceiveQueue count] == 0) && (theCurrentReceive == nil))
+ {
+ shouldDisconnect = YES;
+ }
+ }
+
+ if(shouldDisconnect)
+ {
+ [self performSelector:@selector(close) withObject:nil afterDelay:0 inModes:theRunLoopModes];
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Errors
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns a standard error object for the current errno value.
+ * Errno is used for low-level BSD socket errors.
+**/
+- (NSError *)getErrnoError
+{
+ NSString *errorMsg = [NSString stringWithUTF8String:strerror(errno)];
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
+}
+
+/**
+ * Returns a standard error message for a CFSocket error.
+ * Unfortunately, CFSocket offers no feedback on its errors.
+**/
+- (NSError *)getSocketError
+{
+ NSString *errMsg = @"General CFSocket error";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketCFSocketError userInfo:info];
+}
+
+- (NSError *)getIPv4UnavailableError
+{
+ NSString *errMsg = @"IPv4 is unavailable due to binding/connecting using IPv6 only";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketIPv4Unavailable userInfo:info];
+}
+
+- (NSError *)getIPv6UnavailableError
+{
+ NSString *errMsg = @"IPv6 is unavailable due to binding/connecting using IPv4 only or is not supported on this platform";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketIPv6Unavailable userInfo:info];
+}
+
+- (NSError *)getSendTimeoutError
+{
+ NSString *errMsg = @"Send operation timed out";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketSendTimeoutError userInfo:info];
+}
+- (NSError *)getReceiveTimeoutError
+{
+ NSString *errMsg = @"Receive operation timed out";
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketReceiveTimeoutError userInfo:info];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Diagnostics
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSString *)localHost
+{
+ if(cachedLocalHost) return cachedLocalHost;
+
+ if(theSocket4)
+ return [self localHost:theSocket4];
+ else
+ return [self localHost:theSocket6];
+}
+
+- (UInt16)localPort
+{
+ if(cachedLocalPort > 0) return cachedLocalPort;
+
+ if(theSocket4)
+ return [self localPort:theSocket4];
+ else
+ return [self localPort:theSocket6];
+}
+
+- (NSString *)connectedHost
+{
+ if(cachedConnectedHost) return cachedConnectedHost;
+
+ if(theSocket4)
+ return [self connectedHost:theSocket4];
+ else
+ return [self connectedHost:theSocket6];
+}
+
+- (UInt16)connectedPort
+{
+ if(cachedConnectedPort > 0) return cachedConnectedPort;
+
+ if(theSocket4)
+ return [self connectedPort:theSocket4];
+ else
+ return [self connectedPort:theSocket6];
+}
+
+- (NSString *)localHost:(CFSocketRef)theSocket
+{
+ if(theSocket == NULL) return nil;
+
+ // Unfortunately we can't use CFSocketCopyAddress.
+ // The CFSocket library caches the address the first time you call CFSocketCopyAddress.
+ // So if this is called prior to binding/connecting/sending, it won't be updated again when necessary,
+ // and will continue to return the old value of the socket address.
+
+ NSString *result = nil;
+
+ if(theSocket == theSocket4)
+ {
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if(getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+ {
+ return nil;
+ }
+ result = [self addressHost4:&sockaddr4];
+ }
+ else
+ {
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if(getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+ {
+ return nil;
+ }
+ result = [self addressHost6:&sockaddr6];
+ }
+
+ if(theFlags & kDidBind)
+ {
+ [cachedLocalHost release];
+ cachedLocalHost = [result copy];
+ }
+
+ return result;
+}
+
+- (UInt16)localPort:(CFSocketRef)theSocket
+{
+ if(theSocket == NULL) return 0;
+
+ // Unfortunately we can't use CFSocketCopyAddress.
+ // The CFSocket library caches the address the first time you call CFSocketCopyAddress.
+ // So if this is called prior to binding/connecting/sending, it won't be updated again when necessary,
+ // and will continue to return the old value of the socket address.
+
+ UInt16 result = 0;
+
+ if(theSocket == theSocket4)
+ {
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if(getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+ {
+ return 0;
+ }
+ result = ntohs(sockaddr4.sin_port);
+ }
+ else
+ {
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if(getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+ {
+ return 0;
+ }
+ result = ntohs(sockaddr6.sin6_port);
+ }
+
+ if(theFlags & kDidBind)
+ {
+ cachedLocalPort = result;
+ }
+
+ return result;
+}
+
+- (NSString *)connectedHost:(CFSocketRef)theSocket
+{
+ if(theSocket == NULL) return nil;
+
+ // Unfortunately we can't use CFSocketCopyPeerAddress.
+ // The CFSocket library caches the address the first time you call CFSocketCopyPeerAddress.
+ // So if this is called prior to binding/connecting/sending, it may not be updated again when necessary,
+ // and will continue to return the old value of the socket peer address.
+
+ NSString *result = nil;
+
+ if(theSocket == theSocket4)
+ {
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+ {
+ return nil;
+ }
+ result = [self addressHost4:&sockaddr4];
+ }
+ else
+ {
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+ {
+ return nil;
+ }
+ result = [self addressHost6:&sockaddr6];
+ }
+
+ if(theFlags & kDidConnect)
+ {
+ [cachedConnectedHost release];
+ cachedConnectedHost = [result copy];
+ }
+
+ return result;
+}
+
+- (UInt16)connectedPort:(CFSocketRef)theSocket
+{
+ if(theSocket == NULL) return 0;
+
+ // Unfortunately we can't use CFSocketCopyPeerAddress.
+ // The CFSocket library caches the address the first time you call CFSocketCopyPeerAddress.
+ // So if this is called prior to binding/connecting/sending, it may not be updated again when necessary,
+ // and will continue to return the old value of the socket peer address.
+
+ UInt16 result = 0;
+
+ if(theSocket == theSocket4)
+ {
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+ {
+ return 0;
+ }
+ result = ntohs(sockaddr4.sin_port);
+ }
+ else
+ {
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+ {
+ return 0;
+ }
+ result = ntohs(sockaddr6.sin6_port);
+ }
+
+ if(theFlags & kDidConnect)
+ {
+ cachedConnectedPort = result;
+ }
+
+ return result;
+}
+
+- (BOOL)isConnected
+{
+ return (((theFlags & kDidConnect) != 0) && ((theFlags & kDidClose) == 0));
+}
+
+- (BOOL)isConnectedToHost:(NSString *)host port:(UInt16)port
+{
+ return [[self connectedHost] isEqualToString:host] && ([self connectedPort] == port);
+}
+
+- (BOOL)isClosed
+{
+ return (theFlags & kDidClose) ? YES : NO;
+}
+
+- (BOOL)isIPv4
+{
+ return (theSocket4 != NULL);
+}
+
+- (BOOL)isIPv6
+{
+ return (theSocket6 != NULL);
+}
+
+- (unsigned int)maximumTransmissionUnit
+{
+ CFSocketNativeHandle theNativeSocket;
+ if(theSocket4)
+ theNativeSocket = CFSocketGetNative(theSocket4);
+ else if(theSocket6)
+ theNativeSocket = CFSocketGetNative(theSocket6);
+ else
+ return 0;
+
+ if(theNativeSocket == 0)
+ {
+ return 0;
+ }
+
+ struct ifreq ifr;
+ bzero(&ifr, sizeof(ifr));
+
+ if(if_indextoname(theNativeSocket, ifr.ifr_name) == NULL)
+ {
+ return 0;
+ }
+
+ if(ioctl(theNativeSocket, SIOCGIFMTU, &ifr) >= 0)
+ {
+ return ifr.ifr_mtu;
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Sending
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ if((data == nil) || ([data length] == 0)) return NO;
+ if(theFlags & kForbidSendReceive) return NO;
+ if(theFlags & kDidClose) return NO;
+
+ // This method is only for connected sockets
+ if(![self isConnected]) return NO;
+
+ AsyncSendPacket *packet = [[AsyncSendPacket alloc] initWithData:data address:nil timeout:timeout tag:tag];
+
+ [theSendQueue addObject:packet];
+ [self scheduleDequeueSend];
+
+ [packet release];
+ return YES;
+}
+
+- (BOOL)sendData:(NSData *)data toHost:(NSString *)host port:(UInt16)port withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ if((data == nil) || ([data length] == 0)) return NO;
+ if(theFlags & kForbidSendReceive) return NO;
+ if(theFlags & kDidClose) return NO;
+
+ // This method is only for non-connected sockets
+ if([self isConnected]) return NO;
+
+ NSData *address4 = nil, *address6 = nil;
+ [self convertForSendHost:host port:port intoAddress4:&address4 address6:&address6];
+
+ AsyncSendPacket *packet = nil;
+
+ if(address4 && theSocket4)
+ packet = [[AsyncSendPacket alloc] initWithData:data address:address4 timeout:timeout tag:tag];
+ else if(address6 && theSocket6)
+ packet = [[AsyncSendPacket alloc] initWithData:data address:address6 timeout:timeout tag:tag];
+ else
+ return NO;
+
+ [theSendQueue addObject:packet];
+ [self scheduleDequeueSend];
+
+ [packet release];
+ return YES;
+}
+
+- (BOOL)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ if((data == nil) || ([data length] == 0)) return NO;
+ if(theFlags & kForbidSendReceive) return NO;
+ if(theFlags & kDidClose) return NO;
+
+ // This method is only for non-connected sockets
+ if([self isConnected]) return NO;
+
+ if([remoteAddr length] == sizeof(struct sockaddr_in) && !theSocket4)
+ return NO;
+
+ if([remoteAddr length] == sizeof(struct sockaddr_in6) && !theSocket6)
+ return NO;
+
+ AsyncSendPacket *packet = [[AsyncSendPacket alloc] initWithData:data address:remoteAddr timeout:timeout tag:tag];
+
+ [theSendQueue addObject:packet];
+ [self scheduleDequeueSend];
+
+ [packet release];
+ return YES;
+}
+
+- (BOOL)canAcceptBytes:(CFSocketRef)sockRef
+{
+ if(sockRef == theSocket4)
+ {
+ if(theFlags & kSock4CanAcceptBytes) return YES;
+ }
+ else
+ {
+ if(theFlags & kSock6CanAcceptBytes) return YES;
+ }
+
+ CFSocketNativeHandle theNativeSocket = CFSocketGetNative(sockRef);
+
+ if(theNativeSocket == 0)
+ {
+ NSLog(@"Error - Could not get CFSocketNativeHandle from CFSocketRef");
+ return NO;
+ }
+
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(theNativeSocket, &fds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+
+ return select(FD_SETSIZE, NULL, &fds, NULL, &timeout) > 0;
+}
+
+- (CFSocketRef)socketForPacket:(AsyncSendPacket *)packet
+{
+ if(!theSocket4)
+ return theSocket6;
+ if(!theSocket6)
+ return theSocket4;
+
+ return ([packet->address length] == sizeof(struct sockaddr_in)) ? theSocket4 : theSocket6;
+}
+
+/**
+ * Puts a maybeDequeueSend on the run loop.
+**/
+- (void)scheduleDequeueSend
+{
+ if((theFlags & kDequeueSendScheduled) == 0)
+ {
+ theFlags |= kDequeueSendScheduled;
+ [self performSelector:@selector(maybeDequeueSend) withObject:nil afterDelay:0 inModes:theRunLoopModes];
+ }
+}
+
+/**
+ * This method starts a new send, if needed.
+ * It is called when a user requests a send.
+**/
+- (void)maybeDequeueSend
+{
+ // Unset the flag indicating a call to this method is scheduled
+ theFlags &= ~kDequeueSendScheduled;
+
+ if(theCurrentSend == nil)
+ {
+ if([theSendQueue count] > 0)
+ {
+ // Dequeue next send packet
+ theCurrentSend = [[theSendQueue objectAtIndex:0] retain];
+ [theSendQueue removeObjectAtIndex:0];
+
+ // Start time-out timer.
+ if(theCurrentSend->timeout >= 0.0)
+ {
+ theSendTimer = [NSTimer timerWithTimeInterval:theCurrentSend->timeout
+ target:self
+ selector:@selector(doSendTimeout:)
+ userInfo:nil
+ repeats:NO];
+
+ [self runLoopAddTimer:theSendTimer];
+ }
+
+ // Immediately send, if possible.
+ [self doSend:[self socketForPacket:theCurrentSend]];
+ }
+ else if(theFlags & kCloseAfterSends)
+ {
+ if(theFlags & kCloseAfterReceives)
+ {
+ if(([theReceiveQueue count] == 0) && (theCurrentReceive == nil))
+ {
+ [self close];
+ }
+ }
+ else
+ {
+ [self close];
+ }
+ }
+ }
+}
+
+/**
+ * This method is called when a new read is taken from the read queue or when new data becomes available on the stream.
+**/
+- (void)doSend:(CFSocketRef)theSocket
+{
+ if(theCurrentSend != nil)
+ {
+ if(theSocket != [self socketForPacket:theCurrentSend])
+ {
+ // Current send is for the other socket
+ return;
+ }
+
+ if([self canAcceptBytes:theSocket])
+ {
+ ssize_t result;
+ CFSocketNativeHandle theNativeSocket = CFSocketGetNative(theSocket);
+
+ const void *buf = [theCurrentSend->buffer bytes];
+ NSUInteger bufSize = [theCurrentSend->buffer length];
+
+ if([self isConnected])
+ {
+ result = send(theNativeSocket, buf, (size_t)bufSize, 0);
+ }
+ else
+ {
+ const void *dst = [theCurrentSend->address bytes];
+ NSUInteger dstSize = [theCurrentSend->address length];
+
+ result = sendto(theNativeSocket, buf, (size_t)bufSize, 0, dst, (socklen_t)dstSize);
+ }
+
+ if(theSocket == theSocket4)
+ theFlags &= ~kSock4CanAcceptBytes;
+ else
+ theFlags &= ~kSock6CanAcceptBytes;
+
+ if(result < 0)
+ {
+ [self failCurrentSend:[self getErrnoError]];
+ }
+ else
+ {
+ // If it wasn't bound before, it's bound now
+ theFlags |= kDidBind;
+
+ [self completeCurrentSend];
+ }
+
+ [self scheduleDequeueSend];
+ }
+ else
+ {
+ // Request notification when the socket is ready to send more data
+ CFSocketEnableCallBacks(theSocket, kCFSocketReadCallBack | kCFSocketWriteCallBack);
+ }
+ }
+}
+
+- (void)completeCurrentSend
+{
+ NSAssert (theCurrentSend, @"Trying to complete current send when there is no current send.");
+
+ if ([theDelegate respondsToSelector:@selector(onUdpSocket:didSendDataWithTag:)])
+ {
+ [theDelegate onUdpSocket:self didSendDataWithTag:theCurrentSend->tag];
+ }
+
+ if (theCurrentSend != nil) [self endCurrentSend]; // Caller may have disconnected.
+}
+
+- (void)failCurrentSend:(NSError *)error
+{
+ NSAssert (theCurrentSend, @"Trying to fail current send when there is no current send.");
+
+ if ([theDelegate respondsToSelector:@selector(onUdpSocket:didNotSendDataWithTag:dueToError:)])
+ {
+ [theDelegate onUdpSocket:self didNotSendDataWithTag:theCurrentSend->tag dueToError:error];
+ }
+
+ if (theCurrentSend != nil) [self endCurrentSend]; // Caller may have disconnected.
+}
+
+/**
+ * Ends the current send, and all associated variables such as the send timer.
+**/
+- (void)endCurrentSend
+{
+ NSAssert (theCurrentSend, @"Trying to end current send when there is no current send.");
+
+ [theSendTimer invalidate];
+ theSendTimer = nil;
+
+ [theCurrentSend release];
+ theCurrentSend = nil;
+}
+
+- (void)doSendTimeout:(NSTimer *)timer
+{
+ if (timer != theSendTimer) return; // Old timer. Ignore it.
+ if (theCurrentSend != nil)
+ {
+ [self failCurrentSend:[self getSendTimeoutError]];
+ [self scheduleDequeueSend];
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Receiving
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)receiveWithTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ if(theFlags & kForbidSendReceive) return;
+ if(theFlags & kDidClose) return;
+
+ AsyncReceivePacket *packet = [[AsyncReceivePacket alloc] initWithTimeout:timeout tag:tag];
+
+ [theReceiveQueue addObject:packet];
+ [self scheduleDequeueReceive];
+
+ [packet release];
+}
+
+- (BOOL)hasBytesAvailable:(CFSocketRef)sockRef
+{
+ if(sockRef == theSocket4)
+ {
+ if(theFlags & kSock4HasBytesAvailable) return YES;
+ }
+ else
+ {
+ if(theFlags & kSock6HasBytesAvailable) return YES;
+ }
+
+ CFSocketNativeHandle theNativeSocket = CFSocketGetNative(sockRef);
+
+ if(theNativeSocket == 0)
+ {
+ NSLog(@"Error - Could not get CFSocketNativeHandle from CFSocketRef");
+ return NO;
+ }
+
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(theNativeSocket, &fds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+
+ return select(FD_SETSIZE, &fds, NULL, NULL, &timeout) > 0;
+}
+
+/**
+ * Puts a maybeDequeueReceive on the run loop.
+**/
+- (void)scheduleDequeueReceive
+{
+ if((theFlags & kDequeueReceiveScheduled) == 0)
+ {
+ theFlags |= kDequeueReceiveScheduled;
+ [self performSelector:@selector(maybeDequeueReceive) withObject:nil afterDelay:0 inModes:theRunLoopModes];
+ }
+}
+
+/**
+ * Starts a new receive operation if needed
+**/
+- (void)maybeDequeueReceive
+{
+ // Unset the flag indicating a call to this method is scheduled
+ theFlags &= ~kDequeueReceiveScheduled;
+
+ if (theCurrentReceive == nil)
+ {
+ if([theReceiveQueue count] > 0)
+ {
+ // Dequeue next receive packet
+ theCurrentReceive = [[theReceiveQueue objectAtIndex:0] retain];
+ [theReceiveQueue removeObjectAtIndex:0];
+
+ // Start time-out timer.
+ if (theCurrentReceive->timeout >= 0.0)
+ {
+ theReceiveTimer = [NSTimer timerWithTimeInterval:theCurrentReceive->timeout
+ target:self
+ selector:@selector(doReceiveTimeout:)
+ userInfo:nil
+ repeats:NO];
+
+ [self runLoopAddTimer:theReceiveTimer];
+ }
+
+ // Immediately receive, if possible
+ // We always check both sockets so we don't ever starve one of them.
+ // We also check them in alternating orders to prevent starvation if both of them
+ // have a continuous flow of incoming data.
+ if(theFlags & kFlipFlop)
+ {
+ [self doReceive4];
+ [self doReceive6];
+ }
+ else
+ {
+ [self doReceive6];
+ [self doReceive4];
+ }
+
+ theFlags ^= kFlipFlop;
+ }
+ else if(theFlags & kCloseAfterReceives)
+ {
+ if(theFlags & kCloseAfterSends)
+ {
+ if(([theSendQueue count] == 0) && (theCurrentSend == nil))
+ {
+ [self close];
+ }
+ }
+ else
+ {
+ [self close];
+ }
+ }
+ }
+}
+
+- (void)doReceive4
+{
+ if(theSocket4) [self doReceive:theSocket4];
+}
+
+- (void)doReceive6
+{
+ if(theSocket6) [self doReceive:theSocket6];
+}
+
+- (void)doReceive:(CFSocketRef)theSocket
+{
+ if (theCurrentReceive != nil)
+ {
+ BOOL appIgnoredReceivedData;
+ BOOL userIgnoredReceivedData;
+
+ do
+ {
+ // Set or reset ignored variables.
+ // If the app or user ignores the received data, we'll continue this do-while loop.
+ appIgnoredReceivedData = NO;
+ userIgnoredReceivedData = NO;
+
+ if([self hasBytesAvailable:theSocket])
+ {
+ ssize_t result;
+ CFSocketNativeHandle theNativeSocket = CFSocketGetNative(theSocket);
+
+ // Allocate buffer for recvfrom operation.
+ // If the operation is successful, we'll realloc the buffer to the appropriate size,
+ // and create an NSData wrapper around it without needing to copy any bytes around.
+ void *buf = malloc(maxReceiveBufferSize);
+ size_t bufSize = maxReceiveBufferSize;
+
+ if(theSocket == theSocket4)
+ {
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ result = recvfrom(theNativeSocket, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len);
+
+ if(result >= 0)
+ {
+ NSString *host = [self addressHost4:&sockaddr4];
+ UInt16 port = ntohs(sockaddr4.sin_port);
+
+ if([self isConnected] && ![self isConnectedToHost:host port:port])
+ {
+ // The user connected to an address, and the received data doesn't match the address.
+ // This may happen if the data is received by the kernel prior to the connect call.
+ appIgnoredReceivedData = YES;
+ }
+ else
+ {
+ if(result != bufSize)
+ {
+ buf = realloc(buf, result);
+ }
+ theCurrentReceive->buffer = [[NSData alloc] initWithBytesNoCopy:buf
+ length:result
+ freeWhenDone:YES];
+ theCurrentReceive->host = [host retain];
+ theCurrentReceive->port = port;
+ }
+ }
+
+ theFlags &= ~kSock4HasBytesAvailable;
+ }
+ else
+ {
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ result = recvfrom(theNativeSocket, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len);
+
+ if(result >= 0)
+ {
+ NSString *host = [self addressHost6:&sockaddr6];
+ UInt16 port = ntohs(sockaddr6.sin6_port);
+
+ if([self isConnected] && ![self isConnectedToHost:host port:port])
+ {
+ // The user connected to an address, and the received data doesn't match the address.
+ // This may happen if the data is received by the kernel prior to the connect call.
+ appIgnoredReceivedData = YES;
+ }
+ else
+ {
+ if(result != bufSize)
+ {
+ buf = realloc(buf, result);
+ }
+ theCurrentReceive->buffer = [[NSData alloc] initWithBytesNoCopy:buf
+ length:result
+ freeWhenDone:YES];
+ theCurrentReceive->host = [host retain];
+ theCurrentReceive->port = port;
+ }
+ }
+
+ theFlags &= ~kSock6HasBytesAvailable;
+ }
+
+ // Check to see if we need to free our alloc'd buffer
+ // If the buffer is non-nil, this means it has taken ownership of the buffer
+ if(theCurrentReceive->buffer == nil)
+ {
+ free(buf);
+ }
+
+ if(result < 0)
+ {
+ [self failCurrentReceive:[self getErrnoError]];
+ [self scheduleDequeueReceive];
+ }
+ else if(!appIgnoredReceivedData)
+ {
+ BOOL finished = [self maybeCompleteCurrentReceive];
+
+ if(finished)
+ {
+ [self scheduleDequeueReceive];
+ }
+ else
+ {
+ [theCurrentReceive->buffer release];
+ [theCurrentReceive->host release];
+
+ theCurrentReceive->buffer = nil;
+ theCurrentReceive->host = nil;
+