TCP/TCPListener.m
author morrowa
Thu Jul 02 19:58:11 2009 -0700 (2009-07-02)
changeset 56 6c3b5372a307
parent 47 60f2b46d9a3b
permissions -rwxr-xr-x
Removed unnecessary files. Toned down logging. Added null logging handler to BLIP so client code doesn't have to use logging. Modified test drivers to work against Cocoa versions.
jens@0
     1
//
jens@0
     2
//  TCPListener.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
//  Portions based on TCPServer class from Apple's "CocoaEcho" sample code.
jens@0
     8
jens@0
     9
#import "TCPListener.h"
jens@0
    10
#import "TCPConnection.h"
jens@0
    11
jens@1
    12
#import "Logging.h"
jens@1
    13
#import "Test.h"
jens@0
    14
#import "ExceptionUtils.h"
jens@0
    15
#import "IPAddress.h"
jens@1
    16
jens@0
    17
#include <sys/socket.h>
jens@0
    18
#include <netinet/in.h>
jens@0
    19
#include <unistd.h>
jens@0
    20
jens@0
    21
jens@0
    22
static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, 
jens@0
    23
                                      CFDataRef address, const void *data, void *info);
jens@0
    24
jens@0
    25
@interface TCPListener()
jens@22
    26
- (void) _openBonjour;
jens@22
    27
- (void) _closeBonjour;
jens@0
    28
@property BOOL bonjourPublished;
jens@0
    29
@property NSInteger bonjourError;
jens@0
    30
- (void) _updateTXTRecord;
jens@0
    31
@end
jens@0
    32
jens@0
    33
jens@0
    34
@implementation TCPListener
jens@0
    35
jens@0
    36
jens@49
    37
- (id) init
jens@49
    38
{
jens@49
    39
    self = [super init];
jens@49
    40
    if (self != nil) {
jens@49
    41
        _connectionClass = [TCPConnection class];
jens@49
    42
    }
jens@49
    43
    return self;
jens@49
    44
}
jens@49
    45
jens@49
    46
jens@0
    47
- (id) initWithPort: (UInt16)port
jens@0
    48
{
jens@49
    49
    self = [self init];
jens@0
    50
    if (self != nil) {
jens@0
    51
        _port = port;
jens@0
    52
    }
jens@0
    53
    return self;
jens@0
    54
}
jens@0
    55
jens@0
    56
jens@0
    57
- (void) dealloc 
jens@0
    58
{
jens@0
    59
    [self close];
jens@0
    60
    LogTo(TCP,@"DEALLOC %@",self);
jens@0
    61
    [super dealloc];
jens@0
    62
}
jens@0
    63
jens@0
    64
jens@47
    65
@synthesize port=_port, useIPv6=_useIPv6,
jens@44
    66
            bonjourServiceType=_bonjourServiceType, bonjourServiceOptions=_bonjourServiceOptions,
jens@0
    67
            bonjourPublished=_bonjourPublished, bonjourError=_bonjourError,
jens@22
    68
            bonjourService=_netService,
jens@0
    69
            pickAvailablePort=_pickAvailablePort;
jens@0
    70
jens@0
    71
jens@47
    72
- (id<TCPListenerDelegate>) delegate                      {return _delegate;}
jens@47
    73
- (void) setDelegate: (id<TCPListenerDelegate>) delegate  {_delegate = delegate;}
jens@47
    74
jens@47
    75
jens@0
    76
- (NSString*) description
jens@0
    77
{
jens@0
    78
    return $sprintf(@"%@[port %hu]",self.class,_port);
jens@0
    79
}
jens@0
    80
jens@0
    81
jens@22
    82
// Stores the last error from CFSocketCreate or CFSocketSetAddress into *outError.
jens@0
    83
static void* getLastCFSocketError( NSError **outError ) {
jens@0
    84
    if( outError )
jens@0
    85
        *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil];
jens@0
    86
    return NULL;
jens@0
    87
}
jens@0
    88
jens@0
    89
// Closes a socket (if it's not already NULL), and returns NULL to assign to it.
jens@0
    90
static CFSocketRef closeSocket( CFSocketRef socket ) {
jens@0
    91
    if( socket ) {
jens@0
    92
        CFSocketInvalidate(socket);
jens@0
    93
        CFRelease(socket);
jens@0
    94
    }
jens@0
    95
    return NULL;
jens@0
    96
}
jens@0
    97
