├── AsyncSocket Documentation.html ├── AsyncSocket.h ├── AsyncSocket.m ├── AsyncUdpSocket.h ├── AsyncUdpSocket.m ├── CertTest ├── AppController.h ├── AppController.m ├── CertTest.xcodeproj │ ├── TemplateIcon.icns │ └── project.pbxproj ├── CertTest_Prefix.pch ├── English.lproj │ ├── InfoPlist.strings │ └── MainMenu.xib ├── Info.plist ├── X509Certificate.h ├── X509Certificate.m └── main.m ├── EchoServer ├── AppController.h ├── AppController.m ├── EchoServer.xcodeproj │ └── project.pbxproj ├── EchoServer_Prefix.pch ├── English.lproj │ ├── InfoPlist.strings │ └── MainMenu.nib │ │ ├── designable.nib │ │ └── keyedobjects.nib ├── Info.plist ├── Instructions.txt └── main.m ├── README └── changes.txt /AsyncSocket.h: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSocket.h 3 | // 4 | // This class is in the public domain. 5 | // Originally created by Dustin Voss on Wed Jan 29 2003. 6 | // Updated and maintained by Deusty Designs and the Mac development community. 7 | // 8 | // http://code.google.com/p/cocoaasyncsocket/ 9 | // 10 | 11 | #import 12 | 13 | @class AsyncSocket; 14 | @class AsyncReadPacket; 15 | @class AsyncWritePacket; 16 | 17 | extern NSString *const AsyncSocketException; 18 | extern NSString *const AsyncSocketErrorDomain; 19 | 20 | enum AsyncSocketError 21 | { 22 | AsyncSocketCFSocketError = kCFSocketError, // From CFSocketError enum. 23 | AsyncSocketNoError = 0, // Never used. 24 | AsyncSocketCanceledError, // onSocketWillConnect: returned NO. 25 | AsyncSocketConnectTimeoutError, 26 | AsyncSocketReadMaxedOutError, // Reached set maxLength without completing 27 | AsyncSocketReadTimeoutError, 28 | AsyncSocketWriteTimeoutError 29 | }; 30 | typedef enum AsyncSocketError AsyncSocketError; 31 | 32 | @interface NSObject (AsyncSocketDelegate) 33 | 34 | /** 35 | * In the event of an error, the socket is closed. 36 | * You may call "unreadData" during this call-back to get the last bit of data off the socket. 37 | * When connecting, this delegate method may be called 38 | * before"onSocket:didAcceptNewSocket:" or "onSocket:didConnectToHost:". 39 | **/ 40 | - (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err; 41 | 42 | /** 43 | * Called when a socket disconnects with or without error. If you want to release a socket after it disconnects, 44 | * do so here. It is not safe to do that during "onSocket:willDisconnectWithError:". 45 | * 46 | * If you call the disconnect method, and the socket wasn't already disconnected, 47 | * this delegate method will be called before the disconnect method returns. 48 | **/ 49 | - (void)onSocketDidDisconnect:(AsyncSocket *)sock; 50 | 51 | /** 52 | * Called when a socket accepts a connection. Another socket is spawned to handle it. The new socket will have 53 | * the same delegate and will call "onSocket:didConnectToHost:port:". 54 | **/ 55 | - (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket; 56 | 57 | /** 58 | * Called when a new socket is spawned to handle a connection. This method should return the run-loop of the 59 | * thread on which the new socket and its delegate should operate. If omitted, [NSRunLoop currentRunLoop] is used. 60 | **/ 61 | - (NSRunLoop *)onSocket:(AsyncSocket *)sock wantsRunLoopForNewSocket:(AsyncSocket *)newSocket; 62 | 63 | /** 64 | * Called when a socket is about to connect. This method should return YES to continue, or NO to abort. 65 | * If aborted, will result in AsyncSocketCanceledError. 66 | * 67 | * If the connectToHost:onPort:error: method was called, the delegate will be able to access and configure the 68 | * CFReadStream and CFWriteStream as desired prior to connection. 69 | * 70 | * If the connectToAddress:error: method was called, the delegate will be able to access and configure the 71 | * CFSocket and CFSocketNativeHandle (BSD socket) as desired prior to connection. You will be able to access and 72 | * configure the CFReadStream and CFWriteStream in the onSocket:didConnectToHost:port: method. 73 | **/ 74 | - (BOOL)onSocketWillConnect:(AsyncSocket *)sock; 75 | 76 | /** 77 | * Called when a socket connects and is ready for reading and writing. 78 | * The host parameter will be an IP address, not a DNS name. 79 | **/ 80 | - (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port; 81 | 82 | /** 83 | * Called when a socket has completed reading the requested data into memory. 84 | * Not called if there is an error. 85 | **/ 86 | - (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; 87 | 88 | /** 89 | * Called when a socket has read in data, but has not yet completed the read. 90 | * This would occur if using readToData: or readToLength: methods. 91 | * It may be used to for things such as updating progress bars. 92 | **/ 93 | - (void)onSocket:(AsyncSocket *)sock didReadPartialDataOfLength:(CFIndex)partialLength tag:(long)tag; 94 | 95 | /** 96 | * Called when a socket has completed writing the requested data. Not called if there is an error. 97 | **/ 98 | - (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag; 99 | 100 | /** 101 | * Called when a socket has written some data, but has not yet completed the entire write. 102 | * It may be used to for things such as updating progress bars. 103 | **/ 104 | - (void)onSocket:(AsyncSocket *)sock didWritePartialDataOfLength:(CFIndex)partialLength tag:(long)tag; 105 | 106 | /** 107 | * Called if a read operation has reached its timeout without completing. 108 | * This method allows you to optionally extend the timeout. 109 | * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount. 110 | * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual. 111 | * 112 | * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. 113 | * The length parameter is the number of bytes that have been read so far for the read operation. 114 | * 115 | * Note that this method may be called multiple times for a single read if you return positive numbers. 116 | **/ 117 | - (NSTimeInterval)onSocket:(AsyncSocket *)sock 118 | shouldTimeoutReadWithTag:(long)tag 119 | elapsed:(NSTimeInterval)elapsed 120 | bytesDone:(CFIndex)length; 121 | 122 | /** 123 | * Called if a write operation has reached its timeout without completing. 124 | * This method allows you to optionally extend the timeout. 125 | * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount. 126 | * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual. 127 | * 128 | * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. 129 | * The length parameter is the number of bytes that have been written so far for the write operation. 130 | * 131 | * Note that this method may be called multiple times for a single write if you return positive numbers. 132 | **/ 133 | - (NSTimeInterval)onSocket:(AsyncSocket *)sock 134 | shouldTimeoutWriteWithTag:(long)tag 135 | elapsed:(NSTimeInterval)elapsed 136 | bytesDone:(CFIndex)length; 137 | 138 | /** 139 | * Called after the socket has successfully completed SSL/TLS negotiation. 140 | * This method is not called unless you use the provided startTLS method. 141 | * 142 | * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close, 143 | * and the onSocket:willDisconnectWithError: delegate method will be called with the specific SSL error code. 144 | **/ 145 | - (void)onSocketDidSecure:(AsyncSocket *)sock; 146 | 147 | @end 148 | 149 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 150 | #pragma mark - 151 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 152 | 153 | @interface AsyncSocket : NSObject 154 | { 155 | CFSocketNativeHandle theNativeSocket4; 156 | CFSocketNativeHandle theNativeSocket6; 157 | 158 | CFSocketRef theSocket4; // IPv4 accept or connect socket 159 | CFSocketRef theSocket6; // IPv6 accept or connect socket 160 | 161 | CFReadStreamRef theReadStream; 162 | CFWriteStreamRef theWriteStream; 163 | 164 | CFRunLoopSourceRef theSource4; // For theSocket4 165 | CFRunLoopSourceRef theSource6; // For theSocket6 166 | CFRunLoopRef theRunLoop; 167 | CFSocketContext theContext; 168 | NSArray *theRunLoopModes; 169 | 170 | NSTimer *theConnectTimer; 171 | 172 | NSMutableArray *theReadQueue; 173 | AsyncReadPacket *theCurrentRead; 174 | NSTimer *theReadTimer; 175 | NSMutableData *partialReadBuffer; 176 | 177 | NSMutableArray *theWriteQueue; 178 | AsyncWritePacket *theCurrentWrite; 179 | NSTimer *theWriteTimer; 180 | 181 | id theDelegate; 182 | UInt16 theFlags; 183 | 184 | long theUserData; 185 | } 186 | 187 | - (id)init; 188 | - (id)initWithDelegate:(id)delegate; 189 | - (id)initWithDelegate:(id)delegate userData:(long)userData; 190 | 191 | /* String representation is long but has no "\n". */ 192 | - (NSString *)description; 193 | 194 | /** 195 | * Use "canSafelySetDelegate" to see if there is any pending business (reads and writes) with the current delegate 196 | * before changing it. It is, of course, safe to change the delegate before connecting or accepting connections. 197 | **/ 198 | - (id)delegate; 199 | - (BOOL)canSafelySetDelegate; 200 | - (void)setDelegate:(id)delegate; 201 | 202 | /* User data can be a long, or an id or void * cast to a long. */ 203 | - (long)userData; 204 | - (void)setUserData:(long)userData; 205 | 206 | /* Don't use these to read or write. And don't close them either! */ 207 | - (CFSocketRef)getCFSocket; 208 | - (CFReadStreamRef)getCFReadStream; 209 | - (CFWriteStreamRef)getCFWriteStream; 210 | 211 | // Once one of the accept or connect methods are called, the AsyncSocket instance is locked in 212 | // and the other accept/connect methods can't be called without disconnecting the socket first. 213 | // If the attempt fails or times out, these methods either return NO or 214 | // call "onSocket:willDisconnectWithError:" and "onSockedDidDisconnect:". 215 | 216 | // When an incoming connection is accepted, AsyncSocket invokes several delegate methods. 217 | // These methods are (in chronological order): 218 | // 1. onSocket:didAcceptNewSocket: 219 | // 2. onSocket:wantsRunLoopForNewSocket: 220 | // 3. onSocketWillConnect: 221 | // 222 | // Your server code will need to retain the accepted socket (if you want to accept it). 223 | // The best place to do this is probably in the onSocket:didAcceptNewSocket: method. 224 | // 225 | // After the read and write streams have been setup for the newly accepted socket, 226 | // the onSocket:didConnectToHost:port: method will be called on the proper run loop. 227 | // 228 | // Multithreading Note: If you're going to be moving the newly accepted socket to another run 229 | // loop by implementing onSocket:wantsRunLoopForNewSocket:, then you should wait until the 230 | // onSocket:didConnectToHost:port: method before calling read, write, or startTLS methods. 231 | // Otherwise read/write events are scheduled on the incorrect runloop, and chaos may ensue. 232 | 233 | /** 234 | * Tells the socket to begin listening and accepting connections on the given port. 235 | * When a connection comes in, the AsyncSocket instance will call the various delegate methods (see above). 236 | * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) 237 | **/ 238 | - (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr; 239 | 240 | /** 241 | * This method is the same as acceptOnPort:error: with the additional option 242 | * of specifying which interface to listen on. So, for example, if you were writing code for a server that 243 | * has multiple IP addresses, you could specify which address you wanted to listen on. Or you could use it 244 | * to specify that the socket should only accept connections over ethernet, and not other interfaces such as wifi. 245 | * You may also use the special strings "localhost" or "loopback" to specify that 246 | * the socket only accept connections from the local machine. 247 | * 248 | * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method. 249 | **/ 250 | - (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr; 251 | 252 | /** 253 | * Connects to the given host and port. 254 | * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2") 255 | **/ 256 | - (BOOL)connectToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr; 257 | 258 | /** 259 | * This method is the same as connectToHost:onPort:error: with an additional timeout option. 260 | * To not time out use a negative time interval, or simply use the connectToHost:onPort:error: method. 261 | **/ 262 | - (BOOL)connectToHost:(NSString *)hostname 263 | onPort:(UInt16)port 264 | withTimeout:(NSTimeInterval)timeout 265 | error:(NSError **)errPtr; 266 | 267 | /** 268 | * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object. 269 | * For example, a NSData object returned from NSNetservice's addresses method. 270 | * 271 | * If you have an existing struct sockaddr you can convert it to a NSData object like so: 272 | * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; 273 | * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; 274 | **/ 275 | - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; 276 | 277 | /** 278 | * This method is the same as connectToAddress:error: with an additional timeout option. 279 | * To not time out use a negative time interval, or simply use the connectToAddress:error: method. 280 | **/ 281 | - (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; 282 | 283 | /** 284 | * Disconnects immediately. Any pending reads or writes are dropped. 285 | * If the socket is not already disconnected, the onSocketDidDisconnect delegate method 286 | * will be called immediately, before this method returns. 287 | * 288 | * Please note the recommended way of releasing an AsyncSocket instance (e.g. in a dealloc method) 289 | * [asyncSocket setDelegate:nil]; 290 | * [asyncSocket disconnect]; 291 | * [asyncSocket release]; 292 | **/ 293 | - (void)disconnect; 294 | 295 | /** 296 | * Disconnects after all pending reads have completed. 297 | * After calling this, the read and write methods will do nothing. 298 | * The socket will disconnect even if there are still pending writes. 299 | **/ 300 | - (void)disconnectAfterReading; 301 | 302 | /** 303 | * Disconnects after all pending writes have completed. 304 | * After calling this, the read and write methods will do nothing. 305 | * The socket will disconnect even if there are still pending reads. 306 | **/ 307 | - (void)disconnectAfterWriting; 308 | 309 | /** 310 | * Disconnects after all pending reads and writes have completed. 311 | * After calling this, the read and write methods will do nothing. 312 | **/ 313 | - (void)disconnectAfterReadingAndWriting; 314 | 315 | /* Returns YES if the socket and streams are open, connected, and ready for reading and writing. */ 316 | - (BOOL)isConnected; 317 | 318 | /** 319 | * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected. 320 | * The host will be an IP address. 321 | **/ 322 | - (NSString *)connectedHost; 323 | - (UInt16)connectedPort; 324 | 325 | - (NSString *)localHost; 326 | - (UInt16)localPort; 327 | 328 | /** 329 | * Returns the local or remote address to which this socket is connected, 330 | * specified as a sockaddr structure wrapped in a NSData object. 331 | * 332 | * See also the connectedHost, connectedPort, localHost and localPort methods. 333 | **/ 334 | - (NSData *)connectedAddress; 335 | - (NSData *)localAddress; 336 | 337 | /** 338 | * Returns whether the socket is IPv4 or IPv6. 339 | * An accepting socket may be both. 340 | **/ 341 | - (BOOL)isIPv4; 342 | - (BOOL)isIPv6; 343 | 344 | // The readData and writeData methods won't block. 345 | // 346 | // You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.) 347 | // If a read/write opertion times out, the corresponding "onSocket:shouldTimeout..." delegate method 348 | // is called to optionally allow you to extend the timeout. 349 | // Upon a timeout, the "onSocket:willDisconnectWithError:" method is called, followed by "onSocketDidDisconnect". 350 | // 351 | // The tag is for your convenience. 352 | // You can use it as an array index, step number, state id, pointer, etc., just like the socket's user data. 353 | 354 | /** 355 | * This will read a certain number of bytes into memory, and call the delegate method when those bytes have been read. 356 | * 357 | * If the length is 0, this method does nothing and the delegate is not called. 358 | * If the timeout value is negative, the read operation will not use a timeout. 359 | **/ 360 | - (void)readDataToLength:(CFIndex)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; 361 | 362 | /** 363 | * This reads bytes until (and including) the passed "data" parameter, which acts as a separator. 364 | * The bytes and the separator are returned by the delegate method. 365 | * 366 | * If you pass nil or zero-length data as the "data" parameter, 367 | * the method will do nothing, and the delegate will not be called. 368 | * If the timeout value is negative, the read operation will not use a timeout. 369 | * 370 | * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. 371 | * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for 372 | * a character, the read will prematurely end. 373 | **/ 374 | - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; 375 | 376 | /** 377 | * Same as readDataToData:withTimeout:tag, with the additional restriction that the amount of data read 378 | * may not surpass the given maxLength (specified in bytes). 379 | * 380 | * If you pass a maxLength parameter that is less than the length of the data parameter, 381 | * the method will do nothing, and the delegate will not be called. 382 | * 383 | * If the max length is surpassed, it is treated the same as a timeout - the socket is closed. 384 | * 385 | * Pass -1 as maxLength if no length restriction is desired, or simply use the readDataToData:withTimeout:tag method. 386 | **/ 387 | - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(CFIndex)length tag:(long)tag; 388 | 389 | /** 390 | * Reads the first available bytes that become available on the socket. 391 | * 392 | * If the timeout value is negative, the read operation will not use a timeout. 393 | **/ 394 | - (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; 395 | 396 | /** 397 | * Writes data to the socket, and calls the delegate when finished. 398 | * 399 | * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called. 400 | * If the timeout value is negative, the write operation will not use a timeout. 401 | **/ 402 | - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; 403 | 404 | /** 405 | * Returns progress of current read or write, from 0.0 to 1.0, or NaN if no read/write (use isnan() to check). 406 | * "tag", "done" and "total" will be filled in if they aren't NULL. 407 | **/ 408 | - (float)progressOfReadReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total; 409 | - (float)progressOfWriteReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total; 410 | 411 | /** 412 | * Secures the connection using SSL/TLS. 413 | * 414 | * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes 415 | * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing 416 | * the upgrade to TLS at the same time, without having to wait for the write to finish. 417 | * Any reads or writes scheduled after this method is called will occur over the secured connection. 418 | * 419 | * The possible keys and values for the TLS settings are well documented. 420 | * Some possible keys are: 421 | * - kCFStreamSSLLevel 422 | * - kCFStreamSSLAllowsExpiredCertificates 423 | * - kCFStreamSSLAllowsExpiredRoots 424 | * - kCFStreamSSLAllowsAnyRoot 425 | * - kCFStreamSSLValidatesCertificateChain 426 | * - kCFStreamSSLPeerName 427 | * - kCFStreamSSLCertificates 428 | * - kCFStreamSSLIsServer 429 | * 430 | * Please refer to Apple's documentation for associated values, as well as other possible keys. 431 | * 432 | * If you pass in nil or an empty dictionary, the default settings will be used. 433 | * 434 | * The default settings will check to make sure the remote party's certificate is signed by a 435 | * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired. 436 | * However it will not verify the name on the certificate unless you 437 | * give it a name to verify against via the kCFStreamSSLPeerName key. 438 | * The security implications of this are important to understand. 439 | * Imagine you are attempting to create a secure connection to MySecureServer.com, 440 | * but your socket gets directed to MaliciousServer.com because of a hacked DNS server. 441 | * If you simply use the default settings, and MaliciousServer.com has a valid certificate, 442 | * the default settings will not detect any problems since the certificate is valid. 443 | * To properly secure your connection in this particular scenario you 444 | * should set the kCFStreamSSLPeerName property to "MySecureServer.com". 445 | * If you do not know the peer name of the remote host in advance (for example, you're not sure 446 | * if it will be "domain.com" or "www.domain.com"), then you can use the default settings to validate the 447 | * certificate, and then use the X509Certificate class to verify the issuer after the socket has been secured. 448 | * The X509Certificate class is part of the CocoaAsyncSocket open source project. 449 | **/ 450 | - (void)startTLS:(NSDictionary *)tlsSettings; 451 | 452 | /** 453 | * For handling readDataToData requests, data is necessarily read from the socket in small increments. 454 | * The performance can be much improved by allowing AsyncSocket to read larger chunks at a time and 455 | * store any overflow in a small internal buffer. 456 | * This is termed pre-buffering, as some data may be read for you before you ask for it. 457 | * If you use readDataToData a lot, enabling pre-buffering will result in better performance, especially on the iPhone. 458 | * 459 | * The default pre-buffering state is controlled by the DEFAULT_PREBUFFERING definition. 460 | * It is highly recommended one leave this set to YES. 461 | * 462 | * This method exists in case pre-buffering needs to be disabled by default for some unforeseen reason. 463 | * In that case, this method exists to allow one to easily enable pre-buffering when ready. 464 | **/ 465 | - (void)enablePreBuffering; 466 | 467 | /** 468 | * When you create an AsyncSocket, it is added to the runloop of the current thread. 469 | * So for manually created sockets, it is easiest to simply create the socket on the thread you intend to use it. 470 | * 471 | * If a new socket is accepted, the delegate method onSocket:wantsRunLoopForNewSocket: is called to 472 | * allow you to place the socket on a separate thread. This works best in conjunction with a thread pool design. 473 | * 474 | * If, however, you need to move the socket to a separate thread at a later time, this 475 | * method may be used to accomplish the task. 476 | * 477 | * This method must be called from the thread/runloop the socket is currently running on. 478 | * 479 | * Note: After calling this method, all further method calls to this object should be done from the given runloop. 480 | * Also, all delegate calls will be sent on the given runloop. 481 | **/ 482 | - (BOOL)moveToRunLoop:(NSRunLoop *)runLoop; 483 | 484 | /** 485 | * Allows you to configure which run loop modes the socket uses. 486 | * The default set of run loop modes is NSDefaultRunLoopMode. 487 | * 488 | * If you'd like your socket to continue operation during other modes, you may want to add modes such as 489 | * NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes. 490 | * 491 | * Accepted sockets will automatically inherit the same run loop modes as the listening socket. 492 | * 493 | * Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes. 494 | **/ 495 | - (BOOL)setRunLoopModes:(NSArray *)runLoopModes; 496 | 497 | /** 498 | * Returns the current run loop modes the AsyncSocket instance is operating in. 499 | * The default set of run loop modes is NSDefaultRunLoopMode. 500 | **/ 501 | - (NSArray *)runLoopModes; 502 | 503 | /** 504 | * In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read 505 | * any data that's left on the socket. 506 | **/ 507 | - (NSData *)unreadData; 508 | 509 | /* A few common line separators, for use with the readDataToData:... methods. */ 510 | + (NSData *)CRLFData; // 0x0D0A 511 | + (NSData *)CRData; // 0x0D 512 | + (NSData *)LFData; // 0x0A 513 | + (NSData *)ZeroData; // 0x00 514 | 515 | @end 516 | -------------------------------------------------------------------------------- /AsyncUdpSocket.h: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncUdpSocket.h 3 | // 4 | // This class is in the public domain. 5 | // Originally created by Robbie Hanson on Wed Oct 01 2008. 6 | // Updated and maintained by Deusty Designs and the Mac development community. 7 | // 8 | // http://code.google.com/p/cocoaasyncsocket/ 9 | // 10 | 11 | #import 12 | 13 | @class AsyncSendPacket; 14 | @class AsyncReceivePacket; 15 | 16 | extern NSString *const AsyncUdpSocketException; 17 | extern NSString *const AsyncUdpSocketErrorDomain; 18 | 19 | enum AsyncUdpSocketError 20 | { 21 | AsyncUdpSocketCFSocketError = kCFSocketError, // From CFSocketError enum 22 | AsyncUdpSocketNoError = 0, // Never used 23 | AsyncUdpSocketBadParameter, // Used if given a bad parameter (such as an improper address) 24 | AsyncUdpSocketIPv4Unavailable, // Used if you bind/connect using IPv6 only 25 | AsyncUdpSocketIPv6Unavailable, // Used if you bind/connect using IPv4 only (or iPhone) 26 | AsyncUdpSocketSendTimeoutError, 27 | AsyncUdpSocketReceiveTimeoutError 28 | }; 29 | typedef enum AsyncUdpSocketError AsyncUdpSocketError; 30 | 31 | @interface AsyncUdpSocket : NSObject 32 | { 33 | CFSocketRef theSocket4; // IPv4 socket 34 | CFSocketRef theSocket6; // IPv6 socket 35 | 36 | CFRunLoopSourceRef theSource4; // For theSocket4 37 | CFRunLoopSourceRef theSource6; // For theSocket6 38 | CFRunLoopRef theRunLoop; 39 | CFSocketContext theContext; 40 | NSArray *theRunLoopModes; 41 | 42 | NSMutableArray *theSendQueue; 43 | AsyncSendPacket *theCurrentSend; 44 | NSTimer *theSendTimer; 45 | 46 | NSMutableArray *theReceiveQueue; 47 | AsyncReceivePacket *theCurrentReceive; 48 | NSTimer *theReceiveTimer; 49 | 50 | id theDelegate; 51 | UInt16 theFlags; 52 | 53 | long theUserData; 54 | 55 | NSString *cachedLocalHost; 56 | UInt16 cachedLocalPort; 57 | 58 | NSString *cachedConnectedHost; 59 | UInt16 cachedConnectedPort; 60 | 61 | UInt32 maxReceiveBufferSize; 62 | } 63 | 64 | /** 65 | * Creates new instances of AsyncUdpSocket. 66 | **/ 67 | - (id)init; 68 | - (id)initWithDelegate:(id)delegate; 69 | - (id)initWithDelegate:(id)delegate userData:(long)userData; 70 | 71 | /** 72 | * Creates new instances of AsyncUdpSocket that support only IPv4 or IPv6. 73 | * The other init methods will support both, unless specifically binded or connected to one protocol. 74 | * If you know you'll only be using one protocol, these init methods may be a bit more efficient. 75 | **/ 76 | - (id)initIPv4; 77 | - (id)initIPv6; 78 | 79 | - (id)delegate; 80 | - (void)setDelegate:(id)delegate; 81 | 82 | - (long)userData; 83 | - (void)setUserData:(long)userData; 84 | 85 | /** 86 | * Returns the local address info for the socket. 87 | * 88 | * Note: Address info may not be available until after the socket has been bind'ed, 89 | * or until after data has been sent. 90 | **/ 91 | - (NSString *)localHost; 92 | - (UInt16)localPort; 93 | 94 | /** 95 | * Returns the remote address info for the socket. 96 | * 97 | * Note: Since UDP is connectionless by design, connected address info 98 | * will not be available unless the socket is explicitly connected to a remote host/port 99 | **/ 100 | - (NSString *)connectedHost; 101 | - (UInt16)connectedPort; 102 | 103 | /** 104 | * Returns whether or not this socket has been connected to a single host. 105 | * By design, UDP is a connectionless protocol, and connecting is not needed. 106 | * If connected, the socket will only be able to send/receive data to/from the connected host. 107 | **/ 108 | - (BOOL)isConnected; 109 | 110 | /** 111 | * Returns whether or not this socket has been closed. 112 | * The only way a socket can be closed is if you explicitly call one of the close methods. 113 | **/ 114 | - (BOOL)isClosed; 115 | 116 | /** 117 | * Returns whether or not this socket supports IPv4. 118 | * By default this will be true, unless the socket is specifically initialized as IPv6 only, 119 | * or is binded or connected to an IPv6 address. 120 | **/ 121 | - (BOOL)isIPv4; 122 | 123 | /** 124 | * Returns whether or not this socket supports IPv6. 125 | * By default this will be true, unless the socket is specifically initialized as IPv4 only, 126 | * or is binded or connected to an IPv4 address. 127 | * 128 | * This method will also return false on platforms that do not support IPv6. 129 | * Note: The iPhone does not currently support IPv6. 130 | **/ 131 | - (BOOL)isIPv6; 132 | 133 | /** 134 | * Returns the mtu of the socket. 135 | * If unknown, returns zero. 136 | * 137 | * Sending data larger than this may result in an error. 138 | * This is an advanced topic, and one should understand the wide range of mtu's on networks and the internet. 139 | * Therefore this method is only for reference and may be of little use in many situations. 140 | **/ 141 | - (unsigned int)maximumTransmissionUnit; 142 | 143 | /** 144 | * Binds the UDP socket to the given port and optional address. 145 | * Binding should be done for server sockets that receive data prior to sending it. 146 | * Client sockets can skip binding, 147 | * as the OS will automatically assign the socket an available port when it starts sending data. 148 | * 149 | * You cannot bind a socket after its been connected. 150 | * You can only bind a socket once. 151 | * You can still connect a socket (if desired) after binding. 152 | * 153 | * On success, returns YES. 154 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. 155 | **/ 156 | - (BOOL)bindToPort:(UInt16)port error:(NSError **)errPtr; 157 | - (BOOL)bindToAddress:(NSString *)localAddr port:(UInt16)port error:(NSError **)errPtr; 158 | 159 | /** 160 | * Connects the UDP socket to the given host and port. 161 | * By design, UDP is a connectionless protocol, and connecting is not needed. 162 | * 163 | * Choosing to connect to a specific host/port has the following effect: 164 | * - You will only be able to send data to the connected host/port. 165 | * - You will only be able to receive data from the connected host/port. 166 | * - You will receive ICMP messages that come from the connected host/port, such as "connection refused". 167 | * 168 | * Connecting a UDP socket does not result in any communication on the socket. 169 | * It simply changes the internal state of the socket. 170 | * 171 | * You cannot bind a socket after its been connected. 172 | * You can only connect a socket once. 173 | * 174 | * On success, returns YES. 175 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. 176 | **/ 177 | - (BOOL)connectToHost:(NSString *)host onPort:(UInt16)port error:(NSError **)errPtr; 178 | - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; 179 | 180 | /** 181 | * Join multicast group 182 | * 183 | * Group should be an IP address (eg @"225.228.0.1") 184 | **/ 185 | - (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr; 186 | - (BOOL)joinMulticastGroup:(NSString *)group withAddress:(NSString *)interface error:(NSError **)errPtr; 187 | 188 | /** 189 | * By default, the underlying socket in the OS will not allow you to send broadcast messages. 190 | * In order to send broadcast messages, you need to enable this functionality in the socket. 191 | * 192 | * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is 193 | * delivered to every host on the network. 194 | * The reason this is generally disabled by default is to prevent 195 | * accidental broadcast messages from flooding the network. 196 | **/ 197 | - (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr; 198 | 199 | /** 200 | * Asynchronously sends the given data, with the given timeout and tag. 201 | * 202 | * This method may only be used with a connected socket. 203 | * 204 | * If data is nil or zero-length, this method does nothing and immediately returns NO. 205 | * If the socket is not connected, this method does nothing and immediately returns NO. 206 | **/ 207 | - (BOOL)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; 208 | 209 | /** 210 | * Asynchronously sends the given data, with the given timeout and tag, to the given host and port. 211 | * 212 | * This method cannot be used with a connected socket. 213 | * 214 | * If data is nil or zero-length, this method does nothing and immediately returns NO. 215 | * If the socket is connected, this method does nothing and immediately returns NO. 216 | * If unable to resolve host to a valid IPv4 or IPv6 address, this method returns NO. 217 | **/ 218 | - (BOOL)sendData:(NSData *)data toHost:(NSString *)host port:(UInt16)port withTimeout:(NSTimeInterval)timeout tag:(long)tag; 219 | 220 | /** 221 | * Asynchronously sends the given data, with the given timeout and tag, to the given address. 222 | * 223 | * This method cannot be used with a connected socket. 224 | * 225 | * If data is nil or zero-length, this method does nothing and immediately returns NO. 226 | * If the socket is connected, this method does nothing and immediately returns NO. 227 | **/ 228 | - (BOOL)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag; 229 | 230 | /** 231 | * Asynchronously receives a single datagram packet. 232 | * 233 | * If the receive succeeds, the onUdpSocket:didReceiveData:fromHost:port:tag delegate method will be called. 234 | * Otherwise, a timeout will occur, and the onUdpSocket:didNotReceiveDataWithTag: delegate method will be called. 235 | **/ 236 | - (void)receiveWithTimeout:(NSTimeInterval)timeout tag:(long)tag; 237 | 238 | /** 239 | * Closes the socket immediately. Any pending send or receive operations are dropped. 240 | **/ 241 | - (void)close; 242 | 243 | /** 244 | * Closes after all pending send operations have completed. 245 | * After calling this, the sendData: and receive: methods will do nothing. 246 | * In other words, you won't be able to add any more send or receive operations to the queue. 247 | * The socket will close even if there are still pending receive operations. 248 | **/ 249 | - (void)closeAfterSending; 250 | 251 | /** 252 | * Closes after all pending receive operations have completed. 253 | * After calling this, the sendData: and receive: methods will do nothing. 254 | * In other words, you won't be able to add any more send or receive operations to the queue. 255 | * The socket will close even if there are still pending send operations. 256 | **/ 257 | - (void)closeAfterReceiving; 258 | 259 | /** 260 | * Closes after all pending send and receive operations have completed. 261 | * After calling this, the sendData: and receive: methods will do nothing. 262 | * In other words, you won't be able to add any more send or receive operations to the queue. 263 | **/ 264 | - (void)closeAfterSendingAndReceiving; 265 | 266 | /** 267 | * Gets/Sets the maximum size of the buffer that will be allocated for receive operations. 268 | * The default size is 9216 bytes. 269 | * 270 | * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. 271 | * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. 272 | * 273 | * In practice, however, the size of UDP packets will be much smaller. 274 | * Indeed most protocols will send and receive packets of only a few bytes, 275 | * or will set a limit on the size of packets to prevent fragmentation in the IP layer. 276 | * 277 | * If you set the buffer size too small, the sockets API in the OS will silently discard 278 | * any extra data, and you will not be notified of the error. 279 | **/ 280 | - (UInt32)maxReceiveBufferSize; 281 | - (void)setMaxReceiveBufferSize:(UInt32)max; 282 | 283 | /** 284 | * When you create an AsyncUdpSocket, it is added to the runloop of the current thread. 285 | * So it is easiest to simply create the socket on the thread you intend to use it. 286 | * 287 | * If, however, you need to move the socket to a separate thread at a later time, this 288 | * method may be used to accomplish the task. 289 | * 290 | * This method must be called from the thread/runloop the socket is currently running on. 291 | * 292 | * Note: After calling this method, all further method calls to this object should be done from the given runloop. 293 | * Also, all delegate calls will be sent on the given runloop. 294 | **/ 295 | - (BOOL)moveToRunLoop:(NSRunLoop *)runLoop; 296 | 297 | /** 298 | * Allows you to configure which run loop modes the socket uses. 299 | * The default set of run loop modes is NSDefaultRunLoopMode. 300 | * 301 | * If you'd like your socket to continue operation during other modes, you may want to add modes such as 302 | * NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes. 303 | * 304 | * Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes. 305 | **/ 306 | - (BOOL)setRunLoopModes:(NSArray *)runLoopModes; 307 | 308 | /** 309 | * Returns the current run loop modes the AsyncSocket instance is operating in. 310 | * The default set of run loop modes is NSDefaultRunLoopMode. 311 | **/ 312 | - (NSArray *)runLoopModes; 313 | 314 | @end 315 | 316 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 317 | #pragma mark - 318 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 319 | 320 | @interface NSObject (AsyncUdpSocketDelegate) 321 | 322 | /** 323 | * Called when the datagram with the given tag has been sent. 324 | **/ 325 | - (void)onUdpSocket:(AsyncUdpSocket *)sock didSendDataWithTag:(long)tag; 326 | 327 | /** 328 | * Called if an error occurs while trying to send a datagram. 329 | * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet. 330 | **/ 331 | - (void)onUdpSocket:(AsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error; 332 | 333 | /** 334 | * Called when the socket has received the requested datagram. 335 | * 336 | * Due to the nature of UDP, you may occasionally receive undesired packets. 337 | * These may be rogue UDP packets from unknown hosts, 338 | * or they may be delayed packets arriving after retransmissions have already occurred. 339 | * It's important these packets are properly ignored, while not interfering with the flow of your implementation. 340 | * As an aid, this delegate method has a boolean return value. 341 | * If you ever need to ignore a received packet, simply return NO, 342 | * and AsyncUdpSocket will continue as if the packet never arrived. 343 | * That is, the original receive request will still be queued, and will still timeout as usual if a timeout was set. 344 | * For example, say you requested to receive data, and you set a timeout of 500 milliseconds, using a tag of 15. 345 | * If rogue data arrives after 250 milliseconds, this delegate method would be invoked, and you could simply return NO. 346 | * If the expected data then arrives within the next 250 milliseconds, 347 | * this delegate method will be invoked, with a tag of 15, just as if the rogue data never appeared. 348 | * 349 | * Under normal circumstances, you simply return YES from this method. 350 | **/ 351 | - (BOOL)onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port; 352 | 353 | /** 354 | * Called if an error occurs while trying to receive a requested datagram. 355 | * This is generally due to a timeout, but could potentially be something else if some kind of OS error occurred. 356 | **/ 357 | - (void)onUdpSocket:(AsyncUdpSocket *)sock didNotReceiveDataWithTag:(long)tag dueToError:(NSError *)error; 358 | 359 | /** 360 | * Called when the socket is closed. 361 | * A socket is only closed if you explicitly call one of the close methods. 362 | **/ 363 | - (void)onUdpSocketDidClose:(AsyncUdpSocket *)sock; 364 | 365 | @end 366 | -------------------------------------------------------------------------------- /AsyncUdpSocket.m: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncUdpSocket.m 3 | // 4 | // This class is in the public domain. 5 | // Originally created by Robbie Hanson on Wed Oct 01 2008. 6 | // Updated and maintained by Deusty Designs and the Mac development community. 7 | // 8 | // http://code.google.com/p/cocoaasyncsocket/ 9 | // 10 | 11 | #import "AsyncUdpSocket.h" 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import 17 | #import 18 | 19 | #if TARGET_OS_IPHONE 20 | // Note: You may need to add the CFNetwork Framework to your project 21 | #import 22 | #endif 23 | 24 | 25 | #define SENDQUEUE_CAPACITY 5 // Initial capacity 26 | #define RECEIVEQUEUE_CAPACITY 5 // Initial capacity 27 | 28 | #define DEFAULT_MAX_RECEIVE_BUFFER_SIZE 9216 29 | 30 | NSString *const AsyncUdpSocketException = @"AsyncUdpSocketException"; 31 | NSString *const AsyncUdpSocketErrorDomain = @"AsyncUdpSocketErrorDomain"; 32 | 33 | #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 34 | // Mutex lock used by all instances of AsyncUdpSocket, to protect getaddrinfo. 35 | // Prior to Mac OS X 10.5 this method was not thread-safe. 36 | static NSString *getaddrinfoLock = @"lock"; 37 | #endif 38 | 39 | enum AsyncUdpSocketFlags 40 | { 41 | kDidBind = 1 << 0, // If set, bind has been called. 42 | kDidConnect = 1 << 1, // If set, connect has been called. 43 | kSock4CanAcceptBytes = 1 << 2, // If set, we know socket4 can accept bytes. If unset, it's unknown. 44 | kSock6CanAcceptBytes = 1 << 3, // If set, we know socket6 can accept bytes. If unset, it's unknown. 45 | kSock4HasBytesAvailable = 1 << 4, // If set, we know socket4 has bytes available. If unset, it's unknown. 46 | kSock6HasBytesAvailable = 1 << 5, // If set, we know socket6 has bytes available. If unset, it's unknown. 47 | kForbidSendReceive = 1 << 6, // If set, no new send or receive operations are allowed to be queued. 48 | kCloseAfterSends = 1 << 7, // If set, close as soon as no more sends are queued. 49 | kCloseAfterReceives = 1 << 8, // If set, close as soon as no more receives are queued. 50 | kDidClose = 1 << 9, // If set, the socket has been closed, and should not be used anymore. 51 | kDequeueSendScheduled = 1 << 10, // If set, a maybeDequeueSend operation is already scheduled. 52 | kDequeueReceiveScheduled = 1 << 11, // If set, a maybeDequeueReceive operation is already scheduled. 53 | kFlipFlop = 1 << 12, // Used to alternate between IPv4 and IPv6 sockets. 54 | }; 55 | 56 | @interface AsyncUdpSocket (Private) 57 | 58 | // Run Loop 59 | - (void)runLoopAddSource:(CFRunLoopSourceRef)source; 60 | - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source; 61 | - (void)runLoopAddTimer:(NSTimer *)timer; 62 | - (void)runLoopRemoveTimer:(NSTimer *)timer; 63 | 64 | // Utilities 65 | - (NSString *)addressHost4:(struct sockaddr_in *)pSockaddr4; 66 | - (NSString *)addressHost6:(struct sockaddr_in6 *)pSockaddr6; 67 | - (NSString *)addressHost:(struct sockaddr *)pSockaddr; 68 | 69 | // Disconnect Implementation 70 | - (void)emptyQueues; 71 | - (void)closeSocket4; 72 | - (void)closeSocket6; 73 | - (void)maybeScheduleClose; 74 | 75 | // Errors 76 | - (NSError *)getErrnoError; 77 | - (NSError *)getSocketError; 78 | - (NSError *)getIPv4UnavailableError; 79 | - (NSError *)getIPv6UnavailableError; 80 | - (NSError *)getSendTimeoutError; 81 | - (NSError *)getReceiveTimeoutError; 82 | 83 | // Diagnostics 84 | - (NSString *)connectedHost:(CFSocketRef)socket; 85 | - (UInt16)connectedPort:(CFSocketRef)socket; 86 | - (NSString *)localHost:(CFSocketRef)socket; 87 | - (UInt16)localPort:(CFSocketRef)socket; 88 | 89 | // Sending 90 | - (BOOL)canAcceptBytes:(CFSocketRef)sockRef; 91 | - (void)scheduleDequeueSend; 92 | - (void)maybeDequeueSend; 93 | - (void)doSend:(CFSocketRef)sockRef; 94 | - (void)completeCurrentSend; 95 | - (void)failCurrentSend:(NSError *)error; 96 | - (void)endCurrentSend; 97 | - (void)doSendTimeout:(NSTimer *)timer; 98 | 99 | // Receiving 100 | - (BOOL)hasBytesAvailable:(CFSocketRef)sockRef; 101 | - (void)scheduleDequeueReceive; 102 | - (void)maybeDequeueReceive; 103 | - (void)doReceive4; 104 | - (void)doReceive6; 105 | - (void)doReceive:(CFSocketRef)sockRef; 106 | - (BOOL)maybeCompleteCurrentReceive; 107 | - (void)failCurrentReceive:(NSError *)error; 108 | - (void)endCurrentReceive; 109 | - (void)doReceiveTimeout:(NSTimer *)timer; 110 | 111 | @end 112 | 113 | static void MyCFSocketCallback(CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *); 114 | 115 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 116 | #pragma mark - 117 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 118 | 119 | /** 120 | * The AsyncSendPacket encompasses the instructions for a single send/write. 121 | **/ 122 | @interface AsyncSendPacket : NSObject 123 | { 124 | @public 125 | NSData *buffer; 126 | NSData *address; 127 | NSTimeInterval timeout; 128 | long tag; 129 | } 130 | - (id)initWithData:(NSData *)d address:(NSData *)a timeout:(NSTimeInterval)t tag:(long)i; 131 | @end 132 | 133 | @implementation AsyncSendPacket 134 | 135 | - (id)initWithData:(NSData *)d address:(NSData *)a timeout:(NSTimeInterval)t tag:(long)i 136 | { 137 | if((self = [super init])) 138 | { 139 | buffer = [d retain]; 140 | address = [a retain]; 141 | timeout = t; 142 | tag = i; 143 | } 144 | return self; 145 | } 146 | 147 | - (void)dealloc 148 | { 149 | [buffer release]; 150 | [address release]; 151 | [super dealloc]; 152 | } 153 | 154 | @end 155 | 156 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 157 | #pragma mark - 158 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 159 | 160 | /** 161 | * The AsyncReceivePacket encompasses the instructions for a single receive/read. 162 | **/ 163 | @interface AsyncReceivePacket : NSObject 164 | { 165 | @public 166 | NSTimeInterval timeout; 167 | long tag; 168 | NSMutableData *buffer; 169 | NSString *host; 170 | UInt16 port; 171 | } 172 | - (id)initWithTimeout:(NSTimeInterval)t tag:(long)i; 173 | @end 174 | 175 | @implementation AsyncReceivePacket 176 | 177 | - (id)initWithTimeout:(NSTimeInterval)t tag:(long)i 178 | { 179 | if((self = [super init])) 180 | { 181 | timeout = t; 182 | tag = i; 183 | 184 | buffer = nil; 185 | host = nil; 186 | port = 0; 187 | } 188 | return self; 189 | } 190 | 191 | - (void)dealloc 192 | { 193 | [buffer release]; 194 | [host release]; 195 | [super dealloc]; 196 | } 197 | 198 | @end 199 | 200 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 201 | #pragma mark - 202 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 203 | 204 | @implementation AsyncUdpSocket 205 | 206 | - (id)initWithDelegate:(id)delegate userData:(long)userData enableIPv4:(BOOL)enableIPv4 enableIPv6:(BOOL)enableIPv6 207 | { 208 | if((self = [super init])) 209 | { 210 | theFlags = 0; 211 | theDelegate = delegate; 212 | theUserData = userData; 213 | maxReceiveBufferSize = DEFAULT_MAX_RECEIVE_BUFFER_SIZE; 214 | 215 | theSendQueue = [[NSMutableArray alloc] initWithCapacity:SENDQUEUE_CAPACITY]; 216 | theCurrentSend = nil; 217 | theSendTimer = nil; 218 | 219 | theReceiveQueue = [[NSMutableArray alloc] initWithCapacity:RECEIVEQUEUE_CAPACITY]; 220 | theCurrentReceive = nil; 221 | theReceiveTimer = nil; 222 | 223 | // Socket context 224 | theContext.version = 0; 225 | theContext.info = self; 226 | theContext.retain = nil; 227 | theContext.release = nil; 228 | theContext.copyDescription = nil; 229 | 230 | // Create the sockets 231 | theSocket4 = NULL; 232 | theSocket6 = NULL; 233 | 234 | if(enableIPv4) 235 | { 236 | theSocket4 = CFSocketCreate(kCFAllocatorDefault, 237 | PF_INET, 238 | SOCK_DGRAM, 239 | IPPROTO_UDP, 240 | kCFSocketReadCallBack | kCFSocketWriteCallBack, 241 | (CFSocketCallBack)&MyCFSocketCallback, 242 | &theContext); 243 | } 244 | if(enableIPv6) 245 | { 246 | theSocket6 = CFSocketCreate(kCFAllocatorDefault, 247 | PF_INET6, 248 | SOCK_DGRAM, 249 | IPPROTO_UDP, 250 | kCFSocketReadCallBack | kCFSocketWriteCallBack, 251 | (CFSocketCallBack)&MyCFSocketCallback, 252 | &theContext); 253 | } 254 | 255 | // Disable continuous callbacks for read and write. 256 | // If we don't do this, the socket(s) will just sit there firing read callbacks 257 | // at us hundreds of times a second if we don't immediately read the available data. 258 | if(theSocket4) 259 | { 260 | CFSocketSetSocketFlags(theSocket4, kCFSocketCloseOnInvalidate); 261 | } 262 | if(theSocket6) 263 | { 264 | CFSocketSetSocketFlags(theSocket6, kCFSocketCloseOnInvalidate); 265 | } 266 | 267 | // Get the CFRunLoop to which the socket should be attached. 268 | theRunLoop = CFRunLoopGetCurrent(); 269 | 270 | // Set default run loop modes 271 | theRunLoopModes = [[NSArray arrayWithObject:NSDefaultRunLoopMode] retain]; 272 | 273 | // Attach the sockets to the run loop 274 | 275 | if(theSocket4) 276 | { 277 | theSource4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, theSocket4, 0); 278 | [self runLoopAddSource:theSource4]; 279 | } 280 | 281 | if(theSocket6) 282 | { 283 | theSource6 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, theSocket6, 0); 284 | [self runLoopAddSource:theSource6]; 285 | } 286 | 287 | cachedLocalPort = 0; 288 | cachedConnectedPort = 0; 289 | } 290 | return self; 291 | } 292 | 293 | - (id)init 294 | { 295 | return [self initWithDelegate:nil userData:0 enableIPv4:YES enableIPv6:YES]; 296 | } 297 | 298 | - (id)initWithDelegate:(id)delegate 299 | { 300 | return [self initWithDelegate:delegate userData:0 enableIPv4:YES enableIPv6:YES]; 301 | } 302 | 303 | - (id)initWithDelegate:(id)delegate userData:(long)userData 304 | { 305 | return [self initWithDelegate:delegate userData:userData enableIPv4:YES enableIPv6:YES]; 306 | } 307 | 308 | - (id)initIPv4 309 | { 310 | return [self initWithDelegate:nil userData:0 enableIPv4:YES enableIPv6:NO]; 311 | } 312 | 313 | - (id)initIPv6 314 | { 315 | return [self initWithDelegate:nil userData:0 enableIPv4:NO enableIPv6:YES]; 316 | } 317 | 318 | - (void) dealloc 319 | { 320 | [self close]; 321 | [theSendQueue release]; 322 | [theReceiveQueue release]; 323 | [theRunLoopModes release]; 324 | [cachedLocalHost release]; 325 | [cachedConnectedHost release]; 326 | [NSObject cancelPreviousPerformRequestsWithTarget:theDelegate selector:@selector(onUdpSocketDidClose:) object:self]; 327 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 328 | [super dealloc]; 329 | } 330 | 331 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 332 | #pragma mark Accessors 333 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 334 | 335 | - (id)delegate 336 | { 337 | return theDelegate; 338 | } 339 | 340 | - (void)setDelegate:(id)delegate 341 | { 342 | theDelegate = delegate; 343 | } 344 | 345 | - (long)userData 346 | { 347 | return theUserData; 348 | } 349 | 350 | - (void)setUserData:(long)userData 351 | { 352 | theUserData = userData; 353 | } 354 | 355 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 356 | #pragma mark Run Loop 357 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 358 | 359 | - (void)runLoopAddSource:(CFRunLoopSourceRef)source 360 | { 361 | unsigned i, count = [theRunLoopModes count]; 362 | for(i = 0; i < count; i++) 363 | { 364 | CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; 365 | CFRunLoopAddSource(theRunLoop, source, runLoopMode); 366 | } 367 | } 368 | 369 | - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source 370 | { 371 | unsigned i, count = [theRunLoopModes count]; 372 | for(i = 0; i < count; i++) 373 | { 374 | CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; 375 | CFRunLoopRemoveSource(theRunLoop, source, runLoopMode); 376 | } 377 | } 378 | 379 | - (void)runLoopAddTimer:(NSTimer *)timer 380 | { 381 | unsigned i, count = [theRunLoopModes count]; 382 | for(i = 0; i < count; i++) 383 | { 384 | CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; 385 | CFRunLoopAddTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode); 386 | } 387 | } 388 | 389 | - (void)runLoopRemoveTimer:(NSTimer *)timer 390 | { 391 | unsigned i, count = [theRunLoopModes count]; 392 | for(i = 0; i < count; i++) 393 | { 394 | CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; 395 | CFRunLoopRemoveTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode); 396 | } 397 | } 398 | 399 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 400 | #pragma mark Configuration 401 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 402 | 403 | - (UInt32)maxReceiveBufferSize 404 | { 405 | return maxReceiveBufferSize; 406 | } 407 | 408 | - (void)setMaxReceiveBufferSize:(UInt32)max 409 | { 410 | maxReceiveBufferSize = max; 411 | } 412 | 413 | /** 414 | * See the header file for a full explanation of this method. 415 | **/ 416 | - (BOOL)moveToRunLoop:(NSRunLoop *)runLoop 417 | { 418 | NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), 419 | @"moveToRunLoop must be called from within the current RunLoop!"); 420 | 421 | if(runLoop == nil) 422 | { 423 | return NO; 424 | } 425 | if(theRunLoop == [runLoop getCFRunLoop]) 426 | { 427 | return YES; 428 | } 429 | 430 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 431 | theFlags &= ~kDequeueSendScheduled; 432 | theFlags &= ~kDequeueReceiveScheduled; 433 | 434 | if(theSource4) [self runLoopRemoveSource:theSource4]; 435 | if(theSource6) [self runLoopRemoveSource:theSource6]; 436 | 437 | // We do not retain the timers - they get retained by the runloop when we add them as a source. 438 | // Since we're about to remove them as a source, we retain now, and release again below. 439 | [theSendTimer retain]; 440 | [theReceiveTimer retain]; 441 | 442 | if(theSendTimer) [self runLoopRemoveTimer:theSendTimer]; 443 | if(theReceiveTimer) [self runLoopRemoveTimer:theReceiveTimer]; 444 | 445 | theRunLoop = [runLoop getCFRunLoop]; 446 | 447 | if(theSendTimer) [self runLoopAddTimer:theSendTimer]; 448 | if(theReceiveTimer) [self runLoopAddTimer:theReceiveTimer]; 449 | 450 | // Release timers since we retained them above 451 | [theSendTimer release]; 452 | [theReceiveTimer release]; 453 | 454 | if(theSource4) [self runLoopAddSource:theSource4]; 455 | if(theSource6) [self runLoopAddSource:theSource6]; 456 | 457 | [runLoop performSelector:@selector(maybeDequeueSend) target:self argument:nil order:0 modes:theRunLoopModes]; 458 | [runLoop performSelector:@selector(maybeDequeueReceive) target:self argument:nil order:0 modes:theRunLoopModes]; 459 | [runLoop performSelector:@selector(maybeScheduleClose) target:self argument:nil order:0 modes:theRunLoopModes]; 460 | 461 | return YES; 462 | } 463 | 464 | /** 465 | * See the header file for a full explanation of this method. 466 | **/ 467 | - (BOOL)setRunLoopModes:(NSArray *)runLoopModes 468 | { 469 | NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), 470 | @"setRunLoopModes must be called from within the current RunLoop!"); 471 | 472 | if([runLoopModes count] == 0) 473 | { 474 | return NO; 475 | } 476 | if([theRunLoopModes isEqualToArray:runLoopModes]) 477 | { 478 | return YES; 479 | } 480 | 481 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 482 | theFlags &= ~kDequeueSendScheduled; 483 | theFlags &= ~kDequeueReceiveScheduled; 484 | 485 | if(theSource4) [self runLoopRemoveSource:theSource4]; 486 | if(theSource6) [self runLoopRemoveSource:theSource6]; 487 | 488 | // We do not retain the timers - they get retained by the runloop when we add them as a source. 489 | // Since we're about to remove them as a source, we retain now, and release again below. 490 | [theSendTimer retain]; 491 | [theReceiveTimer retain]; 492 | 493 | if(theSendTimer) [self runLoopRemoveTimer:theSendTimer]; 494 | if(theReceiveTimer) [self runLoopRemoveTimer:theReceiveTimer]; 495 | 496 | [theRunLoopModes release]; 497 | theRunLoopModes = [runLoopModes copy]; 498 | 499 | if(theSendTimer) [self runLoopAddTimer:theSendTimer]; 500 | if(theReceiveTimer) [self runLoopAddTimer:theReceiveTimer]; 501 | 502 | // Release timers since we retained them above 503 | [theSendTimer release]; 504 | [theReceiveTimer release]; 505 | 506 | if(theSource4) [self runLoopAddSource:theSource4]; 507 | if(theSource6) [self runLoopAddSource:theSource6]; 508 | 509 | [self performSelector:@selector(maybeDequeueSend) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 510 | [self performSelector:@selector(maybeDequeueReceive) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 511 | [self performSelector:@selector(maybeScheduleClose) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 512 | 513 | return YES; 514 | } 515 | 516 | - (NSArray *)runLoopModes 517 | { 518 | return [[theRunLoopModes retain] autorelease]; 519 | } 520 | 521 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 522 | #pragma mark Utilities: 523 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 524 | 525 | /** 526 | * Attempts to convert the given host/port into and IPv4 and/or IPv6 data structure. 527 | * The data structure is of type sockaddr_in for IPv4 and sockaddr_in6 for IPv6. 528 | * 529 | * Returns zero on success, or one of the error codes listed in gai_strerror if an error occurs (as per getaddrinfo). 530 | **/ 531 | - (int)convertForBindHost:(NSString *)host 532 | port:(UInt16)port 533 | intoAddress4:(NSData **)address4 534 | address6:(NSData **)address6 535 | { 536 | if(host == nil || ([host length] == 0)) 537 | { 538 | // Use ANY address 539 | struct sockaddr_in nativeAddr; 540 | nativeAddr.sin_len = sizeof(struct sockaddr_in); 541 | nativeAddr.sin_family = AF_INET; 542 | nativeAddr.sin_port = htons(port); 543 | nativeAddr.sin_addr.s_addr = htonl(INADDR_ANY); 544 | memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero)); 545 | 546 | struct sockaddr_in6 nativeAddr6; 547 | nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); 548 | nativeAddr6.sin6_family = AF_INET6; 549 | nativeAddr6.sin6_port = htons(port); 550 | nativeAddr6.sin6_flowinfo = 0; 551 | nativeAddr6.sin6_addr = in6addr_any; 552 | nativeAddr6.sin6_scope_id = 0; 553 | 554 | // Wrap the native address structures for CFSocketSetAddress. 555 | if(address4) *address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)]; 556 | if(address6) *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; 557 | 558 | return 0; 559 | } 560 | else if([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) 561 | { 562 | // Note: getaddrinfo("localhost",...) fails on 10.5.3 563 | 564 | // Use LOOPBACK address 565 | struct sockaddr_in nativeAddr; 566 | nativeAddr.sin_len = sizeof(struct sockaddr_in); 567 | nativeAddr.sin_family = AF_INET; 568 | nativeAddr.sin_port = htons(port); 569 | nativeAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 570 | memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero)); 571 | 572 | struct sockaddr_in6 nativeAddr6; 573 | nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); 574 | nativeAddr6.sin6_family = AF_INET6; 575 | nativeAddr6.sin6_port = htons(port); 576 | nativeAddr6.sin6_flowinfo = 0; 577 | nativeAddr6.sin6_addr = in6addr_loopback; 578 | nativeAddr6.sin6_scope_id = 0; 579 | 580 | // Wrap the native address structures for CFSocketSetAddress. 581 | if(address4) *address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)]; 582 | if(address6) *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; 583 | 584 | return 0; 585 | } 586 | else 587 | { 588 | NSString *portStr = [NSString stringWithFormat:@"%hu", port]; 589 | 590 | #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 591 | @synchronized (getaddrinfoLock) 592 | #endif 593 | { 594 | struct addrinfo hints, *res, *res0; 595 | 596 | memset(&hints, 0, sizeof(hints)); 597 | hints.ai_family = PF_UNSPEC; 598 | hints.ai_socktype = SOCK_DGRAM; 599 | hints.ai_protocol = IPPROTO_UDP; 600 | hints.ai_flags = AI_PASSIVE; 601 | 602 | int error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); 603 | 604 | if(error) return error; 605 | 606 | for(res = res0; res; res = res->ai_next) 607 | { 608 | if(address4 && !*address4 && (res->ai_family == AF_INET)) 609 | { 610 | // Found IPv4 address 611 | // Wrap the native address structures for CFSocketSetAddress. 612 | if(address4) *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; 613 | } 614 | else if(address6 && !*address6 && (res->ai_family == AF_INET6)) 615 | { 616 | // Found IPv6 address 617 | // Wrap the native address structures for CFSocketSetAddress. 618 | if(address6) *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; 619 | } 620 | } 621 | freeaddrinfo(res0); 622 | } 623 | 624 | return 0; 625 | } 626 | } 627 | 628 | /** 629 | * Attempts to convert the given host/port into and IPv4 and/or IPv6 data structure. 630 | * The data structure is of type sockaddr_in for IPv4 and sockaddr_in6 for IPv6. 631 | * 632 | * Returns zero on success, or one of the error codes listed in gai_strerror if an error occurs (as per getaddrinfo). 633 | **/ 634 | - (int)convertForSendHost:(NSString *)host 635 | port:(UInt16)port 636 | intoAddress4:(NSData **)address4 637 | address6:(NSData **)address6 638 | { 639 | if(host == nil || ([host length] == 0)) 640 | { 641 | // We're not binding, so what are we supposed to do with this? 642 | return EAI_NONAME; 643 | } 644 | else if([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) 645 | { 646 | // Note: getaddrinfo("localhost",...) fails on 10.5.3 647 | 648 | // Use LOOPBACK address 649 | struct sockaddr_in nativeAddr; 650 | nativeAddr.sin_len = sizeof(struct sockaddr_in); 651 | nativeAddr.sin_family = AF_INET; 652 | nativeAddr.sin_port = htons(port); 653 | nativeAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 654 | memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero)); 655 | 656 | struct sockaddr_in6 nativeAddr6; 657 | nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); 658 | nativeAddr6.sin6_family = AF_INET6; 659 | nativeAddr6.sin6_port = htons(port); 660 | nativeAddr6.sin6_flowinfo = 0; 661 | nativeAddr6.sin6_addr = in6addr_loopback; 662 | nativeAddr6.sin6_scope_id = 0; 663 | 664 | // Wrap the native address structures for CFSocketSetAddress. 665 | if(address4) *address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)]; 666 | if(address6) *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; 667 | 668 | return 0; 669 | } 670 | else 671 | { 672 | NSString *portStr = [NSString stringWithFormat:@"%hu", port]; 673 | 674 | #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 675 | @synchronized (getaddrinfoLock) 676 | #endif 677 | { 678 | struct addrinfo hints, *res, *res0; 679 | 680 | memset(&hints, 0, sizeof(hints)); 681 | hints.ai_family = PF_UNSPEC; 682 | hints.ai_socktype = SOCK_DGRAM; 683 | hints.ai_protocol = IPPROTO_UDP; 684 | // No passive flag on a send or connect 685 | 686 | int error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); 687 | 688 | if(error) return error; 689 | 690 | for(res = res0; res; res = res->ai_next) 691 | { 692 | if(address4 && !*address4 && (res->ai_family == AF_INET)) 693 | { 694 | // Found IPv4 address 695 | // Wrap the native address structures for CFSocketSetAddress. 696 | if(address4) *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; 697 | } 698 | else if(address6 && !*address6 && (res->ai_family == AF_INET6)) 699 | { 700 | // Found IPv6 address 701 | // Wrap the native address structures for CFSocketSetAddress. 702 | if(address6) *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; 703 | } 704 | } 705 | freeaddrinfo(res0); 706 | } 707 | 708 | return 0; 709 | } 710 | } 711 | 712 | - (NSString *)addressHost4:(struct sockaddr_in *)pSockaddr4 713 | { 714 | char addrBuf[INET_ADDRSTRLEN]; 715 | 716 | if(inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, sizeof(addrBuf)) == NULL) 717 | { 718 | [NSException raise:NSInternalInconsistencyException format:@"Cannot convert address to string."]; 719 | } 720 | 721 | return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; 722 | } 723 | 724 | - (NSString *)addressHost6:(struct sockaddr_in6 *)pSockaddr6 725 | { 726 | char addrBuf[INET6_ADDRSTRLEN]; 727 | 728 | if(inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, sizeof(addrBuf)) == NULL) 729 | { 730 | [NSException raise:NSInternalInconsistencyException format:@"Cannot convert address to string."]; 731 | } 732 | 733 | return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; 734 | } 735 | 736 | - (NSString *)addressHost:(struct sockaddr *)pSockaddr 737 | { 738 | if(pSockaddr->sa_family == AF_INET) 739 | { 740 | return [self addressHost4:(struct sockaddr_in *)pSockaddr]; 741 | } 742 | else 743 | { 744 | return [self addressHost6:(struct sockaddr_in6 *)pSockaddr]; 745 | } 746 | } 747 | 748 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 749 | #pragma mark Socket Implementation: 750 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 751 | 752 | /** 753 | * Binds the underlying socket(s) to the given port. 754 | * The socket(s) will be able to receive data on any interface. 755 | * 756 | * On success, returns YES. 757 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. 758 | **/ 759 | - (BOOL)bindToPort:(UInt16)port error:(NSError **)errPtr 760 | { 761 | return [self bindToAddress:nil port:port error:errPtr]; 762 | } 763 | 764 | /** 765 | * Binds the underlying socket(s) to the given address and port. 766 | * The sockets(s) will be able to receive data only on the given interface. 767 | * 768 | * To receive data on any interface, pass nil or "". 769 | * To receive data only on the loopback interface, pass "localhost" or "loopback". 770 | * 771 | * On success, returns YES. 772 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. 773 | **/ 774 | - (BOOL)bindToAddress:(NSString *)host port:(UInt16)port error:(NSError **)errPtr 775 | { 776 | if(theFlags & kDidClose) 777 | { 778 | [NSException raise:AsyncUdpSocketException 779 | format:@"The socket is closed."]; 780 | } 781 | if(theFlags & kDidBind) 782 | { 783 | [NSException raise:AsyncUdpSocketException 784 | format:@"Cannot bind a socket more than once."]; 785 | } 786 | if(theFlags & kDidConnect) 787 | { 788 | [NSException raise:AsyncUdpSocketException 789 | format:@"Cannot bind after connecting. If needed, bind first, then connect."]; 790 | } 791 | 792 | // Convert the given host/port into native address structures for CFSocketSetAddress 793 | NSData *address4 = nil, *address6 = nil; 794 | 795 | int gai_error = [self convertForBindHost:host port:port intoAddress4:&address4 address6:&address6]; 796 | if(gai_error) 797 | { 798 | if(errPtr) 799 | { 800 | NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; 801 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 802 | 803 | *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:info]; 804 | } 805 | return NO; 806 | } 807 | 808 | NSAssert((address4 || address6), @"address4 and address6 are nil"); 809 | 810 | // Set the SO_REUSEADDR flags 811 | 812 | int reuseOn = 1; 813 | if (theSocket4) setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); 814 | if (theSocket6) setsockopt(CFSocketGetNative(theSocket6), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); 815 | 816 | // Bind the sockets 817 | 818 | if(address4) 819 | { 820 | if(theSocket4) 821 | { 822 | CFSocketError error = CFSocketSetAddress(theSocket4, (CFDataRef)address4); 823 | if(error != kCFSocketSuccess) 824 | { 825 | if(errPtr) *errPtr = [self getSocketError]; 826 | return NO; 827 | } 828 | 829 | if(!address6) 830 | { 831 | // Using IPv4 only 832 | [self closeSocket6]; 833 | } 834 | } 835 | else if(!address6) 836 | { 837 | if(errPtr) *errPtr = [self getIPv4UnavailableError]; 838 | return NO; 839 | } 840 | } 841 | 842 | if(address6) 843 | { 844 | // Note: The iPhone doesn't currently support IPv6 845 | 846 | if(theSocket6) 847 | { 848 | CFSocketError error = CFSocketSetAddress(theSocket6, (CFDataRef)address6); 849 | if(error != kCFSocketSuccess) 850 | { 851 | if(errPtr) *errPtr = [self getSocketError]; 852 | return NO; 853 | } 854 | 855 | if(!address4) 856 | { 857 | // Using IPv6 only 858 | [self closeSocket4]; 859 | } 860 | } 861 | else if(!address4) 862 | { 863 | if(errPtr) *errPtr = [self getIPv6UnavailableError]; 864 | return NO; 865 | } 866 | } 867 | 868 | theFlags |= kDidBind; 869 | return YES; 870 | } 871 | 872 | /** 873 | * Connects the underlying UDP socket to the given host and port. 874 | * If an IPv4 address is resolved, the IPv4 socket is connected, and the IPv6 socket is invalidated and released. 875 | * If an IPv6 address is resolved, the IPv6 socket is connected, and the IPv4 socket is invalidated and released. 876 | * 877 | * On success, returns YES. 878 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. 879 | **/ 880 | - (BOOL)connectToHost:(NSString *)host onPort:(UInt16)port error:(NSError **)errPtr 881 | { 882 | if(theFlags & kDidClose) 883 | { 884 | [NSException raise:AsyncUdpSocketException 885 | format:@"The socket is closed."]; 886 | } 887 | if(theFlags & kDidConnect) 888 | { 889 | [NSException raise:AsyncUdpSocketException 890 | format:@"Cannot connect a socket more than once."]; 891 | } 892 | 893 | // Convert the given host/port into native address structures for CFSocketSetAddress 894 | NSData *address4 = nil, *address6 = nil; 895 | 896 | int error = [self convertForSendHost:host port:port intoAddress4:&address4 address6:&address6]; 897 | if(error) 898 | { 899 | if(errPtr) 900 | { 901 | NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding]; 902 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 903 | 904 | *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info]; 905 | } 906 | return NO; 907 | } 908 | 909 | NSAssert((address4 || address6), @"address4 and address6 are nil"); 910 | 911 | // We only want to connect via a single interface. 912 | // IPv4 is currently preferred, but this may change in the future. 913 | 914 | if(address4) 915 | { 916 | if(theSocket4) 917 | { 918 | CFSocketError sockErr = CFSocketConnectToAddress(theSocket4, (CFDataRef)address4, (CFTimeInterval)0.0); 919 | if(sockErr != kCFSocketSuccess) 920 | { 921 | if(errPtr) *errPtr = [self getSocketError]; 922 | return NO; 923 | } 924 | theFlags |= kDidConnect; 925 | 926 | // We're connected to an IPv4 address, so no need for the IPv6 socket 927 | [self closeSocket6]; 928 | 929 | return YES; 930 | } 931 | else if(!address6) 932 | { 933 | if(errPtr) *errPtr = [self getIPv4UnavailableError]; 934 | return NO; 935 | } 936 | } 937 | 938 | if(address6) 939 | { 940 | // Note: The iPhone doesn't currently support IPv6 941 | 942 | if(theSocket6) 943 | { 944 | CFSocketError sockErr = CFSocketConnectToAddress(theSocket6, (CFDataRef)address6, (CFTimeInterval)0.0); 945 | if(sockErr != kCFSocketSuccess) 946 | { 947 | if(errPtr) *errPtr = [self getSocketError]; 948 | return NO; 949 | } 950 | theFlags |= kDidConnect; 951 | 952 | // We're connected to an IPv6 address, so no need for the IPv4 socket 953 | [self closeSocket4]; 954 | 955 | return YES; 956 | } 957 | else 958 | { 959 | if(errPtr) *errPtr = [self getIPv6UnavailableError]; 960 | return NO; 961 | } 962 | } 963 | 964 | // It shouldn't be possible to get to this point because either address4 or address6 was non-nil. 965 | if(errPtr) *errPtr = nil; 966 | return NO; 967 | } 968 | 969 | /** 970 | * Connects the underlying UDP socket to the remote address. 971 | * If the address is an IPv4 address, the IPv4 socket is connected, and the IPv6 socket is invalidated and released. 972 | * If the address is an IPv6 address, the IPv6 socket is connected, and the IPv4 socket is invalidated and released. 973 | * 974 | * The address is a native address structure, as may be returned from API's such as Bonjour. 975 | * An address may be created manually by simply wrapping a sockaddr_in or sockaddr_in6 in an NSData object. 976 | * 977 | * On success, returns YES. 978 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. 979 | **/ 980 | - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr 981 | { 982 | if(theFlags & kDidClose) 983 | { 984 | [NSException raise:AsyncUdpSocketException 985 | format:@"The socket is closed."]; 986 | } 987 | if(theFlags & kDidConnect) 988 | { 989 | [NSException raise:AsyncUdpSocketException 990 | format:@"Cannot connect a socket more than once."]; 991 | } 992 | 993 | // Is remoteAddr an IPv4 address? 994 | if([remoteAddr length] == sizeof(struct sockaddr_in)) 995 | { 996 | if(theSocket4) 997 | { 998 | CFSocketError error = CFSocketConnectToAddress(theSocket4, (CFDataRef)remoteAddr, (CFTimeInterval)0.0); 999 | if(error != kCFSocketSuccess) 1000 | { 1001 | if(errPtr) *errPtr = [self getSocketError]; 1002 | return NO; 1003 | } 1004 | theFlags |= kDidConnect; 1005 | 1006 | // We're connected to an IPv4 address, so no need for the IPv6 socket 1007 | [self closeSocket6]; 1008 | 1009 | return YES; 1010 | } 1011 | else 1012 | { 1013 | if(errPtr) *errPtr = [self getIPv4UnavailableError]; 1014 | return NO; 1015 | } 1016 | } 1017 | 1018 | // Is remoteAddr an IPv6 address? 1019 | if([remoteAddr length] == sizeof(struct sockaddr_in6)) 1020 | { 1021 | if(theSocket6) 1022 | { 1023 | CFSocketError error = CFSocketConnectToAddress(theSocket6, (CFDataRef)remoteAddr, (CFTimeInterval)0.0); 1024 | if(error != kCFSocketSuccess) 1025 | { 1026 | if(errPtr) *errPtr = [self getSocketError]; 1027 | return NO; 1028 | } 1029 | theFlags |= kDidConnect; 1030 | 1031 | // We're connected to an IPv6 address, so no need for the IPv4 socket 1032 | [self closeSocket4]; 1033 | 1034 | return YES; 1035 | } 1036 | else 1037 | { 1038 | if(errPtr) *errPtr = [self getIPv6UnavailableError]; 1039 | return NO; 1040 | } 1041 | } 1042 | 1043 | // The remoteAddr was invalid 1044 | if(errPtr) 1045 | { 1046 | NSString *errMsg = @"remoteAddr parameter is not a valid address"; 1047 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1048 | 1049 | *errPtr = [NSError errorWithDomain:AsyncUdpSocketErrorDomain 1050 | code:AsyncUdpSocketBadParameter 1051 | userInfo:info]; 1052 | } 1053 | return NO; 1054 | } 1055 | 1056 | /** 1057 | * Join multicast group 1058 | * 1059 | * Group should be a multicast IP address (eg. @"239.255.250.250" for IPv4). 1060 | * Address is local interface for IPv4, but currently defaults under IPv6. 1061 | **/ 1062 | - (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr 1063 | { 1064 | return [self joinMulticastGroup:group withAddress:nil error:errPtr]; 1065 | } 1066 | 1067 | - (BOOL)joinMulticastGroup:(NSString *)group withAddress:(NSString *)address error:(NSError **)errPtr 1068 | { 1069 | if(theFlags & kDidClose) 1070 | { 1071 | [NSException raise:AsyncUdpSocketException 1072 | format:@"The socket is closed."]; 1073 | } 1074 | if(!(theFlags & kDidBind)) 1075 | { 1076 | [NSException raise:AsyncUdpSocketException 1077 | format:@"Must bind a socket before joining a multicast group."]; 1078 | } 1079 | if(theFlags & kDidConnect) 1080 | { 1081 | [NSException raise:AsyncUdpSocketException 1082 | format:@"Cannot join a multicast group if connected."]; 1083 | } 1084 | 1085 | // Get local interface address 1086 | // Convert the given host/port into native address structures for CFSocketSetAddress 1087 | NSData *address4 = nil, *address6 = nil; 1088 | 1089 | int error = [self convertForBindHost:address port:0 intoAddress4:&address4 address6:&address6]; 1090 | if(error) 1091 | { 1092 | if(errPtr) 1093 | { 1094 | NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding]; 1095 | NSString *errDsc = [NSString stringWithFormat:@"Invalid parameter 'address': %@", errMsg]; 1096 | NSDictionary *info = [NSDictionary dictionaryWithObject:errDsc forKey:NSLocalizedDescriptionKey]; 1097 | 1098 | *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info]; 1099 | } 1100 | return NO; 1101 | } 1102 | 1103 | NSAssert((address4 || address6), @"address4 and address6 are nil"); 1104 | 1105 | // Get multicast address (group) 1106 | NSData *group4 = nil, *group6 = nil; 1107 | 1108 | error = [self convertForBindHost:group port:0 intoAddress4:&group4 address6:&group6]; 1109 | if(error) 1110 | { 1111 | if(errPtr) 1112 | { 1113 | NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding]; 1114 | NSString *errDsc = [NSString stringWithFormat:@"Invalid parameter 'group': %@", errMsg]; 1115 | NSDictionary *info = [NSDictionary dictionaryWithObject:errDsc forKey:NSLocalizedDescriptionKey]; 1116 | 1117 | *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info]; 1118 | } 1119 | return NO; 1120 | } 1121 | 1122 | NSAssert((group4 || group6), @"group4 and group6 are nil"); 1123 | 1124 | if(theSocket4 && group4 && address4) 1125 | { 1126 | const struct sockaddr_in* nativeAddress = [address4 bytes]; 1127 | const struct sockaddr_in* nativeGroup = [group4 bytes]; 1128 | 1129 | struct ip_mreq imreq; 1130 | imreq.imr_multiaddr = nativeGroup->sin_addr; 1131 | imreq.imr_interface = nativeAddress->sin_addr; 1132 | 1133 | // JOIN multicast group on default interface 1134 | error = setsockopt(CFSocketGetNative(theSocket4), IPPROTO_IP, IP_ADD_MEMBERSHIP, 1135 | (const void *)&imreq, sizeof(struct ip_mreq)); 1136 | if(error) 1137 | { 1138 | if(errPtr) 1139 | { 1140 | NSString *errMsg = @"Unable to join IPv4 multicast group"; 1141 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1142 | 1143 | *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainPOSIX" code:error userInfo:info]; 1144 | } 1145 | return NO; 1146 | } 1147 | 1148 | // Using IPv4 only 1149 | [self closeSocket6]; 1150 | 1151 | return YES; 1152 | } 1153 | 1154 | if(theSocket6 && group6 && address6) 1155 | { 1156 | const struct sockaddr_in6* nativeGroup = [group6 bytes]; 1157 | 1158 | struct ipv6_mreq imreq; 1159 | imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr; 1160 | imreq.ipv6mr_interface = 0; 1161 | 1162 | // JOIN multicast group on default interface 1163 | error = setsockopt(CFSocketGetNative(theSocket6), IPPROTO_IP, IPV6_JOIN_GROUP, 1164 | (const void *)&imreq, sizeof(struct ipv6_mreq)); 1165 | if(error) 1166 | { 1167 | if(errPtr) 1168 | { 1169 | NSString *errMsg = @"Unable to join IPv6 multicast group"; 1170 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1171 | 1172 | *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainPOSIX" code:error userInfo:info]; 1173 | } 1174 | return NO; 1175 | } 1176 | 1177 | // Using IPv6 only 1178 | [self closeSocket4]; 1179 | 1180 | return YES; 1181 | } 1182 | 1183 | // The given address and group didn't match the existing socket(s). 1184 | // This means there were no compatible combination of all IPv4 or IPv6 socket, group and address. 1185 | if(errPtr) 1186 | { 1187 | NSString *errMsg = @"Invalid group and/or address, not matching existing socket(s)"; 1188 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1189 | 1190 | *errPtr = [NSError errorWithDomain:AsyncUdpSocketErrorDomain 1191 | code:AsyncUdpSocketBadParameter 1192 | userInfo:info]; 1193 | } 1194 | return NO; 1195 | } 1196 | 1197 | /** 1198 | * By default, the underlying socket in the OS will not allow you to send broadcast messages. 1199 | * In order to send broadcast messages, you need to enable this functionality in the socket. 1200 | * 1201 | * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is 1202 | * delivered to every host on the network. 1203 | * The reason this is generally disabled by default is to prevent 1204 | * accidental broadcast messages from flooding the network. 1205 | **/ 1206 | - (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr 1207 | { 1208 | if (theSocket4) 1209 | { 1210 | int value = flag ? 1 : 0; 1211 | int error = setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_BROADCAST, 1212 | (const void *)&value, sizeof(value)); 1213 | if(error) 1214 | { 1215 | if(errPtr) 1216 | { 1217 | NSString *errMsg = @"Unable to enable broadcast message sending"; 1218 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1219 | 1220 | *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainPOSIX" code:error userInfo:info]; 1221 | } 1222 | return NO; 1223 | } 1224 | } 1225 | 1226 | // IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link. 1227 | // The same effect can be achieved by sending a packet to the link-local all hosts multicast group. 1228 | 1229 | return YES; 1230 | } 1231 | 1232 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1233 | #pragma mark Disconnect Implementation: 1234 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1235 | 1236 | - (void)emptyQueues 1237 | { 1238 | if (theCurrentSend) [self endCurrentSend]; 1239 | if (theCurrentReceive) [self endCurrentReceive]; 1240 | 1241 | [theSendQueue removeAllObjects]; 1242 | [theReceiveQueue removeAllObjects]; 1243 | 1244 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueSend) object:nil]; 1245 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueReceive) object:nil]; 1246 | 1247 | theFlags &= ~kDequeueSendScheduled; 1248 | theFlags &= ~kDequeueReceiveScheduled; 1249 | } 1250 | 1251 | - (void)closeSocket4 1252 | { 1253 | if (theSocket4 != NULL) 1254 | { 1255 | CFSocketInvalidate(theSocket4); 1256 | CFRelease(theSocket4); 1257 | theSocket4 = NULL; 1258 | } 1259 | if (theSource4 != NULL) 1260 | { 1261 | [self runLoopRemoveSource:theSource4]; 1262 | CFRelease(theSource4); 1263 | theSource4 = NULL; 1264 | } 1265 | } 1266 | 1267 | - (void)closeSocket6 1268 | { 1269 | if (theSocket6 != NULL) 1270 | { 1271 | CFSocketInvalidate(theSocket6); 1272 | CFRelease(theSocket6); 1273 | theSocket6 = NULL; 1274 | } 1275 | if (theSource6 != NULL) 1276 | { 1277 | [self runLoopRemoveSource:theSource6]; 1278 | CFRelease(theSource6); 1279 | theSource6 = NULL; 1280 | } 1281 | } 1282 | 1283 | - (void)close 1284 | { 1285 | [self emptyQueues]; 1286 | [self closeSocket4]; 1287 | [self closeSocket6]; 1288 | 1289 | theRunLoop = NULL; 1290 | 1291 | // Delay notification to give user freedom to release without returning here and core-dumping. 1292 | if ([theDelegate respondsToSelector:@selector(onUdpSocketDidClose:)]) 1293 | { 1294 | [theDelegate performSelector:@selector(onUdpSocketDidClose:) 1295 | withObject:self 1296 | afterDelay:0 1297 | inModes:theRunLoopModes]; 1298 | } 1299 | 1300 | theFlags |= kDidClose; 1301 | } 1302 | 1303 | - (void)closeAfterSending 1304 | { 1305 | if(theFlags & kDidClose) return; 1306 | 1307 | theFlags |= (kForbidSendReceive | kCloseAfterSends); 1308 | [self maybeScheduleClose]; 1309 | } 1310 | 1311 | - (void)closeAfterReceiving 1312 | { 1313 | if(theFlags & kDidClose) return; 1314 | 1315 | theFlags |= (kForbidSendReceive | kCloseAfterReceives); 1316 | [self maybeScheduleClose]; 1317 | } 1318 | 1319 | - (void)closeAfterSendingAndReceiving 1320 | { 1321 | if(theFlags & kDidClose) return; 1322 | 1323 | theFlags |= (kForbidSendReceive | kCloseAfterSends | kCloseAfterReceives); 1324 | [self maybeScheduleClose]; 1325 | } 1326 | 1327 | - (void)maybeScheduleClose 1328 | { 1329 | BOOL shouldDisconnect = NO; 1330 | 1331 | if(theFlags & kCloseAfterSends) 1332 | { 1333 | if(([theSendQueue count] == 0) && (theCurrentSend == nil)) 1334 | { 1335 | if(theFlags & kCloseAfterReceives) 1336 | { 1337 | if(([theReceiveQueue count] == 0) && (theCurrentReceive == nil)) 1338 | { 1339 | shouldDisconnect = YES; 1340 | } 1341 | } 1342 | else 1343 | { 1344 | shouldDisconnect = YES; 1345 | } 1346 | } 1347 | } 1348 | else if(theFlags & kCloseAfterReceives) 1349 | { 1350 | if(([theReceiveQueue count] == 0) && (theCurrentReceive == nil)) 1351 | { 1352 | shouldDisconnect = YES; 1353 | } 1354 | } 1355 | 1356 | if(shouldDisconnect) 1357 | { 1358 | [self performSelector:@selector(close) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 1359 | } 1360 | } 1361 | 1362 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1363 | #pragma mark Errors 1364 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1365 | 1366 | /** 1367 | * Returns a standard error object for the current errno value. 1368 | * Errno is used for low-level BSD socket errors. 1369 | **/ 1370 | - (NSError *)getErrnoError 1371 | { 1372 | NSString *errorMsg = [NSString stringWithUTF8String:strerror(errno)]; 1373 | NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMsg forKey:NSLocalizedDescriptionKey]; 1374 | 1375 | return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; 1376 | } 1377 | 1378 | /** 1379 | * Returns a standard error message for a CFSocket error. 1380 | * Unfortunately, CFSocket offers no feedback on its errors. 1381 | **/ 1382 | - (NSError *)getSocketError 1383 | { 1384 | NSString *errMsg = @"General CFSocket error"; 1385 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1386 | 1387 | return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketCFSocketError userInfo:info]; 1388 | } 1389 | 1390 | - (NSError *)getIPv4UnavailableError 1391 | { 1392 | NSString *errMsg = @"IPv4 is unavailable due to binding/connecting using IPv6 only"; 1393 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1394 | 1395 | return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketIPv4Unavailable userInfo:info]; 1396 | } 1397 | 1398 | - (NSError *)getIPv6UnavailableError 1399 | { 1400 | NSString *errMsg = @"IPv6 is unavailable due to binding/connecting using IPv4 only or is not supported on this platform"; 1401 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1402 | 1403 | return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketIPv6Unavailable userInfo:info]; 1404 | } 1405 | 1406 | - (NSError *)getSendTimeoutError 1407 | { 1408 | NSString *errMsg = @"Send operation timed out"; 1409 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1410 | 1411 | return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketSendTimeoutError userInfo:info]; 1412 | } 1413 | - (NSError *)getReceiveTimeoutError 1414 | { 1415 | NSString *errMsg = @"Receive operation timed out"; 1416 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1417 | 1418 | return [NSError errorWithDomain:AsyncUdpSocketErrorDomain code:AsyncUdpSocketReceiveTimeoutError userInfo:info]; 1419 | } 1420 | 1421 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1422 | #pragma mark Diagnostics 1423 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1424 | 1425 | - (NSString *)localHost 1426 | { 1427 | if(cachedLocalHost) return cachedLocalHost; 1428 | 1429 | if(theSocket4) 1430 | return [self localHost:theSocket4]; 1431 | else 1432 | return [self localHost:theSocket6]; 1433 | } 1434 | 1435 | - (UInt16)localPort 1436 | { 1437 | if(cachedLocalPort > 0) return cachedLocalPort; 1438 | 1439 | if(theSocket4) 1440 | return [self localPort:theSocket4]; 1441 | else 1442 | return [self localPort:theSocket6]; 1443 | } 1444 | 1445 | - (NSString *)connectedHost 1446 | { 1447 | if(cachedConnectedHost) return cachedConnectedHost; 1448 | 1449 | if(theSocket4) 1450 | return [self connectedHost:theSocket4]; 1451 | else 1452 | return [self connectedHost:theSocket6]; 1453 | } 1454 | 1455 | - (UInt16)connectedPort 1456 | { 1457 | if(cachedConnectedPort > 0) return cachedConnectedPort; 1458 | 1459 | if(theSocket4) 1460 | return [self connectedPort:theSocket4]; 1461 | else 1462 | return [self connectedPort:theSocket6]; 1463 | } 1464 | 1465 | - (NSString *)localHost:(CFSocketRef)theSocket 1466 | { 1467 | if(theSocket == NULL) return nil; 1468 | 1469 | // Unfortunately we can't use CFSocketCopyAddress. 1470 | // The CFSocket library caches the address the first time you call CFSocketCopyAddress. 1471 | // So if this is called prior to binding/connecting/sending, it won't be updated again when necessary, 1472 | // and will continue to return the old value of the socket address. 1473 | 1474 | NSString *result = nil; 1475 | 1476 | if(theSocket == theSocket4) 1477 | { 1478 | struct sockaddr_in sockaddr4; 1479 | socklen_t sockaddr4len = sizeof(sockaddr4); 1480 | 1481 | if(getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) 1482 | { 1483 | return nil; 1484 | } 1485 | result = [self addressHost4:&sockaddr4]; 1486 | } 1487 | else 1488 | { 1489 | struct sockaddr_in6 sockaddr6; 1490 | socklen_t sockaddr6len = sizeof(sockaddr6); 1491 | 1492 | if(getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) 1493 | { 1494 | return nil; 1495 | } 1496 | result = [self addressHost6:&sockaddr6]; 1497 | } 1498 | 1499 | if(theFlags & kDidBind) 1500 | { 1501 | [cachedLocalHost release]; 1502 | cachedLocalHost = [result copy]; 1503 | } 1504 | 1505 | return result; 1506 | } 1507 | 1508 | - (UInt16)localPort:(CFSocketRef)theSocket 1509 | { 1510 | if(theSocket == NULL) return 0; 1511 | 1512 | // Unfortunately we can't use CFSocketCopyAddress. 1513 | // The CFSocket library caches the address the first time you call CFSocketCopyAddress. 1514 | // So if this is called prior to binding/connecting/sending, it won't be updated again when necessary, 1515 | // and will continue to return the old value of the socket address. 1516 | 1517 | UInt16 result = 0; 1518 | 1519 | if(theSocket == theSocket4) 1520 | { 1521 | struct sockaddr_in sockaddr4; 1522 | socklen_t sockaddr4len = sizeof(sockaddr4); 1523 | 1524 | if(getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) 1525 | { 1526 | return 0; 1527 | } 1528 | result = ntohs(sockaddr4.sin_port); 1529 | } 1530 | else 1531 | { 1532 | struct sockaddr_in6 sockaddr6; 1533 | socklen_t sockaddr6len = sizeof(sockaddr6); 1534 | 1535 | if(getsockname(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) 1536 | { 1537 | return 0; 1538 | } 1539 | result = ntohs(sockaddr6.sin6_port); 1540 | } 1541 | 1542 | if(theFlags & kDidBind) 1543 | { 1544 | cachedLocalPort = result; 1545 | } 1546 | 1547 | return result; 1548 | } 1549 | 1550 | - (NSString *)connectedHost:(CFSocketRef)theSocket 1551 | { 1552 | if(theSocket == NULL) return nil; 1553 | 1554 | // Unfortunately we can't use CFSocketCopyPeerAddress. 1555 | // The CFSocket library caches the address the first time you call CFSocketCopyPeerAddress. 1556 | // So if this is called prior to binding/connecting/sending, it may not be updated again when necessary, 1557 | // and will continue to return the old value of the socket peer address. 1558 | 1559 | NSString *result = nil; 1560 | 1561 | if(theSocket == theSocket4) 1562 | { 1563 | struct sockaddr_in sockaddr4; 1564 | socklen_t sockaddr4len = sizeof(sockaddr4); 1565 | 1566 | if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) 1567 | { 1568 | return nil; 1569 | } 1570 | result = [self addressHost4:&sockaddr4]; 1571 | } 1572 | else 1573 | { 1574 | struct sockaddr_in6 sockaddr6; 1575 | socklen_t sockaddr6len = sizeof(sockaddr6); 1576 | 1577 | if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) 1578 | { 1579 | return nil; 1580 | } 1581 | result = [self addressHost6:&sockaddr6]; 1582 | } 1583 | 1584 | if(theFlags & kDidConnect) 1585 | { 1586 | [cachedConnectedHost release]; 1587 | cachedConnectedHost = [result copy]; 1588 | } 1589 | 1590 | return result; 1591 | } 1592 | 1593 | - (UInt16)connectedPort:(CFSocketRef)theSocket 1594 | { 1595 | if(theSocket == NULL) return 0; 1596 | 1597 | // Unfortunately we can't use CFSocketCopyPeerAddress. 1598 | // The CFSocket library caches the address the first time you call CFSocketCopyPeerAddress. 1599 | // So if this is called prior to binding/connecting/sending, it may not be updated again when necessary, 1600 | // and will continue to return the old value of the socket peer address. 1601 | 1602 | UInt16 result = 0; 1603 | 1604 | if(theSocket == theSocket4) 1605 | { 1606 | struct sockaddr_in sockaddr4; 1607 | socklen_t sockaddr4len = sizeof(sockaddr4); 1608 | 1609 | if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) 1610 | { 1611 | return 0; 1612 | } 1613 | result = ntohs(sockaddr4.sin_port); 1614 | } 1615 | else 1616 | { 1617 | struct sockaddr_in6 sockaddr6; 1618 | socklen_t sockaddr6len = sizeof(sockaddr6); 1619 | 1620 | if(getpeername(CFSocketGetNative(theSocket), (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) 1621 | { 1622 | return 0; 1623 | } 1624 | result = ntohs(sockaddr6.sin6_port); 1625 | } 1626 | 1627 | if(theFlags & kDidConnect) 1628 | { 1629 | cachedConnectedPort = result; 1630 | } 1631 | 1632 | return result; 1633 | } 1634 | 1635 | - (BOOL)isConnected 1636 | { 1637 | return (((theFlags & kDidConnect) != 0) && ((theFlags & kDidClose) == 0)); 1638 | } 1639 | 1640 | - (BOOL)isConnectedToHost:(NSString *)host port:(UInt16)port 1641 | { 1642 | return [[self connectedHost] isEqualToString:host] && ([self connectedPort] == port); 1643 | } 1644 | 1645 | - (BOOL)isClosed 1646 | { 1647 | return (theFlags & kDidClose) ? YES : NO; 1648 | } 1649 | 1650 | - (BOOL)isIPv4 1651 | { 1652 | return (theSocket4 != NULL); 1653 | } 1654 | 1655 | - (BOOL)isIPv6 1656 | { 1657 | return (theSocket6 != NULL); 1658 | } 1659 | 1660 | - (unsigned int)maximumTransmissionUnit 1661 | { 1662 | CFSocketNativeHandle theNativeSocket; 1663 | if(theSocket4) 1664 | theNativeSocket = CFSocketGetNative(theSocket4); 1665 | else if(theSocket6) 1666 | theNativeSocket = CFSocketGetNative(theSocket6); 1667 | else 1668 | return 0; 1669 | 1670 | if(theNativeSocket == 0) 1671 | { 1672 | return 0; 1673 | } 1674 | 1675 | struct ifreq ifr; 1676 | bzero(&ifr, sizeof(ifr)); 1677 | 1678 | if(if_indextoname(theNativeSocket, ifr.ifr_name) == NULL) 1679 | { 1680 | return 0; 1681 | } 1682 | 1683 | if(ioctl(theNativeSocket, SIOCGIFMTU, &ifr) >= 0) 1684 | { 1685 | return ifr.ifr_mtu; 1686 | } 1687 | 1688 | return 0; 1689 | } 1690 | 1691 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1692 | #pragma mark Sending 1693 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1694 | 1695 | - (BOOL)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag 1696 | { 1697 | if((data == nil) || ([data length] == 0)) return NO; 1698 | if(theFlags & kForbidSendReceive) return NO; 1699 | if(theFlags & kDidClose) return NO; 1700 | 1701 | // This method is only for connected sockets 1702 | if(![self isConnected]) return NO; 1703 | 1704 | AsyncSendPacket *packet = [[AsyncSendPacket alloc] initWithData:data address:nil timeout:timeout tag:tag]; 1705 | 1706 | [theSendQueue addObject:packet]; 1707 | [self scheduleDequeueSend]; 1708 | 1709 | [packet release]; 1710 | return YES; 1711 | } 1712 | 1713 | - (BOOL)sendData:(NSData *)data toHost:(NSString *)host port:(UInt16)port withTimeout:(NSTimeInterval)timeout tag:(long)tag 1714 | { 1715 | if((data == nil) || ([data length] == 0)) return NO; 1716 | if(theFlags & kForbidSendReceive) return NO; 1717 | if(theFlags & kDidClose) return NO; 1718 | 1719 | // This method is only for non-connected sockets 1720 | if([self isConnected]) return NO; 1721 | 1722 | NSData *address4 = nil, *address6 = nil; 1723 | [self convertForSendHost:host port:port intoAddress4:&address4 address6:&address6]; 1724 | 1725 | AsyncSendPacket *packet = nil; 1726 | 1727 | if(address4 && theSocket4) 1728 | packet = [[AsyncSendPacket alloc] initWithData:data address:address4 timeout:timeout tag:tag]; 1729 | else if(address6 && theSocket6) 1730 | packet = [[AsyncSendPacket alloc] initWithData:data address:address6 timeout:timeout tag:tag]; 1731 | else 1732 | return NO; 1733 | 1734 | [theSendQueue addObject:packet]; 1735 | [self scheduleDequeueSend]; 1736 | 1737 | [packet release]; 1738 | return YES; 1739 | } 1740 | 1741 | - (BOOL)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag 1742 | { 1743 | if((data == nil) || ([data length] == 0)) return NO; 1744 | if(theFlags & kForbidSendReceive) return NO; 1745 | if(theFlags & kDidClose) return NO; 1746 | 1747 | // This method is only for non-connected sockets 1748 | if([self isConnected]) return NO; 1749 | 1750 | if([remoteAddr length] == sizeof(struct sockaddr_in) && !theSocket4) 1751 | return NO; 1752 | 1753 | if([remoteAddr length] == sizeof(struct sockaddr_in6) && !theSocket6) 1754 | return NO; 1755 | 1756 | AsyncSendPacket *packet = [[AsyncSendPacket alloc] initWithData:data address:remoteAddr timeout:timeout tag:tag]; 1757 | 1758 | [theSendQueue addObject:packet]; 1759 | [self scheduleDequeueSend]; 1760 | 1761 | [packet release]; 1762 | return YES; 1763 | } 1764 | 1765 | - (BOOL)canAcceptBytes:(CFSocketRef)sockRef 1766 | { 1767 | if(sockRef == theSocket4) 1768 | { 1769 | if(theFlags & kSock4CanAcceptBytes) return YES; 1770 | } 1771 | else 1772 | { 1773 | if(theFlags & kSock6CanAcceptBytes) return YES; 1774 | } 1775 | 1776 | CFSocketNativeHandle theNativeSocket = CFSocketGetNative(sockRef); 1777 | 1778 | if(theNativeSocket == 0) 1779 | { 1780 | NSLog(@"Error - Could not get CFSocketNativeHandle from CFSocketRef"); 1781 | return NO; 1782 | } 1783 | 1784 | fd_set fds; 1785 | FD_ZERO(&fds); 1786 | FD_SET(theNativeSocket, &fds); 1787 | 1788 | struct timeval timeout; 1789 | timeout.tv_sec = 0; 1790 | timeout.tv_usec = 0; 1791 | 1792 | return select(FD_SETSIZE, NULL, &fds, NULL, &timeout) > 0; 1793 | } 1794 | 1795 | - (CFSocketRef)socketForPacket:(AsyncSendPacket *)packet 1796 | { 1797 | if(!theSocket4) 1798 | return theSocket6; 1799 | if(!theSocket6) 1800 | return theSocket4; 1801 | 1802 | return ([packet->address length] == sizeof(struct sockaddr_in)) ? theSocket4 : theSocket6; 1803 | } 1804 | 1805 | /** 1806 | * Puts a maybeDequeueSend on the run loop. 1807 | **/ 1808 | - (void)scheduleDequeueSend 1809 | { 1810 | if((theFlags & kDequeueSendScheduled) == 0) 1811 | { 1812 | theFlags |= kDequeueSendScheduled; 1813 | [self performSelector:@selector(maybeDequeueSend) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 1814 | } 1815 | } 1816 | 1817 | /** 1818 | * This method starts a new send, if needed. 1819 | * It is called when a user requests a send. 1820 | **/ 1821 | - (void)maybeDequeueSend 1822 | { 1823 | // Unset the flag indicating a call to this method is scheduled 1824 | theFlags &= ~kDequeueSendScheduled; 1825 | 1826 | if(theCurrentSend == nil) 1827 | { 1828 | if([theSendQueue count] > 0) 1829 | { 1830 | // Dequeue next send packet 1831 | theCurrentSend = [[theSendQueue objectAtIndex:0] retain]; 1832 | [theSendQueue removeObjectAtIndex:0]; 1833 | 1834 | // Start time-out timer. 1835 | if(theCurrentSend->timeout >= 0.0) 1836 | { 1837 | theSendTimer = [NSTimer timerWithTimeInterval:theCurrentSend->timeout 1838 | target:self 1839 | selector:@selector(doSendTimeout:) 1840 | userInfo:nil 1841 | repeats:NO]; 1842 | 1843 | [self runLoopAddTimer:theSendTimer]; 1844 | } 1845 | 1846 | // Immediately send, if possible. 1847 | [self doSend:[self socketForPacket:theCurrentSend]]; 1848 | } 1849 | else if(theFlags & kCloseAfterSends) 1850 | { 1851 | if(theFlags & kCloseAfterReceives) 1852 | { 1853 | if(([theReceiveQueue count] == 0) && (theCurrentReceive == nil)) 1854 | { 1855 | [self close]; 1856 | } 1857 | } 1858 | else 1859 | { 1860 | [self close]; 1861 | } 1862 | } 1863 | } 1864 | } 1865 | 1866 | /** 1867 | * This method is called when a new read is taken from the read queue or when new data becomes available on the stream. 1868 | **/ 1869 | - (void)doSend:(CFSocketRef)theSocket 1870 | { 1871 | if(theCurrentSend != nil) 1872 | { 1873 | if(theSocket != [self socketForPacket:theCurrentSend]) 1874 | { 1875 | // Current send is for the other socket 1876 | return; 1877 | } 1878 | 1879 | if([self canAcceptBytes:theSocket]) 1880 | { 1881 | int result; 1882 | CFSocketNativeHandle theNativeSocket = CFSocketGetNative(theSocket); 1883 | 1884 | const void *buf = [theCurrentSend->buffer bytes]; 1885 | unsigned bufSize = [theCurrentSend->buffer length]; 1886 | 1887 | if([self isConnected]) 1888 | { 1889 | result = send(theNativeSocket, buf, bufSize, 0); 1890 | } 1891 | else 1892 | { 1893 | const void *dst = [theCurrentSend->address bytes]; 1894 | unsigned dstSize = [theCurrentSend->address length]; 1895 | 1896 | result = sendto(theNativeSocket, buf, bufSize, 0, dst, dstSize); 1897 | } 1898 | 1899 | if(theSocket == theSocket4) 1900 | theFlags &= ~kSock4CanAcceptBytes; 1901 | else 1902 | theFlags &= ~kSock6CanAcceptBytes; 1903 | 1904 | if(result < 0) 1905 | { 1906 | [self failCurrentSend:[self getErrnoError]]; 1907 | } 1908 | else 1909 | { 1910 | // If it wasn't bound before, it's bound now 1911 | theFlags |= kDidBind; 1912 | 1913 | [self completeCurrentSend]; 1914 | } 1915 | 1916 | [self scheduleDequeueSend]; 1917 | } 1918 | else 1919 | { 1920 | // Request notification when the socket is ready to send more data 1921 | CFSocketEnableCallBacks(theSocket, kCFSocketReadCallBack | kCFSocketWriteCallBack); 1922 | } 1923 | } 1924 | } 1925 | 1926 | - (void)completeCurrentSend 1927 | { 1928 | NSAssert (theCurrentSend, @"Trying to complete current send when there is no current send."); 1929 | 1930 | if ([theDelegate respondsToSelector:@selector(onUdpSocket:didSendDataWithTag:)]) 1931 | { 1932 | [theDelegate onUdpSocket:self didSendDataWithTag:theCurrentSend->tag]; 1933 | } 1934 | 1935 | if (theCurrentSend != nil) [self endCurrentSend]; // Caller may have disconnected. 1936 | } 1937 | 1938 | - (void)failCurrentSend:(NSError *)error 1939 | { 1940 | NSAssert (theCurrentSend, @"Trying to fail current send when there is no current send."); 1941 | 1942 | if ([theDelegate respondsToSelector:@selector(onUdpSocket:didNotSendDataWithTag:dueToError:)]) 1943 | { 1944 | [theDelegate onUdpSocket:self didNotSendDataWithTag:theCurrentSend->tag dueToError:error]; 1945 | } 1946 | 1947 | if (theCurrentSend != nil) [self endCurrentSend]; // Caller may have disconnected. 1948 | } 1949 | 1950 | /** 1951 | * Ends the current send, and all associated variables such as the send timer. 1952 | **/ 1953 | - (void)endCurrentSend 1954 | { 1955 | NSAssert (theCurrentSend, @"Trying to end current send when there is no current send."); 1956 | 1957 | [theSendTimer invalidate]; 1958 | theSendTimer = nil; 1959 | 1960 | [theCurrentSend release]; 1961 | theCurrentSend = nil; 1962 | } 1963 | 1964 | - (void)doSendTimeout:(NSTimer *)timer 1965 | { 1966 | if (timer != theSendTimer) return; // Old timer. Ignore it. 1967 | if (theCurrentSend != nil) 1968 | { 1969 | [self failCurrentSend:[self getSendTimeoutError]]; 1970 | [self scheduleDequeueSend]; 1971 | } 1972 | } 1973 | 1974 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1975 | #pragma mark Receiving 1976 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1977 | 1978 | - (void)receiveWithTimeout:(NSTimeInterval)timeout tag:(long)tag 1979 | { 1980 | if(theFlags & kForbidSendReceive) return; 1981 | if(theFlags & kDidClose) return; 1982 | 1983 | AsyncReceivePacket *packet = [[AsyncReceivePacket alloc] initWithTimeout:timeout tag:tag]; 1984 | 1985 | [theReceiveQueue addObject:packet]; 1986 | [self scheduleDequeueReceive]; 1987 | 1988 | [packet release]; 1989 | } 1990 | 1991 | - (BOOL)hasBytesAvailable:(CFSocketRef)sockRef 1992 | { 1993 | if(sockRef == theSocket4) 1994 | { 1995 | if(theFlags & kSock4HasBytesAvailable) return YES; 1996 | } 1997 | else 1998 | { 1999 | if(theFlags & kSock6HasBytesAvailable) return YES; 2000 | } 2001 | 2002 | CFSocketNativeHandle theNativeSocket = CFSocketGetNative(sockRef); 2003 | 2004 | if(theNativeSocket == 0) 2005 | { 2006 | NSLog(@"Error - Could not get CFSocketNativeHandle from CFSocketRef"); 2007 | return NO; 2008 | } 2009 | 2010 | fd_set fds; 2011 | FD_ZERO(&fds); 2012 | FD_SET(theNativeSocket, &fds); 2013 | 2014 | struct timeval timeout; 2015 | timeout.tv_sec = 0; 2016 | timeout.tv_usec = 0; 2017 | 2018 | return select(FD_SETSIZE, &fds, NULL, NULL, &timeout) > 0; 2019 | } 2020 | 2021 | /** 2022 | * Puts a maybeDequeueReceive on the run loop. 2023 | **/ 2024 | - (void)scheduleDequeueReceive 2025 | { 2026 | if((theFlags & kDequeueReceiveScheduled) == 0) 2027 | { 2028 | theFlags |= kDequeueReceiveScheduled; 2029 | [self performSelector:@selector(maybeDequeueReceive) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 2030 | } 2031 | } 2032 | 2033 | /** 2034 | * Starts a new receive operation if needed 2035 | **/ 2036 | - (void)maybeDequeueReceive 2037 | { 2038 | // Unset the flag indicating a call to this method is scheduled 2039 | theFlags &= ~kDequeueReceiveScheduled; 2040 | 2041 | if (theCurrentReceive == nil) 2042 | { 2043 | if([theReceiveQueue count] > 0) 2044 | { 2045 | // Dequeue next receive packet 2046 | theCurrentReceive = [[theReceiveQueue objectAtIndex:0] retain]; 2047 | [theReceiveQueue removeObjectAtIndex:0]; 2048 | 2049 | // Start time-out timer. 2050 | if (theCurrentReceive->timeout >= 0.0) 2051 | { 2052 | theReceiveTimer = [NSTimer timerWithTimeInterval:theCurrentReceive->timeout 2053 | target:self 2054 | selector:@selector(doReceiveTimeout:) 2055 | userInfo:nil 2056 | repeats:NO]; 2057 | 2058 | [self runLoopAddTimer:theReceiveTimer]; 2059 | } 2060 | 2061 | // Immediately receive, if possible 2062 | // We always check both sockets so we don't ever starve one of them. 2063 | // We also check them in alternating orders to prevent starvation if both of them 2064 | // have a continuous flow of incoming data. 2065 | if(theFlags & kFlipFlop) 2066 | { 2067 | [self doReceive4]; 2068 | [self doReceive6]; 2069 | } 2070 | else 2071 | { 2072 | [self doReceive6]; 2073 | [self doReceive4]; 2074 | } 2075 | 2076 | theFlags ^= kFlipFlop; 2077 | } 2078 | else if(theFlags & kCloseAfterReceives) 2079 | { 2080 | if(theFlags & kCloseAfterSends) 2081 | { 2082 | if(([theSendQueue count] == 0) && (theCurrentSend == nil)) 2083 | { 2084 | [self close]; 2085 | } 2086 | } 2087 | else 2088 | { 2089 | [self close]; 2090 | } 2091 | } 2092 | } 2093 | } 2094 | 2095 | - (void)doReceive4 2096 | { 2097 | if(theSocket4) [self doReceive:theSocket4]; 2098 | } 2099 | 2100 | - (void)doReceive6 2101 | { 2102 | if(theSocket6) [self doReceive:theSocket6]; 2103 | } 2104 | 2105 | - (void)doReceive:(CFSocketRef)theSocket 2106 | { 2107 | if (theCurrentReceive != nil) 2108 | { 2109 | BOOL appIgnoredReceivedData; 2110 | BOOL userIgnoredReceivedData; 2111 | 2112 | do 2113 | { 2114 | // Set or reset ignored variables. 2115 | // If the app or user ignores the received data, we'll continue this do-while loop. 2116 | appIgnoredReceivedData = NO; 2117 | userIgnoredReceivedData = NO; 2118 | 2119 | if([self hasBytesAvailable:theSocket]) 2120 | { 2121 | int result; 2122 | CFSocketNativeHandle theNativeSocket = CFSocketGetNative(theSocket); 2123 | 2124 | // Allocate buffer for recvfrom operation. 2125 | // If the operation is successful, we'll realloc the buffer to the appropriate size, 2126 | // and create an NSData wrapper around it without needing to copy any bytes around. 2127 | void *buf = malloc(maxReceiveBufferSize); 2128 | size_t bufSize = maxReceiveBufferSize; 2129 | 2130 | if(theSocket == theSocket4) 2131 | { 2132 | struct sockaddr_in sockaddr4; 2133 | socklen_t sockaddr4len = sizeof(sockaddr4); 2134 | 2135 | result = recvfrom(theNativeSocket, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len); 2136 | 2137 | if(result >= 0) 2138 | { 2139 | NSString *host = [self addressHost4:&sockaddr4]; 2140 | UInt16 port = ntohs(sockaddr4.sin_port); 2141 | 2142 | if([self isConnected] && ![self isConnectedToHost:host port:port]) 2143 | { 2144 | // The user connected to an address, and the received data doesn't match the address. 2145 | // This may happen if the data is received by the kernel prior to the connect call. 2146 | appIgnoredReceivedData = YES; 2147 | } 2148 | else 2149 | { 2150 | if(result != bufSize) 2151 | { 2152 | buf = realloc(buf, result); 2153 | } 2154 | theCurrentReceive->buffer = [[NSData alloc] initWithBytesNoCopy:buf 2155 | length:result 2156 | freeWhenDone:YES]; 2157 | theCurrentReceive->host = [host retain]; 2158 | theCurrentReceive->port = port; 2159 | } 2160 | } 2161 | 2162 | theFlags &= ~kSock4HasBytesAvailable; 2163 | } 2164 | else 2165 | { 2166 | struct sockaddr_in6 sockaddr6; 2167 | socklen_t sockaddr6len = sizeof(sockaddr6); 2168 | 2169 | result = recvfrom(theNativeSocket, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len); 2170 | 2171 | if(result >= 0) 2172 | { 2173 | NSString *host = [self addressHost6:&sockaddr6]; 2174 | UInt16 port = ntohs(sockaddr6.sin6_port); 2175 | 2176 | if([self isConnected] && ![self isConnectedToHost:host port:port]) 2177 | { 2178 | // The user connected to an address, and the received data doesn't match the address. 2179 | // This may happen if the data is received by the kernel prior to the connect call. 2180 | appIgnoredReceivedData = YES; 2181 | } 2182 | else 2183 | { 2184 | if(result != bufSize) 2185 | { 2186 | buf = realloc(buf, result); 2187 | } 2188 | theCurrentReceive->buffer = [[NSData alloc] initWithBytesNoCopy:buf 2189 | length:result 2190 | freeWhenDone:YES]; 2191 | theCurrentReceive->host = [host retain]; 2192 | theCurrentReceive->port = port; 2193 | } 2194 | } 2195 | 2196 | theFlags &= ~kSock6HasBytesAvailable; 2197 | } 2198 | 2199 | // Check to see if we need to free our alloc'd buffer 2200 | // If the buffer is non-nil, this means it has taken ownership of the buffer 2201 | if(theCurrentReceive->buffer == nil) 2202 | { 2203 | free(buf); 2204 | } 2205 | 2206 | if(result < 0) 2207 | { 2208 | [self failCurrentReceive:[self getErrnoError]]; 2209 | [self scheduleDequeueReceive]; 2210 | } 2211 | else if(!appIgnoredReceivedData) 2212 | { 2213 | BOOL finished = [self maybeCompleteCurrentReceive]; 2214 | 2215 | if(finished) 2216 | { 2217 | [self scheduleDequeueReceive]; 2218 | } 2219 | else 2220 | { 2221 | [theCurrentReceive->buffer release]; 2222 | [theCurrentReceive->host release]; 2223 | 2224 | theCurrentReceive->buffer = nil; 2225 | theCurrentReceive->host = nil; 2226 | 2227 | userIgnoredReceivedData = YES; 2228 | } 2229 | } 2230 | } 2231 | else 2232 | { 2233 | // Request notification when the socket is ready to receive more data 2234 | CFSocketEnableCallBacks(theSocket, kCFSocketReadCallBack | kCFSocketWriteCallBack); 2235 | } 2236 | 2237 | } while(appIgnoredReceivedData || userIgnoredReceivedData); 2238 | } 2239 | } 2240 | 2241 | - (BOOL)maybeCompleteCurrentReceive 2242 | { 2243 | NSAssert (theCurrentReceive, @"Trying to complete current receive when there is no current receive."); 2244 | 2245 | BOOL finished = YES; 2246 | 2247 | if ([theDelegate respondsToSelector:@selector(onUdpSocket:didReceiveData:withTag:fromHost:port:)]) 2248 | { 2249 | finished = [theDelegate onUdpSocket:self 2250 | didReceiveData:theCurrentReceive->buffer 2251 | withTag:theCurrentReceive->tag 2252 | fromHost:theCurrentReceive->host 2253 | port:theCurrentReceive->port]; 2254 | } 2255 | 2256 | if (finished) 2257 | { 2258 | if (theCurrentReceive != nil) [self endCurrentReceive]; // Caller may have disconnected. 2259 | } 2260 | return finished; 2261 | } 2262 | 2263 | - (void)failCurrentReceive:(NSError *)error 2264 | { 2265 | NSAssert (theCurrentReceive, @"Trying to fail current receive when there is no current receive."); 2266 | 2267 | if ([theDelegate respondsToSelector:@selector(onUdpSocket:didNotReceiveDataWithTag:dueToError:)]) 2268 | { 2269 | [theDelegate onUdpSocket:self didNotReceiveDataWithTag:theCurrentReceive->tag dueToError:error]; 2270 | } 2271 | 2272 | if (theCurrentReceive != nil) [self endCurrentReceive]; // Caller may have disconnected. 2273 | } 2274 | 2275 | - (void)endCurrentReceive 2276 | { 2277 | NSAssert (theCurrentReceive, @"Trying to end current receive when there is no current receive."); 2278 | 2279 | [theReceiveTimer invalidate]; 2280 | theReceiveTimer = nil; 2281 | 2282 | [theCurrentReceive release]; 2283 | theCurrentReceive = nil; 2284 | } 2285 | 2286 | - (void)doReceiveTimeout:(NSTimer *)timer 2287 | { 2288 | if (timer != theReceiveTimer) return; // Old timer. Ignore it. 2289 | if (theCurrentReceive != nil) 2290 | { 2291 | [self failCurrentReceive:[self getReceiveTimeoutError]]; 2292 | [self scheduleDequeueReceive]; 2293 | } 2294 | } 2295 | 2296 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2297 | #pragma mark CF Callbacks 2298 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2299 | 2300 | - (void)doCFSocketCallback:(CFSocketCallBackType)type 2301 | forSocket:(CFSocketRef)sock 2302 | withAddress:(NSData *)address 2303 | withData:(const void *)pData 2304 | { 2305 | NSParameterAssert((sock == theSocket4) || (sock == theSocket6)); 2306 | 2307 | switch (type) 2308 | { 2309 | case kCFSocketReadCallBack: 2310 | if(sock == theSocket4) 2311 | theFlags |= kSock4HasBytesAvailable; 2312 | else 2313 | theFlags |= kSock6HasBytesAvailable; 2314 | [self doReceive:sock]; 2315 | break; 2316 | case kCFSocketWriteCallBack: 2317 | if(sock == theSocket4) 2318 | theFlags |= kSock4CanAcceptBytes; 2319 | else 2320 | theFlags |= kSock6CanAcceptBytes; 2321 | [self doSend:sock]; 2322 | break; 2323 | default: 2324 | NSLog (@"AsyncUdpSocket %p received unexpected CFSocketCallBackType %d.", self, type); 2325 | break; 2326 | } 2327 | } 2328 | 2329 | /** 2330 | * This is the callback we setup for CFSocket. 2331 | * This method does nothing but forward the call to it's Objective-C counterpart 2332 | **/ 2333 | static void MyCFSocketCallback(CFSocketRef sref, CFSocketCallBackType type, CFDataRef address, const void *pData, void *pInfo) 2334 | { 2335 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 2336 | 2337 | AsyncUdpSocket *theSocket = [[(AsyncUdpSocket *)pInfo retain] autorelease]; 2338 | [theSocket doCFSocketCallback:type forSocket:sref withAddress:(NSData *)address withData:pData]; 2339 | 2340 | [pool release]; 2341 | } 2342 | 2343 | @end 2344 | -------------------------------------------------------------------------------- /CertTest/AppController.h: -------------------------------------------------------------------------------- 1 | #import 2 | @class AsyncSocket; 3 | 4 | @interface AppController : NSObject 5 | { 6 | AsyncSocket *asyncSocket; 7 | } 8 | 9 | - (IBAction)printCert:(id)sender; 10 | @end 11 | -------------------------------------------------------------------------------- /CertTest/AppController.m: -------------------------------------------------------------------------------- 1 | #import "AppController.h" 2 | #import "AsyncSocket.h" 3 | #import "X509Certificate.h" 4 | 5 | 6 | @implementation AppController 7 | 8 | - (id)init 9 | { 10 | if(self = [super init]) 11 | { 12 | asyncSocket = [[AsyncSocket alloc] initWithDelegate:self]; 13 | } 14 | return self; 15 | } 16 | 17 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 18 | { 19 | NSLog(@"Ready"); 20 | 21 | NSError *err = nil; 22 | if(![asyncSocket connectToHost:@"www.paypal.com" onPort:443 error:&err]) 23 | { 24 | NSLog(@"Error: %@", err); 25 | } 26 | } 27 | 28 | - (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port 29 | { 30 | NSLog(@"onSocket:%p didConnectToHost:%@ port:%hu", sock, host, port); 31 | 32 | // Configure SSL/TLS settings 33 | NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:3]; 34 | 35 | // If you simply want to ensure that the remote host's certificate is valid, 36 | // then you can use an empty dictionary. 37 | 38 | // If you know the name of the remote host, then you should specify the name here. 39 | // 40 | // NOTE: 41 | // You should understand the security implications if you do not specify the peer name. 42 | // Please see the documentation for the startTLS method in AsyncSocket.h for a full discussion. 43 | 44 | [settings setObject:@"www.paypal.com" 45 | forKey:(NSString *)kCFStreamSSLPeerName]; 46 | 47 | // To connect to a test server, with a self-signed certificate, use settings similar to this: 48 | 49 | // // Allow expired certificates 50 | // [settings setObject:[NSNumber numberWithBool:YES] 51 | // forKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates]; 52 | // 53 | // // Allow self-signed certificates 54 | // [settings setObject:[NSNumber numberWithBool:YES] 55 | // forKey:(NSString *)kCFStreamSSLAllowsAnyRoot]; 56 | // 57 | // // In fact, don't even validate the certificate chain 58 | // [settings setObject:[NSNumber numberWithBool:NO] 59 | // forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]; 60 | 61 | [sock startTLS:settings]; 62 | 63 | // You can also pass nil to the startTLS method, which is the same as passing an empty dictionary. 64 | // Again, you should understand the security implications of doing so. 65 | // Please see the documentation for the startTLS method in AsyncSocket.h for a full discussion. 66 | } 67 | 68 | - (void)onSocketDidSecure:(AsyncSocket *)sock 69 | { 70 | NSLog(@"onSocketDidSecure:%p", sock); 71 | } 72 | 73 | - (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err 74 | { 75 | NSLog(@"onSocket:%p willDisconnectWithError:%@", sock, err); 76 | } 77 | 78 | - (void)onSocketDidDisconnect:(AsyncSocket *)sock 79 | { 80 | NSLog(@"onSocketDidDisconnect:%p", sock); 81 | } 82 | 83 | - (IBAction)printCert:(id)sender 84 | { 85 | NSDictionary *cert = [X509Certificate extractCertDictFromAsyncSocket:asyncSocket]; 86 | NSLog(@"X509 Certificate: \n%@", cert); 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /CertTest/CertTest.xcodeproj/TemplateIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roustem/AsyncSocket/bd5a86cd0ad6bb1ddef1f338b6f4630579531f63/CertTest/CertTest.xcodeproj/TemplateIcon.icns -------------------------------------------------------------------------------- /CertTest/CertTest.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1DDD58140DA1D0A300B32029 /* MainMenu.xib */; }; 11 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 12 | 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 13 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 14 | DC51C7050F2E3949007C2DA8 /* AsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = DC51C7040F2E3949007C2DA8 /* AsyncSocket.m */; }; 15 | DC51C70B0F2E3986007C2DA8 /* AppController.m in Sources */ = {isa = PBXBuildFile; fileRef = DC51C70A0F2E3986007C2DA8 /* AppController.m */; }; 16 | DC51C72F0F2E40EA007C2DA8 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC51C72E0F2E40EA007C2DA8 /* Security.framework */; }; 17 | DC51C8F30F2F00C9007C2DA8 /* X509Certificate.m in Sources */ = {isa = PBXBuildFile; fileRef = DC51C8F20F2F00C9007C2DA8 /* X509Certificate.m */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 22 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 23 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; 24 | 1DDD58150DA1D0A300B32029 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; }; 25 | 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 26 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 27 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 28 | 32CA4F630368D1EE00C91783 /* CertTest_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CertTest_Prefix.pch; sourceTree = ""; }; 29 | 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 8D1107320486CEB800E47090 /* CertTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CertTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | DC51C7030F2E3949007C2DA8 /* AsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AsyncSocket.h; path = ../AsyncSocket.h; sourceTree = SOURCE_ROOT; }; 32 | DC51C7040F2E3949007C2DA8 /* AsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AsyncSocket.m; path = ../AsyncSocket.m; sourceTree = SOURCE_ROOT; }; 33 | DC51C7090F2E3986007C2DA8 /* AppController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppController.h; sourceTree = ""; }; 34 | DC51C70A0F2E3986007C2DA8 /* AppController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppController.m; sourceTree = ""; }; 35 | DC51C72E0F2E40EA007C2DA8 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = /System/Library/Frameworks/Security.framework; sourceTree = ""; }; 36 | DC51C8F10F2F00C9007C2DA8 /* X509Certificate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = X509Certificate.h; sourceTree = ""; }; 37 | DC51C8F20F2F00C9007C2DA8 /* X509Certificate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = X509Certificate.m; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | 8D11072E0486CEB800E47090 /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 46 | DC51C72F0F2E40EA007C2DA8 /* Security.framework in Frameworks */, 47 | ); 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXFrameworksBuildPhase section */ 51 | 52 | /* Begin PBXGroup section */ 53 | 080E96DDFE201D6D7F000001 /* Classes */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | DC51C7030F2E3949007C2DA8 /* AsyncSocket.h */, 57 | DC51C7040F2E3949007C2DA8 /* AsyncSocket.m */, 58 | DC51C8F10F2F00C9007C2DA8 /* X509Certificate.h */, 59 | DC51C8F20F2F00C9007C2DA8 /* X509Certificate.m */, 60 | DC51C7090F2E3986007C2DA8 /* AppController.h */, 61 | DC51C70A0F2E3986007C2DA8 /* AppController.m */, 62 | ); 63 | name = Classes; 64 | sourceTree = ""; 65 | }; 66 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 70 | DC51C72E0F2E40EA007C2DA8 /* Security.framework */, 71 | ); 72 | name = "Linked Frameworks"; 73 | sourceTree = ""; 74 | }; 75 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 79 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, 80 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 81 | ); 82 | name = "Other Frameworks"; 83 | sourceTree = ""; 84 | }; 85 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 8D1107320486CEB800E47090 /* CertTest.app */, 89 | ); 90 | name = Products; 91 | sourceTree = ""; 92 | }; 93 | 29B97314FDCFA39411CA2CEA /* CertTest */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 080E96DDFE201D6D7F000001 /* Classes */, 97 | 29B97315FDCFA39411CA2CEA /* Other Sources */, 98 | 29B97317FDCFA39411CA2CEA /* Resources */, 99 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 100 | 19C28FACFE9D520D11CA2CBB /* Products */, 101 | ); 102 | name = CertTest; 103 | sourceTree = ""; 104 | }; 105 | 29B97315FDCFA39411CA2CEA /* Other Sources */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 32CA4F630368D1EE00C91783 /* CertTest_Prefix.pch */, 109 | 29B97316FDCFA39411CA2CEA /* main.m */, 110 | ); 111 | name = "Other Sources"; 112 | sourceTree = ""; 113 | }; 114 | 29B97317FDCFA39411CA2CEA /* Resources */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 8D1107310486CEB800E47090 /* Info.plist */, 118 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, 119 | 1DDD58140DA1D0A300B32029 /* MainMenu.xib */, 120 | ); 121 | name = Resources; 122 | sourceTree = ""; 123 | }; 124 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 128 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, 129 | ); 130 | name = Frameworks; 131 | sourceTree = ""; 132 | }; 133 | /* End PBXGroup section */ 134 | 135 | /* Begin PBXNativeTarget section */ 136 | 8D1107260486CEB800E47090 /* CertTest */ = { 137 | isa = PBXNativeTarget; 138 | buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "CertTest" */; 139 | buildPhases = ( 140 | 8D1107290486CEB800E47090 /* Resources */, 141 | 8D11072C0486CEB800E47090 /* Sources */, 142 | 8D11072E0486CEB800E47090 /* Frameworks */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | ); 148 | name = CertTest; 149 | productInstallPath = "$(HOME)/Applications"; 150 | productName = CertTest; 151 | productReference = 8D1107320486CEB800E47090 /* CertTest.app */; 152 | productType = "com.apple.product-type.application"; 153 | }; 154 | /* End PBXNativeTarget section */ 155 | 156 | /* Begin PBXProject section */ 157 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 158 | isa = PBXProject; 159 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "CertTest" */; 160 | compatibilityVersion = "Xcode 3.1"; 161 | hasScannedForEncodings = 1; 162 | mainGroup = 29B97314FDCFA39411CA2CEA /* CertTest */; 163 | projectDirPath = ""; 164 | projectRoot = ""; 165 | targets = ( 166 | 8D1107260486CEB800E47090 /* CertTest */, 167 | ); 168 | }; 169 | /* End PBXProject section */ 170 | 171 | /* Begin PBXResourcesBuildPhase section */ 172 | 8D1107290486CEB800E47090 /* Resources */ = { 173 | isa = PBXResourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, 177 | 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */, 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXResourcesBuildPhase section */ 182 | 183 | /* Begin PBXSourcesBuildPhase section */ 184 | 8D11072C0486CEB800E47090 /* Sources */ = { 185 | isa = PBXSourcesBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | 8D11072D0486CEB800E47090 /* main.m in Sources */, 189 | DC51C7050F2E3949007C2DA8 /* AsyncSocket.m in Sources */, 190 | DC51C70B0F2E3986007C2DA8 /* AppController.m in Sources */, 191 | DC51C8F30F2F00C9007C2DA8 /* X509Certificate.m in Sources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXSourcesBuildPhase section */ 196 | 197 | /* Begin PBXVariantGroup section */ 198 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { 199 | isa = PBXVariantGroup; 200 | children = ( 201 | 089C165DFE840E0CC02AAC07 /* English */, 202 | ); 203 | name = InfoPlist.strings; 204 | sourceTree = ""; 205 | }; 206 | 1DDD58140DA1D0A300B32029 /* MainMenu.xib */ = { 207 | isa = PBXVariantGroup; 208 | children = ( 209 | 1DDD58150DA1D0A300B32029 /* English */, 210 | ); 211 | name = MainMenu.xib; 212 | sourceTree = ""; 213 | }; 214 | /* End PBXVariantGroup section */ 215 | 216 | /* Begin XCBuildConfiguration section */ 217 | C01FCF4B08A954540054247B /* Debug */ = { 218 | isa = XCBuildConfiguration; 219 | buildSettings = { 220 | ALWAYS_SEARCH_USER_PATHS = NO; 221 | COPY_PHASE_STRIP = NO; 222 | GCC_DYNAMIC_NO_PIC = NO; 223 | GCC_ENABLE_FIX_AND_CONTINUE = YES; 224 | GCC_MODEL_TUNING = G5; 225 | GCC_OPTIMIZATION_LEVEL = 0; 226 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 227 | GCC_PREFIX_HEADER = CertTest_Prefix.pch; 228 | INFOPLIST_FILE = Info.plist; 229 | INSTALL_PATH = "$(HOME)/Applications"; 230 | PRODUCT_NAME = CertTest; 231 | }; 232 | name = Debug; 233 | }; 234 | C01FCF4C08A954540054247B /* Release */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 239 | GCC_MODEL_TUNING = G5; 240 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 241 | GCC_PREFIX_HEADER = CertTest_Prefix.pch; 242 | INFOPLIST_FILE = Info.plist; 243 | INSTALL_PATH = "$(HOME)/Applications"; 244 | PRODUCT_NAME = CertTest; 245 | }; 246 | name = Release; 247 | }; 248 | C01FCF4F08A954540054247B /* Debug */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 252 | GCC_C_LANGUAGE_STANDARD = c99; 253 | GCC_OPTIMIZATION_LEVEL = 0; 254 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 255 | GCC_WARN_UNUSED_VARIABLE = YES; 256 | ONLY_ACTIVE_ARCH = YES; 257 | PREBINDING = NO; 258 | SDKROOT = macosx10.5; 259 | }; 260 | name = Debug; 261 | }; 262 | C01FCF5008A954540054247B /* Release */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 266 | GCC_C_LANGUAGE_STANDARD = c99; 267 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 268 | GCC_WARN_UNUSED_VARIABLE = YES; 269 | PREBINDING = NO; 270 | SDKROOT = macosx10.5; 271 | }; 272 | name = Release; 273 | }; 274 | /* End XCBuildConfiguration section */ 275 | 276 | /* Begin XCConfigurationList section */ 277 | C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "CertTest" */ = { 278 | isa = XCConfigurationList; 279 | buildConfigurations = ( 280 | C01FCF4B08A954540054247B /* Debug */, 281 | C01FCF4C08A954540054247B /* Release */, 282 | ); 283 | defaultConfigurationIsVisible = 0; 284 | defaultConfigurationName = Release; 285 | }; 286 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "CertTest" */ = { 287 | isa = XCConfigurationList; 288 | buildConfigurations = ( 289 | C01FCF4F08A954540054247B /* Debug */, 290 | C01FCF5008A954540054247B /* Release */, 291 | ); 292 | defaultConfigurationIsVisible = 0; 293 | defaultConfigurationName = Release; 294 | }; 295 | /* End XCConfigurationList section */ 296 | }; 297 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 298 | } 299 | -------------------------------------------------------------------------------- /CertTest/CertTest_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'CertTest' target in the 'CertTest' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /CertTest/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roustem/AsyncSocket/bd5a86cd0ad6bb1ddef1f338b6f4630579531f63/CertTest/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /CertTest/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.yourcompany.${PRODUCT_NAME:identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /CertTest/X509Certificate.h: -------------------------------------------------------------------------------- 1 | // 2 | // X509Certificate.h 3 | // 4 | // This class is in the public domain. 5 | // Originally created by Robbie Hanson on Mon Jan 26 2009. 6 | // Updated and maintained by Deusty Designs and the Mac development community. 7 | // 8 | // http://code.google.com/p/cocoaasyncsocket/ 9 | // 10 | // This class is largely derived from Apple's sample code project: SSLSample. 11 | // This class does not extract every bit of available information, just the most common fields. 12 | 13 | #import 14 | @class AsyncSocket; 15 | 16 | // Top Level Keys 17 | #define X509_ISSUER @"Issuer" 18 | #define X509_SUBJECT @"Subject" 19 | #define X509_NOT_VALID_BEFORE @"NotValidBefore" 20 | #define X509_NOT_VALID_AFTER @"NotValidAfter" 21 | #define X509_PUBLIC_KEY @"PublicKey" 22 | #define X509_SERIAL_NUMBER @"SerialNumber" 23 | 24 | // Keys For Issuer/Subject Dictionaries 25 | #define X509_COUNTRY @"Country" 26 | #define X509_ORGANIZATION @"Organization" 27 | #define X509_LOCALITY @"Locality" 28 | #define X509_ORANIZATIONAL_UNIT @"OrganizationalUnit" 29 | #define X509_COMMON_NAME @"CommonName" 30 | #define X509_SURNAME @"Surname" 31 | #define X509_TITLE @"Title" 32 | #define X509_STATE_PROVINCE @"StateProvince" 33 | #define X509_COLLECTIVE_STATE_PROVINCE @"CollectiveStateProvince" 34 | #define X509_EMAIL_ADDRESS @"EmailAddress" 35 | #define X509_STREET_ADDRESS @"StreetAddress" 36 | #define X509_POSTAL_CODE @"PostalCode" 37 | #define X509_OTHERS @"Others" 38 | 39 | @interface X509Certificate : NSObject 40 | 41 | + (NSDictionary *)extractCertDictFromAsyncSocket:(AsyncSocket *)socket; 42 | + (NSDictionary *)extractCertDictFromReadStream:(CFReadStreamRef)readStream; 43 | + (NSDictionary *)extractCertDictFromIdentity:(SecIdentityRef)identity; 44 | + (NSDictionary *)extractCertDictFromCert:(SecCertificateRef)cert; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /CertTest/X509Certificate.m: -------------------------------------------------------------------------------- 1 | // 2 | // X509Certificate.m 3 | // 4 | // This class is in the public domain. 5 | // Originally created by Robbie Hanson on Mon Jan 26 2009. 6 | // Updated and maintained by Deusty Designs and the Mac development community. 7 | // 8 | // http://code.google.com/p/cocoaasyncsocket/ 9 | // 10 | // This class is largely derived from Apple's sample code project: SSLSample. 11 | // This class does not extract every bit of available information, just the most common fields. 12 | 13 | #import "X509Certificate.h" 14 | #import "AsyncSocket.h" 15 | #import 16 | 17 | #define UTC_TIME_STRLEN 13 18 | #define GENERALIZED_TIME_STRLEN 15 19 | 20 | 21 | @implementation X509Certificate 22 | 23 | // Standard app-level memory functions required by CDSA 24 | 25 | static void * appMalloc (uint32 size, void *allocRef) 26 | { 27 | return malloc(size); 28 | } 29 | static void * appCalloc(uint32 num, uint32 size, void *allocRef) 30 | { 31 | return calloc(num, size); 32 | } 33 | static void * appRealloc (void *ptr, uint32 size, void *allocRef) 34 | { 35 | return realloc(ptr, size); 36 | } 37 | static void appFree (void *mem_ptr, void *allocRef) 38 | { 39 | free(mem_ptr); 40 | } 41 | 42 | 43 | static const CSSM_API_MEMORY_FUNCS memFuncs = { 44 | (CSSM_MALLOC)appMalloc, 45 | (CSSM_FREE)appFree, 46 | (CSSM_REALLOC)appRealloc, 47 | (CSSM_CALLOC)appCalloc, 48 | NULL 49 | }; 50 | 51 | static const CSSM_VERSION vers = {2, 0}; 52 | static const CSSM_GUID testGuid = { 0xFADE, 0, 0, { 1,2,3,4,5,6,7,0 }}; 53 | 54 | static BOOL CSSMStartup() 55 | { 56 | CSSM_RETURN crtn; 57 | CSSM_PVC_MODE pvcPolicy = CSSM_PVC_NONE; 58 | 59 | crtn = CSSM_Init (&vers, 60 | CSSM_PRIVILEGE_SCOPE_NONE, 61 | &testGuid, 62 | CSSM_KEY_HIERARCHY_NONE, 63 | &pvcPolicy, 64 | NULL /* reserved */); 65 | 66 | if(crtn != CSSM_OK) 67 | { 68 | cssmPerror("CSSM_Init", crtn); 69 | return NO; 70 | } 71 | else 72 | { 73 | return YES; 74 | } 75 | } 76 | 77 | static CSSM_CL_HANDLE CLStartup() 78 | { 79 | CSSM_CL_HANDLE clHandle; 80 | CSSM_RETURN crtn; 81 | 82 | if(CSSMStartup() == NO) 83 | { 84 | return 0; 85 | } 86 | 87 | crtn = CSSM_ModuleLoad(&gGuidAppleX509CL, 88 | CSSM_KEY_HIERARCHY_NONE, 89 | NULL, // eventHandler 90 | NULL); // AppNotifyCallbackCtx 91 | if(crtn != CSSM_OK) 92 | { 93 | cssmPerror("CSSM_ModuleLoad", crtn); 94 | return 0; 95 | } 96 | 97 | crtn = CSSM_ModuleAttach (&gGuidAppleX509CL, 98 | &vers, 99 | &memFuncs, // memFuncs 100 | 0, // SubserviceID 101 | CSSM_SERVICE_CL, // SubserviceFlags - Where is this used? 102 | 0, // AttachFlags 103 | CSSM_KEY_HIERARCHY_NONE, 104 | NULL, // FunctionTable 105 | 0, // NumFuncTable 106 | NULL, // reserved 107 | &clHandle); 108 | if(crtn != CSSM_OK) 109 | { 110 | cssmPerror("CSSM_ModuleAttach", crtn); 111 | return 0; 112 | } 113 | 114 | return clHandle; 115 | } 116 | 117 | static void CLShutdown(CSSM_CL_HANDLE clHandle) 118 | { 119 | CSSM_RETURN crtn; 120 | 121 | crtn = CSSM_ModuleDetach(clHandle); 122 | if(crtn != CSSM_OK) 123 | { 124 | cssmPerror("CSSM_ModuleDetach", crtn); 125 | } 126 | 127 | crtn = CSSM_ModuleUnload(&gGuidAppleX509CL, NULL, NULL); 128 | if(crtn != CSSM_OK) 129 | { 130 | cssmPerror("CSSM_ModuleUnload", crtn); 131 | } 132 | } 133 | 134 | static BOOL CompareCSSMData(const CSSM_DATA *d1, const CSSM_DATA *d2) 135 | { 136 | if(d1 == NULL || d2 == NULL) 137 | { 138 | return NO; 139 | } 140 | if(d1->Length != d2->Length) 141 | { 142 | return NO; 143 | } 144 | 145 | return memcmp(d1->Data, d2->Data, d1->Length) == 0; 146 | } 147 | 148 | static BOOL CompareOids(const CSSM_OID *oid1, const CSSM_OID *oid2) 149 | { 150 | if(oid1 == NULL || oid2 == NULL) 151 | { 152 | return NO; 153 | } 154 | if(oid1->Length != oid2->Length) 155 | { 156 | return NO; 157 | } 158 | 159 | return memcmp(oid1->Data, oid2->Data, oid1->Length) == 0; 160 | } 161 | 162 | static NSString* KeyForOid(const CSSM_OID *oid) 163 | { 164 | if(CompareOids(oid, &CSSMOID_CountryName)) 165 | { 166 | return X509_COUNTRY; 167 | } 168 | if(CompareOids(oid, &CSSMOID_OrganizationName)) 169 | { 170 | return X509_ORGANIZATION; 171 | } 172 | if(CompareOids(oid, &CSSMOID_LocalityName)) 173 | { 174 | return X509_LOCALITY; 175 | } 176 | if(CompareOids(oid, &CSSMOID_OrganizationalUnitName)) 177 | { 178 | return X509_ORANIZATIONAL_UNIT; 179 | } 180 | if(CompareOids(oid, &CSSMOID_CommonName)) 181 | { 182 | return X509_COMMON_NAME; 183 | } 184 | if(CompareOids(oid, &CSSMOID_Surname)) 185 | { 186 | return X509_SURNAME; 187 | } 188 | if(CompareOids(oid, &CSSMOID_Title)) 189 | { 190 | return X509_TITLE; 191 | } 192 | if(CompareOids(oid, &CSSMOID_StateProvinceName)) 193 | { 194 | return X509_STATE_PROVINCE; 195 | } 196 | if(CompareOids(oid, &CSSMOID_CollectiveStateProvinceName)) 197 | { 198 | return X509_COLLECTIVE_STATE_PROVINCE; 199 | } 200 | if(CompareOids(oid, &CSSMOID_EmailAddress)) 201 | { 202 | return X509_EMAIL_ADDRESS; 203 | } 204 | if(CompareOids(oid, &CSSMOID_StreetAddress)) 205 | { 206 | return X509_STREET_ADDRESS; 207 | } 208 | if(CompareOids(oid, &CSSMOID_PostalCode)) 209 | { 210 | return X509_POSTAL_CODE; 211 | } 212 | 213 | // Not every possible Oid is checked for. 214 | // Feel free to add any you may need. 215 | // They are listed in the Security Framework's aoisattr.h file. 216 | 217 | return nil; 218 | } 219 | 220 | static NSString* DataToString(const CSSM_DATA *data, const CSSM_BER_TAG *type) 221 | { 222 | NSStringEncoding encoding; 223 | switch (*type) 224 | { 225 | case BER_TAG_PRINTABLE_STRING : 226 | case BER_TAG_TELETEX_STRING : 227 | 228 | encoding = NSISOLatin1StringEncoding; 229 | break; 230 | 231 | case BER_TAG_PKIX_BMP_STRING : 232 | case BER_TAG_PKIX_UNIVERSAL_STRING : 233 | case BER_TAG_PKIX_UTF8_STRING : 234 | 235 | encoding = NSUTF8StringEncoding; 236 | break; 237 | 238 | default : 239 | return nil; 240 | } 241 | 242 | NSString *result = [[NSString alloc] initWithBytes:data->Data 243 | length:data->Length 244 | encoding:encoding]; 245 | return [result autorelease]; 246 | } 247 | 248 | static NSDate* TimeToDate(const char *str, unsigned len) 249 | { 250 | BOOL isUTC; 251 | unsigned i; 252 | long year, month, day, hour, minute, second; 253 | 254 | // Check for null or empty strings 255 | if(str == NULL || len == 0) 256 | { 257 | return nil; 258 | } 259 | 260 | // Ignore NULL termination 261 | if(str[len - 1] == '\0') 262 | { 263 | len--; 264 | } 265 | 266 | // Check for proper string length 267 | if(len == UTC_TIME_STRLEN) 268 | { 269 | // 2-digit year, not Y2K compliant 270 | isUTC = YES; 271 | } 272 | else if(len == GENERALIZED_TIME_STRLEN) 273 | { 274 | // 4-digit year 275 | isUTC = NO; 276 | } 277 | else 278 | { 279 | // Unknown format 280 | return nil; 281 | } 282 | 283 | // Check that all characters except last are digits 284 | for(i = 0; i < (len - 1); i++) 285 | { 286 | if(!(isdigit(str[i]))) 287 | { 288 | return nil; 289 | } 290 | } 291 | 292 | // Check last character is a 'Z' 293 | if(str[len - 1] != 'Z' ) 294 | { 295 | return nil; 296 | } 297 | 298 | // Start parsing 299 | i = 0; 300 | char tmp[5]; 301 | 302 | // Year 303 | if(isUTC) 304 | { 305 | tmp[0] = str[i++]; 306 | tmp[1] = str[i++]; 307 | tmp[2] = '\0'; 308 | 309 | year = strtol(tmp, NULL, 10); 310 | 311 | // 2-digit year: 312 | // 0 <= year < 50 : assume century 21 313 | // 50 <= year < 70 : illegal per PKIX 314 | // 70 < year <= 99 : assume century 20 315 | 316 | if(year < 50) 317 | { 318 | year += 2000; 319 | } 320 | else if(year < 70) 321 | { 322 | return nil; 323 | } 324 | else 325 | { 326 | year += 1900; 327 | } 328 | } 329 | else 330 | { 331 | tmp[0] = str[i++]; 332 | tmp[1] = str[i++]; 333 | tmp[2] = str[i++]; 334 | tmp[3] = str[i++]; 335 | tmp[4] = '\0'; 336 | 337 | year = strtol(tmp, NULL, 10); 338 | } 339 | 340 | // Month 341 | tmp[0] = str[i++]; 342 | tmp[1] = str[i++]; 343 | tmp[2] = '\0'; 344 | 345 | month = strtol(tmp, NULL, 10); 346 | 347 | // Months are represented in format from 1 to 12 348 | if(month > 12 || month <= 0) 349 | { 350 | return nil; 351 | } 352 | 353 | // Day 354 | tmp[0] = str[i++]; 355 | tmp[1] = str[i++]; 356 | tmp[2] = '\0'; 357 | 358 | day = strtol(tmp, NULL, 10); 359 | 360 | // Days are represented in format from 1 to 31 361 | if(day > 31 || day <= 0) 362 | { 363 | return nil; 364 | } 365 | 366 | // Hour 367 | tmp[0] = str[i++]; 368 | tmp[1] = str[i++]; 369 | tmp[2] = '\0'; 370 | 371 | hour = strtol(tmp, NULL, 10); 372 | 373 | // Hours are represented in format from 0 to 23 374 | if(hour > 23 || hour < 0) 375 | { 376 | return nil; 377 | } 378 | 379 | // Minute 380 | tmp[0] = str[i++]; 381 | tmp[1] = str[i++]; 382 | tmp[2] = '\0'; 383 | 384 | minute = strtol(tmp, NULL, 10); 385 | 386 | // Minutes are represented in format from 0 to 59 387 | if(minute > 59 || minute < 0) 388 | { 389 | return nil; 390 | } 391 | 392 | // Second 393 | tmp[0] = str[i++]; 394 | tmp[1] = str[i++]; 395 | tmp[2] = '\0'; 396 | 397 | second = strtol(tmp, NULL, 10); 398 | 399 | // Seconds are represented in format from 0 to 59 400 | if(second > 59 || second < 0) 401 | { 402 | return nil; 403 | } 404 | 405 | CFGregorianDate gDate = { year, month, day, hour, minute, second }; 406 | CFAbsoluteTime aTime = CFGregorianDateGetAbsoluteTime(gDate, NULL); 407 | 408 | return [NSDate dateWithTimeIntervalSinceReferenceDate:aTime]; 409 | } 410 | 411 | static NSData* RawToData(const CSSM_DATA *data) 412 | { 413 | if(data == NULL) 414 | { 415 | return nil; 416 | } 417 | 418 | return [NSData dataWithBytes:data->Data length:data->Length]; 419 | } 420 | 421 | static NSDictionary* X509NameToDictionary(const CSSM_X509_NAME *x509Name) 422 | { 423 | if(x509Name == NULL) 424 | { 425 | return nil; 426 | } 427 | 428 | NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:6]; 429 | NSMutableArray *others = [NSMutableArray arrayWithCapacity:6]; 430 | 431 | UInt32 i, j; 432 | for(i = 0; i < x509Name->numberOfRDNs; i++) 433 | { 434 | const CSSM_X509_RDN *name = &x509Name->RelativeDistinguishedName[i]; 435 | 436 | for(j = 0; j < name->numberOfPairs; j++) 437 | { 438 | const CSSM_X509_TYPE_VALUE_PAIR *pair = &name->AttributeTypeAndValue[j]; 439 | 440 | NSString *value = DataToString(&pair->value, &pair->valueType); 441 | if(value) 442 | { 443 | NSString *key = KeyForOid(&pair->type); 444 | if(key) 445 | [result setObject:value forKey:key]; 446 | else 447 | [others addObject:value]; 448 | } 449 | } 450 | } 451 | 452 | if([others count] > 0) 453 | { 454 | [result setObject:others forKey:X509_OTHERS]; 455 | } 456 | 457 | return result; 458 | } 459 | 460 | static void AddCSSMField(const CSSM_FIELD *field, NSMutableDictionary *dict) 461 | { 462 | const CSSM_DATA *fieldData = &field->FieldValue; 463 | const CSSM_OID *fieldOid = &field->FieldOid; 464 | 465 | if(CompareOids(fieldOid, &CSSMOID_X509V1SerialNumber)) 466 | { 467 | NSData *data = RawToData(fieldData); 468 | if(data) 469 | { 470 | [dict setObject:data forKey:X509_SERIAL_NUMBER]; 471 | } 472 | } 473 | else if(CompareOids(fieldOid, &CSSMOID_X509V1IssuerNameCStruct)) 474 | { 475 | CSSM_X509_NAME_PTR issuer = (CSSM_X509_NAME_PTR)fieldData->Data; 476 | if(issuer && fieldData->Length == sizeof(CSSM_X509_NAME)) 477 | { 478 | NSDictionary *issuerDict = X509NameToDictionary(issuer); 479 | if(issuerDict) 480 | { 481 | [dict setObject:issuerDict forKey:X509_ISSUER]; 482 | } 483 | } 484 | } 485 | else if(CompareOids(fieldOid, &CSSMOID_X509V1SubjectNameCStruct)) 486 | { 487 | CSSM_X509_NAME_PTR subject = (CSSM_X509_NAME_PTR)fieldData->Data; 488 | if(subject && fieldData->Length == sizeof(CSSM_X509_NAME)) 489 | { 490 | NSDictionary *subjectDict = X509NameToDictionary(subject); 491 | if(subjectDict) 492 | { 493 | [dict setObject:subjectDict forKey:X509_SUBJECT]; 494 | } 495 | } 496 | } 497 | else if(CompareOids(fieldOid, &CSSMOID_X509V1ValidityNotBefore)) 498 | { 499 | CSSM_X509_TIME_PTR time = (CSSM_X509_TIME_PTR)fieldData->Data; 500 | if(time && fieldData->Length == sizeof(CSSM_X509_TIME)) 501 | { 502 | NSDate *date = TimeToDate((const char *)time->time.Data, time->time.Length); 503 | if(date) 504 | { 505 | [dict setObject:date forKey:X509_NOT_VALID_BEFORE]; 506 | } 507 | } 508 | } 509 | else if(CompareOids(fieldOid, &CSSMOID_X509V1ValidityNotAfter)) 510 | { 511 | CSSM_X509_TIME_PTR time = (CSSM_X509_TIME_PTR)fieldData->Data; 512 | if(time && fieldData->Length == sizeof(CSSM_X509_TIME)) 513 | { 514 | NSDate *date = TimeToDate((const char *)time->time.Data, time->time.Length); 515 | if(date) 516 | { 517 | [dict setObject:date forKey:X509_NOT_VALID_AFTER]; 518 | } 519 | } 520 | } 521 | else if(CompareOids(fieldOid, &CSSMOID_X509V1SubjectPublicKeyCStruct)) 522 | { 523 | CSSM_X509_SUBJECT_PUBLIC_KEY_INFO_PTR pubKeyInfo = (CSSM_X509_SUBJECT_PUBLIC_KEY_INFO_PTR)fieldData->Data; 524 | if(pubKeyInfo && fieldData->Length == sizeof(CSSM_X509_SUBJECT_PUBLIC_KEY_INFO)) 525 | { 526 | NSData *data = RawToData(&pubKeyInfo->subjectPublicKey); 527 | if(data) 528 | { 529 | [dict setObject:data forKey:X509_PUBLIC_KEY]; 530 | } 531 | } 532 | } 533 | } 534 | 535 | + (NSDictionary *)extractCertDictFromAsyncSocket:(AsyncSocket *)socket 536 | { 537 | if(socket == nil) 538 | { 539 | return nil; 540 | } 541 | 542 | return [self extractCertDictFromReadStream:[socket getCFReadStream]]; 543 | } 544 | 545 | + (NSDictionary *)extractCertDictFromReadStream:(CFReadStreamRef)readStream 546 | { 547 | if(readStream == NULL) 548 | { 549 | return nil; 550 | } 551 | 552 | NSDictionary *result = nil; 553 | 554 | CFArrayRef certs = CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates); 555 | if(certs && CFArrayGetCount(certs) > 0) 556 | { 557 | // The first cert in the chain is the subject cert 558 | SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, 0); 559 | 560 | result = [self extractCertDictFromCert:cert]; 561 | } 562 | 563 | if(certs) CFRelease(certs); 564 | 565 | return result; 566 | } 567 | 568 | + (NSDictionary *)extractCertDictFromIdentity:(SecIdentityRef)identity 569 | { 570 | if(identity == NULL) 571 | { 572 | return nil; 573 | } 574 | 575 | NSDictionary *result = nil; 576 | SecCertificateRef cert = NULL; 577 | 578 | OSStatus err = SecIdentityCopyCertificate(identity, &cert); 579 | if(err) 580 | { 581 | cssmPerror("SecIdentityCopyCertificate", err); 582 | return nil; 583 | } 584 | else 585 | { 586 | result = [self extractCertDictFromCert:cert]; 587 | } 588 | 589 | if(cert) CFRelease(cert); 590 | 591 | return result; 592 | } 593 | 594 | + (NSDictionary *)extractCertDictFromCert:(SecCertificateRef)cert 595 | { 596 | CSSM_CL_HANDLE clHandle = CLStartup(); 597 | if(clHandle == 0) 598 | { 599 | return nil; 600 | } 601 | 602 | NSMutableDictionary *result = nil; 603 | 604 | CSSM_DATA certData; 605 | if(SecCertificateGetData(cert, &certData) == noErr) 606 | { 607 | uint32 i; 608 | uint32 numFields; 609 | CSSM_FIELD_PTR fieldPtr; 610 | 611 | CSSM_RETURN crtn = CSSM_CL_CertGetAllFields(clHandle, &certData, &numFields, &fieldPtr); 612 | if(crtn == CSSM_OK) 613 | { 614 | result = [NSMutableDictionary dictionaryWithCapacity:6]; 615 | 616 | for(i = 0; i < numFields; i++) 617 | { 618 | AddCSSMField(&fieldPtr[i], result); 619 | } 620 | 621 | CSSM_CL_FreeFields(clHandle, numFields, &fieldPtr); 622 | } 623 | } 624 | 625 | CLShutdown(clHandle); 626 | 627 | return result; 628 | } 629 | 630 | @end 631 | -------------------------------------------------------------------------------- /CertTest/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CertTest 4 | // 5 | // Created by Robbie Hanson on 1/26/09. 6 | // Copyright Deusty Designs, LLC. 2009. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /EchoServer/AppController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class AsyncSocket; 4 | 5 | @interface AppController : NSObject 6 | { 7 | AsyncSocket *listenSocket; 8 | NSMutableArray *connectedSockets; 9 | 10 | BOOL isRunning; 11 | 12 | IBOutlet id logView; 13 | IBOutlet id portField; 14 | IBOutlet id startStopButton; 15 | } 16 | - (IBAction)startStop:(id)sender; 17 | @end 18 | -------------------------------------------------------------------------------- /EchoServer/AppController.m: -------------------------------------------------------------------------------- 1 | #import "AppController.h" 2 | #import "AsyncSocket.h" 3 | 4 | #define WELCOME_MSG 0 5 | #define ECHO_MSG 1 6 | #define WARNING_MSG 2 7 | 8 | #define READ_TIMEOUT 15.0 9 | #define READ_TIMEOUT_EXTENSION 10.0 10 | 11 | #define FORMAT(format, ...) [NSString stringWithFormat:(format), ##__VA_ARGS__] 12 | 13 | @interface AppController (PrivateAPI) 14 | - (void)logError:(NSString *)msg; 15 | - (void)logInfo:(NSString *)msg; 16 | - (void)logMessage:(NSString *)msg; 17 | @end 18 | 19 | 20 | @implementation AppController 21 | 22 | - (id)init 23 | { 24 | if((self = [super init])) 25 | { 26 | listenSocket = [[AsyncSocket alloc] initWithDelegate:self]; 27 | connectedSockets = [[NSMutableArray alloc] initWithCapacity:1]; 28 | 29 | isRunning = NO; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)awakeFromNib 35 | { 36 | [logView setString:@""]; 37 | } 38 | 39 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 40 | { 41 | NSLog(@"Ready"); 42 | 43 | // Advanced options - enable the socket to contine operations even during modal dialogs, and menu browsing 44 | [listenSocket setRunLoopModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; 45 | } 46 | 47 | - (void)scrollToBottom 48 | { 49 | NSScrollView *scrollView = [logView enclosingScrollView]; 50 | NSPoint newScrollOrigin; 51 | 52 | if ([[scrollView documentView] isFlipped]) 53 | newScrollOrigin = NSMakePoint(0.0F, NSMaxY([[scrollView documentView] frame])); 54 | else 55 | newScrollOrigin = NSMakePoint(0.0F, 0.0F); 56 | 57 | [[scrollView documentView] scrollPoint:newScrollOrigin]; 58 | } 59 | 60 | - (void)logError:(NSString *)msg 61 | { 62 | NSString *paragraph = [NSString stringWithFormat:@"%@\n", msg]; 63 | 64 | NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:1]; 65 | [attributes setObject:[NSColor redColor] forKey:NSForegroundColorAttributeName]; 66 | 67 | NSAttributedString *as = [[NSAttributedString alloc] initWithString:paragraph attributes:attributes]; 68 | [as autorelease]; 69 | 70 | [[logView textStorage] appendAttributedString:as]; 71 | [self scrollToBottom]; 72 | } 73 | 74 | - (void)logInfo:(NSString *)msg 75 | { 76 | NSString *paragraph = [NSString stringWithFormat:@"%@\n", msg]; 77 | 78 | NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:1]; 79 | [attributes setObject:[NSColor purpleColor] forKey:NSForegroundColorAttributeName]; 80 | 81 | NSAttributedString *as = [[NSAttributedString alloc] initWithString:paragraph attributes:attributes]; 82 | [as autorelease]; 83 | 84 | [[logView textStorage] appendAttributedString:as]; 85 | [self scrollToBottom]; 86 | } 87 | 88 | - (void)logMessage:(NSString *)msg 89 | { 90 | NSString *paragraph = [NSString stringWithFormat:@"%@\n", msg]; 91 | 92 | NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:1]; 93 | [attributes setObject:[NSColor blackColor] forKey:NSForegroundColorAttributeName]; 94 | 95 | NSAttributedString *as = [[NSAttributedString alloc] initWithString:paragraph attributes:attributes]; 96 | [as autorelease]; 97 | 98 | [[logView textStorage] appendAttributedString:as]; 99 | [self scrollToBottom]; 100 | } 101 | 102 | - (IBAction)startStop:(id)sender 103 | { 104 | if(!isRunning) 105 | { 106 | int port = [portField intValue]; 107 | 108 | if(port < 0 || port > 65535) 109 | { 110 | port = 0; 111 | } 112 | 113 | NSError *error = nil; 114 | if(![listenSocket acceptOnPort:port error:&error]) 115 | { 116 | [self logError:FORMAT(@"Error starting server: %@", error)]; 117 | return; 118 | } 119 | 120 | [self logInfo:FORMAT(@"Echo server started on port %hu", [listenSocket localPort])]; 121 | isRunning = YES; 122 | 123 | [portField setEnabled:NO]; 124 | [startStopButton setTitle:@"Stop"]; 125 | } 126 | else 127 | { 128 | // Stop accepting connections 129 | [listenSocket disconnect]; 130 | 131 | // Stop any client connections 132 | int i; 133 | for(i = 0; i < [connectedSockets count]; i++) 134 | { 135 | // Call disconnect on the socket, 136 | // which will invoke the onSocketDidDisconnect: method, 137 | // which will remove the socket from the list. 138 | [[connectedSockets objectAtIndex:i] disconnect]; 139 | } 140 | 141 | [self logInfo:@"Stopped Echo server"]; 142 | isRunning = false; 143 | 144 | [portField setEnabled:YES]; 145 | [startStopButton setTitle:@"Start"]; 146 | } 147 | } 148 | 149 | - (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket 150 | { 151 | [connectedSockets addObject:newSocket]; 152 | } 153 | 154 | - (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port 155 | { 156 | [self logInfo:FORMAT(@"Accepted client %@:%hu", host, port)]; 157 | 158 | NSString *welcomeMsg = @"Welcome to the AsyncSocket Echo Server\r\n"; 159 | NSData *welcomeData = [welcomeMsg dataUsingEncoding:NSUTF8StringEncoding]; 160 | 161 | [sock writeData:welcomeData withTimeout:-1 tag:WELCOME_MSG]; 162 | 163 | [sock readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0]; 164 | } 165 | 166 | - (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag 167 | { 168 | if(tag == ECHO_MSG) 169 | { 170 | [sock readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0]; 171 | } 172 | } 173 | 174 | - (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag 175 | { 176 | NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length] - 2)]; 177 | NSString *msg = [[[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding] autorelease]; 178 | if(msg) 179 | { 180 | [self logMessage:msg]; 181 | } 182 | else 183 | { 184 | [self logError:@"Error converting received data into UTF-8 String"]; 185 | } 186 | 187 | // Even if we were unable to write the incoming data to the log, 188 | // we're still going to echo it back to the client. 189 | [sock writeData:data withTimeout:-1 tag:ECHO_MSG]; 190 | } 191 | 192 | /** 193 | * This method is called if a read has timed out. 194 | * It allows us to optionally extend the timeout. 195 | * We use this method to issue a warning to the user prior to disconnecting them. 196 | **/ 197 | - (NSTimeInterval)onSocket:(AsyncSocket *)sock 198 | shouldTimeoutReadWithTag:(long)tag 199 | elapsed:(NSTimeInterval)elapsed 200 | bytesDone:(CFIndex)length 201 | { 202 | if(elapsed <= READ_TIMEOUT) 203 | { 204 | NSString *warningMsg = @"Are you still there?\r\n"; 205 | NSData *warningData = [warningMsg dataUsingEncoding:NSUTF8StringEncoding]; 206 | 207 | [sock writeData:warningData withTimeout:-1 tag:WARNING_MSG]; 208 | 209 | return READ_TIMEOUT_EXTENSION; 210 | } 211 | 212 | return 0.0; 213 | } 214 | 215 | - (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err 216 | { 217 | [self logInfo:FORMAT(@"Client Disconnected: %@:%hu", [sock connectedHost], [sock connectedPort])]; 218 | } 219 | 220 | - (void)onSocketDidDisconnect:(AsyncSocket *)sock 221 | { 222 | [connectedSockets removeObject:sock]; 223 | } 224 | 225 | @end 226 | -------------------------------------------------------------------------------- /EchoServer/EchoServer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 44; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 29B97318FDCFA39411CA2CEA /* MainMenu.nib */; }; 11 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 12 | 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 13 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 14 | DC50DAB60E4673910071BAD9 /* AsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = DC50DAB50E4673910071BAD9 /* AsyncSocket.m */; }; 15 | DC7619050E26F49700A77CA5 /* AppController.m in Sources */ = {isa = PBXBuildFile; fileRef = DC7619040E26F49700A77CA5 /* AppController.m */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 20 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 21 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; 22 | 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 23 | 29B97319FDCFA39411CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = ""; }; 24 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 25 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 26 | 32CA4F630368D1EE00C91783 /* EchoServer_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EchoServer_Prefix.pch; sourceTree = ""; }; 27 | 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 8D1107320486CEB800E47090 /* EchoServer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EchoServer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | DC50DAB40E4673830071BAD9 /* AsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AsyncSocket.h; path = ../AsyncSocket.h; sourceTree = SOURCE_ROOT; }; 30 | DC50DAB50E4673910071BAD9 /* AsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AsyncSocket.m; path = ../AsyncSocket.m; sourceTree = SOURCE_ROOT; }; 31 | DC7619030E26F49700A77CA5 /* AppController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppController.h; sourceTree = ""; }; 32 | DC7619040E26F49700A77CA5 /* AppController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppController.m; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | 8D11072E0486CEB800E47090 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | 080E96DDFE201D6D7F000001 /* Classes */ = { 48 | isa = PBXGroup; 49 | children = ( 50 | DC50DAB40E4673830071BAD9 /* AsyncSocket.h */, 51 | DC50DAB50E4673910071BAD9 /* AsyncSocket.m */, 52 | DC7619030E26F49700A77CA5 /* AppController.h */, 53 | DC7619040E26F49700A77CA5 /* AppController.m */, 54 | ); 55 | name = Classes; 56 | sourceTree = ""; 57 | }; 58 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 62 | ); 63 | name = "Linked Frameworks"; 64 | sourceTree = ""; 65 | }; 66 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 70 | 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, 71 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 72 | ); 73 | name = "Other Frameworks"; 74 | sourceTree = ""; 75 | }; 76 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 8D1107320486CEB800E47090 /* EchoServer.app */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | 29B97314FDCFA39411CA2CEA /* EchoServer */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 080E96DDFE201D6D7F000001 /* Classes */, 88 | 29B97315FDCFA39411CA2CEA /* Other Sources */, 89 | 29B97317FDCFA39411CA2CEA /* Resources */, 90 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 91 | 19C28FACFE9D520D11CA2CBB /* Products */, 92 | ); 93 | name = EchoServer; 94 | sourceTree = ""; 95 | }; 96 | 29B97315FDCFA39411CA2CEA /* Other Sources */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 32CA4F630368D1EE00C91783 /* EchoServer_Prefix.pch */, 100 | 29B97316FDCFA39411CA2CEA /* main.m */, 101 | ); 102 | name = "Other Sources"; 103 | sourceTree = ""; 104 | }; 105 | 29B97317FDCFA39411CA2CEA /* Resources */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 8D1107310486CEB800E47090 /* Info.plist */, 109 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, 110 | 29B97318FDCFA39411CA2CEA /* MainMenu.nib */, 111 | ); 112 | name = Resources; 113 | sourceTree = ""; 114 | }; 115 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 119 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, 120 | ); 121 | name = Frameworks; 122 | sourceTree = ""; 123 | }; 124 | /* End PBXGroup section */ 125 | 126 | /* Begin PBXNativeTarget section */ 127 | 8D1107260486CEB800E47090 /* EchoServer */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "EchoServer" */; 130 | buildPhases = ( 131 | 8D1107290486CEB800E47090 /* Resources */, 132 | 8D11072C0486CEB800E47090 /* Sources */, 133 | 8D11072E0486CEB800E47090 /* Frameworks */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | ); 139 | name = EchoServer; 140 | productInstallPath = "$(HOME)/Applications"; 141 | productName = EchoServer; 142 | productReference = 8D1107320486CEB800E47090 /* EchoServer.app */; 143 | productType = "com.apple.product-type.application"; 144 | }; 145 | /* End PBXNativeTarget section */ 146 | 147 | /* Begin PBXProject section */ 148 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 149 | isa = PBXProject; 150 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "EchoServer" */; 151 | compatibilityVersion = "Xcode 3.0"; 152 | hasScannedForEncodings = 1; 153 | mainGroup = 29B97314FDCFA39411CA2CEA /* EchoServer */; 154 | projectDirPath = ""; 155 | projectRoot = ""; 156 | targets = ( 157 | 8D1107260486CEB800E47090 /* EchoServer */, 158 | ); 159 | }; 160 | /* End PBXProject section */ 161 | 162 | /* Begin PBXResourcesBuildPhase section */ 163 | 8D1107290486CEB800E47090 /* Resources */ = { 164 | isa = PBXResourcesBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */, 168 | 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, 169 | ); 170 | runOnlyForDeploymentPostprocessing = 0; 171 | }; 172 | /* End PBXResourcesBuildPhase section */ 173 | 174 | /* Begin PBXSourcesBuildPhase section */ 175 | 8D11072C0486CEB800E47090 /* Sources */ = { 176 | isa = PBXSourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 8D11072D0486CEB800E47090 /* main.m in Sources */, 180 | DC7619050E26F49700A77CA5 /* AppController.m in Sources */, 181 | DC50DAB60E4673910071BAD9 /* AsyncSocket.m in Sources */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXSourcesBuildPhase section */ 186 | 187 | /* Begin PBXVariantGroup section */ 188 | 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { 189 | isa = PBXVariantGroup; 190 | children = ( 191 | 089C165DFE840E0CC02AAC07 /* English */, 192 | ); 193 | name = InfoPlist.strings; 194 | sourceTree = ""; 195 | }; 196 | 29B97318FDCFA39411CA2CEA /* MainMenu.nib */ = { 197 | isa = PBXVariantGroup; 198 | children = ( 199 | 29B97319FDCFA39411CA2CEA /* English */, 200 | ); 201 | name = MainMenu.nib; 202 | sourceTree = ""; 203 | }; 204 | /* End PBXVariantGroup section */ 205 | 206 | /* Begin XCBuildConfiguration section */ 207 | C01FCF4B08A954540054247B /* Debug */ = { 208 | isa = XCBuildConfiguration; 209 | buildSettings = { 210 | COPY_PHASE_STRIP = NO; 211 | GCC_DYNAMIC_NO_PIC = NO; 212 | GCC_ENABLE_FIX_AND_CONTINUE = YES; 213 | GCC_MODEL_TUNING = G5; 214 | GCC_OPTIMIZATION_LEVEL = 0; 215 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 216 | GCC_PREFIX_HEADER = EchoServer_Prefix.pch; 217 | INFOPLIST_FILE = Info.plist; 218 | INSTALL_PATH = "$(HOME)/Applications"; 219 | PRODUCT_NAME = EchoServer; 220 | WRAPPER_EXTENSION = app; 221 | ZERO_LINK = YES; 222 | }; 223 | name = Debug; 224 | }; 225 | C01FCF4C08A954540054247B /* Release */ = { 226 | isa = XCBuildConfiguration; 227 | buildSettings = { 228 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 229 | GCC_MODEL_TUNING = G5; 230 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 231 | GCC_PREFIX_HEADER = EchoServer_Prefix.pch; 232 | INFOPLIST_FILE = Info.plist; 233 | INSTALL_PATH = "$(HOME)/Applications"; 234 | PRODUCT_NAME = EchoServer; 235 | WRAPPER_EXTENSION = app; 236 | }; 237 | name = Release; 238 | }; 239 | C01FCF4F08A954540054247B /* Debug */ = { 240 | isa = XCBuildConfiguration; 241 | buildSettings = { 242 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 243 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 244 | GCC_WARN_MISSING_PARENTHESES = YES; 245 | GCC_WARN_SHADOW = YES; 246 | GCC_WARN_UNUSED_VARIABLE = YES; 247 | PREBINDING = NO; 248 | SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk"; 249 | }; 250 | name = Debug; 251 | }; 252 | C01FCF5008A954540054247B /* Release */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | ARCHS = ( 256 | ppc, 257 | i386, 258 | ); 259 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 260 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 261 | GCC_WARN_MISSING_PARENTHESES = YES; 262 | GCC_WARN_SHADOW = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | PREBINDING = NO; 265 | SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk"; 266 | }; 267 | name = Release; 268 | }; 269 | /* End XCBuildConfiguration section */ 270 | 271 | /* Begin XCConfigurationList section */ 272 | C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "EchoServer" */ = { 273 | isa = XCConfigurationList; 274 | buildConfigurations = ( 275 | C01FCF4B08A954540054247B /* Debug */, 276 | C01FCF4C08A954540054247B /* Release */, 277 | ); 278 | defaultConfigurationIsVisible = 0; 279 | defaultConfigurationName = Release; 280 | }; 281 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "EchoServer" */ = { 282 | isa = XCConfigurationList; 283 | buildConfigurations = ( 284 | C01FCF4F08A954540054247B /* Debug */, 285 | C01FCF5008A954540054247B /* Release */, 286 | ); 287 | defaultConfigurationIsVisible = 0; 288 | defaultConfigurationName = Release; 289 | }; 290 | /* End XCConfigurationList section */ 291 | }; 292 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 293 | } 294 | -------------------------------------------------------------------------------- /EchoServer/EchoServer_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'EchoServer' target in the 'EchoServer' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /EchoServer/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roustem/AsyncSocket/bd5a86cd0ad6bb1ddef1f338b6f4630579531f63/EchoServer/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /EchoServer/English.lproj/MainMenu.nib/keyedobjects.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roustem/AsyncSocket/bd5a86cd0ad6bb1ddef1f338b6f4630579531f63/EchoServer/English.lproj/MainMenu.nib/keyedobjects.nib -------------------------------------------------------------------------------- /EchoServer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.yourcompany.EchoServer 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /EchoServer/Instructions.txt: -------------------------------------------------------------------------------- 1 | First, build and run the EchoServer. 2 | You can set it to run on a specific port, or allow to pick an available port. 3 | Now start the echo server. It will output the port it started on. 4 | 5 | You can now use telnet to connect to the echo server and test it. 6 | Using the Terminal (/Applications/Utilities/Terminal.app) 7 | type in the following: 8 | telnet localhost [port] 9 | 10 | where "[port]" is replaced by the port the Echo server is running on. 11 | 12 | Type in anything you want, and hit return. 13 | Whatever you typed in will be displayed in the Echo Server, and echoed back to you. 14 | 15 | To end your telnet session hit Ctrl-], and type "quit". -------------------------------------------------------------------------------- /EchoServer/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // EchoServer 4 | // 5 | // Created by Robbie Hanson on 7/10/08. 6 | // Copyright __MyCompanyName__ 2008. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | CocoaAsyncSocket supports TCP and UDP. The AsyncSocket class is for TCP, and the AsyncUdpSocket class is for UDP. Each class is described below. 2 | 3 | AsyncSocket is a TCP/IP socket networking library that wraps CFSocket and CFStream. It offers asynchronous operation, and a native cocoa class complete with delegate support. Here are the key features: 4 | 5 | Queued non-blocking reads and writes, with optional timeouts. You tell it what to read or write, and it will call you when it's done. 6 | Automatic socket acceptance. If you tell it to accept connections, it will call you with new instances of itself for each connection. You can, of course, disconnect them immediately. 7 | Delegate support. Errors, connections, accepts, read completions, write completions, progress, and disconnections all result in a call to your delegate method. 8 | Run-loop based, not thread based. Although you can use it on main or worker threads, you don't have to. It calls the delegate methods asynchronously using NSRunLoop. The delegate methods include a socket parameter, allowing you to distinguish between many instances. 9 | Self-contained in one class. You don't need to muck around with streams or sockets. The class handles all of that. 10 | Support for TCP streams over IPv4 and IPv6. 11 | The library is public domain, originally written by Dustin Voss. Now available in a public setting to allow and encourage its continued support. 12 | 13 | AsyncUdpSocket is a UDP/IP socket networking library that wraps CFSocket. It works almost exactly like the TCP version, but is designed specifically for UDP. This includes queued non-blocking send/receive operations, full delegate support, run-loop based, self-contained class, and support for IPv4 and IPv6. 14 | 15 | -------------------------------------------------------------------------------- /changes.txt: -------------------------------------------------------------------------------- 1 | The last version of AsyncSocket from Dustin Voss was version 4.3 released on 2005-11-23. This document lists the bug fixes provided by Deusty Designs, prior to the creation of the Google Code project. 2 | 3 | CHANGES IN VERSION 4.3.1: 4 | 5 | Bugfix: 6 | If user called acceptOnPort:0, then the OS would automatically pick a port for you. 7 | This is what is supposed to happen, except that it would pick a different port for IPv4 and IPv6 8 | Added code to make sure both protocols are listening on the same port 9 | 10 | Added comments in many places 11 | 12 | Altered bits of code to match Apple's coding style guidelines 13 | 14 | Renamed method "attachAcceptSockets" to "attachSocketsToRunLoop:error:" 15 | 16 | 17 | 18 | 19 | CHANGES IN VERSION 4.3.2 20 | 21 | Removed polling - it's not needed 22 | 23 | Added another delegate method: onSocket:didReadPartialDataOfLength:tag: 24 | Often, when using the AsyncSocket class, it was important to display a progress bar to the user. 25 | This was possible using Timers, and calling progressOfRead, but it wasn't the easiest solution. 26 | This delegate method will allow for automatic notification when using readToLength: or readToData: 27 | 28 | 29 | 30 | 31 | CHANGES IN VERSION 4.3.3 32 | 33 | Bugfix: 34 | The methods connectedHost, connectedPort, localHost, and localPort all assumed IPv4 connection. 35 | In other words they all assumed theSocket was valid, causing a crash when the OS connected via IPv6. 36 | Updated all methods to properly check either theSocket or theSocket6. 37 | 38 | Bugfix: 39 | In the doStreamOpen method, there was an assumption that IPv4 was used and thus a valid theSocket variable. 40 | This was not always the case, causing this to fail: 41 | CFSocketCopyPeerAddress(theSocket) 42 | Fixed the problem by simply using the connectedHost and connectedPort methods. 43 | 44 | Tweak: 45 | Added safety check in addressPort: method: 46 | if (cfaddr == NULL) return 0; 47 | 48 | Tweak: 49 | The kCFStreamPropertyShouldCloseNativeSocket was previously getting set in the configureStreamsAndReturnError: method. 50 | This may have been OK, but it would have caused a problem if the following sequence occurred: 51 | A socket was accepted and doAcceptWithSocket: method was called, 52 | This method then encoutered an error while attaching the streams to the run loop. 53 | If this sequence happened, the BSD socket would not have been closed. 54 | So I moved this into the createStreams... methods. 55 | 56 | 57 | 58 | 59 | CHANGES IN VERSION 4.3.4 60 | 61 | Bugfix: 62 | In doReadTimeout: the code properly calls endCurrentWrite and then closes with an error. 63 | In doWriteTimeout: the code was calling completeCurrentWrite and then closes with an error. 64 | This resulted in the delegate being told that his write completed (when it didn't) 65 | immediately prior to disconnecting with an error. 66 | 67 | 68 | 69 | 70 | CHANGES IN VERSION 4.3.5 71 | 72 | Added support for accepting on IPv6 address. 73 | 74 | Bugfix: 75 | In acceptOnAddress, changed dataWithBytesNoCopy to dataWithBytes. 76 | This was needed because the bytes were about to go out of scope. 77 | 78 | Bugfix: 79 | Added return statement to doStreamOpen after closewithError call. 80 | This was needed or else the didConnect delegate could potentially get called 81 | immediately after willCloseWithError and didClose. 82 | 83 | Bugfix: 84 | We were receiving several reports of crashes in AsyncSocket. 85 | The problem seemed to be that, in specific circumstances, 86 | the readStream callback and/or writeStream callback would be invoked AFTER 87 | the readStream and writeStream were closed. 88 | This isn't supposed to happen, however we did find evidence that it was an issue several years ago. 89 | It was assumed that the problem has since been fixed. 90 | Perhaps the problem still exists, but only in very rare cases which we just happened to be encountering. 91 | In any case, we used the same precautions that were used previously. 92 | In the close methods, we specifically unregister for callbacks now. 93 | 94 | 95 | 96 | 97 | There have been MANY more bug fixes and changes. Please consult the google code changes list: 98 | http://code.google.com/p/cocoaasyncsocket/source/list --------------------------------------------------------------------------------