Connections opened by listeners now close correctly.
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];
110 [netService release];
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,
154 reader=_reader, writer=_writer, server=_server, openTimeout=_openTimeout;
156 - (id<TCPConnectionDelegate>) delegate {return _delegate;}
157 - (void) setDelegate: (id<TCPConnectionDelegate>) delegate {_delegate = delegate;}
165 - (NSString*) actualSecurityLevel
167 return _reader.securityLevel;
171 - (NSArray*) peerSSLCerts
173 return _reader.peerSSLCerts ?: _writer.peerSSLCerts;
177 - (void) _setStreamProperty: (id)value forKey: (NSString*)key
179 [_reader setProperty: value forKey: (CFStringRef)key];
180 [_writer setProperty: value forKey: (CFStringRef)key];
185 #pragma mark OPENING / CLOSING:
190 if( _status<=kTCP_Closed && _reader ) {
191 _reader.SSLProperties = _sslProperties;
192 _writer.SSLProperties = _sslProperties;
195 if( ! [sAllConnections my_containsObjectIdenticalTo: self] )
196 [sAllConnections addObject: self];
197 self.status = kTCP_Opening;
198 if( _openTimeout > 0 )
199 [self performSelector: @selector(_openTimeoutExpired) withObject: nil afterDelay: _openTimeout];
203 - (void) _stopOpenTimer
205 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_openTimeoutExpired) object: nil];
208 - (void) _openTimeoutExpired
210 if( _status == kTCP_Opening ) {
211 LogTo(TCP,@"%@: timed out waiting to open",self);
212 [self _stream: _reader gotError: [NSError errorWithDomain: NSPOSIXErrorDomain
213 code: ETIMEDOUT userInfo: nil]];
220 if( _status > kTCP_Closed ) {
221 LogTo(TCP,@"%@ disconnecting",self);
222 [_writer disconnect];
223 [_reader disconnect];
224 self.status = kTCP_Disconnected;
226 [self _stopOpenTimer];
232 [self closeWithTimeout: 60.0];
235 - (void) closeWithTimeout: (NSTimeInterval)timeout
237 [self _stopOpenTimer];
238 if( _status == kTCP_Opening ) {
239 LogTo(TCP,@"%@ canceling open",self);
241 } else if( _status == kTCP_Open ) {
242 LogTo(TCP,@"%@ closing",self);
243 self.status = kTCP_Closing;
246 if( ! [self _checkIfClosed] ) {
249 else if( timeout != INFINITY )
250 [self performSelector: @selector(_closeTimeoutExpired)
251 withObject: nil afterDelay: timeout];
257 - (void) _closeTimeoutExpired
259 if( _status==kTCP_Closing )
263 - (void) _stopCloseTimer
265 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_closeTimeoutExpired) object: nil];
270 if( _status == kTCP_Closing ) {
271 LogTo(TCP,@"%@: _unclose!",self);
274 [self _stopCloseTimer];
275 self.status = kTCP_Open;
280 /** Subclasses can override this to customize what happens when -close is called. */
288 - (BOOL) _checkIfClosed
290 if( _status == kTCP_Closing && _writer==nil && _reader==nil ) {
298 // called by my streams when they close (after my -close is called)
301 [[self retain] autorelease];
302 if( _status != kTCP_Closed && _status != kTCP_Disconnected ) {
303 LogTo(TCP,@"%@ is now closed",self);
304 TCPConnectionStatus prevStatus = _status;
305 self.status = (prevStatus==kTCP_Closing ?kTCP_Closed :kTCP_Disconnected);
306 if( prevStatus==kTCP_Opening )
307 [self tellDelegate: @selector(connection:failedToOpen:) withObject: self.error];
309 [self tellDelegate: @selector(connectionDidClose:) withObject: nil];
311 [self _stopCloseTimer];
312 [self _stopOpenTimer];
313 [sAllConnections removeObjectIdenticalTo: self];
317 + (void) closeAllWithTimeout: (NSTimeInterval)timeout
319 NSArray *connections = [sAllConnections copy];
320 for( TCPConnection *conn in connections )
321 [conn closeWithTimeout: timeout];
322 [connections release];
325 + (void) waitTillAllClosed
327 while( sAllConnections.count ) {
328 if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
329 beforeDate: [NSDate distantFuture]] )
336 #pragma mark STREAM CALLBACKS:
339 - (void) _streamOpened: (TCPStream*)stream
342 self.address = stream.peerAddress;
343 if( _status==kTCP_Opening && _reader.isOpen && _writer.isOpen ) {
344 LogTo(TCP,@"%@ opened; address=%@",self,_address);
345 [self _stopOpenTimer];
346 self.status = kTCP_Open;
347 [self tellDelegate: @selector(connectionDidOpen:) withObject: nil];
352 - (BOOL) _streamPeerCertAvailable: (TCPStream*)stream
355 if( ! _checkedPeerCert ) {
357 _checkedPeerCert = YES;
358 if( stream.securityLevel != nil ) {
359 NSArray *certs = stream.peerSSLCerts;
360 if( ! certs && ! _isIncoming )
361 allow = NO; // Server MUST have a cert!
363 SecCertificateRef cert = certs.count ?(SecCertificateRef)[certs objectAtIndex:0] :NULL;
364 if ([TCPEndpoint respondsToSelector: @selector(describeCert:)])
365 LogTo(TCP,@"%@: Peer cert = %@",self,[TCPEndpoint describeCert: cert]);
366 if( [_delegate respondsToSelector: @selector(connection:authorizeSSLPeer:)] )
367 allow = [_delegate connection: self authorizeSSLPeer: cert];
370 }@catch( NSException *x ) {
371 MYReportException(x,@"TCPConnection _streamPeerCertAvailable");
372 _checkedPeerCert = NO;
376 [self _stream: stream
377 gotError: [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
378 code: errSSLClosedAbort
385 - (void) _stream: (TCPStream*)stream gotError: (NSError*)error
387 LogTo(TCP,@"%@ got %@ on %@",self,error,stream.class);
389 [[self retain] autorelease];
390 setObj(&_error,error);
391 [_reader disconnect];
392 [_writer disconnect];
396 - (void) _streamGotEOF: (TCPStream*)stream
398 LogTo(TCP,@"%@ got EOF on %@",self,stream);
400 if( _status == kTCP_Closing ) {
401 [self _streamCanClose: stream];
402 [self _checkIfClosed];
404 [self _stream: stream
405 gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
410 // Called as soon as a stream is ready to close, after its -close method has been called.
411 - (void) _streamCanClose: (TCPStream*)stream
413 if( ! _reader.isActive && !_writer.isActive ) {
414 LogTo(TCPVerbose,@"Both streams are ready to close now!");
415 [_reader disconnect];
416 [_writer disconnect];
421 // Called after I called -close on a stream and it finished closing:
422 - (void) _streamDisconnected: (TCPStream*)stream
424 LogTo(TCP,@"%@: disconnected %@",self,stream);
425 if( stream == _reader )
426 setObj(&_reader,nil);
427 else if( stream == _writer )
428 setObj(&_writer,nil);
431 if( !_reader.isOpen && !_writer.isOpen )
440 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
442 Redistribution and use in source and binary forms, with or without modification, are permitted
443 provided that the following conditions are met:
445 * Redistributions of source code must retain the above copyright notice, this list of conditions
446 and the following disclaimer.
447 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
448 and the following disclaimer in the documentation and/or other materials provided with the
451 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
452 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
453 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
454 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
455 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
456 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
457 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
458 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.