jens@0
    98
// opens a socket of a given protocol, either ipv4 or ipv6.
jens@0
    99
- (CFSocketRef) _openProtocol: (SInt32) protocolFamily 
jens@0
   100
                      address: (struct sockaddr*)address
jens@0
   101
                        error: (NSError**)error
jens@0
   102
{
jens@0
   103
    CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
jens@0
   104
    CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
jens@0
   105
                                        kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt);
jens@0
   106
    if( ! socket ) 
jens@0
   107
        return getLastCFSocketError(error);   // CFSocketCreate leaves error code in errno
jens@0
   108
    
jens@0
   109
    int yes = 1;
jens@0
   110
    setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
jens@0
   111
    
jens@0
   112
    NSData *addressData = [NSData dataWithBytes:address length:address->sa_len];
jens@0
   113
    if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) {
jens@0
   114
        getLastCFSocketError(error);
jens@0
   115
        return closeSocket(socket);
jens@0
   116
    }
jens@0
   117
    // set up the run loop source for the socket
jens@0
   118
    CFRunLoopRef cfrl = CFRunLoopGetCurrent();
jens@0
   119
    CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
jens@0
   120
    CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
jens@0
   121
    CFRelease(source);
jens@0
   122
    return socket;
jens@0
   123
}
jens@0
   124
jens@0
   125
- (BOOL) _failedToOpen: (NSError*)error
jens@0
   126
{
jens@0
   127
    LogTo(TCP,@"%@ failed to open: %@",self,error);
jens@0
   128
    [self tellDelegate: @selector(listener:failedToOpen:) withObject: error];
jens@0
   129
    return NO;
jens@0
   130
}
jens@0
   131
jens@0
   132
jens@0
   133
- (BOOL) open: (NSError**)outError 
jens@0
   134
{
jens@0
   135
    // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us
jens@0
   136
    do{
jens@0
   137
        struct sockaddr_in addr4;
jens@0
   138
        memset(&addr4, 0, sizeof(addr4));
jens@0
   139
        addr4.sin_len = sizeof(addr4);
jens@0
   140
        addr4.sin_family = AF_INET;
jens@0
   141
        addr4.sin_port = htons(_port);
jens@0
   142
        addr4.sin_addr.s_addr = htonl(INADDR_ANY);
jens@0
   143
jens@0
   144
        NSError *error;
jens@0
   145
        _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error];
jens@0
   146
        if( ! _ipv4socket ) {
jens@0
   147
            if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) {
jens@0
   148
                LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1);
jens@8
   149
                self.port += 1;        // try the next port
jens@0
   150
            } else {
jens@0
   151
                if( outError ) *outError = error;
jens@0
   152
                return [self _failedToOpen: error];
jens@0
   153
            }
jens@0
   154
        }
jens@0
   155
    }while( ! _ipv4socket );
jens@0
   156
    
jens@0
   157
    if (0 == _port) {
jens@0
   158
        // now that the binding was successful, we get the port number 
danpreston@38
   159
        NSData *addr = [NSMakeCollectable( CFSocketCopyAddress(_ipv4socket) ) autorelease];
jens@0
   160
        const struct sockaddr_in *addr4 = addr.bytes;
jens@0
   161
        self.port = ntohs(addr4->sin_port);
jens@0
   162
    }
jens@0
   163
    
jens@0
   164
    if( _useIPv6 ) {
jens@0
   165
        // set up the IPv6 endpoint
jens@0
   166
        struct sockaddr_in6 addr6;
jens@0
   167
        memset(&addr6, 0, sizeof(addr6));
jens@0
   168
        addr6.sin6_len = sizeof(addr6);
jens@0
   169
        addr6.sin6_family = AF_INET6;
jens@0
   170
        addr6.sin6_port = htons(_port);
jens@0
   171
        memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
jens@0
   172
        
jens@26
   173
        NSError *error;
jens@26
   174
        _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: &error];
jens@0
   175
        if( ! _ipv6socket ) {
jens@0
   176
            _ipv4socket = closeSocket(_ipv4socket);
jens@26
   177
            [self _failedToOpen: error];
jens@26
   178
            if (outError) *outError = error;
jens@26
   179
            return NO;
jens@0
   180
        }
