TCP/TCPStream.m
author Jens Alfke <jens@mooseyard.com>
Fri May 23 17:37:36 2008 -0700 (2008-05-23)
changeset 0 9d67172bb323
child 1 8267d5c429c4
permissions -rw-r--r--
First checkin after breaking out of Cloudy
jens@0
     1
//
jens@0
     2
//  TCPStream.m
jens@0
     3
//  MYNetwork
jens@0
     4
//
jens@0
     5
//  Created by Jens Alfke on 5/10/08.
jens@0
     6
//  Copyright 2008 Jens Alfke. All rights reserved.
jens@0
     7
//
jens@0
     8
jens@0
     9
#import "TCPStream.h"
jens@0
    10
#import "TCP_Internal.h"
jens@0
    11
jens@0
    12
jens@0
    13
extern const CFStringRef _kCFStreamPropertySSLClientSideAuthentication; // in CFNetwork
jens@0
    14
jens@0
    15
static NSError* fixStreamError( NSError *error );
jens@0
    16
jens@0
    17
jens@0
    18
@implementation TCPStream
jens@0
    19
jens@0
    20
jens@0
    21
- (id) initWithConnection: (TCPConnection*)conn stream: (NSStream*)stream
jens@0
    22
{
jens@0
    23
    self = [super init];
jens@0
    24
    if (self != nil) {
jens@0
    25
        _conn = [conn retain];
jens@0
    26
        _stream = [stream retain];
jens@0
    27
        _stream.delegate = self;
jens@0
    28
        [_stream scheduleInRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
jens@0
    29
        LogTo(TCPVerbose,@"%@ initialized; status=%i", self,_stream.streamStatus);
jens@0
    30
    }
jens@0
    31
    return self;
jens@0
    32
}
jens@0
    33
jens@0
    34
jens@0
    35
- (void) dealloc
jens@0
    36
{
jens@0
    37
    LogTo(TCP,@"DEALLOC %@",self);
jens@0
    38
    if( _stream )
jens@0
    39
        [self disconnect];
jens@0
    40
    [super dealloc];
jens@0
    41
}
jens@0
    42
jens@0
    43
jens@0
    44
- (id) propertyForKey: (CFStringRef)cfStreamProperty
jens@0
    45
{
jens@0
    46
    return nil; // abstract -- overridden by TCPReader and TCPWriter
jens@0
    47
}
jens@0
    48
jens@0
    49
- (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty
jens@0
    50
{ // abstract -- overridden by TCPReader and TCPWriter
jens@0
    51
}
jens@0
    52
jens@0
    53
jens@0
    54
#pragma mark -
jens@0
    55
#pragma mark SSL:
jens@0
    56
jens@0
    57
jens@0
    58
- (NSString*) securityLevel                 {return [_stream propertyForKey: NSStreamSocketSecurityLevelKey];}
jens@0
    59
jens@0
    60
- (NSDictionary*) SSLProperties             {return [self propertyForKey: kCFStreamPropertySSLSettings];}
jens@0
    61
jens@0
    62
- (void) setSSLProperties: (NSDictionary*)p
jens@0
    63
{
jens@0
    64
    LogTo(TCPVerbose,@"%@ SSL settings := %@",self,p);
jens@0
    65
    [self setProperty: p forKey: kCFStreamPropertySSLSettings];
jens@0
    66
    
jens@0
    67
    id clientAuth = [p objectForKey: kTCPPropertySSLClientSideAuthentication];
jens@0
    68
    if( clientAuth )
jens@0
    69
        [self setProperty: clientAuth forKey: _kCFStreamPropertySSLClientSideAuthentication];
jens@0
    70
}
jens@0
    71
jens@0
    72
- (NSArray*) peerSSLCerts
jens@0
    73
{
jens@0
    74
    Assert(self.isOpen);
jens@0
    75
    return [self propertyForKey: kCFStreamPropertySSLPeerCertificates];
jens@0
    76
}
jens@0
    77
jens@0
    78
jens@0
    79
#pragma mark -
jens@0
    80
#pragma mark OPENING/CLOSING:
jens@0
    81
jens@0
    82
jens@0
    83
- (void) open
jens@0
    84
{
jens@0
    85
    Assert(_stream);
jens@0
    86
    AssertEq(_stream.streamStatus,NSStreamStatusNotOpen);
jens@0
    87
    LogTo(TCP,@"Opening %@",self);
jens@0
    88
    [_stream open];
jens@0
    89
}
jens@0
    90
jens@0
    91
jens@0
    92
- (void) disconnect
jens@0
    93
{
jens@0
    94
    if( _stream ) {
jens@0
    95
        LogTo(TCP,@"Disconnect %@",self);
jens@0
    96
        _stream.delegate = nil;
jens@0
    97
        [_stream close];
jens@0
    98
        setObj(&_stream,nil);
jens@0
    99
    }
jens@0
   100
    setObj(&_conn,nil);
jens@0
   101
}
jens@0
   102
jens@0
   103
jens@0
   104
- (BOOL) close
jens@0
   105
{
jens@0
   106
    if( self.isBusy ) {
jens@0
   107
        _shouldClose = YES;
jens@0
   108
        return NO;
jens@0
   109
    } else {
jens@0
   110
        LogTo(TCP,@"Closing %@",self);
jens@0
   111
        [[self retain] autorelease];    // don't let myself be dealloced in the midst of this
jens@0
   112
        [_conn _streamClosed: self];    // have to do this before disconnect
jens@0
   113
        [self disconnect];
jens@0
   114
        return YES;
jens@0
   115
    }
jens@0
   116
}
jens@0
   117
jens@0
   118
jens@0
   119
- (BOOL) isOpen
jens@0
   120
{
jens@0
   121
    NSStreamStatus status = _stream.streamStatus;
jens@0
   122
    return status >= NSStreamStatusOpen && status < NSStreamStatusAtEnd;
jens@0
   123
}
jens@0
   124
jens@0
   125
- (BOOL) isBusy
jens@0
   126
{
jens@0
   127
    return NO;  // abstract
jens@0
   128
}
jens@0
   129
jens@0
   130
jens@0
   131
- (void) _opened
jens@0
   132
{
jens@0
   133
    [_conn _streamOpened: self];
jens@0
   134
}
jens@0
   135
jens@0
   136
- (void) _canRead
jens@0
   137
{
jens@0
   138
    // abstract
jens@0
   139
}
jens@0
   140
jens@0
   141
- (void) _canWrite
jens@0
   142
{
jens@0
   143
    // abstract
jens@0
   144
}
jens@0
   145
jens@0
   146
- (void) _gotEOF
jens@0
   147
{
jens@0
   148
    if( self.isBusy )
jens@0
   149
        [self _gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]];
jens@0
   150
    else {
jens@0
   151
        [self retain];
jens@0
   152
        [_conn _streamGotEOF: self];
jens@0
   153
        [self disconnect];
jens@0
   154
        [self release];
jens@0
   155
    }
jens@0
   156
}
jens@0
   157
jens@0
   158
- (BOOL) _gotError: (NSError*)error
jens@0
   159
{
jens@0
   160
    [_conn _stream: self gotError: fixStreamError(error)];
jens@0
   161
    return NO;
jens@0
   162
}
jens@0
   163
jens@0
   164
- (BOOL) _gotError
jens@0
   165
{
jens@0
   166
    NSError *error = _stream.streamError;
jens@0
   167
    if( ! error )
jens@0
   168
        error = [NSError errorWithDomain: NSPOSIXErrorDomain code: EIO userInfo: nil]; //fallback
jens@0
   169
    return [self _gotError: error];
jens@0
   170
}
jens@0
   171
jens@0
   172
jens@0
   173
- (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)streamEvent 
jens@0
   174
{
jens@0
   175
    [[self retain] autorelease];
jens@0
   176
    switch(streamEvent) {
jens@0
   177
        case NSStreamEventOpenCompleted:
jens@0
   178
            LogTo(TCPVerbose,@"%@ opened",self);
jens@0
   179
            [self _opened];
jens@0
   180
            break;
jens@0
   181
        case NSStreamEventHasBytesAvailable:
jens@0
   182
            if( ! [_conn _streamPeerCertAvailable: self] )
jens@0
   183
                return;
jens@0
   184
            LogTo(TCPVerbose,@"%@ can read",self);
jens@0
   185
            [self _canRead];
jens@0
   186
            break;
jens@0
   187
        case NSStreamEventHasSpaceAvailable:
jens@0
   188
            if( ! [_conn _streamPeerCertAvailable: self] )
jens@0
   189
                return;
jens@0
   190
            LogTo(TCPVerbose,@"%@ can write",self);
jens@0
   191
            [self _canWrite];
jens@0
   192
            break;
jens@0
   193
        case NSStreamEventErrorOccurred:
jens@0
   194
            LogTo(TCPVerbose,@"%@ got error",self);
jens@0
   195
            [self _gotError];
jens@0
   196
            break;
jens@0
   197
        case NSStreamEventEndEncountered:
jens@0
   198
            LogTo(TCPVerbose,@"%@ got EOF",self);
jens@0
   199
            [self _gotEOF];
jens@0
   200
            break;
jens@0
   201
        default:
jens@0
   202
            Warn(@"%@: unknown NSStreamEvent %i",self,streamEvent);
jens@0
   203
            break;
jens@0
   204
    }
jens@0
   205
    
jens@0
   206
    // If I was previously asked to close, try again in case I'm no longer busy
jens@0
   207
    if( _shouldClose )
jens@0
   208
        [self close];
jens@0
   209
}
jens@0
   210
jens@0
   211
jens@0
   212
@end
jens@0
   213
jens@0
   214
jens@0
   215
jens@0
   216
jens@0
   217
@implementation TCPReader
jens@0
   218
jens@0
   219
jens@0
   220
- (TCPWriter*) writer
jens@0
   221
{
jens@0
   222
    return _conn.writer;
jens@0
   223
}
jens@0
   224
jens@0
   225
jens@0
   226
- (id) propertyForKey: (CFStringRef)cfStreamProperty
jens@0
   227
{
jens@0
   228
    CFTypeRef value = CFReadStreamCopyProperty((CFReadStreamRef)_stream,cfStreamProperty);
jens@0
   229
    return [(id)CFMakeCollectable(value) autorelease];
jens@0
   230
}
jens@0
   231
jens@0
   232
- (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty
jens@0
   233
{
jens@0
   234
    if( ! CFReadStreamSetProperty((CFReadStreamRef)_stream,cfStreamProperty,(CFTypeRef)value) )
jens@0
   235
        Warn(@"%@ didn't accept property '%@'", self,cfStreamProperty);
jens@0
   236
}
jens@0
   237
jens@0
   238
jens@0
   239
@end
jens@0
   240
jens@0
   241
jens@0
   242
jens@0
   243
jens@0
   244
static NSError* fixStreamError( NSError *error )
jens@0
   245
{
jens@0
   246
    // NSStream incorrectly returns SSL errors without the correct error domain:
jens@0
   247
    if( $equal(error.domain,@"NSUnknownErrorDomain") ) {
jens@0
   248
        int code = error.code;
jens@0
   249
        if( -9899 <= code && code <= -9800 ) {
jens@0
   250
            NSMutableDictionary *userInfo = error.userInfo.mutableCopy;
jens@0
   251
            if( ! [userInfo objectForKey: NSLocalizedFailureReasonErrorKey] ) {
jens@0
   252
                // look up error message:
jens@0
   253
                NSBundle *secBundle = [NSBundle bundleWithPath: @"/System/Library/Frameworks/Security.framework"];
jens@0
   254
                NSString *message = [secBundle localizedStringForKey: $sprintf(@"%i",code)
jens@0
   255
                                                               value: nil
jens@0
   256
                                                               table: @"SecErrorMessages"];
jens@0
   257
                if( message ) {
jens@0
   258
                    if( ! userInfo ) userInfo = $mdict();
jens@0
   259
                    [userInfo setObject: message forKey: NSLocalizedFailureReasonErrorKey];
jens@0
   260
                }
jens@0
   261
            }
jens@0
   262
            error = [NSError errorWithDomain: NSStreamSocketSSLErrorDomain
jens@0
   263
                                        code: code userInfo: userInfo];
jens@0
   264
        } else
jens@0
   265
            Warn(@"NSStream returned error with unknown domain: %@",error);
jens@0
   266
    }
jens@0
   267
    return error;
jens@0
   268
}
jens@0
   269
jens@0
   270
/*
jens@0
   271
 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@0
   272
 
jens@0
   273
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@0
   274
 provided that the following conditions are met:
jens@0
   275
 
jens@0
   276
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@0
   277
 and the following disclaimer.
jens@0
   278
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@0
   279
 and the following disclaimer in the documentation and/or other materials provided with the
jens@0
   280
 distribution.
jens@0
   281
 
jens@0
   282
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@0
   283
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@0
   284
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@0
   285
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@0
   286
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@0
   287
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@0
   288
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@0
   289
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@0
   290
 */