TCP/TCPStream.m
changeset 0 9d67172bb323
child 1 8267d5c429c4
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/TCP/TCPStream.m	Fri May 23 17:37:36 2008 -0700
     1.3 @@ -0,0 +1,290 @@
     1.4 +//
     1.5 +//  TCPStream.m
     1.6 +//  MYNetwork
     1.7 +//
     1.8 +//  Created by Jens Alfke on 5/10/08.
     1.9 +//  Copyright 2008 Jens Alfke. All rights reserved.
    1.10 +//
    1.11 +
    1.12 +#import "TCPStream.h"
    1.13 +#import "TCP_Internal.h"
    1.14 +
    1.15 +
    1.16 +extern const CFStringRef _kCFStreamPropertySSLClientSideAuthentication; // in CFNetwork
    1.17 +
    1.18 +static NSError* fixStreamError( NSError *error );
    1.19 +
    1.20 +
    1.21 +@implementation TCPStream
    1.22 +
    1.23 +
    1.24 +- (id) initWithConnection: (TCPConnection*)conn stream: (NSStream*)stream
    1.25 +{
    1.26 +    self = [super init];
    1.27 +    if (self != nil) {
    1.28 +        _conn = [conn retain];
    1.29 +        _stream = [stream retain];
    1.30 +        _stream.delegate = self;
    1.31 +        [_stream scheduleInRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
    1.32 +        LogTo(TCPVerbose,@"%@ initialized; status=%i", self,_stream.streamStatus);
    1.33 +    }
    1.34 +    return self;
    1.35 +}
    1.36 +
    1.37 +
    1.38 +- (void) dealloc
    1.39 +{
    1.40 +    LogTo(TCP,@"DEALLOC %@",self);
    1.41 +    if( _stream )
    1.42 +        [self disconnect];
    1.43 +    [super dealloc];
    1.44 +}
    1.45 +
    1.46 +
    1.47 +- (id) propertyForKey: (CFStringRef)cfStreamProperty
    1.48 +{
    1.49 +    return nil; // abstract -- overridden by TCPReader and TCPWriter
    1.50 +}
    1.51 +
    1.52 +- (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty
    1.53 +{ // abstract -- overridden by TCPReader and TCPWriter
    1.54 +}
    1.55 +
    1.56 +
    1.57 +#pragma mark -
    1.58 +#pragma mark SSL:
    1.59 +
    1.60 +
    1.61 +- (NSString*) securityLevel                 {return [_stream propertyForKey: NSStreamSocketSecurityLevelKey];}
    1.62 +
    1.63 +- (NSDictionary*) SSLProperties             {return [self propertyForKey: kCFStreamPropertySSLSettings];}
    1.64 +
    1.65 +- (void) setSSLProperties: (NSDictionary*)p
    1.66 +{
    1.67 +    LogTo(TCPVerbose,@"%@ SSL settings := %@",self,p);
    1.68 +    [self setProperty: p forKey: kCFStreamPropertySSLSettings];
    1.69 +    
    1.70 +    id clientAuth = [p objectForKey: kTCPPropertySSLClientSideAuthentication];
    1.71 +    if( clientAuth )
    1.72 +        [self setProperty: clientAuth forKey: _kCFStreamPropertySSLClientSideAuthentication];
    1.73 +}
    1.74 +
    1.75 +- (NSArray*) peerSSLCerts
    1.76 +{
    1.77 +    Assert(self.isOpen);
    1.78 +    return [self propertyForKey: kCFStreamPropertySSLPeerCertificates];
    1.79 +}
    1.80 +
    1.81 +
    1.82 +#pragma mark -
    1.83 +#pragma mark OPENING/CLOSING:
    1.84 +
    1.85 +
    1.86 +- (void) open
    1.87 +{
    1.88 +    Assert(_stream);
    1.89 +    AssertEq(_stream.streamStatus,NSStreamStatusNotOpen);
    1.90 +    LogTo(TCP,@"Opening %@",self);
    1.91 +    [_stream open];
    1.92 +}
    1.93 +
    1.94 +
    1.95 +- (void) disconnect
    1.96 +{
    1.97 +    if( _stream ) {
    1.98 +        LogTo(TCP,@"Disconnect %@",self);
    1.99 +        _stream.delegate = nil;
   1.100 +        [_stream close];
   1.101 +        setObj(&_stream,nil);
   1.102 +    }
   1.103 +    setObj(&_conn,nil);
   1.104 +}
   1.105 +
   1.106 +
   1.107 +- (BOOL) close
   1.108 +{
   1.109 +    if( self.isBusy ) {
   1.110 +        _shouldClose = YES;
   1.111 +        return NO;
   1.112 +    } else {
   1.113 +        LogTo(TCP,@"Closing %@",self);
   1.114 +        [[self retain] autorelease];    // don't let myself be dealloced in the midst of this
   1.115 +        [_conn _streamClosed: self];    // have to do this before disconnect
   1.116 +        [self disconnect];
   1.117 +        return YES;
   1.118 +    }
   1.119 +}
   1.120 +
   1.121 +
   1.122 +- (BOOL) isOpen
   1.123 +{
   1.124 +    NSStreamStatus status = _stream.streamStatus;
   1.125 +    return status >= NSStreamStatusOpen && status < NSStreamStatusAtEnd;
   1.126 +}
   1.127 +
   1.128 +- (BOOL) isBusy
   1.129 +{
   1.130 +    return NO;  // abstract
   1.131 +}
   1.132 +
   1.133 +
   1.134 +- (void) _opened
   1.135 +{
   1.136 +    [_conn _streamOpened: self];
   1.137 +}
   1.138 +
   1.139 +- (void) _canRead
   1.140 +{
   1.141 +    // abstract
   1.142 +}
   1.143 +
   1.144 +- (void) _canWrite
   1.145 +{
   1.146 +    // abstract
   1.147 +}
   1.148 +
   1.149 +- (void) _gotEOF
   1.150 +{
   1.151 +    if( self.isBusy )
   1.152 +        [self _gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
   1.153 +    else {
   1.154 +        [self retain];
   1.155 +        [_conn _streamGotEOF: self];
   1.156 +        [self disconnect];
   1.157 +        [self release];
   1.158 +    }
   1.159 +}
   1.160 +
   1.161 +- (BOOL) _gotError: (NSError*)error
   1.162 +{
   1.163 +    [_conn _stream: self gotError: fixStreamError(error)];
   1.164 +    return NO;
   1.165 +}
   1.166 +
   1.167 +- (BOOL) _gotError
   1.168 +{
   1.169 +    NSError *error = _stream.streamError;
   1.170 +    if( ! error )
   1.171 +        error = [NSError errorWithDomain: NSPOSIXErrorDomain code: EIO userInfo: nil]; //fallback
   1.172 +    return [self _gotError: error];
   1.173 +}
   1.174 +
   1.175 +
   1.176 +- (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)streamEvent 
   1.177 +{
   1.178 +    [[self retain] autorelease];
   1.179 +    switch(streamEvent) {
   1.180 +        case NSStreamEventOpenCompleted:
   1.181 +            LogTo(TCPVerbose,@"%@ opened",self);
   1.182 +            [self _opened];
   1.183 +            break;
   1.184 +        case NSStreamEventHasBytesAvailable:
   1.185 +            if( ! [_conn _streamPeerCertAvailable: self] )
   1.186 +                return;
   1.187 +            LogTo(TCPVerbose,@"%@ can read",self);
   1.188 +            [self _canRead];
   1.189 +            break;
   1.190 +        case NSStreamEventHasSpaceAvailable:
   1.191 +            if( ! [_conn _streamPeerCertAvailable: self] )
   1.192 +                return;
   1.193 +            LogTo(TCPVerbose,@"%@ can write",self);
   1.194 +            [self _canWrite];
   1.195 +            break;
   1.196 +        case NSStreamEventErrorOccurred:
   1.197 +            LogTo(TCPVerbose,@"%@ got error",self);
   1.198 +            [self _gotError];
   1.199 +            break;
   1.200 +        case NSStreamEventEndEncountered:
   1.201 +            LogTo(TCPVerbose,@"%@ got EOF",self);
   1.202 +            [self _gotEOF];
   1.203 +            break;
   1.204 +        default:
   1.205 +            Warn(@"%@: unknown NSStreamEvent %i",self,streamEvent);
   1.206 +            break;
   1.207 +    }
   1.208 +    
   1.209 +    // If I was previously asked to close, try again in case I'm no longer busy
   1.210 +    if( _shouldClose )
   1.211 +        [self close];
   1.212 +}
   1.213 +
   1.214 +
   1.215 +@end
   1.216 +
   1.217 +
   1.218 +
   1.219 +
   1.220 +@implementation TCPReader
   1.221 +
   1.222 +
   1.223 +- (TCPWriter*) writer
   1.224 +{
   1.225 +    return _conn.writer;
   1.226 +}
   1.227 +
   1.228 +
   1.229 +- (id) propertyForKey: (CFStringRef)cfStreamProperty
   1.230 +{
   1.231 +    CFTypeRef value = CFReadStreamCopyProperty((CFReadStreamRef)_stream,cfStreamProperty);
   1.232 +    return [(id)CFMakeCollectable(value) autorelease];
   1.233 +}
   1.234 +
   1.235 +- (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty
   1.236 +{
   1.237 +    if( ! CFReadStreamSetProperty((CFReadStreamRef)_stream,cfStreamProperty,(CFTypeRef)value) )
   1.238 +        Warn(@"%@ didn't accept property '%@'", self,cfStreamProperty);
   1.239 +}
   1.240 +
   1.241 +
   1.242 +@end
   1.243 +
   1.244 +
   1.245 +
   1.246 +
   1.247 +static NSError* fixStreamError( NSError *error )
   1.248 +{
   1.249 +    // NSStream incorrectly returns SSL errors without the correct error domain:
   1.250 +    if( $equal(error.domain,@"NSUnknownErrorDomain") ) {
   1.251 +        int code = error.code;
   1.252 +        if( -9899 <= code && code <= -9800 ) {
   1.253 +            NSMutableDictionary *userInfo = error.userInfo.mutableCopy;
   1.254 +            if( ! [userInfo objectForKey: NSLocalizedFailureReasonErrorKey] ) {
   1.255 +                // look up error message:
   1.256 +                NSBundle *secBundle = [NSBundle bundleWithPath: @"/System/Library/Frameworks/Security.framework"];
   1.257 +                NSString *message = [secBundle localizedStringForKey: $sprintf(@"%i",code)
   1.258 +                                                               value: nil
   1.259 +                                                               table: @"SecErrorMessages"];
   1.260 +                if( message ) {
   1.261 +                    if( ! userInfo ) userInfo = $mdict();
   1.262 +                    [userInfo setObject: message forKey: NSLocalizedFailureReasonErrorKey];
   1.263 +                }
   1.264 +            }
   1.265 +            error = [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
   1.266 +                                        code: code userInfo: userInfo];
   1.267 +        } else
   1.268 +            Warn(@"NSStream returned error with unknown domain: %@",error);
   1.269 +    }
   1.270 +    return error;
   1.271 +}
   1.272 +
   1.273 +/*
   1.274 + Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   1.275 + 
   1.276 + Redistribution and use in source and binary forms, with or without modification, are permitted
   1.277 + provided that the following conditions are met:
   1.278 + 
   1.279 + * Redistributions of source code must retain the above copyright notice, this list of conditions
   1.280 + and the following disclaimer.
   1.281 + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   1.282 + and the following disclaimer in the documentation and/or other materials provided with the
   1.283 + distribution.
   1.284 + 
   1.285 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   1.286 + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   1.287 + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   1.288 + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   1.289 + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   1.290 +  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   1.291 + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   1.292 + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   1.293 + */