jens@0
   181
    }
jens@0
   182
    
jens@22
   183
    [self _openBonjour];
jens@0
   184
jens@0
   185
    LogTo(TCP,@"%@ is open",self);
jens@0
   186
    [self tellDelegate: @selector(listenerDidOpen:) withObject: nil];
jens@0
   187
    return YES;
jens@0
   188
}
jens@0
   189
jens@0
   190
- (BOOL) open
jens@0
   191
{
jens@0
   192
    return [self open: nil];
jens@0
   193
}
jens@0
   194
    
jens@0
   195
jens@0
   196
- (void) close 
jens@0
   197
{
jens@0
   198
    if( _ipv4socket ) {
jens@22
   199
        [self _closeBonjour];
jens@0
   200
        _ipv4socket = closeSocket(_ipv4socket);
jens@0
   201
        _ipv6socket = closeSocket(_ipv6socket);
jens@0
   202
jens@0
   203
        LogTo(BLIP,@"%@ is closed",self);
jens@0
   204
        [self tellDelegate: @selector(listenerDidClose:) withObject: nil];
jens@0
   205
    }
jens@0
   206
}
jens@0
   207
jens@0
   208
jens@0
   209
- (BOOL) isOpen
jens@0
   210
{
jens@0
   211
    return _ipv4socket != NULL;
jens@0
   212
}
jens@0
   213
jens@0
   214
jens@0
   215
#pragma mark -
jens@0
   216
#pragma mark ACCEPTING CONNECTIONS:
jens@0
   217
jens@0
   218
jens@0
   219
@synthesize connectionClass = _connectionClass;
jens@0
   220
jens@0
   221
jens@0
   222
- (BOOL) acceptConnection: (CFSocketNativeHandle)socket
jens@0
   223
{
jens@0
   224
    IPAddress *addr = [IPAddress addressOfSocket: socket];
jens@0
   225
    if( ! addr )
jens@0
   226
        return NO;
jens@0
   227
    if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)]
jens@0
   228
       && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] )
jens@0
   229
        return NO;
jens@0
   230
    
jens@0
   231
    Assert(_connectionClass);
jens@0
   232
    TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket
jens@0
   233
                                                                      listener: self];
jens@0
   234
    if( ! conn )
jens@0
   235
        return NO;
jens@0
   236
    
jens@0
   237
    if( _sslProperties ) {
jens@0
   238
        conn.SSLProperties = _sslProperties;
jens@0
   239
        [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer];
jens@0
   240
    }
jens@0
   241
    [conn open];
jens@0
   242
    [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn];
jens@0
   243
    return YES;
jens@0
   244
}
jens@0
   245
jens@0
   246
jens@0
   247
static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) 
jens@0
   248
{
jens@0
   249
    TCPListener *server = (TCPListener *)info;
jens@0
   250
    if (kCFSocketAcceptCallBack == type) { 
jens@0
   251
        CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
jens@0
   252
        BOOL accepted = NO;
jens@0
   253
        @try{
jens@0
   254
            accepted = [server acceptConnection: nativeSocketHandle];
jens@0
   255
        }catchAndReport(@"TCPListenerAcceptCallBack");
jens@0
   256
        if( ! accepted )
jens@0
   257
            close(nativeSocketHandle);
jens@0
   258
    }
jens@0
   259
}
jens@0
   260
jens@0
   261
jens@0
   262
#pragma mark -
jens@0
   263
#pragma mark BONJOUR:
jens@0
   264
jens@0
   265
jens@22
   266
- (void) _openBonjour
jens@22
   267
{
jens@22
   268
    if( self.isOpen && _bonjourServiceType && !_netService) {
jens@22
   269
        // instantiate the NSNetService object that will advertise on our behalf.
jens@22
   270
        _netService = [[NSNetService alloc] initWithDomain: @"local." 
jens@22
   271
                                                      type: _bonjourServiceType
jens@22
   272
                                                      name: _bonjourServiceName ?:@""
jens@22
   273
                                                      port: _port];
jens@22
   274
        if( _netService ) {
jens@22
   275
            [_netService setDelegate:self];
jens@22
   276
            if( _bonjourTXTRecord )
jens@22
   277
                [self _updateTXTRecord];
jens@44
   278
            [_netService publishWithOptions: _bonjourServiceOptions];
jens@22
   279
        } else {
jens@22
   280
            self.bonjourError = -1;
jens@22
   281
            Warn(@"%@: Failed to create NSNetService",self);
jens@22
   282
        }
jens@22
   283
    }
jens@22
   284
}
jens@22
   285
