Fixed: The -connection:failedToOpen: delegate method wasn't being called.
5 // Created by Jens Alfke on 5/18/08.
6 // Copyright 2008 Jens Alfke. All rights reserved.
9 #import "TCP_Internal.h"
14 #import "ExceptionUtils.h"
18 // SecureTransport.h is missing on iPhone, with its SSL constants:
20 errSSLClosedAbort = -9806, /* connection closed via error */
26 NSString* const TCPErrorDomain = @"TCP";
29 @interface TCPConnection ()
30 @property TCPConnectionStatus status;
31 @property (retain) IPAddress *address;
32 - (BOOL) _checkIfClosed;
37 @implementation TCPConnection
40 static NSMutableArray *sAllConnections;
43 - (Class) readerClass {return [TCPReader class];}
44 - (Class) writerClass {return [TCPWriter class];}
47 - (id) _initWithAddress: (IPAddress*)address
48 inputStream: (NSInputStream*)input
49 outputStream: (NSOutputStream*)output
53 if( !input || !output ) {
54 LogTo(TCP,@"Failed to create %@: addr=%@, in=%@, out=%@",
55 self.class,address,input,output);
59 _address = [address copy];
60 _reader = [[[self readerClass] alloc] initWithConnection: self stream: input];
61 _writer = [[[self writerClass] alloc] initWithConnection: self stream: output];
62 LogTo(TCP,@"%@ initialized, address=%@",self,address);
69 - (id) initToAddress: (IPAddress*)address
70 localPort: (UInt16)localPort
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);
79 [(id)CFMakeCollectable(input) autorelease];
81 [(id)CFMakeCollectable(output) autorelease];
83 [NSStream getStreamsToHost: [NSHost hostWithAddress: address.ipv4name]
86 outputStream: &output];
88 return [self _initWithAddress: address inputStream: input outputStream: output];
89 //FIX: Support localPort!
92 - (id) initToAddress: (IPAddress*)address
94 return [self initToAddress: address localPort: 0];
97 - (id) initToNetService: (NSNetService*)service
99 IPAddress *address = nil;
100 NSInputStream *input;
101 NSOutputStream *output;
102 if( [service getInputStream: &input outputStream: &output] ) {
103 NSArray *addresses = service.addresses;
104 if( addresses.count > 0 )
105 address = [[[IPAddress alloc] initWithSockAddr: [[addresses objectAtIndex: 0] bytes]] autorelease];
110 return [self _initWithAddress: address inputStream: input outputStream: output];
114 - (id) initIncomingFromSocket: (CFSocketNativeHandle)socket
115 listener: (TCPListener*)listener
117 CFReadStreamRef readStream = NULL;
118 CFWriteStreamRef writeStream = NULL;
119 CFStreamCreatePairWithSocket(kCFAllocatorDefault, socket, &readStream, &writeStream);
120 self = [self _initWithAddress: [IPAddress addressOfSocket: socket]
121 inputStream: (NSInputStream*)readStream
122 outputStream: (NSOutputStream*)writeStream];
125 _server = [listener retain];
126 CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
127 CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
135 LogTo(TCP,@"DEALLOC %@",self);
143 - (NSString*) description
145 return $sprintf(@"%@[%@ %@]",self.class,(_isIncoming ?@"from" :@"to"),_address);
149 @synthesize address=_address, isIncoming=_isIncoming, status=_status, delegate=_delegate,
150 reader=_reader, writer=_writer, server=_server;
159 - (NSString*) actualSecurityLevel
161 return _reader.securityLevel;
165 - (NSArray*) peerSSLCerts
167 return _reader.peerSSLCerts ?: _writer.peerSSLCerts;
171 - (void) _setStreamProperty: (id)value forKey: (NSString*)key
173 [_reader setProperty: value forKey: (CFStringRef)key];
174 [_writer setProperty: value forKey: (CFStringRef)key];
179 #pragma mark OPENING / CLOSING:
184 if( _status<=kTCP_Closed && _reader ) {
185 _reader.SSLProperties = _sslProperties;
186 _writer.SSLProperties = _sslProperties;
189 if( ! [sAllConnections my_containsObjectIdenticalTo: self] )
190 [sAllConnections addObject: self];
191 self.status = kTCP_Opening;
198 if( _status > kTCP_Closed ) {
199 LogTo(TCP,@"%@ disconnecting",self);
200 [_writer disconnect];
201 setObj(&_writer,nil);
202 [_reader disconnect];
203 setObj(&_reader,nil);
204 self.status = kTCP_Disconnected;
211 [self closeWithTimeout: 60.0];
214 - (void) closeWithTimeout: (NSTimeInterval)timeout
216 if( _status == kTCP_Opening ) {
217 LogTo(TCP,@"%@ canceling open",self);
219 } else if( _status == kTCP_Open ) {
220 LogTo(TCP,@"%@ closing",self);
221 self.status = kTCP_Closing;
225 if( ! [self _checkIfClosed] ) {
228 else if( timeout != INFINITY )
229 [self performSelector: @selector(_closeTimeoutExpired)
230 withObject: nil afterDelay: timeout];
236 - (void) _closeTimeoutExpired
238 if( _status==kTCP_Closing )
243 - (BOOL) _checkIfClosed
245 if( _status == kTCP_Closing && _writer==nil && _reader==nil ) {
253 // called by my streams when they close (after my -close is called)
256 if( _status != kTCP_Closed && _status != kTCP_Disconnected ) {
257 LogTo(TCP,@"%@ is now closed",self);
258 TCPConnectionStatus prevStatus = _status;
259 self.status = (prevStatus==kTCP_Closing ?kTCP_Closed :kTCP_Disconnected);
260 if( prevStatus==kTCP_Opening )
261 [self tellDelegate: @selector(connection:failedToOpen:) withObject: self.error];
263 [self tellDelegate: @selector(connectionDidClose:) withObject: nil];
265 [NSObject cancelPreviousPerformRequestsWithTarget: self
266 selector: @selector(_closeTimeoutExpired)
268 [sAllConnections removeObjectIdenticalTo: self];
272 + (void) closeAllWithTimeout: (NSTimeInterval)timeout
274 NSArray *connections = [sAllConnections copy];
275 for( TCPConnection *conn in connections )
276 [conn closeWithTimeout: timeout];
277 [connections release];
280 + (void) waitTillAllClosed
282 while( sAllConnections.count ) {
283 if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
284 beforeDate: [NSDate distantFuture]] )
291 #pragma mark STREAM CALLBACKS:
294 - (void) _streamOpened: (TCPStream*)stream
297 self.address = stream.peerAddress;
298 if( _status==kTCP_Opening && _reader.isOpen && _writer.isOpen ) {
299 LogTo(TCP,@"%@ opened; address=%@",self,_address);
300 self.status = kTCP_Open;
301 [self tellDelegate: @selector(connectionDidOpen:) withObject: nil];
306 - (BOOL) _streamPeerCertAvailable: (TCPStream*)stream
309 if( ! _checkedPeerCert ) {
311 _checkedPeerCert = YES;
312 if( stream.securityLevel != nil ) {
313 NSArray *certs = stream.peerSSLCerts;
314 if( ! certs && ! _isIncoming )
315 allow = NO; // Server MUST have a cert!
317 SecCertificateRef cert = certs.count ?(SecCertificateRef)[certs objectAtIndex:0] :NULL;
318 LogTo(TCP,@"%@: Peer cert = %@",self,cert);
319 if( [_delegate respondsToSelector: @selector(connection:authorizeSSLPeer:)] )
320 allow = [_delegate connection: self authorizeSSLPeer: cert];
323 }@catch( NSException *x ) {
324 MYReportException(x,@"TCPConnection _streamPeerCertAvailable");
325 _checkedPeerCert = NO;
329 [self _stream: stream
330 gotError: [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
331 code: errSSLClosedAbort
338 - (void) _stream: (TCPStream*)stream gotError: (NSError*)error
340 LogTo(TCP,@"%@ got %@ on %@",self,error,stream.class);
342 setObj(&_error,error);
343 [_reader disconnect];
344 setObj(&_reader,nil);
345 [_writer disconnect];
346 setObj(&_writer,nil);
350 - (void) _streamGotEOF: (TCPStream*)stream
352 LogTo(TCP,@"%@ got EOF on %@",self,stream);
353 if( stream == _reader ) {
354 setObj(&_reader,nil);
355 // This is the expected way for he peer to initiate closing the connection.
356 if( _status==kTCP_Open ) {
357 [self closeWithTimeout: INFINITY];
360 } else if( stream == _writer ) {
361 setObj(&_writer,nil);
364 if( _status == kTCP_Closing ) {
365 [self _checkIfClosed];
367 [self _stream: stream
368 gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
373 // Called after I called -close on a stream and it finished closing:
374 - (void) _streamClosed: (TCPStream*)stream
376 LogTo(TCP,@"%@ finished closing %@",self,stream);
377 if( stream == _reader )
378 setObj(&_reader,nil);
379 else if( stream == _writer )
380 setObj(&_writer,nil);
381 if( !_reader.isOpen && !_writer.isOpen )
390 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
392 Redistribution and use in source and binary forms, with or without modification, are permitted
393 provided that the following conditions are met:
395 * Redistributions of source code must retain the above copyright notice, this list of conditions
396 and the following disclaimer.
397 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
398 and the following disclaimer in the documentation and/or other materials provided with the
401 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
402 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
403 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
404 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
405 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
406 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
407 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
408 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.