Added -[TCPConnection initToBonjourService:] since MYBonjourService no longer vends an NSNetService.
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);
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];
91 - (id) initToNetService: (NSNetService*)service
93 IPAddress *address = nil;
95 NSOutputStream *output;
96 if( [service getInputStream: &input outputStream: &output] ) {
97 NSArray *addresses = service.addresses;
98 if( addresses.count > 0 )
99 address = [[[IPAddress alloc] initWithSockAddr: [[addresses objectAtIndex: 0] bytes]] autorelease];
104 return [self _initWithAddress: address inputStream: input outputStream: output];
107 - (id) initToBonjourService: (MYBonjourService*)service;
109 NSNetService *netService = [[NSNetService alloc] initWithDomain: service.domain
110 type: service.type name: service.name];
111 self = [self initToNetService: netService];
117 - (id) initIncomingFromSocket: (CFSocketNativeHandle)socket
118 listener: (TCPListener*)listener
120 CFReadStreamRef readStream = NULL;
121 CFWriteStreamRef writeStream = NULL;
122 CFStreamCreatePairWithSocket(kCFAllocatorDefault, socket, &readStream, &writeStream);
123 self = [self _initWithAddress: [IPAddress addressOfSocket: socket]
124 inputStream: (NSInputStream*)CFMakeCollectable(readStream)
125 outputStream: (NSOutputStream*)CFMakeCollectable(writeStream)];
128 _server = [listener retain];
129 CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
130 CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
132 if(readStream) CFRelease(readStream);
133 if(writeStream) CFRelease(writeStream);
140 LogTo(TCP,@"DEALLOC %@",self);
148 - (NSString*) description
150 return $sprintf(@"%@[%@ %@]",self.class,(_isIncoming ?@"from" :@"to"),_address);
154 @synthesize address=_address, isIncoming=_isIncoming, status=_status, delegate=_delegate,
155 reader=_reader, writer=_writer, server=_server, openTimeout=_openTimeout;
164 - (NSString*) actualSecurityLevel
166 return _reader.securityLevel;
170 - (NSArray*) peerSSLCerts
172 return _reader.peerSSLCerts ?: _writer.peerSSLCerts;
176 - (void) _setStreamProperty: (id)value forKey: (NSString*)key
178 [_reader setProperty: value forKey: (CFStringRef)key];
179 [_writer setProperty: value forKey: (CFStringRef)key];
184 #pragma mark OPENING / CLOSING:
189 if( _status<=kTCP_Closed && _reader ) {
190 _reader.SSLProperties = _sslProperties;
191 _writer.SSLProperties = _sslProperties;
194 if( ! [sAllConnections my_containsObjectIdenticalTo: self] )
195 [sAllConnections addObject: self];
196 self.status = kTCP_Opening;
197 if( _openTimeout > 0 )
198 [self performSelector: @selector(_openTimeoutExpired) withObject: nil afterDelay: _openTimeout];
202 - (void) _stopOpenTimer
204 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_openTimeoutExpired) object: nil];
207 - (void) _openTimeoutExpired
209 if( _status == kTCP_Opening ) {
210 LogTo(TCP,@"%@: timed out waiting to open",self);
211 [self _stream: _reader gotError: [NSError errorWithDomain: NSPOSIXErrorDomain
212 code: ETIMEDOUT userInfo: nil]];
219 if( _status > kTCP_Closed ) {
220 LogTo(TCP,@"%@ disconnecting",self);
221 [_writer disconnect];
222 [_reader disconnect];
223 self.status = kTCP_Disconnected;
225 [self _stopOpenTimer];
231 [self closeWithTimeout: 60.0];
234 - (void) closeWithTimeout: (NSTimeInterval)timeout
236 [self _stopOpenTimer];
237 if( _status == kTCP_Opening ) {
238 LogTo(TCP,@"%@ canceling open",self);
240 } else if( _status == kTCP_Open ) {
241 LogTo(TCP,@"%@ closing",self);
242 self.status = kTCP_Closing;
245 if( ! [self _checkIfClosed] ) {
248 else if( timeout != INFINITY )
249 [self performSelector: @selector(_closeTimeoutExpired)
250 withObject: nil afterDelay: timeout];
256 - (void) _closeTimeoutExpired
258 if( _status==kTCP_Closing )
262 - (void) _stopCloseTimer
264 [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_closeTimeoutExpired) object: nil];
269 if( _status == kTCP_Closing ) {
270 LogTo(TCP,@"%@: _unclose!",self);
273 [self _stopCloseTimer];
274 self.status = kTCP_Open;
279 /** Subclasses can override this to customize what happens when -close is called. */
287 - (BOOL) _checkIfClosed
289 if( _status == kTCP_Closing && _writer==nil && _reader==nil ) {
297 // called by my streams when they close (after my -close is called)
300 [[self retain] autorelease];
301 if( _status != kTCP_Closed && _status != kTCP_Disconnected ) {
302 LogTo(TCP,@"%@ is now closed",self);
303 TCPConnectionStatus prevStatus = _status;
304 self.status = (prevStatus==kTCP_Closing ?kTCP_Closed :kTCP_Disconnected);
305 if( prevStatus==kTCP_Opening )
306 [self tellDelegate: @selector(connection:failedToOpen:) withObject: self.error];
308 [self tellDelegate: @selector(connectionDidClose:) withObject: nil];
310 [self _stopCloseTimer];
311 [self _stopOpenTimer];
312 [sAllConnections removeObjectIdenticalTo: self];
316 + (void) closeAllWithTimeout: (NSTimeInterval)timeout
318 NSArray *connections = [sAllConnections copy];
319 for( TCPConnection *conn in connections )
320 [conn closeWithTimeout: timeout];
321 [connections release];
324 + (void) waitTillAllClosed
326 while( sAllConnections.count ) {
327 if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
328 beforeDate: [NSDate distantFuture]] )
335 #pragma mark STREAM CALLBACKS:
338 - (void) _streamOpened: (TCPStream*)stream
341 self.address = stream.peerAddress;
342 if( _status==kTCP_Opening && _reader.isOpen && _writer.isOpen ) {
343 LogTo(TCP,@"%@ opened; address=%@",self,_address);
344 [self _stopOpenTimer];
345 self.status = kTCP_Open;
346 [self tellDelegate: @selector(connectionDidOpen:) withObject: nil];
351 - (BOOL) _streamPeerCertAvailable: (TCPStream*)stream
354 if( ! _checkedPeerCert ) {
356 _checkedPeerCert = YES;
357 if( stream.securityLevel != nil ) {
358 NSArray *certs = stream.peerSSLCerts;
359 if( ! certs && ! _isIncoming )
360 allow = NO; // Server MUST have a cert!
362 SecCertificateRef cert = certs.count ?(SecCertificateRef)[certs objectAtIndex:0] :NULL;
363 LogTo(TCP,@"%@: Peer cert = %@",self,[TCPEndpoint describeCert: cert]);
364 if( [_delegate respondsToSelector: @selector(connection:authorizeSSLPeer:)] )
365 allow = [_delegate connection: self authorizeSSLPeer: cert];
368 }@catch( NSException *x ) {
369 MYReportException(x,@"TCPConnection _streamPeerCertAvailable");
370 _checkedPeerCert = NO;
374 [self _stream: stream
375 gotError: [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
376 code: errSSLClosedAbort
383 - (void) _stream: (TCPStream*)stream gotError: (NSError*)error
385 LogTo(TCP,@"%@ got %@ on %@",self,error,stream.class);
387 [[self retain] autorelease];
388 setObj(&_error,error);
389 [_reader disconnect];
390 [_writer disconnect];
394 - (void) _streamGotEOF: (TCPStream*)stream
396 LogTo(TCP,@"%@ got EOF on %@",self,stream);
398 if( _status == kTCP_Closing ) {
399 [self _streamCanClose: stream];
400 [self _checkIfClosed];
402 [self _stream: stream
403 gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
408 // Called as soon as a stream is ready to close, after its -close method has been called.
409 - (void) _streamCanClose: (TCPStream*)stream
411 if( ! _reader.isActive && !_writer.isActive ) {
412 LogTo(TCPVerbose,@"Both streams are ready to close now!");
413 [_reader disconnect];
414 [_writer disconnect];
419 // Called after I called -close on a stream and it finished closing:
420 - (void) _streamDisconnected: (TCPStream*)stream
422 LogTo(TCP,@"%@: disconnected %@",self,stream);
423 if( stream == _reader )
424 setObj(&_reader,nil);
425 else if( stream == _writer )
426 setObj(&_writer,nil);
429 if( !_reader.isOpen && !_writer.isOpen )
438 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
440 Redistribution and use in source and binary forms, with or without modification, are permitted
441 provided that the following conditions are met:
443 * Redistributions of source code must retain the above copyright notice, this list of conditions
444 and the following disclaimer.
445 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
446 and the following disclaimer in the documentation and/or other materials provided with the
449 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
450 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
451 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
452 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
453 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
454 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
455 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
456 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.