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
71 NSInputStream *input = nil;
72 NSOutputStream *output = nil;
74 // +getStreamsToHost: is missing for some stupid reason on iPhone. Grrrrrrrrrr.
75 CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)address.hostname, address.port,
76 (CFReadStreamRef*)&input, (CFWriteStreamRef*)&output);
78 [(id)CFMakeCollectable(input) autorelease];
80 [(id)CFMakeCollectable(output) autorelease];
82 [NSStream getStreamsToHost: [NSHost hostWithAddress: address.ipv4name]
85 outputStream: &output];
87 return [self _initWithAddress: address inputStream: input outputStream: output];
90 - (id) initToNetService: (NSNetService*)service
92 IPAddress *address = nil;
94 NSOutputStream *output;
95 if( [service getInputStream: &input outputStream: &output] ) {
96 NSArray *addresses = service.addresses;
97 if( addresses.count > 0 )
98 address = [[[IPAddress alloc] initWithSockAddr: [[addresses objectAtIndex: 0] bytes]] autorelease];
103 return [self _initWithAddress: address inputStream: input outputStream: output];
107 - (id) initIncomingFromSocket: (CFSocketNativeHandle)socket
108 listener: (TCPListener*)listener
110 CFReadStreamRef readStream = NULL;
111 CFWriteStreamRef writeStream = NULL;
112 CFStreamCreatePairWithSocket(kCFAllocatorDefault, socket, &readStream, &writeStream);
113 self = [self _initWithAddress: [IPAddress addressOfSocket: socket]
114 inputStream: (NSInputStream*)readStream
115 outputStream: (NSOutputStream*)writeStream];
118 _server = [listener retain];
119 CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
120 CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
128 LogTo(TCP,@"DEALLOC %@",self);
136 - (NSString*) description
138 return $sprintf(@"%@[%@ %@]",self.class,(_isIncoming ?@"from" :@"to"),_address);
142 @synthesize address=_address, isIncoming=_isIncoming, status=_status, delegate=_delegate,
143 reader=_reader, writer=_writer, server=_server, openTimeout=_openTimeout;
152 - (NSString*) actualSecurityLevel
154 return _reader.securityLevel;
158 - (NSArray*) peerSSLCerts
160 return _reader.peerSSLCerts ?: _writer.peerSSLCerts;
164 - (void) _setStreamProperty: (id)value forKey: (NSString*)key
166 [_reader setProperty: value forKey: (CFStringRef)key];
167 [_writer setProperty: value forKey: (CFStringRef)key];
172 #pragma mark OPENING / CLOSING:
177 if( _status<=kTCP_Closed && _reader ) {
178 _reader.SSLProperties = _sslProperties;
179 _writer.SSLProperties = _sslProperties;
182 if( ! [sAllConnections my_containsObjectIdenticalTo: self] )
183 [sAllConnections addObject: self];
184 self.status = kTCP_Opening;
185 if( _openTimeout > 0 )
186 [self performSelector: @selector(_openTimeoutExpired) withObject: nil afterDelay: _openTimeout];
190 - (void) _stopOpenTimer
192 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_openTimeoutExpired) object: nil];
195 - (void) _openTimeoutExpired
197 if( _status == kTCP_Opening ) {
198 LogTo(TCP,@"%@: timed out waiting to open",self);
199 [self _stream: _reader gotError: [NSError errorWithDomain: NSPOSIXErrorDomain
200 code: ETIMEDOUT userInfo: nil]];
207 if( _status > kTCP_Closed ) {
208 LogTo(TCP,@"%@ disconnecting",self);
209 [_writer disconnect];
210 [_reader disconnect];
211 self.status = kTCP_Disconnected;
213 [self _stopOpenTimer];
219 [self closeWithTimeout: 60.0];
222 - (void) closeWithTimeout: (NSTimeInterval)timeout
224 [self _stopOpenTimer];
225 if( _status == kTCP_Opening ) {
226 LogTo(TCP,@"%@ canceling open",self);
228 } else if( _status == kTCP_Open ) {
229 LogTo(TCP,@"%@ closing",self);
230 self.status = kTCP_Closing;
233 if( ! [self _checkIfClosed] ) {
236 else if( timeout != INFINITY )
237 [self performSelector: @selector(_closeTimeoutExpired)
238 withObject: nil afterDelay: timeout];
244 - (void) _closeTimeoutExpired
246 if( _status==kTCP_Closing )
250 - (void) _stopCloseTimer
252 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_closeTimeoutExpired) object: nil];
257 if( _status == kTCP_Closing ) {
258 LogTo(TCP,@"%@: _unclose!",self);
261 [self _stopCloseTimer];
262 self.status = kTCP_Open;
267 /** Subclasses can override this to customize what happens when -close is called. */
275 - (BOOL) _checkIfClosed
277 if( _status == kTCP_Closing && _writer==nil && _reader==nil ) {
285 // called by my streams when they close (after my -close is called)
288 [[self retain] autorelease];
289 if( _status != kTCP_Closed && _status != kTCP_Disconnected ) {
290 LogTo(TCP,@"%@ is now closed",self);
291 TCPConnectionStatus prevStatus = _status;
292 self.status = (prevStatus==kTCP_Closing ?kTCP_Closed :kTCP_Disconnected);
293 if( prevStatus==kTCP_Opening )
294 [self tellDelegate: @selector(connection:failedToOpen:) withObject: self.error];
296 [self tellDelegate: @selector(connectionDidClose:) withObject: nil];
298 [self _stopCloseTimer];
299 [self _stopOpenTimer];
300 [sAllConnections removeObjectIdenticalTo: self];
304 + (void) closeAllWithTimeout: (NSTimeInterval)timeout
306 NSArray *connections = [sAllConnections copy];
307 for( TCPConnection *conn in connections )
308 [conn closeWithTimeout: timeout];
309 [connections release];
312 + (void) waitTillAllClosed
314 while( sAllConnections.count ) {
315 if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
316 beforeDate: [NSDate distantFuture]] )
323 #pragma mark STREAM CALLBACKS:
326 - (void) _streamOpened: (TCPStream*)stream
329 self.address = stream.peerAddress;
330 if( _status==kTCP_Opening && _reader.isOpen && _writer.isOpen ) {
331 LogTo(TCP,@"%@ opened; address=%@",self,_address);
332 [self _stopOpenTimer];
333 self.status = kTCP_Open;
334 [self tellDelegate: @selector(connectionDidOpen:) withObject: nil];
339 - (BOOL) _streamPeerCertAvailable: (TCPStream*)stream
342 if( ! _checkedPeerCert ) {
344 _checkedPeerCert = YES;
345 if( stream.securityLevel != nil ) {
346 NSArray *certs = stream.peerSSLCerts;
347 if( ! certs && ! _isIncoming )
348 allow = NO; // Server MUST have a cert!
350 SecCertificateRef cert = certs.count ?(SecCertificateRef)[certs objectAtIndex:0] :NULL;
351 LogTo(TCP,@"%@: Peer cert = %@",self,cert);
352 if( [_delegate respondsToSelector: @selector(connection:authorizeSSLPeer:)] )
353 allow = [_delegate connection: self authorizeSSLPeer: cert];
356 }@catch( NSException *x ) {
357 MYReportException(x,@"TCPConnection _streamPeerCertAvailable");
358 _checkedPeerCert = NO;
362 [self _stream: stream
363 gotError: [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
364 code: errSSLClosedAbort
371 - (void) _stream: (TCPStream*)stream gotError: (NSError*)error
373 LogTo(TCP,@"%@ got %@ on %@",self,error,stream.class);
375 [[self retain] autorelease];
376 setObj(&_error,error);
377 [_reader disconnect];
378 [_writer disconnect];
382 - (void) _streamGotEOF: (TCPStream*)stream
384 LogTo(TCP,@"%@ got EOF on %@",self,stream);
386 if( _status == kTCP_Closing ) {
387 [self _streamCanClose: stream];
388 [self _checkIfClosed];
390 [self _stream: stream
391 gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
396 // Called as soon as a stream is ready to close, after its -close method has been called.
397 - (void) _streamCanClose: (TCPStream*)stream
399 if( ! _reader.isActive && !_writer.isActive ) {
400 LogTo(TCPVerbose,@"Both streams are ready to close now!");
401 [_reader disconnect];
402 [_writer disconnect];
407 // Called after I called -close on a stream and it finished closing:
408 - (void) _streamDisconnected: (TCPStream*)stream
410 LogTo(TCP,@"%@: disconnected %@",self,stream);
411 if( stream == _reader )
412 setObj(&_reader,nil);
413 else if( stream == _writer )
414 setObj(&_writer,nil);
417 if( !_reader.isOpen && !_writer.isOpen )
426 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
428 Redistribution and use in source and binary forms, with or without modification, are permitted
429 provided that the following conditions are met:
431 * Redistributions of source code must retain the above copyright notice, this list of conditions
432 and the following disclaimer.
433 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
434 and the following disclaimer in the documentation and/or other materials provided with the
437 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
438 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
439 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
440 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
441 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
442 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
443 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
444 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.