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