jens@22
   286
- (void) _closeBonjour
jens@22
   287
{
jens@22
   288
    if( _netService ) {
jens@22
   289
        [_netService stop];
jens@22
   290
        [_netService release];
jens@22
   291
        _netService = nil;
jens@22
   292
        self.bonjourPublished = NO;
jens@22
   293
    }
jens@22
   294
    if( self.bonjourError )
jens@22
   295
        self.bonjourError = 0;
jens@22
   296
}
jens@22
   297
jens@22
   298
jens@22
   299
- (NSString*) bonjourServiceName {return _bonjourServiceName;}
jens@22
   300
jens@22
   301
- (void) setBonjourServiceName: (NSString*)name
jens@22
   302
{
jens@22
   303
    if( ! $equal(name,_bonjourServiceName) ) {
jens@22
   304
        [self _closeBonjour];
jens@22
   305
        setObj(&_bonjourServiceName,name);
jens@22
   306
        [self _openBonjour];
jens@22
   307
    }
jens@22
   308
}
jens@22
   309
jens@22
   310
jens@0
   311
- (NSDictionary*) bonjourTXTRecord
jens@0
   312
{
jens@0
   313
    return _bonjourTXTRecord;
jens@0
   314
}
jens@0
   315
jens@0
   316
- (void) setBonjourTXTRecord: (NSDictionary*)txt
jens@0
   317
{
jens@0
   318
    if( ifSetObj(&_bonjourTXTRecord,txt) )
jens@0
   319
        [self _updateTXTRecord];
jens@0
   320
}
jens@0
   321
jens@0
   322
- (void) _updateTXTRecord
jens@0
   323
{
jens@0
   324
    if( _netService ) {
jens@0
   325
        NSData *data;
jens@0
   326
        if( _bonjourTXTRecord ) {
jens@0
   327
            data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord];
jens@0
   328
            if( data )
jens@0
   329
                LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length);
jens@0
   330
            else
jens@0
   331
                Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord);
jens@0
   332
        } else
jens@0
   333
            data = nil;
jens@0
   334
        [_netService setTXTRecordData: data];
jens@0
   335
    }
jens@0
   336
}
jens@0
   337
jens@0
   338
jens@0
   339
- (void)netServiceWillPublish:(NSNetService *)sender
jens@0
   340
{
jens@0
   341
    LogTo(BLIP,@"%@: Advertising %@",self,sender);
jens@0
   342
    self.bonjourPublished = YES;
jens@0
   343
}
jens@0
   344
jens@0
   345
- (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
jens@0
   346
{
jens@0
   347
    self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue];
jens@0
   348
    LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError);
jens@0
   349
    [_netService release];
jens@0
   350
    _netService = nil;
jens@0
   351
}
jens@0
   352
jens@0
   353
- (void)netServiceDidStop:(NSNetService *)sender
jens@0
   354
{
jens@0
   355
    LogTo(BLIP,@"%@: Stopped advertising %@",self,sender);
jens@0
   356
    self.bonjourPublished = NO;
jens@0
   357
    [_netService release];
jens@0
   358
    _netService = nil;
jens@0
   359
}
jens@0
   360
jens@0
   361
jens@0
   362
@end
jens@0
   363
jens@0
   364
jens@0
   365
jens@0
   366
/*
jens@0
   367
 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@0
   368
 
jens@0
   369
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@0
   370
 provided that the following conditions are met:
jens@0
   371
 
jens@0
   372
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@0
   373
 and the following disclaimer.
jens@0
   374
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@0
   375
 and the following disclaimer in the documentation and/or other materials provided with the
jens@0
   376
 distribution.
jens@0
   377
 
jens@0
   378
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@0
   379
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@0
   380
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@0
   381
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@0
   382
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@0
   383
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@0
   384
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@0
   385
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@0
   386
 */