Tweaked release to be immediate instead of on autorelease pool.
5 // Created by Jens Alfke on 5/18/08.
6 // Copyright 2008 Jens Alfke. All rights reserved.
9 #import "TCP_Internal.h"
11 #import "MYBonjourService.h"
15 #import "ExceptionUtils.h"
18 #if TARGET_OS_IPHONE && !defined(__SEC_TYPES__)
19 // SecureTransport.h is missing on iPhone, with its SSL constants:
21 errSSLClosedAbort = -9806, /* connection closed via error */
27 NSString* const TCPErrorDomain = @"TCP";
30 @interface TCPConnection ()
31 @property TCPConnectionStatus status;
32 @property (retain) IPAddress *address;
33 - (BOOL) _checkIfClosed;
38 @implementation TCPConnection
41 static NSMutableArray *sAllConnections;
44 - (Class) readerClass {return [TCPReader class];}
45 - (Class) writerClass {return [TCPWriter class];}
48 - (id) _initWithAddress: (IPAddress*)address
49 inputStream: (NSInputStream*)input
50 outputStream: (NSOutputStream*)output
54 if( !input || !output ) {
55 LogTo(TCP,@"Failed to create %@: addr=%@, in=%@, out=%@",
56 self.class,address,input,output);
60 _address = [address copy];
61 _reader = [[[self readerClass] alloc] initWithConnection: self stream: input];
62 _writer = [[[self writerClass] alloc] initWithConnection: self stream: output];
63 LogTo(TCP,@"%@ initialized, address=%@",self,address);
70 - (id) initToAddress: (IPAddress*)address
72 NSInputStream *input = nil;
73 NSOutputStream *output = nil;
75 // +getStreamsToHost: is missing for some stupid reason on iPhone. Grrrrrrrrrr.
76 CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)address.hostname, address.port,
77 (CFReadStreamRef*)&input, (CFWriteStreamRef*)&output);
78 if( input ) [NSMakeCollectable(input) autorelease];
79 if( output ) [NSMakeCollectable(output) autorelease];
81 [NSStream getStreamsToHost: [NSHost hostWithAddress: address.ipv4name]
84 outputStream: &output];
86 return [self _initWithAddress: address inputStream: input outputStream: output];
89 - (id) initToNetService: (NSNetService*)service
91 IPAddress *address = nil;
93 NSOutputStream *output;
94 if( [service getInputStream: &input outputStream: &output] ) {
95 NSArray *addresses = service.addresses;
96 if( addresses.count > 0 )
97 address = [[[IPAddress alloc] initWithSockAddr: [[addresses objectAtIndex: 0] bytes]] autorelease];
102 return [self _initWithAddress: address inputStream: input outputStream: output];
105 - (id) initToBonjourService: (MYBonjourService*)service;
107 NSNetService *netService = [[NSNetService alloc] initWithDomain: service.domain
108 type: service.type name: service.name];
109 self = [self initToNetService: netService];
115 - (id) initIncomingFromSocket: (CFSocketNativeHandle)socket
116 listener: (TCPListener*)listener
118 CFReadStreamRef readStream = NULL;
119 CFWriteStreamRef writeStream = NULL;
120 CFStreamCreatePairWithSocket(kCFAllocatorDefault, socket, &readStream, &writeStream);
121 if( readStream ) [NSMakeCollectable(readStream) autorelease];
122 if( writeStream ) [NSMakeCollectable(writeStream) autorelease];
124 self = [self _initWithAddress: [IPAddress addressOfSocket: socket]
125 inputStream: (NSInputStream*)readStream
126 outputStream: (NSOutputStream*)writeStream];
129 _server = [listener retain];
130 CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
131 CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
139 LogTo(TCP,@"DEALLOC %@",self);
147 - (NSString*) description
149 return $sprintf(@"%@[%@ %@]",self.class,(_isIncoming ?@"from" :@"to"),_address);
153 @synthesize address=_address, isIncoming=_isIncoming, status=_status, delegate=_delegate,
154 reader=_reader, writer=_writer, server=_server, openTimeout=_openTimeout;
163 - (NSString*) actualSecurityLevel
165 return _reader.securityLevel;
169 - (NSArray*) peerSSLCerts
171 return _reader.peerSSLCerts ?: _writer.peerSSLCerts;
175 - (void) _setStreamProperty: (id)value forKey: (NSString*)key
177 [_reader setProperty: value forKey: (CFStringRef)key];
178 [_writer setProperty: value forKey: (CFStringRef)key];
183 #pragma mark OPENING / CLOSING:
188 if( _status<=kTCP_Closed && _reader ) {
189 _reader.SSLProperties = _sslProperties;
190 _writer.SSLProperties = _sslProperties;
193 if( ! [sAllConnections my_containsObjectIdenticalTo: self] )
194 [sAllConnections addObject: self];
195 self.status = kTCP_Opening;
196 if( _openTimeout > 0 )
197 [self performSelector: @selector(_openTimeoutExpired) withObject: nil afterDelay: _openTimeout];
201 - (void) _stopOpenTimer
203 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_openTimeoutExpired) object: nil];
206 - (void) _openTimeoutExpired
208 if( _status == kTCP_Opening ) {
209 LogTo(TCP,@"%@: timed out waiting to open",self);
210 [self _stream: _reader gotError: [NSError errorWithDomain: NSPOSIXErrorDomain
211 code: ETIMEDOUT userInfo: nil]];
218 if( _status > kTCP_Closed ) {
219 LogTo(TCP,@"%@ disconnecting",self);
220 [_writer disconnect];
221 [_reader disconnect];
222 self.status = kTCP_Disconnected;
224 [self _stopOpenTimer];
230 [self closeWithTimeout: 60.0];
233 - (void) closeWithTimeout: (NSTimeInterval)timeout
235 [self _stopOpenTimer];
236 if( _status == kTCP_Opening ) {
237 LogTo(TCP,@"%@ canceling open",self);
239 } else if( _status == kTCP_Open ) {
240 LogTo(TCP,@"%@ closing",self);
241 self.status = kTCP_Closing;
244 if( ! [self _checkIfClosed] ) {
247 else if( timeout != INFINITY )
248 [self performSelector: @selector(_closeTimeoutExpired)
249 withObject: nil afterDelay: timeout];
255 - (void) _closeTimeoutExpired
257 if( _status==kTCP_Closing )
261 - (void) _stopCloseTimer
263 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_closeTimeoutExpired) object: nil];
268 if( _status == kTCP_Closing ) {
269 LogTo(TCP,@"%@: _unclose!",self);
272 [self _stopCloseTimer];
273 self.status = kTCP_Open;
278 /** Subclasses can override this to customize what happens when -close is called. */
286 - (BOOL) _checkIfClosed
288 if( _status == kTCP_Closing && _writer==nil && _reader==nil ) {
296 // called by my streams when they close (after my -close is called)
299 [[self retain] autorelease];
300 if( _status != kTCP_Closed && _status != kTCP_Disconnected ) {
301 LogTo(TCP,@"%@ is now closed",self);
302 TCPConnectionStatus prevStatus = _status;
303 self.status = (prevStatus==kTCP_Closing ?kTCP_Closed :kTCP_Disconnected);
304 if( prevStatus==kTCP_Opening )
305 [self tellDelegate: @selector(connection:failedToOpen:) withObject: self.error];
307 [self tellDelegate: @selector(connectionDidClose:) withObject: nil];
309 [self _stopCloseTimer];
310 [self _stopOpenTimer];
311 [sAllConnections removeObjectIdenticalTo: self];
315 + (void) closeAllWithTimeout: (NSTimeInterval)timeout
317 NSArray *connections = [sAllConnections copy];
318 for( TCPConnection *conn in connections )
319 [conn closeWithTimeout: timeout];
320 [connections release];
323 + (void) waitTillAllClosed
325 while( sAllConnections.count ) {
326 if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
327 beforeDate: [NSDate distantFuture]] )
334 #pragma mark STREAM CALLBACKS:
337 - (void) _streamOpened: (TCPStream*)stream
340 self.address = stream.peerAddress;
341 if( _status==kTCP_Opening && _reader.isOpen && _writer.isOpen ) {
342 LogTo(TCP,@"%@ opened; address=%@",self,_address);
343 [self _stopOpenTimer];
344 self.status = kTCP_Open;
345 [self tellDelegate: @selector(connectionDidOpen:) withObject: nil];
350 - (BOOL) _streamPeerCertAvailable: (TCPStream*)stream
353 if( ! _checkedPeerCert ) {
355 _checkedPeerCert = YES;
356 if( stream.securityLevel != nil ) {
357 NSArray *certs = stream.peerSSLCerts;
358 if( ! certs && ! _isIncoming )
359 allow = NO; // Server MUST have a cert!
361 SecCertificateRef cert = certs.count ?(SecCertificateRef)[certs objectAtIndex:0] :NULL;
362 LogTo(TCP,@"%@: Peer cert = %@",self,[TCPEndpoint describeCert: cert]);
363 if( [_delegate respondsToSelector: @selector(connection:authorizeSSLPeer:)] )
364 allow = [_delegate connection: self authorizeSSLPeer: cert];
367 }@catch( NSException *x ) {
368 MYReportException(x,@"TCPConnection _streamPeerCertAvailable");
369 _checkedPeerCert = NO;
373 [self _stream: stream
374 gotError: [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
375 code: errSSLClosedAbort
382 - (void) _stream: (TCPStream*)stream gotError: (NSError*)error
384 LogTo(TCP,@"%@ got %@ on %@",self,error,stream.class);
386 [[self retain] autorelease];
387 setObj(&_error,error);
388 [_reader disconnect];
389 [_writer disconnect];
393 - (void) _streamGotEOF: (TCPStream*)stream
395 LogTo(TCP,@"%@ got EOF on %@",self,stream);
397 if( _status == kTCP_Closing ) {
398 [self _streamCanClose: stream];
399 [self _checkIfClosed];
401 [self _stream: stream
402 gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
407 // Called as soon as a stream is ready to close, after its -close method has been called.
408 - (void) _streamCanClose: (TCPStream*)stream
410 if( ! _reader.isActive && !_writer.isActive ) {
411 LogTo(TCPVerbose,@"Both streams are ready to close now!");
412 [_reader disconnect];
413 [_writer disconnect];
418 // Called after I called -close on a stream and it finished closing:
419 - (void) _streamDisconnected: (TCPStream*)stream
421 LogTo(TCP,@"%@: disconnected %@",self,stream);
422 if( stream == _reader )
423 setObj(&_reader,nil);
424 else if( stream == _writer )
425 setObj(&_writer,nil);
428 if( !_reader.isOpen && !_writer.isOpen )
437 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
439 Redistribution and use in source and binary forms, with or without modification, are permitted
440 provided that the following conditions are met:
442 * Redistributions of source code must retain the above copyright notice, this list of conditions
443 and the following disclaimer.
444 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
445 and the following disclaimer in the documentation and/or other materials provided with the
448 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
449 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
450 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
451 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
452 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
453 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
454 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
455 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.