Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
805 | - | 1 | // |
2 | // AsyncSocket.m |
||
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 "AsyncSocket.h" |
||
12 | #import <sys/socket.h> |
||
13 | #import <netinet/in.h> |
||
14 | #import <arpa/inet.h> |
||
15 | #import <netdb.h> |
||
16 | |||
17 | #if TARGET_OS_IPHONE |
||
18 | // Note: You may need to add the CFNetwork Framework to your project |
||
19 | #import <CFNetwork/CFNetwork.h> |
||
20 | #endif |
||
21 | |||
22 | #pragma mark Declarations |
||
23 | |||
24 | #define DEFAULT_PREBUFFERING YES // Whether pre-buffering is enabled by default |
||
25 | |||
26 | #define READQUEUE_CAPACITY 5 // Initial capacity |
||
27 | #define WRITEQUEUE_CAPACITY 5 // Initial capacity |
||
28 | #define READALL_CHUNKSIZE 256 // Incremental increase in buffer size |
||
29 | #define WRITE_CHUNKSIZE (1024 * 4) // Limit on size of each write pass |
||
30 | |||
31 | NSString *const AsyncSocketException = @"AsyncSocketException"; |
||
32 | NSString *const AsyncSocketErrorDomain = @"AsyncSocketErrorDomain"; |
||
33 | |||
34 | #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 |
||
35 | // Mutex lock used by all instances of AsyncSocket, to protect getaddrinfo. |
||
36 | // Prior to Mac OS X 10.5 this method was not thread-safe. |
||
37 | static NSString *getaddrinfoLock = @"lock"; |
||
38 | #endif |
||
39 | |||
40 | enum AsyncSocketFlags |
||
41 | { |
||
42 | kEnablePreBuffering = 1 << 0, // If set, pre-buffering is enabled |
||
43 | kDidPassConnectMethod = 1 << 1, // If set, disconnection results in delegate call |
||
44 | kDidCompleteOpenForRead = 1 << 2, // If set, open callback has been called for read stream |
||
45 | kDidCompleteOpenForWrite = 1 << 3, // If set, open callback has been called for write stream |
||
46 | kStartingReadTLS = 1 << 4, // If set, we're waiting for TLS negotiation to complete |
||
47 | kStartingWriteTLS = 1 << 5, // If set, we're waiting for TLS negotiation to complete |
||
48 | kForbidReadsWrites = 1 << 6, // If set, no new reads or writes are allowed |
||
49 | kDisconnectAfterReads = 1 << 7, // If set, disconnect after no more reads are queued |
||
50 | kDisconnectAfterWrites = 1 << 8, // If set, disconnect after no more writes are queued |
||
51 | kClosingWithError = 1 << 9, // If set, the socket is being closed due to an error |
||
52 | kDequeueReadScheduled = 1 << 10, // If set, a maybeDequeueRead operation is already scheduled |
||
53 | kDequeueWriteScheduled = 1 << 11, // If set, a maybeDequeueWrite operation is already scheduled |
||
54 | kSocketCanAcceptBytes = 1 << 12, // If set, we know socket can accept bytes. If unset, it's unknown. |
||
55 | kSocketHasBytesAvailable = 1 << 13, // If set, we know socket has bytes available. If unset, it's unknown. |
||
56 | }; |
||
57 | |||
58 | @interface AsyncSocket (Private) |
||
59 | |||
60 | // Connecting |
||
61 | - (void)startConnectTimeout:(NSTimeInterval)timeout; |
||
62 | - (void)endConnectTimeout; |
||
63 | |||
64 | // Socket Implementation |
||
65 | - (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr; |
||
66 | - (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr; |
||
67 | - (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr; |
||
68 | - (BOOL)configureSocketAndReturnError:(NSError **)errPtr; |
||
69 | - (BOOL)connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; |
||
70 | - (void)doAcceptWithSocket:(CFSocketNativeHandle)newSocket; |
||
71 | - (void)doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)err; |
||
72 | |||
73 | // Stream Implementation |
||
74 | - (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr; |
||
75 | - (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr; |
||
76 | - (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr; |
||
77 | - (BOOL)configureStreamsAndReturnError:(NSError **)errPtr; |
||
78 | - (BOOL)openStreamsAndReturnError:(NSError **)errPtr; |
||
79 | - (void)doStreamOpen; |
||
80 | - (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr; |
||
81 | |||
82 | // Disconnect Implementation |
||
83 | - (void)closeWithError:(NSError *)err; |
||
84 | - (void)recoverUnreadData; |
||
85 | - (void)emptyQueues; |
||
86 | - (void)close; |
||
87 | |||
88 | // Errors |
||
89 | - (NSError *)getErrnoError; |
||
90 | - (NSError *)getAbortError; |
||
91 | - (NSError *)getStreamError; |
||
92 | - (NSError *)getSocketError; |
||
93 | - (NSError *)getConnectTimeoutError; |
||
94 | - (NSError *)getReadMaxedOutError; |
||
95 | - (NSError *)getReadTimeoutError; |
||
96 | - (NSError *)getWriteTimeoutError; |
||
97 | - (NSError *)errorFromCFStreamError:(CFStreamError)err; |
||
98 | |||
99 | // Diagnostics |
||
100 | - (BOOL)isSocketConnected; |
||
101 | - (BOOL)areStreamsConnected; |
||
102 | - (NSString *)connectedHost:(CFSocketRef)socket; |
||
103 | - (UInt16)connectedPort:(CFSocketRef)socket; |
||
104 | - (NSString *)localHost:(CFSocketRef)socket; |
||
105 | - (UInt16)localPort:(CFSocketRef)socket; |
||
106 | - (NSString *)addressHost:(CFDataRef)cfaddr; |
||
107 | - (UInt16)addressPort:(CFDataRef)cfaddr; |
||
108 | |||
109 | // Reading |
||
110 | - (void)doBytesAvailable; |
||
111 | - (void)completeCurrentRead; |
||
112 | - (void)endCurrentRead; |
||
113 | - (void)scheduleDequeueRead; |
||
114 | - (void)maybeDequeueRead; |
||
115 | - (void)doReadTimeout:(NSTimer *)timer; |
||
116 | |||
117 | // Writing |
||
118 | - (void)doSendBytes; |
||
119 | - (void)completeCurrentWrite; |
||
120 | - (void)endCurrentWrite; |
||
121 | - (void)scheduleDequeueWrite; |
||
122 | - (void)maybeDequeueWrite; |
||
123 | - (void)maybeScheduleDisconnect; |
||
124 | - (void)doWriteTimeout:(NSTimer *)timer; |
||
125 | |||
126 | // Run Loop |
||
127 | - (void)runLoopAddSource:(CFRunLoopSourceRef)source; |
||
128 | - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source; |
||
129 | - (void)runLoopAddTimer:(NSTimer *)timer; |
||
130 | - (void)runLoopRemoveTimer:(NSTimer *)timer; |
||
131 | - (void)runLoopUnscheduleReadStream; |
||
132 | - (void)runLoopUnscheduleWriteStream; |
||
133 | |||
134 | // Security |
||
135 | - (void)maybeStartTLS; |
||
136 | - (void)onTLSHandshakeSuccessful; |
||
137 | |||
138 | // Callbacks |
||
139 | - (void)doCFCallback:(CFSocketCallBackType)type forSocket:(CFSocketRef)sock withAddress:(NSData *)address withData:(const void *)pData; |
||
140 | - (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream; |
||
141 | - (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream; |
||
142 | |||
143 | @end |
||
144 | |||
145 | static void MyCFSocketCallback(CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *); |
||
146 | static void MyCFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo); |
||
147 | static void MyCFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo); |
||
148 | |||
149 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
150 | #pragma mark - |
||
151 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
152 | |||
153 | /** |
||
154 | * The AsyncReadPacket encompasses the instructions for any given read. |
||
155 | * The content of a read packet allows the code to determine if we're: |
||
156 | * - reading to a certain length |
||
157 | * - reading to a certain separator |
||
158 | * - or simply reading the first chunk of available data |
||
159 | **/ |
||
160 | @interface AsyncReadPacket : NSObject |
||
161 | { |
||
162 | @public |
||
163 | NSMutableData *buffer; |
||
164 | CFIndex bytesDone; |
||
165 | NSTimeInterval timeout; |
||
166 | CFIndex maxLength; |
||
167 | long tag; |
||
168 | NSData *term; |
||
169 | BOOL readAllAvailableData; |
||
170 | } |
||
171 | - (id)initWithData:(NSMutableData *)d |
||
172 | timeout:(NSTimeInterval)t |
||
173 | tag:(long)i |
||
174 | readAllAvailable:(BOOL)a |
||
175 | terminator:(NSData *)e |
||
176 | maxLength:(CFIndex)m; |
||
177 | |||
178 | - (unsigned)readLengthForTerm; |
||
179 | |||
180 | - (unsigned)prebufferReadLengthForTerm; |
||
181 | - (CFIndex)searchForTermAfterPreBuffering:(CFIndex)numBytes; |
||
182 | @end |
||
183 | |||
184 | @implementation AsyncReadPacket |
||
185 | |||
186 | - (id)initWithData:(NSMutableData *)d |
||
187 | timeout:(NSTimeInterval)t |
||
188 | tag:(long)i |
||
189 | readAllAvailable:(BOOL)a |
||
190 | terminator:(NSData *)e |
||
191 | maxLength:(CFIndex)m |
||
192 | { |
||
193 | if((self = [super init])) |
||
194 | { |
||
195 | buffer = [d retain]; |
||
196 | timeout = t; |
||
197 | tag = i; |
||
198 | readAllAvailableData = a; |
||
199 | term = [e copy]; |
||
200 | bytesDone = 0; |
||
201 | maxLength = m; |
||
202 | } |
||
203 | return self; |
||
204 | } |
||
205 | |||
206 | /** |
||
207 | * For read packets with a set terminator, returns the safe length of data that can be read |
||
208 | * without going over a terminator, or the maxLength. |
||
209 | * |
||
210 | * It is assumed the terminator has not already been read. |
||
211 | **/ |
||
212 | - (unsigned)readLengthForTerm |
||
213 | { |
||
214 | NSAssert(term != nil, @"Searching for term in data when there is no term."); |
||
215 | |||
216 | // What we're going to do is look for a partial sequence of the terminator at the end of the buffer. |
||
217 | // If a partial sequence occurs, then we must assume the next bytes to arrive will be the rest of the term, |
||
218 | // and we can only read that amount. |
||
219 | // Otherwise, we're safe to read the entire length of the term. |
||
220 | |||
221 | unsigned result = [term length]; |
||
222 | |||
223 | // Shortcut when term is a single byte |
||
224 | if(result == 1) return result; |
||
225 | |||
226 | // i = index within buffer at which to check data |
||
227 | // j = length of term to check against |
||
228 | |||
229 | // Note: Beware of implicit casting rules |
||
230 | // This could give you -1: MAX(0, (0 - [term length] + 1)); |
||
231 | |||
232 | CFIndex i = MAX(0, (CFIndex)(bytesDone - [term length] + 1)); |
||
233 | CFIndex j = MIN([term length] - 1, bytesDone); |
||
234 | |||
235 | while(i < bytesDone) |
||
236 | { |
||
237 | const void *subBuffer = [buffer bytes] + i; |
||
238 | |||
239 | if(memcmp(subBuffer, [term bytes], j) == 0) |
||
240 | { |
||
241 | result = [term length] - j; |
||
242 | break; |
||
243 | } |
||
244 | |||
245 | i++; |
||
246 | j--; |
||
247 | } |
||
248 | |||
249 | if(maxLength > 0) |
||
250 | return MIN(result, (maxLength - bytesDone)); |
||
251 | else |
||
252 | return result; |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * Assuming pre-buffering is enabled, returns the amount of data that can be read |
||
257 | * without going over the maxLength. |
||
258 | **/ |
||
259 | - (unsigned)prebufferReadLengthForTerm |
||
260 | { |
||
261 | if(maxLength > 0) |
||
262 | return MIN(READALL_CHUNKSIZE, (maxLength - bytesDone)); |
||
263 | else |
||
264 | return READALL_CHUNKSIZE; |
||
265 | } |
||
266 | |||
267 | /** |
||
268 | * For read packets with a set terminator, scans the packet buffer for the term. |
||
269 | * It is assumed the terminator had not been fully read prior to the new bytes. |
||
270 | * |
||
271 | * If the term is found, the number of excess bytes after the term are returned. |
||
272 | * If the term is not found, this method will return -1. |
||
273 | * |
||
274 | * Note: A return value of zero means the term was found at the very end. |
||
275 | **/ |
||
276 | - (CFIndex)searchForTermAfterPreBuffering:(CFIndex)numBytes |
||
277 | { |
||
278 | NSAssert(term != nil, @"Searching for term in data when there is no term."); |
||
279 | |||
280 | // We try to start the search such that the first new byte read matches up with the last byte of the term. |
||
281 | // We continue searching forward after this until the term no longer fits into the buffer. |
||
282 | |||
283 | // Note: Beware of implicit casting rules |
||
284 | // This could give you -1: MAX(0, 1 - 1 - [term length] + 1); |
||
285 | |||
286 | CFIndex i = MAX(0, (CFIndex)(bytesDone - numBytes - [term length] + 1)); |
||
287 | |||
288 | while(i + [term length] <= bytesDone) |
||
289 | { |
||
290 | const void *subBuffer = [buffer bytes] + i; |
||
291 | |||
292 | if(memcmp(subBuffer, [term bytes], [term length]) == 0) |
||
293 | { |
||
294 | return bytesDone - (i + [term length]); |
||
295 | } |
||
296 | |||
297 | i++; |
||
298 | } |
||
299 | |||
300 | return -1; |
||
301 | } |
||
302 | |||
303 | - (void)dealloc |
||
304 | { |
||
305 | [buffer release]; |
||
306 | [term release]; |
||
307 | [super dealloc]; |
||
308 | } |
||
309 | |||
310 | @end |
||
311 | |||
312 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
313 | #pragma mark - |
||
314 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
315 | |||
316 | /** |
||
317 | * The AsyncWritePacket encompasses the instructions for any given write. |
||
318 | **/ |
||
319 | @interface AsyncWritePacket : NSObject |
||
320 | { |
||
321 | @public |
||
322 | NSData *buffer; |
||
323 | CFIndex bytesDone; |
||
324 | long tag; |
||
325 | NSTimeInterval timeout; |
||
326 | } |
||
327 | - (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; |
||
328 | @end |
||
329 | |||
330 | @implementation AsyncWritePacket |
||
331 | |||
332 | - (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i |
||
333 | { |
||
334 | if((self = [super init])) |
||
335 | { |
||
336 | buffer = [d retain]; |
||
337 | timeout = t; |
||
338 | tag = i; |
||
339 | bytesDone = 0; |
||
340 | } |
||
341 | return self; |
||
342 | } |
||
343 | |||
344 | - (void)dealloc |
||
345 | { |
||
346 | [buffer release]; |
||
347 | [super dealloc]; |
||
348 | } |
||
349 | |||
350 | @end |
||
351 | |||
352 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
353 | #pragma mark - |
||
354 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
355 | |||
356 | /** |
||
357 | * The AsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues. |
||
358 | * This class my be altered to support more than just TLS in the future. |
||
359 | **/ |
||
360 | @interface AsyncSpecialPacket : NSObject |
||
361 | { |
||
362 | @public |
||
363 | NSDictionary *tlsSettings; |
||
364 | } |
||
365 | - (id)initWithTLSSettings:(NSDictionary *)settings; |
||
366 | @end |
||
367 | |||
368 | @implementation AsyncSpecialPacket |
||
369 | |||
370 | - (id)initWithTLSSettings:(NSDictionary *)settings |
||
371 | { |
||
372 | if((self = [super init])) |
||
373 | { |
||
374 | tlsSettings = [settings copy]; |
||
375 | } |
||
376 | return self; |
||
377 | } |
||
378 | |||
379 | - (void)dealloc |
||
380 | { |
||
381 | [tlsSettings release]; |
||
382 | [super dealloc]; |
||
383 | } |
||
384 | |||
385 | @end |
||
386 | |||
387 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
388 | #pragma mark - |
||
389 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
390 | |||
391 | @implementation AsyncSocket |
||
392 | |||
393 | - (id)init |
||
394 | { |
||
395 | return [self initWithDelegate:nil userData:0]; |
||
396 | } |
||
397 | |||
398 | - (id)initWithDelegate:(id)delegate |
||
399 | { |
||
400 | return [self initWithDelegate:delegate userData:0]; |
||
401 | } |
||
402 | |||
403 | // Designated initializer. |
||
404 | - (id)initWithDelegate:(id)delegate userData:(long)userData |
||
405 | { |
||
406 | if((self = [super init])) |
||
407 | { |
||
408 | theFlags = DEFAULT_PREBUFFERING ? kEnablePreBuffering : 0; |
||
409 | theDelegate = delegate; |
||
410 | theUserData = userData; |
||
411 | |||
412 | theSocket4 = NULL; |
||
413 | theSource4 = NULL; |
||
414 | theSocket6 = NULL; |
||
415 | theSource6 = NULL; |
||
416 | theRunLoop = NULL; |
||
417 | theReadStream = NULL; |
||
418 | theWriteStream = NULL; |
||
419 | |||
420 | theConnectTimer = nil; |
||
421 | |||
422 | theReadQueue = [[NSMutableArray alloc] initWithCapacity:READQUEUE_CAPACITY]; |
||
423 | theCurrentRead = nil; |
||
424 | theReadTimer = nil; |
||
425 | |||
426 | partialReadBuffer = [[NSMutableData alloc] initWithCapacity:READALL_CHUNKSIZE]; |
||
427 | |||
428 | theWriteQueue = [[NSMutableArray alloc] initWithCapacity:WRITEQUEUE_CAPACITY]; |
||
429 | theCurrentWrite = nil; |
||
430 | theWriteTimer = nil; |
||
431 | |||
432 | // Socket context |
||
433 | NSAssert(sizeof(CFSocketContext) == sizeof(CFStreamClientContext), @"CFSocketContext != CFStreamClientContext"); |
||
434 | theContext.version = 0; |
||
435 | theContext.info = self; |
||
436 | theContext.retain = nil; |
||
437 | theContext.release = nil; |
||
438 | theContext.copyDescription = nil; |
||
439 | |||
440 | // Default run loop modes |
||
441 | theRunLoopModes = [[NSArray arrayWithObject:NSDefaultRunLoopMode] retain]; |
||
442 | } |
||
443 | return self; |
||
444 | } |
||
445 | |||
446 | // The socket may been initialized in a connected state and auto-released, so this should close it down cleanly. |
||
447 | - (void)dealloc |
||
448 | { |
||
449 | [self close]; |
||
450 | [theReadQueue release]; |
||
451 | [theWriteQueue release]; |
||
452 | [theRunLoopModes release]; |
||
453 | [partialReadBuffer release]; |
||
454 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
||
455 | [super dealloc]; |
||
456 | } |
||
457 | |||
458 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
459 | #pragma mark Accessors |
||
460 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
461 | |||
462 | - (long)userData |
||
463 | { |
||
464 | return theUserData; |
||
465 | } |
||
466 | |||
467 | - (void)setUserData:(long)userData |
||
468 | { |
||
469 | theUserData = userData; |
||
470 | } |
||
471 | |||
472 | - (id)delegate |
||
473 | { |
||
474 | return theDelegate; |
||
475 | } |
||
476 | |||
477 | - (void)setDelegate:(id)delegate |
||
478 | { |
||
479 | theDelegate = delegate; |
||
480 | } |
||
481 | |||
482 | - (BOOL)canSafelySetDelegate |
||
483 | { |
||
484 | return ([theReadQueue count] == 0 && [theWriteQueue count] == 0 && theCurrentRead == nil && theCurrentWrite == nil); |
||
485 | } |
||
486 | |||
487 | - (CFSocketRef)getCFSocket |
||
488 | { |
||
489 | if(theSocket4) |
||
490 | return theSocket4; |
||
491 | else |
||
492 | return theSocket6; |
||
493 | } |
||
494 | |||
495 | - (CFReadStreamRef)getCFReadStream |
||
496 | { |
||
497 | return theReadStream; |
||
498 | } |
||
499 | |||
500 | - (CFWriteStreamRef)getCFWriteStream |
||
501 | { |
||
502 | return theWriteStream; |
||
503 | } |
||
504 | |||
505 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
506 | #pragma mark Progress |
||
507 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
508 | |||
509 | - (float)progressOfReadReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total |
||
510 | { |
||
511 | // Check to make sure we're actually reading something right now, |
||
512 | // and that the read packet isn't an AsyncSpecialPacket (upgrade to TLS). |
||
513 | if (!theCurrentRead || ![theCurrentRead isKindOfClass:[AsyncReadPacket class]]) return NAN; |
||
514 | |||
515 | // It's only possible to know the progress of our read if we're reading to a certain length |
||
516 | // If we're reading to data, we of course have no idea when the data will arrive |
||
517 | // If we're reading to timeout, then we have no idea when the next chunk of data will arrive. |
||
518 | BOOL hasTotal = (theCurrentRead->readAllAvailableData == NO && theCurrentRead->term == nil); |
||
519 | |||
520 | CFIndex d = theCurrentRead->bytesDone; |
||
521 | CFIndex t = hasTotal ? [theCurrentRead->buffer length] : 0; |
||
522 | if (tag != NULL) *tag = theCurrentRead->tag; |
||
523 | if (done != NULL) *done = d; |
||
524 | if (total != NULL) *total = t; |
||
525 | float ratio = (float)d/(float)t; |
||
526 | return isnan(ratio) ? 1.0F : ratio; // 0 of 0 bytes is 100% done. |
||
527 | } |
||
528 | |||
529 | - (float)progressOfWriteReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total |
||
530 | { |
||
531 | // Check to make sure we're actually writing something right now, |
||
532 | // and that the write packet isn't an AsyncSpecialPacket (upgrade to TLS). |
||
533 | if (!theCurrentWrite || ![theCurrentWrite isKindOfClass:[AsyncWritePacket class]]) return NAN; |
||
534 | |||
535 | CFIndex d = theCurrentWrite->bytesDone; |
||
536 | CFIndex t = [theCurrentWrite->buffer length]; |
||
537 | if (tag != NULL) *tag = theCurrentWrite->tag; |
||
538 | if (done != NULL) *done = d; |
||
539 | if (total != NULL) *total = t; |
||
540 | return (float)d/(float)t; |
||
541 | } |
||
542 | |||
543 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
544 | #pragma mark Run Loop |
||
545 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
546 | |||
547 | - (void)runLoopAddSource:(CFRunLoopSourceRef)source |
||
548 | { |
||
549 | unsigned i, count = [theRunLoopModes count]; |
||
550 | for(i = 0; i < count; i++) |
||
551 | { |
||
552 | CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
||
553 | CFRunLoopAddSource(theRunLoop, source, runLoopMode); |
||
554 | } |
||
555 | } |
||
556 | |||
557 | - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source |
||
558 | { |
||
559 | unsigned i, count = [theRunLoopModes count]; |
||
560 | for(i = 0; i < count; i++) |
||
561 | { |
||
562 | CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
||
563 | CFRunLoopRemoveSource(theRunLoop, source, runLoopMode); |
||
564 | } |
||
565 | } |
||
566 | |||
567 | - (void)runLoopAddTimer:(NSTimer *)timer |
||
568 | { |
||
569 | unsigned i, count = [theRunLoopModes count]; |
||
570 | for(i = 0; i < count; i++) |
||
571 | { |
||
572 | CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
||
573 | CFRunLoopAddTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode); |
||
574 | } |
||
575 | } |
||
576 | |||
577 | - (void)runLoopRemoveTimer:(NSTimer *)timer |
||
578 | { |
||
579 | unsigned i, count = [theRunLoopModes count]; |
||
580 | for(i = 0; i < count; i++) |
||
581 | { |
||
582 | CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
||
583 | CFRunLoopRemoveTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode); |
||
584 | } |
||
585 | } |
||
586 | |||
587 | - (void)runLoopUnscheduleReadStream |
||
588 | { |
||
589 | unsigned i, count = [theRunLoopModes count]; |
||
590 | for(i = 0; i < count; i++) |
||
591 | { |
||
592 | CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
||
593 | CFReadStreamUnscheduleFromRunLoop(theReadStream, theRunLoop, runLoopMode); |
||
594 | } |
||
595 | CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL); |
||
596 | } |
||
597 | |||
598 | - (void)runLoopUnscheduleWriteStream |
||
599 | { |
||
600 | unsigned i, count = [theRunLoopModes count]; |
||
601 | for(i = 0; i < count; i++) |
||
602 | { |
||
603 | CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
||
604 | CFWriteStreamUnscheduleFromRunLoop(theWriteStream, theRunLoop, runLoopMode); |
||
605 | } |
||
606 | CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL); |
||
607 | } |
||
608 | |||
609 | |||
610 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
611 | #pragma mark Configuration |
||
612 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
613 | |||
614 | /** |
||
615 | * See the header file for a full explanation of pre-buffering. |
||
616 | **/ |
||
617 | - (void)enablePreBuffering |
||
618 | { |
||
619 | theFlags |= kEnablePreBuffering; |
||
620 | } |
||
621 | |||
622 | /** |
||
623 | * See the header file for a full explanation of this method. |
||
624 | **/ |
||
625 | - (BOOL)moveToRunLoop:(NSRunLoop *)runLoop |
||
626 | { |
||
627 | NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), |
||
628 | @"moveToRunLoop must be called from within the current RunLoop!"); |
||
629 | |||
630 | if(runLoop == nil) |
||
631 | { |
||
632 | return NO; |
||
633 | } |
||
634 | if(theRunLoop == [runLoop getCFRunLoop]) |
||
635 | { |
||
636 | return YES; |
||
637 | } |
||
638 | |||
639 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
||
640 | theFlags &= ~kDequeueReadScheduled; |
||
641 | theFlags &= ~kDequeueWriteScheduled; |
||
642 | |||
643 | if(theReadStream && theWriteStream) |
||
644 | { |
||
645 | [self runLoopUnscheduleReadStream]; |
||
646 | [self runLoopUnscheduleWriteStream]; |
||
647 | } |
||
648 | |||
649 | if(theSource4) [self runLoopRemoveSource:theSource4]; |
||
650 | if(theSource6) [self runLoopRemoveSource:theSource6]; |
||
651 | |||
652 | // We do not retain the timers - they get retained by the runloop when we add them as a source. |
||
653 | // Since we're about to remove them as a source, we retain now, and release again below. |
||
654 | [theReadTimer retain]; |
||
655 | [theWriteTimer retain]; |
||
656 | |||
657 | if(theReadTimer) [self runLoopRemoveTimer:theReadTimer]; |
||
658 | if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer]; |
||
659 | |||
660 | theRunLoop = [runLoop getCFRunLoop]; |
||
661 | |||
662 | if(theReadTimer) [self runLoopAddTimer:theReadTimer]; |
||
663 | if(theWriteTimer) [self runLoopAddTimer:theWriteTimer]; |
||
664 | |||
665 | // Release timers since we retained them above |
||
666 | [theReadTimer release]; |
||
667 | [theWriteTimer release]; |
||
668 | |||
669 | if(theSource4) [self runLoopAddSource:theSource4]; |
||
670 | if(theSource6) [self runLoopAddSource:theSource6]; |
||
671 | |||
672 | if(theReadStream && theWriteStream) |
||
673 | { |
||
674 | if(![self attachStreamsToRunLoop:runLoop error:nil]) |
||
675 | { |
||
676 | return NO; |
||
677 | } |
||
678 | } |
||
679 | |||
680 | [runLoop performSelector:@selector(maybeDequeueRead) target:self argument:nil order:0 modes:theRunLoopModes]; |
||
681 | [runLoop performSelector:@selector(maybeDequeueWrite) target:self argument:nil order:0 modes:theRunLoopModes]; |
||
682 | [runLoop performSelector:@selector(maybeScheduleDisconnect) target:self argument:nil order:0 modes:theRunLoopModes]; |
||
683 | |||
684 | return YES; |
||
685 | } |
||
686 | |||
687 | /** |
||
688 | * See the header file for a full explanation of this method. |
||
689 | **/ |
||
690 | - (BOOL)setRunLoopModes:(NSArray *)runLoopModes |
||
691 | { |
||
692 | NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), |
||
693 | @"setRunLoopModes must be called from within the current RunLoop!"); |
||
694 | |||
695 | if([runLoopModes count] == 0) |
||
696 | { |
||
697 | return NO; |
||
698 | } |
||
699 | if([theRunLoopModes isEqualToArray:runLoopModes]) |
||
700 | { |
||
701 | return YES; |
||
702 | } |
||
703 | |||
704 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; |
||
705 | theFlags &= ~kDequeueReadScheduled; |
||
706 | theFlags &= ~kDequeueWriteScheduled; |
||
707 | |||
708 | if(theReadStream && theWriteStream) |
||
709 | { |
||
710 | [self runLoopUnscheduleReadStream]; |
||
711 | [self runLoopUnscheduleWriteStream]; |
||
712 | } |
||
713 | |||
714 | if(theSource4) [self runLoopRemoveSource:theSource4]; |
||
715 | if(theSource6) [self runLoopRemoveSource:theSource6]; |
||
716 | |||
717 | // We do not retain the timers - they get retained by the runloop when we add them as a source. |
||
718 | // Since we're about to remove them as a source, we retain now, and release again below. |
||
719 | [theReadTimer retain]; |
||
720 | [theWriteTimer retain]; |
||
721 | |||
722 | if(theReadTimer) [self runLoopRemoveTimer:theReadTimer]; |
||
723 | if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer]; |
||
724 | |||
725 | [theRunLoopModes release]; |
||
726 | theRunLoopModes = [runLoopModes copy]; |
||
727 | |||
728 | if(theReadTimer) [self runLoopAddTimer:theReadTimer]; |
||
729 | if(theWriteTimer) [self runLoopAddTimer:theWriteTimer]; |
||
730 | |||
731 | // Release timers since we retained them above |
||
732 | [theReadTimer release]; |
||
733 | [theWriteTimer release]; |
||
734 | |||
735 | if(theSource4) [self runLoopAddSource:theSource4]; |
||
736 | if(theSource6) [self runLoopAddSource:theSource6]; |
||
737 | |||
738 | if(theReadStream && theWriteStream) |
||
739 | { |
||
740 | // Note: theRunLoop variable is a CFRunLoop, and NSRunLoop is NOT toll-free bridged with CFRunLoop. |
||
741 | // So we cannot pass theRunLoop to the method below, which is expecting a NSRunLoop parameter. |
||
742 | // Instead we pass nil, which will result in the method properly using the current run loop. |
||
743 | |||
744 | if(![self attachStreamsToRunLoop:nil error:nil]) |
||
745 | { |
||
746 | return NO; |
||
747 | } |
||
748 | } |
||
749 | |||
750 | [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; |
||
751 | [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; |
||
752 | [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; |
||
753 | |||
754 | return YES; |
||
755 | } |
||
756 | |||
757 | - (NSArray *)runLoopModes |
||
758 | { |
||
759 | return [[theRunLoopModes retain] autorelease]; |
||
760 | } |
||
761 | |||
762 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
763 | #pragma mark Accepting |
||
764 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
765 | |||
766 | - (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr |
||
767 | { |
||
768 | return [self acceptOnInterface:nil port:port error:errPtr]; |
||
769 | } |
||
770 | |||
771 | /** |
||
772 | * To accept on a certain interface, pass the address to accept on. |
||
773 | * To accept on any interface, pass nil or an empty string. |
||
774 | * To accept only connections from localhost pass "localhost" or "loopback". |
||
775 | **/ |
||
776 | - (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr |
||
777 | { |
||
778 | if (theDelegate == NULL) |
||
779 | { |
||
780 | [NSException raise:AsyncSocketException |
||
781 | format:@"Attempting to accept without a delegate. Set a delegate first."]; |
||
782 | } |
||
783 | |||
784 | if (theSocket4 != NULL || theSocket6 != NULL) |
||
785 | { |
||
786 | [NSException raise:AsyncSocketException |
||
787 | format:@"Attempting to accept while connected or accepting connections. Disconnect first."]; |
||
788 | } |
||
789 | |||
790 | // Set up the listen sockaddr structs if needed. |
||
791 | |||
792 | NSData *address4 = nil, *address6 = nil; |
||
793 | if(interface == nil || ([interface length] == 0)) |
||
794 | { |
||
795 | // Accept on ANY address |
||
796 | struct sockaddr_in nativeAddr4; |
||
797 | nativeAddr4.sin_len = sizeof(struct sockaddr_in); |
||
798 | nativeAddr4.sin_family = AF_INET; |
||
799 | nativeAddr4.sin_port = htons(port); |
||
800 | nativeAddr4.sin_addr.s_addr = htonl(INADDR_ANY); |
||
801 | memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); |
||
802 | |||
803 | struct sockaddr_in6 nativeAddr6; |
||
804 | nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); |
||
805 | nativeAddr6.sin6_family = AF_INET6; |
||
806 | nativeAddr6.sin6_port = htons(port); |
||
807 | nativeAddr6.sin6_flowinfo = 0; |
||
808 | nativeAddr6.sin6_addr = in6addr_any; |
||
809 | nativeAddr6.sin6_scope_id = 0; |
||
810 | |||
811 | // Wrap the native address structures for CFSocketSetAddress. |
||
812 | address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; |
||
813 | address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; |
||
814 | } |
||
815 | else if([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"]) |
||
816 | { |
||
817 | // Accept only on LOOPBACK address |
||
818 | struct sockaddr_in nativeAddr4; |
||
819 | nativeAddr4.sin_len = sizeof(struct sockaddr_in); |
||
820 | nativeAddr4.sin_family = AF_INET; |
||
821 | nativeAddr4.sin_port = htons(port); |
||
822 | nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
||
823 | memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); |
||
824 | |||
825 | struct sockaddr_in6 nativeAddr6; |
||
826 | nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); |
||
827 | nativeAddr6.sin6_family = AF_INET6; |
||
828 | nativeAddr6.sin6_port = htons(port); |
||
829 | nativeAddr6.sin6_flowinfo = 0; |
||
830 | nativeAddr6.sin6_addr = in6addr_loopback; |
||
831 | nativeAddr6.sin6_scope_id = 0; |
||
832 | |||
833 | // Wrap the native address structures for CFSocketSetAddress. |
||
834 | address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; |
||
835 | address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; |
||
836 | } |
||
837 | else |
||
838 | { |
||
839 | NSString *portStr = [NSString stringWithFormat:@"%hu", port]; |
||
840 | |||
841 | #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 |
||
842 | @synchronized (getaddrinfoLock) |
||
843 | #endif |
||
844 | { |
||
845 | struct addrinfo hints, *res, *res0; |
||
846 | |||
847 | memset(&hints, 0, sizeof(hints)); |
||
848 | hints.ai_family = PF_UNSPEC; |
||
849 | hints.ai_socktype = SOCK_STREAM; |
||
850 | hints.ai_protocol = IPPROTO_TCP; |
||
851 | hints.ai_flags = AI_PASSIVE; |
||
852 | |||
853 | int error = getaddrinfo([interface UTF8String], [portStr UTF8String], &hints, &res0); |
||
854 | |||
855 | if(error) |
||
856 | { |
||
857 | if(errPtr) |
||
858 | { |
||
859 | NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding]; |
||
860 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
||
861 | |||
862 | *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info]; |
||
863 | } |
||
864 | } |
||
865 | |||
866 | for(res = res0; res; res = res->ai_next) |
||
867 | { |
||
868 | if(!address4 && (res->ai_family == AF_INET)) |
||
869 | { |
||
870 | // Found IPv4 address |
||
871 | // Wrap the native address structures for CFSocketSetAddress. |
||
872 | address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; |
||
873 | } |
||
874 | else if(!address6 && (res->ai_family == AF_INET6)) |
||
875 | { |
||
876 | // Found IPv6 address |
||
877 | // Wrap the native address structures for CFSocketSetAddress. |
||
878 | address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; |
||
879 | } |
||
880 | } |
||
881 | freeaddrinfo(res0); |
||
882 | } |
||
883 | |||
884 | if(!address4 && !address6) return NO; |
||
885 | } |
||
886 | |||
887 | // Create the sockets. |
||
888 | |||
889 | if (address4) |
||
890 | { |
||
891 | theSocket4 = [self newAcceptSocketForAddress:address4 error:errPtr]; |
||
892 | if (theSocket4 == NULL) goto Failed; |
||
893 | } |
||
894 | |||
895 | if (address6) |
||
896 | { |
||
897 | theSocket6 = [self newAcceptSocketForAddress:address6 error:errPtr]; |
||
898 | |||
899 | // Note: The iPhone doesn't currently support IPv6 |
||
900 | |||
901 | #if !TARGET_OS_IPHONE |
||
902 | if (theSocket6 == NULL) goto Failed; |
||
903 | #endif |
||
904 | } |
||
905 | |||
906 | // Attach the sockets to the run loop so that callback methods work |
||
907 | |||
908 | [self attachSocketsToRunLoop:nil error:nil]; |
||
909 | |||
910 | // Set the SO_REUSEADDR flags. |
||
911 | |||
912 | int reuseOn = 1; |
||
913 | if (theSocket4) setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); |
||
914 | if (theSocket6) setsockopt(CFSocketGetNative(theSocket6), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); |
||
915 | |||
916 | // Set the local bindings which causes the sockets to start listening. |
||
917 | |||
918 | CFSocketError err; |
||
919 | if (theSocket4) |
||
920 | { |
||
921 | err = CFSocketSetAddress (theSocket4, (CFDataRef)address4); |
||
922 | if (err != kCFSocketSuccess) goto Failed; |
||
923 | |||
924 | //NSLog(@"theSocket4: %hu", [self localPort:theSocket4]); |
||
925 | } |
||
926 | |||
927 | if(port == 0 && theSocket4 && theSocket6) |
||
928 | { |
||
929 | // The user has passed in port 0, which means he wants to allow the kernel to choose the port for them |
||
930 | // However, the kernel will choose a different port for both theSocket4 and theSocket6 |
||
931 | // So we grab the port the kernel choose for theSocket4, and set it as the port for theSocket6 |
||
932 | UInt16 chosenPort = [self localPort:theSocket4]; |
||
933 | |||
934 | struct sockaddr_in6 *pSockAddr6 = (struct sockaddr_in6 *)[address6 bytes]; |
||
935 | pSockAddr6->sin6_port = htons(chosenPort); |
||
936 | } |
||
937 | |||
938 | if (theSocket6) |
||
939 | { |
||
940 | err = CFSocketSetAddress (theSocket6, (CFDataRef)address6); |
||
941 | if (err != kCFSocketSuccess) goto Failed; |
||
942 | |||
943 | //NSLog(@"theSocket6: %hu", [self localPort:theSocket6]); |
||
944 | } |
||
945 | |||
946 | theFlags |= kDidPassConnectMethod; |
||
947 | return YES; |
||
948 | |||
949 | Failed: |
||
950 | if(errPtr) *errPtr = [self getSocketError]; |
||
951 | if(theSocket4 != NULL) |
||
952 | { |
||
953 | CFSocketInvalidate(theSocket4); |
||
954 | CFRelease(theSocket4); |
||
955 | theSocket4 = NULL; |
||
956 | } |
||
957 | if(theSocket6 != NULL) |
||
958 | { |
||
959 | CFSocketInvalidate(theSocket6); |
||
960 | CFRelease(theSocket6); |
||
961 | theSocket6 = NULL; |
||
962 | } |
||
963 | return NO; |
||
964 | } |
||
965 | |||
966 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
967 | #pragma mark Connecting |
||
968 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
969 | |||
970 | - (BOOL)connectToHost:(NSString*)hostname onPort:(UInt16)port error:(NSError **)errPtr |
||
971 | { |
||
972 | return [self connectToHost:hostname onPort:port withTimeout:-1 error:errPtr]; |
||
973 | } |
||
974 | |||
975 | /** |
||
976 | * This method creates an initial CFReadStream and CFWriteStream to the given host on the given port. |
||
977 | * The connection is then opened, and the corresponding CFSocket will be extracted after the connection succeeds. |
||
978 | * |
||
979 | * Thus the delegate will have access to the CFReadStream and CFWriteStream prior to connection, |
||
980 | * specifically in the onSocketWillConnect: method. |
||
981 | **/ |
||
982 | - (BOOL)connectToHost:(NSString *)hostname |
||
983 | onPort:(UInt16)port |
||
984 | withTimeout:(NSTimeInterval)timeout |
||
985 | error:(NSError **)errPtr |
||
986 | { |
||
987 | if(theDelegate == NULL) |
||
988 | { |
||
989 | [NSException raise:AsyncSocketException |
||
990 | format:@"Attempting to connect without a delegate. Set a delegate first."]; |
||
991 | } |
||
992 | |||
993 | if(theSocket4 != NULL || theSocket6 != NULL) |
||
994 | { |
||
995 | [NSException raise:AsyncSocketException |
||
996 | format:@"Attempting to connect while connected or accepting connections. Disconnect first."]; |
||
997 | } |
||
998 | |||
999 | if(![self createStreamsToHost:hostname onPort:port error:errPtr]) goto Failed; |
||
1000 | if(![self attachStreamsToRunLoop:nil error:errPtr]) goto Failed; |
||
1001 | if(![self configureStreamsAndReturnError:errPtr]) goto Failed; |
||
1002 | if(![self openStreamsAndReturnError:errPtr]) goto Failed; |
||
1003 | |||
1004 | [self startConnectTimeout:timeout]; |
||
1005 | theFlags |= kDidPassConnectMethod; |
||
1006 | |||
1007 | return YES; |
||
1008 | |||
1009 | Failed: |
||
1010 | [self close]; |
||
1011 | return NO; |
||
1012 | } |
||
1013 | |||
1014 | - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr |
||
1015 | { |
||
1016 | return [self connectToAddress:remoteAddr withTimeout:-1 error:errPtr]; |
||
1017 | } |
||
1018 | |||
1019 | /** |
||
1020 | * This method creates an initial CFSocket to the given address. |
||
1021 | * The connection is then opened, and the corresponding CFReadStream and CFWriteStream will be |
||
1022 | * created from the low-level sockets after the connection succeeds. |
||
1023 | * |
||
1024 | * Thus the delegate will have access to the CFSocket and CFSocketNativeHandle (BSD socket) prior to connection, |
||
1025 | * specifically in the onSocketWillConnect: method. |
||
1026 | * |
||
1027 | * Note: The NSData parameter is expected to be a sockaddr structure. For example, an NSData object returned from |
||
1028 | * NSNetservice addresses method. |
||
1029 | * If you have an existing struct sockaddr you can convert it to an NSData object like so: |
||
1030 | * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; |
||
1031 | * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; |
||
1032 | **/ |
||
1033 | - (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr |
||
1034 | { |
||
1035 | if (theDelegate == NULL) |
||
1036 | { |
||
1037 | [NSException raise:AsyncSocketException |
||
1038 | format:@"Attempting to connect without a delegate. Set a delegate first."]; |
||
1039 | } |
||
1040 | |||
1041 | if (theSocket4 != NULL || theSocket6 != NULL) |
||
1042 | { |
||
1043 | [NSException raise:AsyncSocketException |
||
1044 | format:@"Attempting to connect while connected or accepting connections. Disconnect first."]; |
||
1045 | } |
||
1046 | |||
1047 | if(![self createSocketForAddress:remoteAddr error:errPtr]) goto Failed; |
||
1048 | if(![self attachSocketsToRunLoop:nil error:errPtr]) goto Failed; |
||
1049 | if(![self configureSocketAndReturnError:errPtr]) goto Failed; |
||
1050 | if(![self connectSocketToAddress:remoteAddr error:errPtr]) goto Failed; |
||
1051 | |||
1052 | [self startConnectTimeout:timeout]; |
||
1053 | theFlags |= kDidPassConnectMethod; |
||
1054 | |||
1055 | return YES; |
||
1056 | |||
1057 | Failed: |
||
1058 | [self close]; |
||
1059 | return NO; |
||
1060 | } |
||
1061 | |||
1062 | - (void)startConnectTimeout:(NSTimeInterval)timeout |
||
1063 | { |
||
1064 | if(timeout >= 0.0) |
||
1065 | { |
||
1066 | theConnectTimer = [NSTimer timerWithTimeInterval:timeout |
||
1067 | target:self |
||
1068 | selector:@selector(doConnectTimeout:) |
||
1069 | userInfo:nil |
||
1070 | repeats:NO]; |
||
1071 | [self runLoopAddTimer:theConnectTimer]; |
||
1072 | } |
||
1073 | } |
||
1074 | |||
1075 | - (void)endConnectTimeout |
||
1076 | { |
||
1077 | [theConnectTimer invalidate]; |
||
1078 | theConnectTimer = nil; |
||
1079 | } |
||
1080 | |||
1081 | - (void)doConnectTimeout:(NSTimer *)timer |
||
1082 | { |
||
1083 | [self endConnectTimeout]; |
||
1084 | [self closeWithError:[self getConnectTimeoutError]]; |
||
1085 | } |
||
1086 | |||
1087 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
1088 | #pragma mark Socket Implementation |
||
1089 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
1090 | |||
1091 | /** |
||
1092 | * Creates the accept sockets. |
||
1093 | * Returns true if either IPv4 or IPv6 is created. |
||
1094 | * If either is missing, an error is returned (even though the method may return true). |
||
1095 | **/ |
||
1096 | - (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr |
||
1097 | { |
||
1098 | struct sockaddr *pSockAddr = (struct sockaddr *)[addr bytes]; |
||
1099 | int addressFamily = pSockAddr->sa_family; |
||
1100 | |||
1101 | CFSocketRef theSocket = CFSocketCreate(kCFAllocatorDefault, |
||
1102 | addressFamily, |
||
1103 | SOCK_STREAM, |
||
1104 | 0, |
||
1105 | kCFSocketAcceptCallBack, // Callback flags |
||
1106 | (CFSocketCallBack)&MyCFSocketCallback, // Callback method |
||
1107 | &theContext); |
||
1108 | |||
1109 | if(theSocket == NULL) |
||
1110 | { |
||
1111 | if(errPtr) *errPtr = [self getSocketError]; |
||
1112 | } |
||
1113 | |||
1114 | return theSocket; |
||
1115 | } |
||
1116 | |||
1117 | - (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr |
||
1118 | { |
||
1119 | struct sockaddr *pSockAddr = (struct sockaddr *)[remoteAddr bytes]; |
||
1120 | |||
1121 | if(pSockAddr->sa_family == AF_INET) |
||
1122 | { |
||
1123 | theSocket4 = CFSocketCreate(NULL, // Default allocator |
||
1124 | PF_INET, // Protocol Family |
||
1125 | SOCK_STREAM, // Socket Type |
||
1126 | IPPROTO_TCP, // Protocol |
||
1127 | kCFSocketConnectCallBack, // Callback flags |
||
1128 | (CFSocketCallBack)&MyCFSocketCallback, // Callback method |
||
1129 | &theContext); // Socket Context |
||
1130 | |||
1131 | if(theSocket4 == NULL) |
||
1132 | { |
||
1133 | if (errPtr) *errPtr = [self getSocketError]; |
||
1134 | return NO; |
||
1135 | } |
||
1136 | } |
||
1137 | else if(pSockAddr->sa_family == AF_INET6) |
||
1138 | { |
||
1139 | theSocket6 = CFSocketCreate(NULL, // Default allocator |
||
1140 | PF_INET6, // Protocol Family |
||
1141 | SOCK_STREAM, // Socket Type |
||
1142 | IPPROTO_TCP, // Protocol |
||
1143 | kCFSocketConnectCallBack, // Callback flags |
||
1144 | (CFSocketCallBack)&MyCFSocketCallback, // Callback method |
||
1145 | &theContext); // Socket Context |
||
1146 | |||
1147 | if(theSocket6 == NULL) |
||
1148 | { |
||
1149 | if (errPtr) *errPtr = [self getSocketError]; |
||
1150 | return NO; |
||
1151 | } |
||
1152 | } |
||
1153 | else |
||
1154 | { |
||
1155 | if (errPtr) *errPtr = [self getSocketError]; |
||
1156 | return NO; |
||
1157 | } |
||
1158 | |||
1159 | return YES; |
||
1160 | } |
||
1161 | |||
1162 | /** |
||
1163 | * Adds the CFSocket's to the run-loop so that callbacks will work properly. |
||
1164 | **/ |
||
1165 | - (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr |
||
1166 | { |
||
1167 | // Get the CFRunLoop to which the socket should be attached. |
||
1168 | theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop]; |
||
1169 | |||
1170 | if(theSocket4) |
||
1171 | { |
||
1172 | theSource4 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket4, 0); |
||
1173 | [self runLoopAddSource:theSource4]; |
||
1174 | } |
||
1175 | |||
1176 | if(theSocket6) |
||
1177 | { |
||
1178 | theSource6 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket6, 0); |
||
1179 | [self runLoopAddSource:theSource6]; |
||
1180 | } |
||
1181 | |||
1182 | return YES; |
||
1183 | } |
||
1184 | |||
1185 | /** |
||
1186 | * Allows the delegate method to configure the CFSocket or CFNativeSocket as desired before we connect. |
||
1187 | * Note that the CFReadStream and CFWriteStream will not be available until after the connection is opened. |
||
1188 | **/ |
||
1189 | - (BOOL)configureSocketAndReturnError:(NSError **)errPtr |
||
1190 | { |
||
1191 | // Call the delegate method for further configuration. |
||
1192 | if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)]) |
||
1193 | { |
||
1194 | if([theDelegate onSocketWillConnect:self] == NO) |
||
1195 | { |
||
1196 | if (errPtr) *errPtr = [self getAbortError]; |
||
1197 | return NO; |
||
1198 | } |
||
1199 | } |
||
1200 | return YES; |
||
1201 | } |
||
1202 | |||
1203 | - (BOOL)connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr |
||
1204 | { |
||
1205 | // Start connecting to the given address in the background |
||
1206 | // The MyCFSocketCallback method will be called when the connection succeeds or fails |
||
1207 | if(theSocket4) |
||
1208 | { |
||
1209 | CFSocketError err = CFSocketConnectToAddress(theSocket4, (CFDataRef)remoteAddr, -1); |
||
1210 | if(err != kCFSocketSuccess) |
||
1211 | { |
||
1212 | if (errPtr) *errPtr = [self getSocketError]; |
||
1213 | return NO; |
||
1214 | } |
||
1215 | } |
||
1216 | else if(theSocket6) |
||
1217 | { |
||
1218 | CFSocketError err = CFSocketConnectToAddress(theSocket6, (CFDataRef)remoteAddr, -1); |
||
1219 | if(err != kCFSocketSuccess) |
||
1220 | { |
||
1221 | if (errPtr) *errPtr = [self getSocketError]; |
||
1222 | return NO; |
||
1223 | } |
||
1224 | } |
||
1225 | |||
1226 | return YES; |
||
1227 | } |
||
1228 | |||
1229 | /** |
||
1230 | * Attempt to make the new socket. |
||
1231 | * If an error occurs, ignore this event. |
||
1232 | **/ |
||
1233 | - (void)doAcceptWithSocket:(CFSocketNativeHandle)newNative |
||
1234 | { |
||
1235 | // New socket inherits same delegate and run loop modes. |
||
1236 | // Note: We use [self class] to support subclassing AsyncSocket. |
||
1237 | AsyncSocket *newSocket = [[[[self class] alloc] initWithDelegate:theDelegate] autorelease]; |
||
1238 | [newSocket setRunLoopModes:theRunLoopModes]; |
||
1239 | |||
1240 | if(newSocket) |
||
1241 | { |
||
1242 | if ([theDelegate respondsToSelector:@selector(onSocket:didAcceptNewSocket:)]) |
||
1243 | [theDelegate onSocket:self didAcceptNewSocket:newSocket]; |
||
1244 | |||
1245 | NSRunLoop *runLoop = nil; |
||
1246 | if ([theDelegate respondsToSelector:@selector(onSocket:wantsRunLoopForNewSocket:)]) |
||
1247 | runLoop = [theDelegate onSocket:self wantsRunLoopForNewSocket:newSocket]; |
||
1248 | |||
1249 | BOOL pass = YES; |
||
1250 | |||
1251 | if(pass && ![newSocket createStreamsFromNative:newNative error:nil]) pass = NO; |
||
1252 | if(pass && ![newSocket attachStreamsToRunLoop:runLoop error:nil]) pass = NO; |
||
1253 | if(pass && ![newSocket configureStreamsAndReturnError:nil]) pass = NO; |
||
1254 | if(pass && ![newSocket openStreamsAndReturnError:nil]) pass = NO; |
||
1255 | |||
1256 | if(pass) |
||
1257 | newSocket->theFlags |= kDidPassConnectMethod; |
||
1258 | else { |
||
1259 | // No NSError, but errors will still get logged from the above functions. |
||
1260 | [newSocket close]; |
||
1261 | } |
||
1262 | |||
1263 | } |
||
1264 | } |
||
1265 | |||
1266 | /** |
||
1267 | * Description forthcoming... |
||
1268 | **/ |
||
1269 | - (void)doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)socketError |
||
1270 | { |
||
1271 | NSParameterAssert ((sock == theSocket4) || (sock == theSocket6)); |
||
1272 | |||
1273 | if(socketError == kCFSocketTimeout || socketError == kCFSocketError) |
||
1274 | { |
||
1275 | [self closeWithError:[self getSocketError]]; |
||
1276 | return; |
||
1277 | } |
||
1278 | |||
1279 | // Get the underlying native (BSD) socket |
||
1280 | CFSocketNativeHandle nativeSocket = CFSocketGetNative(sock); |
||
1281 | |||
1282 | // Setup the socket so that invalidating the socket will not close the native socket |
||
1283 | CFSocketSetSocketFlags(sock, 0); |
||
1284 | |||
1285 | // Invalidate and release the CFSocket - All we need from here on out is the nativeSocket |
||
1286 | // Note: If we don't invalidate the socket (leaving the native socket open) |
||
1287 | // then theReadStream and theWriteStream won't function properly. |
||
1288 | // Specifically, their callbacks won't work, with the exception of kCFStreamEventOpenCompleted. |
||
1289 | // I'm not entirely sure why this is, but I'm guessing that events on the socket fire to the CFSocket we created, |
||
1290 | // as opposed to the CFReadStream/CFWriteStream. |
||
1291 | |||
1292 | CFSocketInvalidate(sock); |
||
1293 | CFRelease(sock); |
||
1294 | theSocket4 = NULL; |
||
1295 | theSocket6 = NULL; |
||
1296 | |||
1297 | NSError *err; |
||
1298 | BOOL pass = YES; |
||
1299 | |||
1300 | if(pass && ![self createStreamsFromNative:nativeSocket error:&err]) pass = NO; |
||
1301 | if(pass && ![self attachStreamsToRunLoop:nil error:&err]) pass = NO; |
||
1302 | if(pass && ![self openStreamsAndReturnError:&err]) pass = NO; |
||
1303 | |||
1304 | if(!pass) |
||
1305 | { |
||
1306 | [self closeWithError:err]; |
||
1307 | } |
||
1308 | } |
||
1309 | |||
1310 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
1311 | #pragma mark Stream Implementation |
||
1312 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
1313 | |||
1314 | /** |
||
1315 | * Creates the CFReadStream and CFWriteStream from the given native socket. |
||
1316 | * The CFSocket may be extracted from either stream after the streams have been opened. |
||
1317 | * |
||
1318 | * Note: The given native socket must already be connected! |
||
1319 | **/ |
||
1320 | - (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr |
||
1321 | { |
||
1322 | // Create the socket & streams. |
||
1323 | CFStreamCreatePairWithSocket(kCFAllocatorDefault, native, &theReadStream, &theWriteStream); |
||
1324 | if (theReadStream == NULL || theWriteStream == NULL) |
||
1325 | { |
||
1326 | NSError *err = [self getStreamError]; |
||
1327 | |||
1328 | NSLog (@"AsyncSocket %p couldn't create streams from accepted socket: %@", self, err); |
||
1329 | |||
1330 | if (errPtr) *errPtr = err; |
||
1331 | return NO; |
||
1332 | } |
||
1333 | |||
1334 | // Ensure the CF & BSD socket is closed when the streams are closed. |
||
1335 | CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); |
||
1336 | CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); |
||
1337 | |||
1338 | return YES; |
||
1339 | } |
||
1340 | |||
1341 | /** |
||
1342 | * Creates the CFReadStream and CFWriteStream from the given hostname and port number. |
||
1343 | * The CFSocket may be extracted from either stream after the streams have been opened. |
||
1344 | **/ |
||
1345 | - (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr |
||
1346 | { |
||
1347 | // Create the socket & streams. |
||
1348 | CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)hostname, port, &theReadStream, &theWriteStream); |
||
1349 | if (theReadStream == NULL || theWriteStream == NULL) |
||
1350 | { |
||
1351 | if (errPtr) *errPtr = [self getStreamError]; |
||
1352 | return NO; |
||
1353 | } |
||
1354 | |||
1355 | // Ensure the CF & BSD socket is closed when the streams are closed. |
||
1356 | CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); |
||
1357 | CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); |
||
1358 | |||
1359 | return YES; |
||
1360 | } |
||
1361 | |||
1362 | - (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr |
||
1363 | { |
||
1364 | // Get the CFRunLoop to which the socket should be attached. |
||
1365 | theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop]; |
||
1366 | |||
1367 | // Setup read stream callbacks |
||
1368 | |||
1369 | CFOptionFlags readStreamEvents = kCFStreamEventHasBytesAvailable | |
||
1370 | kCFStreamEventErrorOccurred | |
||
1371 | kCFStreamEventEndEncountered | |
||
1372 | kCFStreamEventOpenCompleted; |
||
1373 | |||
1374 | if (!CFReadStreamSetClient(theReadStream, |
||
1375 | readStreamEvents, |
||
1376 | (CFReadStreamClientCallBack)&MyCFReadStreamCallback, |
||
1377 | (CFStreamClientContext *)(&theContext))) |
||
1378 | { |
||
1379 | NSError *err = [self getStreamError]; |
||
1380 | |||
1381 | NSLog (@"AsyncSocket %p couldn't attach read stream to run-loop,", self); |
||
1382 | NSLog (@"Error: %@", err); |
||
1383 | |||
1384 | if (errPtr) *errPtr = err; |
||
1385 | return NO; |
||
1386 | } |
||
1387 | |||
1388 | // Setup write stream callbacks |
||
1389 | |||
1390 | CFOptionFlags writeStreamEvents = kCFStreamEventCanAcceptBytes | |
||
1391 | kCFStreamEventErrorOccurred | |
||
1392 | kCFStreamEventEndEncountered | |
||
1393 | kCFStreamEventOpenCompleted; |
||
1394 | |||
1395 | if (!CFWriteStreamSetClient (theWriteStream, |
||
1396 | writeStreamEvents, |
||
1397 | (CFWriteStreamClientCallBack)&MyCFWriteStreamCallback, |
||
1398 | (CFStreamClientContext *)(&theContext))) |
||
1399 | { |
||
1400 | NSError *err = [self getStreamError]; |
||
1401 | |||
1402 | NSLog (@"AsyncSocket %p couldn't attach write stream to run-loop,", self); |
||
1403 | NSLog (@"Error: %@", err); |
||
1404 | |||
1405 | if (errPtr) *errPtr = err; |
||
1406 | return NO; |
||
1407 | } |
||
1408 | |||
1409 | // Add read and write streams to run loop |
||
1410 | |||
1411 | unsigned i, count = [theRunLoopModes count]; |
||
1412 | for(i = 0; i < count; i++) |
||
1413 | { |
||
1414 | CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
||
1415 | CFReadStreamScheduleWithRunLoop(theReadStream, theRunLoop, runLoopMode); |
||
1416 | CFWriteStreamScheduleWithRunLoop(theWriteStream, theRunLoop, runLoopMode); |
||
1417 | } |
||
1418 | |||
1419 | return YES; |
||
1420 | } |
||
1421 | |||
1422 | /** |
||
1423 | * Allows the delegate method to configure the CFReadStream and/or CFWriteStream as desired before we connect. |
||
1424 | * Note that the CFSocket and CFNativeSocket will not be available until after the connection is opened. |
||
1425 | **/ |
||
1426 | - (BOOL)configureStreamsAndReturnError:(NSError **)errPtr |
||
1427 | { |
||
1428 | // Call the delegate method for further configuration. |
||
1429 | if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)]) |
||
1430 | { |
||
1431 | if([theDelegate onSocketWillConnect:self] == NO) |
||
1432 | { |
||
1433 | if (errPtr) *errPtr = [self getAbortError]; |
||
1434 | return NO; |
||
1435 | } |
||
1436 | } |
||
1437 | return YES; |
||
1438 | } |
||
1439 | |||
1440 | - (BOOL)openStreamsAndReturnError:(NSError **)errPtr |
||
1441 | { |
||
1442 | BOOL pass = YES; |
||
1443 | |||
1444 | if(pass && !CFReadStreamOpen (theReadStream)) |
||
1445 | { |
||
1446 | NSLog (@"AsyncSocket %p couldn't open read stream,", self); |
||
1447 | pass = NO; |
||
1448 | } |
||
1449 | |||
1450 | if(pass && !CFWriteStreamOpen (theWriteStream)) |
||
1451 | { |
||
1452 | NSLog (@"AsyncSocket %p couldn't open write stream,", self); |
||
1453 | pass = NO; |
||
1454 | } |
||
1455 | |||
1456 | if(!pass) |
||
1457 | { |
||
1458 | if (errPtr) *errPtr = [self getStreamError]; |
||
1459 | } |
||
1460 | |||
1461 | return pass; |
||
1462 | } |
||
1463 | |||
1464 | /** |
||
1465 | * Called when read or write streams open. |
||
1466 | * When the socket is connected and both streams are open, consider the AsyncSocket instance to be ready. |
||
1467 | **/ |
||
1468 | - (void)doStreamOpen |
||
1469 | { |
||
1470 | NSError *err = nil; |
||
1471 | if ((theFlags & kDidCompleteOpenForRead) && (theFlags & kDidCompleteOpenForWrite)) |
||
1472 | { |
||
1473 | // Get the socket. |
||
1474 | if (![self setSocketFromStreamsAndReturnError: &err]) |
||
1475 | { |
||
1476 | NSLog (@"AsyncSocket %p couldn't get socket from streams, %@. Disconnecting.", self, err); |
||
1477 | [self closeWithError:err]; |
||
1478 | return; |
||
1479 | } |
||
1480 | |||
1481 | // Stop the connection attempt timeout timer |
||
1482 | [self endConnectTimeout]; |
||
1483 | |||
1484 | if ([theDelegate respondsToSelector:@selector(onSocket:didConnectToHost:port:)]) |
||
1485 | { |
||
1486 | [theDelegate onSocket:self didConnectToHost:[self connectedHost] port:[self connectedPort]]; |
||
1487 | } |
||
1488 | |||
1489 | // Immediately deal with any already-queued requests. |
||
1490 | [self maybeDequeueRead]; |
||
1491 | [self maybeDequeueWrite]; |
||
1492 | } |
||
1493 | } |
||
1494 | |||
1495 | - (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr |
||
1496 | { |
||
1497 | // Get the CFSocketNativeHandle from theReadStream |
||
1498 | CFSocketNativeHandle native; |
||
1499 | CFDataRef nativeProp = CFReadStreamCopyProperty(theReadStream, kCFStreamPropertySocketNativeHandle); |
||
1500 | if(nativeProp == NULL) |
||
1501 | { |
||
1502 | if (errPtr) *errPtr = [self getStreamError]; |
||
1503 | return NO; |
||
1504 | } |
||
1505 | |||
1506 | CFDataGetBytes(nativeProp, CFRangeMake(0, CFDataGetLength(nativeProp)), (UInt8 *)&native); |
||
1507 | CFRelease(nativeProp); |
||
1508 | |||
1509 | CFSocketRef theSocket = CFSocketCreateWithNative(kCFAllocatorDefault, native, 0, NULL, NULL); |
||
1510 | if(theSocket == NULL) |
||
1511 | { |
||
1512 | if (errPtr) *errPtr = [self getSocketError]; |
||
1513 | return NO; |
||
1514 | } |
||
1515 | |||
1516 | // Determine whether the connection was IPv4 or IPv6 |
||
1517 | CFDataRef peeraddr = CFSocketCopyPeerAddress(theSocket); |
||
1518 | if(peeraddr == NULL) |
||
1519 | { |
||
1520 | NSLog(@"AsyncSocket couldn't determine IP version of socket"); |
||
1521 | |||
1522 | CFRelease(theSocket); |
||
1523 | |||
1524 | if (errPtr) *errPtr = [self getSocketError]; |
||
1525 | return NO; |
||
1526 | } |
||
1527 | struct sockaddr *sa = (struct sockaddr *)CFDataGetBytePtr(peeraddr); |
||
1528 | |||
1529 | if(sa->sa_family == AF_INET) |
||
1530 | { |
||
1531 | theSocket4 = theSocket; |
||
1532 | } |
||
1533 | else |
||
1534 | { |
||
1535 | theSocket6 = theSocket; |
||
1536 | } |
||
1537 | |||
1538 | CFRelease(peeraddr); |
||
1539 | |||
1540 | return YES; |
||
1541 | } |
||
1542 | |||
1543 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
1544 | #pragma mark Disconnect Implementation |
||
1545 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
1546 | |||
1547 | // Sends error message and disconnects |
||
1548 | - (void)closeWithError:(NSError *)err |
||
1549 | { |
||
1550 | theFlags |= kClosingWithError; |
||
1551 | |||
1552 | if (theFlags & kDidPassConnectMethod) |
||
1553 | { |
||
1554 | // Try to salvage what data we can. |
||
1555 | [self recoverUnreadData]; |
||
1556 | |||
1557 | // Let the delegate know, so it can try to recover if it likes. |
||
1558 | if ([theDelegate respondsToSelector:@selector(onSocket:willDisconnectWithError:)]) |
||
1559 | { |
||
1560 | [theDelegate onSocket:self willDisconnectWithError:err]; |
||
1561 | } |
||
1562 | } |
||
1563 | [self close]; |
||
1564 | } |
||
1565 | |||
1566 | // Prepare partially read data for recovery. |
||
1567 | - (void)recoverUnreadData |
||
1568 | { |
||
1569 | if(theCurrentRead != nil) |
||
1570 | { |
||
1571 | // We never finished the current read. |
||
1572 | // Check to see if it's a normal read packet (not AsyncSpecialPacket) and if it had read anything yet. |
||
1573 | |||
1574 | if(([theCurrentRead isKindOfClass:[AsyncReadPacket class]]) && (theCurrentRead->bytesDone > 0)) |
||
1575 | { |
||
1576 | // We need to move its data into the front of the partial read buffer. |
||
1577 | |||
1578 | [partialReadBuffer replaceBytesInRange:NSMakeRange(0, 0) |
||
1579 | withBytes:[theCurrentRead->buffer bytes] |
||
1580 | length:theCurrentRead->bytesDone]; |
||
1581 | } |
||
1582 | } |
||
1583 | |||
1584 | [self emptyQueues]; |
||
1585 | } |
||
1586 | |||
1587 | - (void)emptyQueues |
||
1588 | { |
||
1589 | if (theCurrentRead != nil) [self endCurrentRead]; |
||
1590 | if (theCurrentWrite != nil) [self endCurrentWrite]; |
||
1591 | |||
1592 | [theReadQueue removeAllObjects]; |
||
1593 | [theWriteQueue removeAllObjects]; |
||
1594 | |||
1595 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueRead) object:nil]; |
||
1596 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueWrite) object:nil]; |
||
1597 | |||
1598 | theFlags &= ~kDequeueReadScheduled; |
||
1599 | theFlags &= ~kDequeueWriteScheduled; |
||
1600 | } |
||
1601 | |||
1602 | /** |
||
1603 | * Disconnects. This is called for both error and clean disconnections. |
||
1604 | **/ |
||
1605 | - (void)close |
||
1606 | { |
||
1607 | // Empty queues |
||
1608 | [self emptyQueues]; |
||
1609 | |||
1610 | // Clear partialReadBuffer (pre-buffer and also unreadData buffer in case of error) |
||
1611 | [partialReadBuffer replaceBytesInRange:NSMakeRange(0, [partialReadBuffer length]) withBytes:NULL length:0]; |
||
1612 | |||
1613 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(disconnect) object:nil]; |
||
1614 | |||
1615 | // Stop the connection attempt timeout timer |
||
1616 | if (theConnectTimer != nil) |
||
1617 | { |
||
1618 | [self endConnectTimeout]; |
||
1619 | } |
||
1620 | |||
1621 | // Close streams. |
||
1622 | if (theReadStream != NULL) |
||
1623 | { |
||
1624 | [self runLoopUnscheduleReadStream]; |
||
1625 | CFReadStreamClose(theReadStream); |
||
1626 | CFRelease(theReadStream); |
||
1627 | theReadStream = NULL; |
||
1628 | } |
||
1629 | if (theWriteStream != NULL) |
||
1630 | { |
||
1631 | [self runLoopUnscheduleWriteStream]; |
||
1632 | CFWriteStreamClose(theWriteStream); |
||
1633 | CFRelease(theWriteStream); |
||
1634 | theWriteStream = NULL; |
||
1635 | } |
||
1636 | |||
1637 | // Close sockets. |
||
1638 | if (theSocket4 != NULL) |
||
1639 | { |
||
1640 | CFSocketInvalidate (theSocket4); |
||
1641 | CFRelease (theSocket4); |
||
1642 | theSocket4 = NULL; |
||
1643 | } |
||
1644 | if (theSocket6 != NULL) |
||
1645 | { |
||
1646 | CFSocketInvalidate (theSocket6); |
||
1647 | CFRelease (theSocket6); |
||
1648 | theSocket6 = NULL; |
||
1649 | } |
||
1650 | if (theSource4 != NULL) |
||
1651 | { |
||
1652 | [self runLoopRemoveSource:theSource4]; |
||
1653 | CFRelease (theSource4); |
||
1654 | theSource4 = NULL; |
||
1655 | } |
||
1656 | if (theSource6 != NULL) |
||
1657 | { |
||
1658 | [self runLoopRemoveSource:theSource6]; |
||
1659 | CFRelease (theSource6); |
||
1660 | theSource6 = NULL; |
||
1661 | } |
||
1662 | theRunLoop = NULL; |
||
1663 | |||
1664 | // If the client has passed the connect/accept method, then the connection has at least begun. |
||
1665 | // Notify delegate that it is now ending. |
||
1666 | BOOL shouldCallDelegate = (theFlags & kDidPassConnectMethod); |
||
1667 | |||
1668 | // Clear all flags (except the pre-buffering flag, which should remain as is) |
||
1669 | theFlags &= kEnablePreBuffering; |
||
1670 | |||
1671 | if (shouldCallDelegate) |
||
1672 | { |
||
1673 | if ([theDelegate respondsToSelector: @selector(onSocketDidDisconnect:)]) |
||
1674 | { |
||
1675 | [theDelegate onSocketDidDisconnect:self]; |
||
1676 | } |
||
1677 | } |
||
1678 | |||
1679 | // Do not access any instance variables after calling onSocketDidDisconnect. |
||
1680 | // This gives the delegate freedom to release us without returning here and crashing. |
||
1681 | } |
||
1682 | |||
1683 | /** |
||
1684 | * Disconnects immediately. Any pending reads or writes are dropped. |
||
1685 | **/ |
||
1686 | - (void)disconnect |
||
1687 | { |
||
1688 | [self close]; |
||
1689 | } |
||
1690 | |||
1691 | /** |
||
1692 | * Diconnects after all pending reads have completed. |
||
1693 | **/ |
||
1694 | - (void)disconnectAfterReading |
||
1695 | { |
||
1696 | theFlags |= (kForbidReadsWrites | kDisconnectAfterReads); |
||
1697 | |||
1698 | [self maybeScheduleDisconnect]; |
||
1699 | } |
||
1700 | |||
1701 | /** |
||
1702 | * Disconnects after all pending writes have completed. |
||
1703 | **/ |
||
1704 | - (void)disconnectAfterWriting |
||
1705 | { |
||
1706 | theFlags |= (kForbidReadsWrites | kDisconnectAfterWrites); |
||
1707 | |||
1708 | [self maybeScheduleDisconnect]; |
||
1709 | } |
||
1710 | |||
1711 | /** |
||
1712 | * Disconnects after all pending reads and writes have completed. |
||
1713 | **/ |
||
1714 | - (void)disconnectAfterReadingAndWriting |
||
1715 | { |
||
1716 | theFlags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); |
||
1717 | |||
1718 | [self maybeScheduleDisconnect]; |
||
1719 | } |
||
1720 | |||
1721 | /** |
||
1722 | * Schedules a call to disconnect if possible. |
||
1723 | * That is, if all writes have completed, and we're set to disconnect after writing, |
||
1724 | * or if all reads have completed, and we're set to disconnect after reading. |
||
1725 | **/ |
||
1726 | - (void)maybeScheduleDisconnect |
||
1727 | { |
||
1728 | BOOL shouldDisconnect = NO; |
||
1729 | |||
1730 | if(theFlags & kDisconnectAfterReads) |
||
1731 | { |
||
1732 | if(([theReadQueue count] == 0) && (theCurrentRead == nil)) |
||
1733 | { |
||
1734 | if(theFlags & kDisconnectAfterWrites) |
||
1735 | { |
||
1736 | if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) |
||
1737 | { |
||
1738 | shouldDisconnect = YES; |
||
1739 | } |
||
1740 | } |
||
1741 | else |
||
1742 | { |
||
1743 | shouldDisconnect = YES; |
||
1744 | } |
||
1745 | } |
||
1746 | } |
||
1747 | else if(theFlags & kDisconnectAfterWrites) |
||
1748 | { |
||
1749 | if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) |
||
1750 | { |
||
1751 | shouldDisconnect = YES; |
||
1752 | } |
||
1753 | } |
||
1754 | |||
1755 | if(shouldDisconnect) |
||
1756 | { |
||
1757 | [self performSelector:@selector(disconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; |
||
1758 | } |
||
1759 | } |
||
1760 | |||
1761 | /** |
||
1762 | * In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read |
||
1763 | * any data that's left on the socket. |
||
1764 | **/ |
||
1765 | - (NSData *)unreadData |
||
1766 | { |
||
1767 | // Ensure this method will only return data in the event of an error |
||
1768 | if(!(theFlags & kClosingWithError)) return nil; |
||
1769 | |||
1770 | if(theReadStream == NULL) return nil; |
||
1771 | |||
1772 | CFIndex totalBytesRead = [partialReadBuffer length]; |
||
1773 | BOOL error = NO; |
||
1774 | while(!error && CFReadStreamHasBytesAvailable(theReadStream)) |
||
1775 | { |
||
1776 | [partialReadBuffer increaseLengthBy:READALL_CHUNKSIZE]; |
||
1777 | |||
1778 | // Number of bytes to read is space left in packet buffer. |
||
1779 | CFIndex bytesToRead = [partialReadBuffer length] - totalBytesRead; |
||
1780 | |||
1781 | // Read data into packet buffer |
||
1782 | UInt8 *packetbuf = (UInt8 *)( [partialReadBuffer mutableBytes] + totalBytesRead ); |
||
1783 | CFIndex bytesRead = CFReadStreamRead(theReadStream, packetbuf, bytesToRead); |
||
1784 | |||
1785 | // Check results |
||
1786 | if(bytesRead < 0) |
||
1787 | { |
||
1788 | error = YES; |
||
1789 | } |
||
1790 | else |
||
1791 | { |
||
1792 | totalBytesRead += bytesRead; |
||
1793 | } |
||
1794 | } |
||
1795 | |||
1796 | [partialReadBuffer setLength:totalBytesRead]; |
||
1797 | |||
1798 | return partialReadBuffer; |
||
1799 | } |
||
1800 | |||
1801 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
1802 | #pragma mark Errors |
||
1803 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
1804 | |||
1805 | /** |
||
1806 | * Returns a standard error object for the current errno value. |
||
1807 | * Errno is used for low-level BSD socket errors. |
||
1808 | **/ |
||
1809 | - (NSError *)getErrnoError |
||
1810 | { |
||
1811 | NSString *errorMsg = [NSString stringWithUTF8String:strerror(errno)]; |
||
1812 | NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMsg forKey:NSLocalizedDescriptionKey]; |
||
1813 | |||
1814 | return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; |
||
1815 | } |
||
1816 | |||
1817 | /** |
||
1818 | * Returns a standard error message for a CFSocket error. |
||
1819 | * Unfortunately, CFSocket offers no feedback on its errors. |
||
1820 | **/ |
||
1821 | - (NSError *)getSocketError |
||
1822 | { |
||
1823 | NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCFSocketError", |
||
1824 | @"AsyncSocket", [NSBundle mainBundle], |
||
1825 | @"General CFSocket error", nil); |
||
1826 | |||
1827 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
||
1828 | |||
1829 | return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info]; |
||
1830 | } |
||
1831 | |||
1832 | - (NSError *)getStreamError |
||
1833 | { |
||
1834 | CFStreamError err; |
||
1835 | if (theReadStream != NULL) |
||
1836 | { |
||
1837 | err = CFReadStreamGetError (theReadStream); |
||
1838 | if (err.error != 0) return [self errorFromCFStreamError: err]; |
||
1839 | } |
||
1840 | |||
1841 | if (theWriteStream != NULL) |
||
1842 | { |
||
1843 | err = CFWriteStreamGetError (theWriteStream); |
||
1844 | if (err.error != 0) return [self errorFromCFStreamError: err]; |
||
1845 | } |
||
1846 | |||
1847 | return nil; |
||
1848 | } |
||
1849 | |||
1850 | /** |
||
1851 | * Returns a standard AsyncSocket abort error. |
||
1852 | **/ |
||
1853 | - (NSError *)getAbortError |
||
1854 | { |
||
1855 | NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCanceledError", |
||
1856 | @"AsyncSocket", [NSBundle mainBundle], |
||
1857 | @"Connection canceled", nil); |
||
1858 | |||
1859 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
||
1860 | |||
1861 | return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCanceledError userInfo:info]; |
||
1862 | } |
||
1863 | |||
1864 | /** |
||
1865 | * Returns a standard AsyncSocket connect timeout error. |
||
1866 | **/ |
||
1867 | - (NSError *)getConnectTimeoutError |
||
1868 | { |
||
1869 | NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketConnectTimeoutError", |
||
1870 | @"AsyncSocket", [NSBundle mainBundle], |
||
1871 | @"Attempt to connect to host timed out", nil); |
||
1872 | |||
1873 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
||
1874 | |||
1875 | return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketConnectTimeoutError userInfo:info]; |
||
1876 | } |
||
1877 | |||
1878 | /** |
||
1879 | * Returns a standard AsyncSocket maxed out error. |
||
1880 | **/ |
||
1881 | - (NSError *)getReadMaxedOutError |
||
1882 | { |
||
1883 | NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadMaxedOutError", |
||
1884 | @"AsyncSocket", [NSBundle mainBundle], |
||
1885 | @"Read operation reached set maximum length", nil); |
||
1886 | |||
1887 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
||
1888 | |||
1889 | return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadMaxedOutError userInfo:info]; |
||
1890 | } |
||
1891 | |||
1892 | /** |
||
1893 | * Returns a standard AsyncSocket read timeout error. |
||
1894 | **/ |
||
1895 | - (NSError *)getReadTimeoutError |
||
1896 | { |
||
1897 | NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadTimeoutError", |
||
1898 | @"AsyncSocket", [NSBundle mainBundle], |
||
1899 | @"Read operation timed out", nil); |
||
1900 | |||
1901 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
||
1902 | |||
1903 | return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadTimeoutError userInfo:info]; |
||
1904 | } |
||
1905 | |||
1906 | /** |
||
1907 | * Returns a standard AsyncSocket write timeout error. |
||
1908 | **/ |
||
1909 | - (NSError *)getWriteTimeoutError |
||
1910 | { |
||
1911 | NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketWriteTimeoutError", |
||
1912 | @"AsyncSocket", [NSBundle mainBundle], |
||
1913 | @"Write operation timed out", nil); |
||
1914 | |||
1915 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
||
1916 | |||
1917 | return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketWriteTimeoutError userInfo:info]; |
||
1918 | } |
||
1919 | |||
1920 | - (NSError *)errorFromCFStreamError:(CFStreamError)err |
||
1921 | { |
||
1922 | if (err.domain == 0 && err.error == 0) return nil; |
||
1923 | |||
1924 | // Can't use switch; these constants aren't int literals. |
||
1925 | NSString *domain = @"CFStreamError (unlisted domain)"; |
||
1926 | NSString *message = nil; |
||
1927 | |||
1928 | if(err.domain == kCFStreamErrorDomainPOSIX) { |
||
1929 | domain = NSPOSIXErrorDomain; |
||
1930 | } |
||
1931 | else if(err.domain == kCFStreamErrorDomainMacOSStatus) { |
||
1932 | domain = NSOSStatusErrorDomain; |
||
1933 | } |
||
1934 | else if(err.domain == kCFStreamErrorDomainMach) { |
||
1935 | domain = NSMachErrorDomain; |
||
1936 | } |
||
1937 | else if(err.domain == kCFStreamErrorDomainNetDB) |
||
1938 | { |
||
1939 | domain = @"kCFStreamErrorDomainNetDB"; |
||
1940 | message = [NSString stringWithCString:gai_strerror(err.error) encoding:NSASCIIStringEncoding]; |
||
1941 | } |
||
1942 | else if(err.domain == kCFStreamErrorDomainNetServices) { |
||
1943 | domain = @"kCFStreamErrorDomainNetServices"; |
||
1944 | } |
||
1945 | else if(err.domain == kCFStreamErrorDomainSOCKS) { |
||
1946 | domain = @"kCFStreamErrorDomainSOCKS"; |
||
1947 | } |
||
1948 | else if(err.domain == kCFStreamErrorDomainSystemConfiguration) { |
||
1949 | domain = @"kCFStreamErrorDomainSystemConfiguration"; |
||
1950 | } |
||
1951 | else if(err.domain == kCFStreamErrorDomainSSL) { |
||
1952 | domain = @"kCFStreamErrorDomainSSL"; |
||
1953 | } |
||
1954 | |||
1955 | NSDictionary *info = nil; |
||
1956 | if(message != nil) |
||
1957 | { |
||
1958 | info = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey]; |
||
1959 | } |
||
1960 | return [NSError errorWithDomain:domain code:err.error userInfo:info]; |
||
1961 | } |
||
1962 | |||
1963 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
1964 | #pragma mark Diagnostics |
||
1965 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
1966 | |||
1967 | - (BOOL)isConnected |
||
1968 | { |
||
1969 | return [self isSocketConnected] && [self areStreamsConnected]; |
||
1970 | } |
||
1971 | |||
1972 | - (NSString *)connectedHost |
||
1973 | { |
||
1974 | if(theSocket4) |
||
1975 | return [self connectedHost:theSocket4]; |
||
1976 | else |
||
1977 | return [self connectedHost:theSocket6]; |
||
1978 | } |
||
1979 | |||
1980 | - (UInt16)connectedPort |
||
1981 | { |
||
1982 | if(theSocket4) |
||
1983 | return [self connectedPort:theSocket4]; |
||
1984 | else |
||
1985 | return [self connectedPort:theSocket6]; |
||
1986 | } |
||
1987 | |||
1988 | - (NSString *)localHost |
||
1989 | { |
||
1990 | if(theSocket4) |
||
1991 | return [self localHost:theSocket4]; |
||
1992 | else |
||
1993 | return [self localHost:theSocket6]; |
||
1994 | } |
||
1995 | |||
1996 | - (UInt16)localPort |
||
1997 | { |
||
1998 | if(theSocket4) |
||
1999 | return [self localPort:theSocket4]; |
||
2000 | else |
||
2001 | return [self localPort:theSocket6]; |
||
2002 | } |
||
2003 | |||
2004 | - (NSString *)connectedHost:(CFSocketRef)theSocket |
||
2005 | { |
||
2006 | if (theSocket == NULL) return nil; |
||
2007 | |||
2008 | CFDataRef peeraddr; |
||
2009 | NSString *peerstr = nil; |
||
2010 | |||
2011 | if((peeraddr = CFSocketCopyPeerAddress(theSocket))) |
||
2012 | { |
||
2013 | peerstr = [self addressHost:peeraddr]; |
||
2014 | CFRelease (peeraddr); |
||
2015 | } |
||
2016 | |||
2017 | return peerstr; |
||
2018 | } |
||
2019 | |||
2020 | - (UInt16)connectedPort:(CFSocketRef)theSocket |
||
2021 | { |
||
2022 | if (theSocket == NULL) return 0; |
||
2023 | |||
2024 | CFDataRef peeraddr; |
||
2025 | UInt16 peerport = 0; |
||
2026 | |||
2027 | if((peeraddr = CFSocketCopyPeerAddress(theSocket))) |
||
2028 | { |
||
2029 | peerport = [self addressPort:peeraddr]; |
||
2030 | CFRelease (peeraddr); |
||
2031 | } |
||
2032 | |||
2033 | return peerport; |
||
2034 | } |
||
2035 | |||
2036 | - (NSString *)localHost:(CFSocketRef)theSocket |
||
2037 | { |
||
2038 | if (theSocket == NULL) return nil; |
||
2039 | |||
2040 | CFDataRef selfaddr; |
||
2041 | NSString *selfstr = nil; |
||
2042 | |||
2043 | if((selfaddr = CFSocketCopyAddress(theSocket))) |
||
2044 | { |
||
2045 | selfstr = [self addressHost:selfaddr]; |
||
2046 | CFRelease (selfaddr); |
||
2047 | } |
||
2048 | |||
2049 | return selfstr; |
||
2050 | } |
||
2051 | |||
2052 | - (UInt16)localPort:(CFSocketRef)theSocket |
||
2053 | { |
||
2054 | if (theSocket == NULL) return 0; |
||
2055 | |||
2056 | CFDataRef selfaddr; |
||
2057 | UInt16 selfport = 0; |
||
2058 | |||
2059 | if ((selfaddr = CFSocketCopyAddress(theSocket))) |
||
2060 | { |
||
2061 | selfport = [self addressPort:selfaddr]; |
||
2062 | CFRelease (selfaddr); |
||
2063 | } |
||
2064 | |||
2065 | return selfport; |
||
2066 | } |
||
2067 | |||
2068 | - (NSString *)addressHost:(CFDataRef)cfaddr |
||
2069 | { |
||
2070 | if (cfaddr == NULL) return nil; |
||
2071 | |||
2072 | char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ]; |
||
2073 | struct sockaddr *pSockAddr = (struct sockaddr *) CFDataGetBytePtr (cfaddr); |
||
2074 | struct sockaddr_in *pSockAddrV4 = (struct sockaddr_in *)pSockAddr; |
||
2075 | struct sockaddr_in6 *pSockAddrV6 = (struct sockaddr_in6 *)pSockAddr; |
||
2076 | |||
2077 | const void *pAddr = (pSockAddr->sa_family == AF_INET) ? |
||
2078 | (void *)(&(pSockAddrV4->sin_addr)) : |
||
2079 | (void *)(&(pSockAddrV6->sin6_addr)); |
||
2080 | |||
2081 | const char *pStr = inet_ntop (pSockAddr->sa_family, pAddr, addrBuf, sizeof(addrBuf)); |
||
2082 | if (pStr == NULL) [NSException raise: NSInternalInconsistencyException |
||
2083 | format: @"Cannot convert address to string."]; |
||
2084 | |||
2085 | return [NSString stringWithCString:pStr encoding:NSASCIIStringEncoding]; |
||
2086 | } |
||
2087 | |||
2088 | - (UInt16)addressPort:(CFDataRef)cfaddr |
||
2089 | { |
||
2090 | if (cfaddr == NULL) return 0; |
||
2091 | |||
2092 | struct sockaddr_in *pAddr = (struct sockaddr_in *) CFDataGetBytePtr (cfaddr); |
||
2093 | return ntohs (pAddr->sin_port); |
||
2094 | } |
||
2095 | |||
2096 | - (NSData *)connectedAddress |
||
2097 | { |
||
2098 | CFSocketRef theSocket; |
||
2099 | |||
2100 | if (theSocket4) |
||
2101 | theSocket = theSocket4; |
||
2102 | else |
||
2103 | theSocket = theSocket6; |
||
2104 | |||
2105 | if (theSocket == NULL) return nil; |
||
2106 | |||
2107 | CFDataRef peeraddr = CFSocketCopyPeerAddress(theSocket); |
||
2108 | |||
2109 | if (peeraddr == NULL) return nil; |
||
2110 | |||
2111 | #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 |
||
2112 | NSData *result = [NSData dataWithBytes:CFDataGetBytePtr(peeraddr) length:CFDataGetLength(peeraddr)]; |
||
2113 | CFRelease(peeraddr); |
||
2114 | return result; |
||
2115 | #else |
||
2116 | return [(NSData *)NSMakeCollectable(peeraddr) autorelease]; |
||
2117 | #endif |
||
2118 | } |
||
2119 | |||
2120 | - (NSData *)localAddress |
||
2121 | { |
||
2122 | CFSocketRef theSocket; |
||
2123 | |||
2124 | if (theSocket4) |
||
2125 | theSocket = theSocket4; |
||
2126 | else |
||
2127 | theSocket = theSocket6; |
||
2128 | |||
2129 | if (theSocket == NULL) return nil; |
||
2130 | |||
2131 | CFDataRef selfaddr = CFSocketCopyAddress(theSocket); |
||
2132 | |||
2133 | if (selfaddr == NULL) return nil; |
||
2134 | |||
2135 | #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 |
||
2136 | NSData *result = [NSData dataWithBytes:CFDataGetBytePtr(selfaddr) length:CFDataGetLength(selfaddr)]; |
||
2137 | CFRelease(selfaddr); |
||
2138 | return result; |
||
2139 | #else |
||
2140 | return [(NSData *)NSMakeCollectable(selfaddr) autorelease]; |
||
2141 | #endif |
||
2142 | } |
||
2143 | |||
2144 | - (BOOL)isIPv4 |
||
2145 | { |
||
2146 | return (theSocket4 != NULL); |
||
2147 | } |
||
2148 | |||
2149 | - (BOOL)isIPv6 |
||
2150 | { |
||
2151 | return (theSocket6 != NULL); |
||
2152 | } |
||
2153 | |||
2154 | - (BOOL)isSocketConnected |
||
2155 | { |
||
2156 | if(theSocket4 != NULL) |
||
2157 | return CFSocketIsValid(theSocket4); |
||
2158 | else if(theSocket6 != NULL) |
||
2159 | return CFSocketIsValid(theSocket6); |
||
2160 | else |
||
2161 | return NO; |
||
2162 | } |
||
2163 | |||
2164 | - (BOOL)areStreamsConnected |
||
2165 | { |
||
2166 | CFStreamStatus s; |
||
2167 | |||
2168 | if (theReadStream != NULL) |
||
2169 | { |
||
2170 | s = CFReadStreamGetStatus (theReadStream); |
||
2171 | if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusReading || s == kCFStreamStatusError) ) |
||
2172 | return NO; |
||
2173 | } |
||
2174 | else return NO; |
||
2175 | |||
2176 | if (theWriteStream != NULL) |
||
2177 | { |
||
2178 | s = CFWriteStreamGetStatus (theWriteStream); |
||
2179 | if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusWriting || s == kCFStreamStatusError) ) |
||
2180 | return NO; |
||
2181 | } |
||
2182 | else return NO; |
||
2183 | |||
2184 | return YES; |
||
2185 | } |
||
2186 | |||
2187 | - (NSString *)description |
||
2188 | { |
||
2189 | static const char *statstr[] = {"not open","opening","open","reading","writing","at end","closed","has error"}; |
||
2190 | CFStreamStatus rs = (theReadStream != NULL) ? CFReadStreamGetStatus(theReadStream) : 0; |
||
2191 | CFStreamStatus ws = (theWriteStream != NULL) ? CFWriteStreamGetStatus(theWriteStream) : 0; |
||
2192 | |||
2193 | NSString *peerstr, *selfstr; |
||
2194 | CFDataRef peeraddr4 = NULL, peeraddr6 = NULL, selfaddr4 = NULL, selfaddr6 = NULL; |
||
2195 | |||
2196 | if (theSocket4 || theSocket6) |
||
2197 | { |
||
2198 | if (theSocket4) peeraddr4 = CFSocketCopyPeerAddress(theSocket4); |
||
2199 | if (theSocket6) peeraddr6 = CFSocketCopyPeerAddress(theSocket6); |
||
2200 | |||
2201 | if(theSocket4 && theSocket6) |
||
2202 | { |
||
2203 | peerstr = [NSString stringWithFormat: @"%@/%@ %u", |
||
2204 | [self addressHost:peeraddr4], [self addressHost:peeraddr6], [self addressPort:peeraddr4]]; |
||
2205 | } |
||
2206 | else if(theSocket4) |
||
2207 | { |
||
2208 | peerstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:peeraddr4], [self addressPort:peeraddr4]]; |
||
2209 | } |
||
2210 | else |
||
2211 | { |
||
2212 | peerstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:peeraddr6], [self addressPort:peeraddr6]]; |
||
2213 | } |
||
2214 | |||
2215 | if(peeraddr4) CFRelease(peeraddr4); |
||
2216 | if(peeraddr6) CFRelease(peeraddr6); |
||
2217 | peeraddr4 = NULL; |
||
2218 | peeraddr6 = NULL; |
||
2219 | } |
||
2220 | else peerstr = @"nowhere"; |
||
2221 | |||
2222 | if (theSocket4 || theSocket6) |
||
2223 | { |
||
2224 | if (theSocket4) selfaddr4 = CFSocketCopyAddress (theSocket4); |
||
2225 | if (theSocket6) selfaddr6 = CFSocketCopyAddress (theSocket6); |
||
2226 | |||
2227 | if (theSocket4 && theSocket6) |
||
2228 | { |
||
2229 | selfstr = [NSString stringWithFormat: @"%@/%@ %u", |
||
2230 | [self addressHost:selfaddr4], [self addressHost:selfaddr6], [self addressPort:selfaddr4]]; |
||
2231 | } |
||
2232 | else if (theSocket4) |
||
2233 | { |
||
2234 | selfstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:selfaddr4], [self addressPort:selfaddr4]]; |
||
2235 | } |
||
2236 | else |
||
2237 | { |
||
2238 | selfstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:selfaddr6], [self addressPort:selfaddr6]]; |
||
2239 | } |
||
2240 | |||
2241 | if(selfaddr4) CFRelease(selfaddr4); |
||
2242 | if(selfaddr6) CFRelease(selfaddr6); |
||
2243 | selfaddr4 = NULL; |
||
2244 | selfaddr6 = NULL; |
||
2245 | } |
||
2246 | else selfstr = @"nowhere"; |
||
2247 | |||
2248 | NSMutableString *ms = [[NSMutableString alloc] initWithCapacity:150]; |
||
2249 | |||
2250 | [ms appendString:[NSString stringWithFormat:@"<AsyncSocket %p", self]]; |
||
2251 | [ms appendString:[NSString stringWithFormat:@" local %@ remote %@ ", selfstr, peerstr]]; |
||
2252 | |||
2253 | unsigned readQueueCount = (unsigned)[theReadQueue count]; |
||
2254 | unsigned writeQueueCount = (unsigned)[theWriteQueue count]; |
||
2255 | |||
2256 | [ms appendString:[NSString stringWithFormat:@"has queued %u reads %u writes, ", readQueueCount, writeQueueCount]]; |
||
2257 | |||
2258 | if (theCurrentRead == nil) |
||
2259 | [ms appendString: @"no current read, "]; |
||
2260 | else |
||
2261 | { |
||
2262 | int percentDone; |
||
2263 | if ([theCurrentRead->buffer length] != 0) |
||
2264 | percentDone = (float)theCurrentRead->bytesDone / |
||
2265 | (float)[theCurrentRead->buffer length] * 100.0F; |
||
2266 | else |
||
2267 | percentDone = 100.0F; |
||
2268 | |||
2269 | [ms appendString: [NSString stringWithFormat:@"currently read %u bytes (%d%% done), ", |
||
2270 | (unsigned int)[theCurrentRead->buffer length], |
||
2271 | theCurrentRead->bytesDone ? percentDone : 0]]; |
||
2272 | } |
||
2273 | |||
2274 | if (theCurrentWrite == nil) |
||
2275 | [ms appendString: @"no current write, "]; |
||
2276 | else |
||
2277 | { |
||
2278 | int percentDone; |
||
2279 | if ([theCurrentWrite->buffer length] != 0) |
||
2280 | percentDone = (float)theCurrentWrite->bytesDone / |
||
2281 | (float)[theCurrentWrite->buffer length] * 100.0F; |
||
2282 | else |
||
2283 | percentDone = 100.0F; |
||
2284 | |||
2285 | [ms appendString: [NSString stringWithFormat:@"currently written %u (%d%%), ", |
||
2286 | (unsigned int)[theCurrentWrite->buffer length], |
||
2287 | theCurrentWrite->bytesDone ? percentDone : 0]]; |
||
2288 | } |
||
2289 | |||
2290 | [ms appendString:[NSString stringWithFormat:@"read stream %p %s, ", theReadStream, statstr[rs]]]; |
||
2291 | [ms appendString:[NSString stringWithFormat:@"write stream %p %s", theWriteStream, statstr[ws]]]; |
||
2292 | |||
2293 | if(theFlags & kDisconnectAfterReads) |
||
2294 | { |
||
2295 | if(theFlags & kDisconnectAfterWrites) |
||
2296 | [ms appendString: @", will disconnect after reads & writes"]; |
||
2297 | else |
||
2298 | [ms appendString: @", will disconnect after reads"]; |
||
2299 | } |
||
2300 | else if(theFlags & kDisconnectAfterWrites) |
||
2301 | { |
||
2302 | [ms appendString: @", will disconnect after writes"]; |
||
2303 | } |
||
2304 | |||
2305 | if (![self isConnected]) [ms appendString: @", not connected"]; |
||
2306 | |||
2307 | [ms appendString:@">"]; |
||
2308 | |||
2309 | return [ms autorelease]; |
||
2310 | } |
||
2311 | |||
2312 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
2313 | #pragma mark Reading |
||
2314 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
2315 | |||
2316 | - (void)readDataToLength:(CFIndex)length withTimeout:(NSTimeInterval)timeout tag:(long)tag |
||
2317 | { |
||
2318 | if(length == 0) return; |
||
2319 | if(theFlags & kForbidReadsWrites) return; |
||
2320 | |||
2321 | NSMutableData *buffer = [[NSMutableData alloc] initWithLength:length]; |
||
2322 | AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer |
||
2323 | timeout:timeout |
||
2324 | tag:tag |
||
2325 | readAllAvailable:NO |
||
2326 | terminator:nil |
||
2327 | maxLength:length]; |
||
2328 | |||
2329 | [theReadQueue addObject:packet]; |
||
2330 | [self scheduleDequeueRead]; |
||
2331 | |||
2332 | [packet release]; |
||
2333 | [buffer release]; |
||
2334 | } |
||
2335 | |||
2336 | - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag |
||
2337 | { |
||
2338 | [self readDataToData:data withTimeout:timeout maxLength:-1 tag:tag]; |
||
2339 | } |
||
2340 | |||
2341 | - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(CFIndex)length tag:(long)tag |
||
2342 | { |
||
2343 | if(data == nil || [data length] == 0) return; |
||
2344 | if(length >= 0 && length < [data length]) return; |
||
2345 | if(theFlags & kForbidReadsWrites) return; |
||
2346 | |||
2347 | NSMutableData *buffer = [[NSMutableData alloc] initWithLength:0]; |
||
2348 | AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer |
||
2349 | timeout:timeout |
||
2350 | tag:tag |
||
2351 | readAllAvailable:NO |
||
2352 | terminator:data |
||
2353 | maxLength:length]; |
||
2354 | |||
2355 | [theReadQueue addObject:packet]; |
||
2356 | [self scheduleDequeueRead]; |
||
2357 | |||
2358 | [packet release]; |
||
2359 | [buffer release]; |
||
2360 | } |
||
2361 | |||
2362 | - (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag |
||
2363 | { |
||
2364 | if (theFlags & kForbidReadsWrites) return; |
||
2365 | |||
2366 | NSMutableData *buffer = [[NSMutableData alloc] initWithLength:0]; |
||
2367 | AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer |
||
2368 | timeout:timeout |
||
2369 | tag:tag |
||
2370 | readAllAvailable:YES |
||
2371 | terminator:nil |
||
2372 | maxLength:-1]; |
||
2373 | |||
2374 | [theReadQueue addObject:packet]; |
||
2375 | [self scheduleDequeueRead]; |
||
2376 | |||
2377 | [packet release]; |
||
2378 | [buffer release]; |
||
2379 | } |
||
2380 | |||
2381 | /** |
||
2382 | * Puts a maybeDequeueRead on the run loop. |
||
2383 | * An assumption here is that selectors will be performed consecutively within their priority. |
||
2384 | **/ |
||
2385 | - (void)scheduleDequeueRead |
||
2386 | { |
||
2387 | if((theFlags & kDequeueReadScheduled) == 0) |
||
2388 | { |
||
2389 | theFlags |= kDequeueReadScheduled; |
||
2390 | [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; |
||
2391 | } |
||
2392 | } |
||
2393 | |||
2394 | /** |
||
2395 | * This method starts a new read, if needed. |
||
2396 | * It is called when a user requests a read, |
||
2397 | * or when a stream opens that may have requested reads sitting in the queue, etc. |
||
2398 | **/ |
||
2399 | - (void)maybeDequeueRead |
||
2400 | { |
||
2401 | // Unset the flag indicating a call to this method is scheduled |
||
2402 | theFlags &= ~kDequeueReadScheduled; |
||
2403 | |||
2404 | // If we're not currently processing a read AND we have an available read stream |
||
2405 | if((theCurrentRead == nil) && (theReadStream != NULL)) |
||
2406 | { |
||
2407 | if([theReadQueue count] > 0) |
||
2408 | { |
||
2409 | // Dequeue the next object in the write queue |
||
2410 | theCurrentRead = [[theReadQueue objectAtIndex:0] retain]; |
||
2411 | [theReadQueue removeObjectAtIndex:0]; |
||
2412 | |||
2413 | if([theCurrentRead isKindOfClass:[AsyncSpecialPacket class]]) |
||
2414 | { |
||
2415 | // Attempt to start TLS |
||
2416 | theFlags |= kStartingReadTLS; |
||
2417 | |||
2418 | // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are both set |
||
2419 | [self maybeStartTLS]; |
||
2420 | } |
||
2421 | else |
||
2422 | { |
||
2423 | // Start time-out timer |
||
2424 | if(theCurrentRead->timeout >= 0.0) |
||
2425 | { |
||
2426 | theReadTimer = [NSTimer timerWithTimeInterval:theCurrentRead->timeout |
||
2427 | target:self |
||
2428 | selector:@selector(doReadTimeout:) |
||
2429 | userInfo:nil |
||
2430 | repeats:NO]; |
||
2431 | [self runLoopAddTimer:theReadTimer]; |
||
2432 | } |
||
2433 | |||
2434 | // Immediately read, if possible |
||
2435 | [self doBytesAvailable]; |
||
2436 | } |
||
2437 | } |
||
2438 | else if(theFlags & kDisconnectAfterReads) |
||
2439 | { |
||
2440 | if(theFlags & kDisconnectAfterWrites) |
||
2441 | { |
||
2442 | if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) |
||
2443 | { |
||
2444 | [self disconnect]; |
||
2445 | } |
||
2446 | } |
||
2447 | else |
||
2448 | { |
||
2449 | [self disconnect]; |
||
2450 | } |
||
2451 | } |
||
2452 | } |
||
2453 | } |
||
2454 | |||
2455 | /** |
||
2456 | * Call this method in doBytesAvailable instead of CFReadStreamHasBytesAvailable(). |
||
2457 | * This method supports pre-buffering properly as well as the kSocketHasBytesAvailable flag. |
||
2458 | **/ |
||
2459 | - (BOOL)hasBytesAvailable |
||
2460 | { |
||
2461 | if ((theFlags & kSocketHasBytesAvailable) || ([partialReadBuffer length] > 0)) |
||
2462 | { |
||
2463 | return YES; |
||
2464 | } |
||
2465 | else |
||
2466 | { |
||
2467 | return CFReadStreamHasBytesAvailable(theReadStream); |
||
2468 | } |
||
2469 | } |
||
2470 | |||
2471 | /** |
||
2472 | * Call this method in doBytesAvailable instead of CFReadStreamRead(). |
||
2473 | * This method support pre-buffering properly. |
||
2474 | **/ |
||
2475 | - (CFIndex)readIntoBuffer:(UInt8 *)buffer maxLength:(CFIndex)length |
||
2476 | { |
||
2477 | if([partialReadBuffer length] > 0) |
||
2478 | { |
||
2479 | // Determine the maximum amount of data to read |
||
2480 | CFIndex bytesToRead = MIN(length, [partialReadBuffer length]); |
||
2481 | |||
2482 | // Copy the bytes from the buffer |
||
2483 | memcpy(buffer, [partialReadBuffer bytes], bytesToRead); |
||
2484 | |||
2485 | // Remove the copied bytes from the buffer |
||
2486 | [partialReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToRead) withBytes:NULL length:0]; |
||
2487 | |||
2488 | return bytesToRead; |
||
2489 | } |
||
2490 | else |
||
2491 | { |
||
2492 | // Unset the "has-bytes-available" flag |
||
2493 | theFlags &= ~kSocketHasBytesAvailable; |
||
2494 | |||
2495 | return CFReadStreamRead(theReadStream, buffer, length); |
||
2496 | } |
||
2497 | } |
||
2498 | |||
2499 | /** |
||
2500 | * This method is called when a new read is taken from the read queue or when new data becomes available on the stream. |
||
2501 | **/ |
||
2502 | - (void)doBytesAvailable |
||
2503 | { |
||
2504 | // If data is available on the stream, but there is no read request, then we don't need to process the data yet. |
||
2505 | // Also, if there is a read request, but no read stream setup yet, we can't process any data yet. |
||
2506 | if((theCurrentRead != nil) && (theReadStream != NULL)) |
||
2507 | { |
||
2508 | // Note: This method is not called if theCurrentRead is an AsyncSpecialPacket (startTLS packet) |
||
2509 | |||
2510 | CFIndex totalBytesRead = 0; |
||
2511 | |||
2512 | BOOL done = NO; |
||
2513 | BOOL socketError = NO; |
||
2514 | BOOL maxoutError = NO; |
||
2515 | |||
2516 | while(!done && !socketError && !maxoutError && [self hasBytesAvailable]) |
||
2517 | { |
||
2518 | BOOL didPreBuffer = NO; |
||
2519 | |||
2520 | // There are 3 types of read packets: |
||
2521 | // |
||
2522 | // 1) Read a specific length of data. |
||
2523 | // 2) Read all available data. |
||
2524 | // 3) Read up to a particular terminator. |
||
2525 | |||
2526 | if(theCurrentRead->readAllAvailableData == YES) |
||
2527 | { |
||
2528 | // We're reading all available data. |
||
2529 | // |
||
2530 | // Make sure there is at least READALL_CHUNKSIZE bytes available. |
||
2531 | // We don't want to increase the buffer any more than this or we'll waste space. |
||
2532 | // With prebuffering it's possible to read in a small chunk on the first read. |
||
2533 | |||
2534 | unsigned buffInc = READALL_CHUNKSIZE - ([theCurrentRead->buffer length] - theCurrentRead->bytesDone); |
||
2535 | [theCurrentRead->buffer increaseLengthBy:buffInc]; |
||
2536 | } |
||
2537 | else if(theCurrentRead->term != nil) |
||
2538 | { |
||
2539 | // We're reading up to a terminator. |
||
2540 | // |
||
2541 | // We may only want to read a few bytes. |
||
2542 | // Just enough to ensure we don't go past our term or over our max limit. |
||
2543 | // Unless pre-buffering is enabled, in which case we may want to read in a larger chunk. |
||
2544 | |||
2545 | // If we already have data pre-buffered, we obviously don't want to pre-buffer it again. |
||
2546 | // So in this case we'll just read as usual. |
||
2547 | |||
2548 | if(([partialReadBuffer length] > 0) || !(theFlags & kEnablePreBuffering)) |
||
2549 | { |
||
2550 | unsigned maxToRead = [theCurrentRead readLengthForTerm]; |
||
2551 | |||
2552 | unsigned bufInc = maxToRead - ([theCurrentRead->buffer length] - theCurrentRead->bytesDone); |
||
2553 | [theCurrentRead->buffer increaseLengthBy:bufInc]; |
||
2554 | } |
||
2555 | else |
||
2556 | { |
||
2557 | didPreBuffer = YES; |
||
2558 | unsigned maxToRead = [theCurrentRead prebufferReadLengthForTerm]; |
||
2559 | |||
2560 | unsigned buffInc = maxToRead - ([theCurrentRead->buffer length] - theCurrentRead->bytesDone); |
||
2561 | [theCurrentRead->buffer increaseLengthBy:buffInc]; |
||
2562 | |||
2563 | } |
||
2564 | } |
||
2565 | |||
2566 | // Number of bytes to read is space left in packet buffer. |
||
2567 | CFIndex bytesToRead = [theCurrentRead->buffer length] - theCurrentRead->bytesDone; |
||
2568 | |||
2569 | // Read data into packet buffer |
||
2570 | UInt8 *subBuffer = (UInt8 *)([theCurrentRead->buffer mutableBytes] + theCurrentRead->bytesDone); |
||
2571 | CFIndex bytesRead = [self readIntoBuffer:subBuffer maxLength:bytesToRead]; |
||
2572 | |||
2573 | // Check results |
||
2574 | if(bytesRead < 0) |
||
2575 | { |
||
2576 | socketError = YES; |
||
2577 | } |
||
2578 | else |
||
2579 | { |
||
2580 | // Update total amount read for the current read |
||
2581 | theCurrentRead->bytesDone += bytesRead; |
||
2582 | |||
2583 | // Update total amount read in this method invocation |
||
2584 | totalBytesRead += bytesRead; |
||
2585 | } |
||
2586 | |||
2587 | // Is packet done? |
||
2588 | if(theCurrentRead->readAllAvailableData != YES) |
||
2589 | { |
||
2590 | if(theCurrentRead->term != nil) |
||
2591 | { |
||
2592 | if(didPreBuffer) |
||
2593 | { |
||
2594 | // Search for the terminating sequence within the big chunk we just read. |
||
2595 | CFIndex overflow = [theCurrentRead searchForTermAfterPreBuffering:bytesRead]; |
||
2596 | |||
2597 | if(overflow > 0) |
||
2598 | { |
||
2599 | // Copy excess data into partialReadBuffer |
||
2600 | NSMutableData *buffer = theCurrentRead->buffer; |
||
2601 | const void *overflowBuffer = [buffer bytes] + theCurrentRead->bytesDone - overflow; |
||
2602 | |||
2603 | [partialReadBuffer appendBytes:overflowBuffer length:overflow]; |
||
2604 | |||
2605 | // Update the bytesDone variable. |
||
2606 | // Note: The completeCurrentRead method will trim the buffer for us. |
||
2607 | theCurrentRead->bytesDone -= overflow; |
||
2608 | } |
||
2609 | |||
2610 | done = (overflow >= 0); |
||
2611 | } |
||
2612 | else |
||
2613 | { |
||
2614 | // Search for the terminating sequence at the end of the buffer |
||
2615 | int termlen = [theCurrentRead->term length]; |
||
2616 | if(theCurrentRead->bytesDone >= termlen) |
||
2617 | { |
||
2618 | const void *buf = [theCurrentRead->buffer bytes] + (theCurrentRead->bytesDone - termlen); |
||
2619 | const void *seq = [theCurrentRead->term bytes]; |
||
2620 | done = (memcmp (buf, seq, termlen) == 0); |
||
2621 | } |
||
2622 | } |
||
2623 | |||
2624 | if(!done && theCurrentRead->maxLength >= 0 && theCurrentRead->bytesDone >= theCurrentRead->maxLength) |
||
2625 | { |
||
2626 | // There's a set maxLength, and we've reached that maxLength without completing the read |
||
2627 | maxoutError = YES; |
||
2628 | } |
||
2629 | } |
||
2630 | else |
||
2631 | { |
||
2632 | // Done when (sized) buffer is full. |
||
2633 | done = ([theCurrentRead->buffer length] == theCurrentRead->bytesDone); |
||
2634 | } |
||
2635 | } |
||
2636 | // else readAllAvailable doesn't end until all readable is read. |
||
2637 | } |
||
2638 | |||
2639 | if(theCurrentRead->readAllAvailableData && theCurrentRead->bytesDone > 0) |
||
2640 | { |
||
2641 | // Ran out of bytes, so the "read-all-available-data" type packet is done |
||
2642 | done = YES; |
||
2643 | } |
||
2644 | |||
2645 | if(done) |
||
2646 | { |
||
2647 | [self completeCurrentRead]; |
||
2648 | if (!socketError) [self scheduleDequeueRead]; |
||
2649 | } |
||
2650 | else if(totalBytesRead > 0) |
||
2651 | { |
||
2652 | // We're not done with the readToLength or readToData yet, but we have read in some bytes |
||
2653 | if ([theDelegate respondsToSelector:@selector(onSocket:didReadPartialDataOfLength:tag:)]) |
||
2654 | { |
||
2655 | [theDelegate onSocket:self didReadPartialDataOfLength:totalBytesRead tag:theCurrentRead->tag]; |
||
2656 | } |
||
2657 | } |
||
2658 | |||
2659 | if(socketError) |
||
2660 | { |
||
2661 | CFStreamError err = CFReadStreamGetError(theReadStream); |
||
2662 | [self closeWithError:[self errorFromCFStreamError:err]]; |
||
2663 | return; |
||
2664 | } |
||
2665 | if(maxoutError) |
||
2666 | { |
||
2667 | [self closeWithError:[self getReadMaxedOutError]]; |
||
2668 | return; |
||
2669 | } |
||
2670 | } |
||
2671 | } |
||
2672 | |||
2673 | // Ends current read and calls delegate. |
||
2674 | - (void)completeCurrentRead |
||
2675 | { |
||
2676 | NSAssert(theCurrentRead, @"Trying to complete current read when there is no current read."); |
||
2677 | |||
2678 | [theCurrentRead->buffer setLength:theCurrentRead->bytesDone]; |
||
2679 | if([theDelegate respondsToSelector:@selector(onSocket:didReadData:withTag:)]) |
||
2680 | { |
||
2681 | [theDelegate onSocket:self didReadData:theCurrentRead->buffer withTag:theCurrentRead->tag]; |
||
2682 | } |
||
2683 | |||
2684 | if (theCurrentRead != nil) [self endCurrentRead]; // Caller may have disconnected. |
||
2685 | } |
||
2686 | |||
2687 | // Ends current read. |
||
2688 | - (void)endCurrentRead |
||
2689 | { |
||
2690 | NSAssert(theCurrentRead, @"Trying to end current read when there is no current read."); |
||
2691 | |||
2692 | [theReadTimer invalidate]; |
||
2693 | theReadTimer = nil; |
||
2694 | |||
2695 | [theCurrentRead release]; |
||
2696 | theCurrentRead = nil; |
||
2697 | } |
||
2698 | |||
2699 | - (void)doReadTimeout:(NSTimer *)timer |
||
2700 | { |
||
2701 | NSTimeInterval timeoutExtension = 0.0; |
||
2702 | |||
2703 | if([theDelegate respondsToSelector:@selector(onSocket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) |
||
2704 | { |
||
2705 | timeoutExtension = [theDelegate onSocket:self shouldTimeoutReadWithTag:theCurrentRead->tag |
||
2706 | elapsed:theCurrentRead->timeout |
||
2707 | bytesDone:theCurrentRead->bytesDone]; |
||
2708 | } |
||
2709 | |||
2710 | if(timeoutExtension > 0.0) |
||
2711 | { |
||
2712 | theCurrentRead->timeout += timeoutExtension; |
||
2713 | |||
2714 | theReadTimer = [NSTimer timerWithTimeInterval:timeoutExtension |
||
2715 | target:self |
||
2716 | selector:@selector(doReadTimeout:) |
||
2717 | userInfo:nil |
||
2718 | repeats:NO]; |
||
2719 | [self runLoopAddTimer:theReadTimer]; |
||
2720 | } |
||
2721 | else |
||
2722 | { |
||
2723 | // Do not call endCurrentRead here. |
||
2724 | // We must allow the delegate access to any partial read in the unreadData method. |
||
2725 | |||
2726 | [self closeWithError:[self getReadTimeoutError]]; |
||
2727 | } |
||
2728 | } |
||
2729 | |||
2730 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
2731 | #pragma mark Writing |
||
2732 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
2733 | |||
2734 | - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag |
||
2735 | { |
||
2736 | if (data == nil || [data length] == 0) return; |
||
2737 | if (theFlags & kForbidReadsWrites) return; |
||
2738 | |||
2739 | AsyncWritePacket *packet = [[AsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; |
||
2740 | |||
2741 | [theWriteQueue addObject:packet]; |
||
2742 | [self scheduleDequeueWrite]; |
||
2743 | |||
2744 | [packet release]; |
||
2745 | } |
||
2746 | |||
2747 | - (void)scheduleDequeueWrite |
||
2748 | { |
||
2749 | if((theFlags & kDequeueWriteScheduled) == 0) |
||
2750 | { |
||
2751 | theFlags |= kDequeueWriteScheduled; |
||
2752 | [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; |
||
2753 | } |
||
2754 | } |
||
2755 | |||
2756 | /** |
||
2757 | * Conditionally starts a new write. |
||
2758 | * |
||
2759 | * IF there is not another write in process |
||
2760 | * AND there is a write queued |
||
2761 | * AND we have a write stream available |
||
2762 | * |
||
2763 | * This method also handles auto-disconnect post read/write completion. |
||
2764 | **/ |
||
2765 | - (void)maybeDequeueWrite |
||
2766 | { |
||
2767 | // Unset the flag indicating a call to this method is scheduled |
||
2768 | theFlags &= ~kDequeueWriteScheduled; |
||
2769 | |||
2770 | // If we're not currently processing a write AND we have an available write stream |
||
2771 | if((theCurrentWrite == nil) && (theWriteStream != NULL)) |
||
2772 | { |
||
2773 | if([theWriteQueue count] > 0) |
||
2774 | { |
||
2775 | // Dequeue the next object in the write queue |
||
2776 | theCurrentWrite = [[theWriteQueue objectAtIndex:0] retain]; |
||
2777 | [theWriteQueue removeObjectAtIndex:0]; |
||
2778 | |||
2779 | if([theCurrentWrite isKindOfClass:[AsyncSpecialPacket class]]) |
||
2780 | { |
||
2781 | // Attempt to start TLS |
||
2782 | theFlags |= kStartingWriteTLS; |
||
2783 | |||
2784 | // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are both set |
||
2785 | [self maybeStartTLS]; |
||
2786 | } |
||
2787 | else |
||
2788 | { |
||
2789 | // Start time-out timer |
||
2790 | if(theCurrentWrite->timeout >= 0.0) |
||
2791 | { |
||
2792 | theWriteTimer = [NSTimer timerWithTimeInterval:theCurrentWrite->timeout |
||
2793 | target:self |
||
2794 | selector:@selector(doWriteTimeout:) |
||
2795 | userInfo:nil |
||
2796 | repeats:NO]; |
||
2797 | [self runLoopAddTimer:theWriteTimer]; |
||
2798 | } |
||
2799 | |||
2800 | // Immediately write, if possible |
||
2801 | [self doSendBytes]; |
||
2802 | } |
||
2803 | } |
||
2804 | else if(theFlags & kDisconnectAfterWrites) |
||
2805 | { |
||
2806 | if(theFlags & kDisconnectAfterReads) |
||
2807 | { |
||
2808 | if(([theReadQueue count] == 0) && (theCurrentRead == nil)) |
||
2809 | { |
||
2810 | [self disconnect]; |
||
2811 | } |
||
2812 | } |
||
2813 | else |
||
2814 | { |
||
2815 | [self disconnect]; |
||
2816 | } |
||
2817 | } |
||
2818 | } |
||
2819 | } |
||
2820 | |||
2821 | /** |
||
2822 | * Call this method in doSendBytes instead of CFWriteStreamCanAcceptBytes(). |
||
2823 | * This method supports the kSocketCanAcceptBytes flag. |
||
2824 | **/ |
||
2825 | - (BOOL)canAcceptBytes |
||
2826 | { |
||
2827 | if (theFlags & kSocketCanAcceptBytes) |
||
2828 | { |
||
2829 | return YES; |
||
2830 | } |
||
2831 | else |
||
2832 | { |
||
2833 | return CFWriteStreamCanAcceptBytes(theWriteStream); |
||
2834 | } |
||
2835 | } |
||
2836 | |||
2837 | - (void)doSendBytes |
||
2838 | { |
||
2839 | if((theCurrentWrite != nil) && (theWriteStream != NULL)) |
||
2840 | { |
||
2841 | // Note: This method is not called if theCurrentWrite is an AsyncSpecialPacket (startTLS packet) |
||
2842 | |||
2843 | CFIndex totalBytesWritten = 0; |
||
2844 | |||
2845 | BOOL done = NO; |
||
2846 | BOOL error = NO; |
||
2847 | |||
2848 | while (!done && !error && [self canAcceptBytes]) |
||
2849 | { |
||
2850 | // Figure out what to write. |
||
2851 | CFIndex bytesRemaining = [theCurrentWrite->buffer length] - theCurrentWrite->bytesDone; |
||
2852 | CFIndex bytesToWrite = (bytesRemaining < WRITE_CHUNKSIZE) ? bytesRemaining : WRITE_CHUNKSIZE; |
||
2853 | UInt8 *writestart = (UInt8 *)([theCurrentWrite->buffer bytes] + theCurrentWrite->bytesDone); |
||
2854 | |||
2855 | // Write. |
||
2856 | CFIndex bytesWritten = CFWriteStreamWrite(theWriteStream, writestart, bytesToWrite); |
||
2857 | |||
2858 | // Unset the "can accept bytes" flag |
||
2859 | theFlags &= ~kSocketCanAcceptBytes; |
||
2860 | |||
2861 | // Check results |
||
2862 | if (bytesWritten < 0) |
||
2863 | { |
||
2864 | error = YES; |
||
2865 | } |
||
2866 | else |
||
2867 | { |
||
2868 | // Update total amount read for the current write |
||
2869 | theCurrentWrite->bytesDone += bytesWritten; |
||
2870 | |||
2871 | // Update total amount written in this method invocation |
||
2872 | totalBytesWritten += bytesWritten; |
||
2873 | |||
2874 | // Is packet done? |
||
2875 | done = ([theCurrentWrite->buffer length] == theCurrentWrite->bytesDone); |
||
2876 | } |
||
2877 | } |
||
2878 | |||
2879 | if(done) |
||
2880 | { |
||
2881 | [self completeCurrentWrite]; |
||
2882 | [self scheduleDequeueWrite]; |
||
2883 | } |
||
2884 | else if(error) |
||
2885 | { |
||
2886 | CFStreamError err = CFWriteStreamGetError(theWriteStream); |
||
2887 | [self closeWithError:[self errorFromCFStreamError:err]]; |
||
2888 | return; |
||
2889 | } |
||
2890 | else |
||
2891 | { |
||
2892 | // We're not done with the entire write, but we have written some bytes |
||
2893 | if ([theDelegate respondsToSelector:@selector(onSocket:didWritePartialDataOfLength:tag:)]) |
||
2894 | { |
||
2895 | [theDelegate onSocket:self didWritePartialDataOfLength:totalBytesWritten tag:theCurrentWrite->tag]; |
||
2896 | } |
||
2897 | } |
||
2898 | } |
||
2899 | } |
||
2900 | |||
2901 | // Ends current write and calls delegate. |
||
2902 | - (void)completeCurrentWrite |
||
2903 | { |
||
2904 | NSAssert(theCurrentWrite, @"Trying to complete current write when there is no current write."); |
||
2905 | |||
2906 | if ([theDelegate respondsToSelector:@selector(onSocket:didWriteDataWithTag:)]) |
||
2907 | { |
||
2908 | [theDelegate onSocket:self didWriteDataWithTag:theCurrentWrite->tag]; |
||
2909 | } |
||
2910 | |||
2911 | if (theCurrentWrite != nil) [self endCurrentWrite]; // Caller may have disconnected. |
||
2912 | } |
||
2913 | |||
2914 | // Ends current write. |
||
2915 | - (void)endCurrentWrite |
||
2916 | { |
||
2917 | NSAssert(theCurrentWrite, @"Trying to complete current write when there is no current write."); |
||
2918 | |||
2919 | [theWriteTimer invalidate]; |
||
2920 | theWriteTimer = nil; |
||
2921 | |||
2922 | [theCurrentWrite release]; |
||
2923 | theCurrentWrite = nil; |
||
2924 | } |
||
2925 | |||
2926 | - (void)doWriteTimeout:(NSTimer *)timer |
||
2927 | { |
||
2928 | NSTimeInterval timeoutExtension = 0.0; |
||
2929 | |||
2930 | if([theDelegate respondsToSelector:@selector(onSocket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) |
||
2931 | { |
||
2932 | timeoutExtension = [theDelegate onSocket:self shouldTimeoutWriteWithTag:theCurrentWrite->tag |
||
2933 | elapsed:theCurrentWrite->timeout |
||
2934 | bytesDone:theCurrentWrite->bytesDone]; |
||
2935 | } |
||
2936 | |||
2937 | if(timeoutExtension > 0.0) |
||
2938 | { |
||
2939 | theCurrentWrite->timeout += timeoutExtension; |
||
2940 | |||
2941 | theWriteTimer = [NSTimer timerWithTimeInterval:timeoutExtension |
||
2942 | target:self |
||
2943 | selector:@selector(doWriteTimeout:) |
||
2944 | userInfo:nil |
||
2945 | repeats:NO]; |
||
2946 | [self runLoopAddTimer:theWriteTimer]; |
||
2947 | } |
||
2948 | else |
||
2949 | { |
||
2950 | [self closeWithError:[self getWriteTimeoutError]]; |
||
2951 | } |
||
2952 | } |
||
2953 | |||
2954 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
2955 | #pragma mark Security |
||
2956 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
2957 | |||
2958 | - (void)startTLS:(NSDictionary *)tlsSettings |
||
2959 | { |
||
2960 | if(tlsSettings == nil) |
||
2961 | { |
||
2962 | // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary, |
||
2963 | // but causes problems if we later try to fetch the remote host's certificate. |
||
2964 | // |
||
2965 | // To be exact, it causes the following to return NULL instead of the normal result: |
||
2966 | // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates) |
||
2967 | // |
||
2968 | // So we use an empty dictionary instead, which works perfectly. |
||
2969 | |||
2970 | tlsSettings = [NSDictionary dictionary]; |
||
2971 | } |
||
2972 | |||
2973 | AsyncSpecialPacket *packet = [[AsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings]; |
||
2974 | |||
2975 | [theReadQueue addObject:packet]; |
||
2976 | [self scheduleDequeueRead]; |
||
2977 | |||
2978 | [theWriteQueue addObject:packet]; |
||
2979 | [self scheduleDequeueWrite]; |
||
2980 | |||
2981 | [packet release]; |
||
2982 | } |
||
2983 | |||
2984 | - (void)maybeStartTLS |
||
2985 | { |
||
2986 | // We can't start TLS until: |
||
2987 | // - All queued reads prior to the user calling StartTLS are complete |
||
2988 | // - All queued writes prior to the user calling StartTLS are complete |
||
2989 | // |
||
2990 | // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set |
||
2991 | |||
2992 | if((theFlags & kStartingReadTLS) && (theFlags & kStartingWriteTLS)) |
||
2993 | { |
||
2994 | AsyncSpecialPacket *tlsPacket = (AsyncSpecialPacket *)theCurrentRead; |
||
2995 | |||
2996 | BOOL didStartOnReadStream = CFReadStreamSetProperty(theReadStream, kCFStreamPropertySSLSettings, |
||
2997 | (CFDictionaryRef)tlsPacket->tlsSettings); |
||
2998 | BOOL didStartOnWriteStream = CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertySSLSettings, |
||
2999 | (CFDictionaryRef)tlsPacket->tlsSettings); |
||
3000 | |||
3001 | if(!didStartOnReadStream || !didStartOnWriteStream) |
||
3002 | { |
||
3003 | [self closeWithError:[self getSocketError]]; |
||
3004 | } |
||
3005 | } |
||
3006 | } |
||
3007 | |||
3008 | - (void)onTLSHandshakeSuccessful |
||
3009 | { |
||
3010 | if((theFlags & kStartingReadTLS) && (theFlags & kStartingWriteTLS)) |
||
3011 | { |
||
3012 | theFlags &= ~kStartingReadTLS; |
||
3013 | theFlags &= ~kStartingWriteTLS; |
||
3014 | |||
3015 | if([theDelegate respondsToSelector:@selector(onSocketDidSecure:)]) |
||
3016 | { |
||
3017 | [theDelegate onSocketDidSecure:self]; |
||
3018 | } |
||
3019 | |||
3020 | [self endCurrentRead]; |
||
3021 | [self endCurrentWrite]; |
||
3022 | |||
3023 | [self scheduleDequeueRead]; |
||
3024 | [self scheduleDequeueWrite]; |
||
3025 | } |
||
3026 | } |
||
3027 | |||
3028 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
3029 | #pragma mark CF Callbacks |
||
3030 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
3031 | |||
3032 | - (void)doCFSocketCallback:(CFSocketCallBackType)type |
||
3033 | forSocket:(CFSocketRef)sock |
||
3034 | withAddress:(NSData *)address |
||
3035 | withData:(const void *)pData |
||
3036 | { |
||
3037 | NSParameterAssert ((sock == theSocket4) || (sock == theSocket6)); |
||
3038 | |||
3039 | switch (type) |
||
3040 | { |
||
3041 | case kCFSocketConnectCallBack: |
||
3042 | // The data argument is either NULL or a pointer to an SInt32 error code, if the connect failed. |
||
3043 | if(pData) |
||
3044 | [self doSocketOpen:sock withCFSocketError:kCFSocketError]; |
||
3045 | else |
||
3046 | [self doSocketOpen:sock withCFSocketError:kCFSocketSuccess]; |
||
3047 | break; |
||
3048 | case kCFSocketAcceptCallBack: |
||
3049 | [self doAcceptWithSocket: *((CFSocketNativeHandle *)pData)]; |
||
3050 | break; |
||
3051 | default: |
||
3052 | NSLog (@"AsyncSocket %p received unexpected CFSocketCallBackType %d.", self, type); |
||
3053 | break; |
||
3054 | } |
||
3055 | } |
||
3056 | |||
3057 | - (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream |
||
3058 | { |
||
3059 | NSParameterAssert(theReadStream != NULL); |
||
3060 | |||
3061 | CFStreamError err; |
||
3062 | switch (type) |
||
3063 | { |
||
3064 | case kCFStreamEventOpenCompleted: |
||
3065 | theFlags |= kDidCompleteOpenForRead; |
||
3066 | [self doStreamOpen]; |
||
3067 | break; |
||
3068 | case kCFStreamEventHasBytesAvailable: |
||
3069 | if(theFlags & kStartingReadTLS) { |
||
3070 | [self onTLSHandshakeSuccessful]; |
||
3071 | } |
||
3072 | else { |
||
3073 | theFlags |= kSocketHasBytesAvailable; |
||
3074 | [self doBytesAvailable]; |
||
3075 | } |
||
3076 | break; |
||
3077 | case kCFStreamEventErrorOccurred: |
||
3078 | case kCFStreamEventEndEncountered: |
||
3079 | err = CFReadStreamGetError (theReadStream); |
||
3080 | [self closeWithError: [self errorFromCFStreamError:err]]; |
||
3081 | break; |
||
3082 | default: |
||
3083 | NSLog (@"AsyncSocket %p received unexpected CFReadStream callback, CFStreamEventType %d.", self, type); |
||
3084 | } |
||
3085 | } |
||
3086 | |||
3087 | - (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream |
||
3088 | { |
||
3089 | NSParameterAssert(theWriteStream != NULL); |
||
3090 | |||
3091 | CFStreamError err; |
||
3092 | switch (type) |
||
3093 | { |
||
3094 | case kCFStreamEventOpenCompleted: |
||
3095 | theFlags |= kDidCompleteOpenForWrite; |
||
3096 | [self doStreamOpen]; |
||
3097 | break; |
||
3098 | case kCFStreamEventCanAcceptBytes: |
||
3099 | if(theFlags & kStartingWriteTLS) { |
||
3100 | [self onTLSHandshakeSuccessful]; |
||
3101 | } |
||
3102 | else { |
||
3103 | theFlags |= kSocketCanAcceptBytes; |
||
3104 | [self doSendBytes]; |
||
3105 | } |
||
3106 | break; |
||
3107 | case kCFStreamEventErrorOccurred: |
||
3108 | case kCFStreamEventEndEncountered: |
||
3109 | err = CFWriteStreamGetError (theWriteStream); |
||
3110 | [self closeWithError: [self errorFromCFStreamError:err]]; |
||
3111 | break; |
||
3112 | default: |
||
3113 | NSLog (@"AsyncSocket %p received unexpected CFWriteStream callback, CFStreamEventType %d.", self, type); |
||
3114 | } |
||
3115 | } |
||
3116 | |||
3117 | /** |
||
3118 | * This is the callback we setup for CFSocket. |
||
3119 | * This method does nothing but forward the call to it's Objective-C counterpart |
||
3120 | **/ |
||
3121 | static void MyCFSocketCallback (CFSocketRef sref, CFSocketCallBackType type, CFDataRef address, const void *pData, void *pInfo) |
||
3122 | { |
||
3123 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
||
3124 | |||
3125 | AsyncSocket *theSocket = [[(AsyncSocket *)pInfo retain] autorelease]; |
||
3126 | [theSocket doCFSocketCallback:type forSocket:sref withAddress:(NSData *)address withData:pData]; |
||
3127 | |||
3128 | [pool release]; |
||
3129 | } |
||
3130 | |||
3131 | /** |
||
3132 | * This is the callback we setup for CFReadStream. |
||
3133 | * This method does nothing but forward the call to it's Objective-C counterpart |
||
3134 | **/ |
||
3135 | static void MyCFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo) |
||
3136 | { |
||
3137 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
||
3138 | |||
3139 | AsyncSocket *theSocket = [[(AsyncSocket *)pInfo retain] autorelease]; |
||
3140 | [theSocket doCFReadStreamCallback:type forStream:stream]; |
||
3141 | |||
3142 | [pool release]; |
||
3143 | } |
||
3144 | |||
3145 | /** |
||
3146 | * This is the callback we setup for CFWriteStream. |
||
3147 | * This method does nothing but forward the call to it's Objective-C counterpart |
||
3148 | **/ |
||
3149 | static void MyCFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) |
||
3150 | { |
||
3151 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
||
3152 | |||
3153 | AsyncSocket *theSocket = [[(AsyncSocket *)pInfo retain] autorelease]; |
||
3154 | [theSocket doCFWriteStreamCallback:type forStream:stream]; |
||
3155 | |||
3156 | [pool release]; |
||
3157 | } |
||
3158 | |||
3159 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
3160 | #pragma mark Class Methods |
||
3161 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
||
3162 | |||
3163 | // Return line separators. |
||
3164 | + (NSData *)CRLFData |
||
3165 | { |
||
3166 | return [NSData dataWithBytes:"\x0D\x0A" length:2]; |
||
3167 | } |
||
3168 | |||
3169 | + (NSData *)CRData |
||
3170 | { |
||
3171 | return [NSData dataWithBytes:"\x0D" length:1]; |
||
3172 | } |
||
3173 | |||
3174 | + (NSData *)LFData |
||
3175 | { |
||
3176 | return [NSData dataWithBytes:"\x0A" length:1]; |
||
3177 | } |
||
3178 | |||
3179 | + (NSData *)ZeroData |
||
3180 | { |
||
3181 | return [NSData dataWithBytes:"" length:1]; |
||
3182 | } |
||
3183 | |||
3184 | @end |