Implemented new close protocol with 'bye' meta-message.
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 )
258 - (BOOL) _checkIfClosed
260 if( _status == kTCP_Closing && _writer==nil && _reader==nil ) {
268 // called by my streams when they close (after my -close is called)
271 [[self retain] autorelease];
272 if( _status != kTCP_Closed && _status != kTCP_Disconnected ) {
273 LogTo(TCP,@"%@ is now closed",self);
274 TCPConnectionStatus prevStatus = _status;
275 self.status = (prevStatus==kTCP_Closing ?kTCP_Closed :kTCP_Disconnected);
276 if( prevStatus==kTCP_Opening )
277 [self tellDelegate: @selector(connection:failedToOpen:) withObject: self.error];
279 [self tellDelegate: @selector(connectionDidClose:) withObject: nil];
281 [NSObject cancelPreviousPerformRequestsWithTarget: self
282 selector: @selector(_closeTimeoutExpired)
284 [self _stopOpenTimer];
285 [sAllConnections removeObjectIdenticalTo: self];
289 + (void) closeAllWithTimeout: (NSTimeInterval)timeout
291 NSArray *connections = [sAllConnections copy];
292 for( TCPConnection *conn in connections )
293 [conn closeWithTimeout: timeout];
294 [connections release];
297 + (void) waitTillAllClosed
299 while( sAllConnections.count ) {
300 if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
301 beforeDate: [NSDate distantFuture]] )
308 #pragma mark STREAM CALLBACKS:
311 - (void) _streamOpened: (TCPStream*)stream
314 self.address = stream.peerAddress;
315 if( _status==kTCP_Opening && _reader.isOpen && _writer.isOpen ) {
316 LogTo(TCP,@"%@ opened; address=%@",self,_address);
317 [self _stopOpenTimer];
318 self.status = kTCP_Open;
319 [self tellDelegate: @selector(connectionDidOpen:) withObject: nil];
324 - (BOOL) _streamPeerCertAvailable: (TCPStream*)stream
327 if( ! _checkedPeerCert ) {
329 _checkedPeerCert = YES;
330 if( stream.securityLevel != nil ) {
331 NSArray *certs = stream.peerSSLCerts;
332 if( ! certs && ! _isIncoming )
333 allow = NO; // Server MUST have a cert!
335 SecCertificateRef cert = certs.count ?(SecCertificateRef)[certs objectAtIndex:0] :NULL;
336 LogTo(TCP,@"%@: Peer cert = %@",self,cert);
337 if( [_delegate respondsToSelector: @selector(connection:authorizeSSLPeer:)] )
338 allow = [_delegate connection: self authorizeSSLPeer: cert];
341 }@catch( NSException *x ) {
342 MYReportException(x,@"TCPConnection _streamPeerCertAvailable");
343 _checkedPeerCert = NO;
347 [self _stream: stream
348 gotError: [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
349 code: errSSLClosedAbort
356 - (void) _stream: (TCPStream*)stream gotError: (NSError*)error
358 LogTo(TCP,@"%@ got %@ on %@",self,error,stream.class);
360 [[self retain] autorelease];
361 setObj(&_error,error);
362 [_reader disconnect];
363 [_writer disconnect];
367 - (void) _streamGotEOF: (TCPStream*)stream
369 LogTo(TCP,@"%@ got EOF on %@",self,stream);
371 if( _status == kTCP_Closing ) {
372 [self _streamCanClose: stream];
373 [self _checkIfClosed];
375 [self _stream: stream
376 gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
381 // Called as soon as a stream is ready to close, after its -close method has been called.
382 - (void) _streamCanClose: (TCPStream*)stream
384 if( ! _reader.isActive && !_writer.isActive ) {
385 LogTo(TCPVerbose,@"Both streams are ready to close now!");
386 [_reader disconnect];
387 [_writer disconnect];
392 // Called after I called -close on a stream and it finished closing:
393 - (void) _streamDisconnected: (TCPStream*)stream
395 LogTo(TCP,@"%@: disconnected %@",self,stream);
396 if( stream == _reader )
397 setObj(&_reader,nil);
398 else if( stream == _writer )
399 setObj(&_writer,nil);
402 if( !_reader.isOpen && !_writer.isOpen )
411 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
413 Redistribution and use in source and binary forms, with or without modification, are permitted
414 provided that the following conditions are met:
416 * Redistributions of source code must retain the above copyright notice, this list of conditions
417 and the following disclaimer.
418 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
419 and the following disclaimer in the documentation and/or other materials provided with the
422 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
423 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
424 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
425 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
426 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
427 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
428 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
429 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.