BLIP/BLIPTest.m
author Jens Alfke <jens@mooseyard.com>
Fri Jul 24 14:06:28 2009 -0700 (2009-07-24)
changeset 63 5e4855a592ee
parent 37 7c7d5a0cb4d6
permissions -rw-r--r--
* The BLIPConnection receivedRequest: delegate method now returns BOOL. If the method returns NO (or if the method isn't implemented in the delegate), that means it didn't handle the message at all; an error will be returned to the sender.
* If the connection closes unexpectedly due to an error, then the auto-generated responses to pending requests will contain that error. This makes it easier to display a meaningful error message in the handler for the request.
     1 //
     2 //  BLIPTest.m
     3 //  MYNetwork
     4 //
     5 //  Created by Jens Alfke on 5/13/08.
     6 //  Copyright 2008 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #ifndef NDEBUG
    10 
    11 
    12 #import "BLIPRequest.h"
    13 #import "BLIPProperties.h"
    14 #import "BLIPConnection.h"
    15 
    16 #import "IPAddress.h"
    17 #import "Target.h"
    18 #import "CollectionUtils.h"
    19 #import "Logging.h"
    20 #import "Test.h"
    21 
    22 #import <Security/Security.h>
    23 #import <SecurityInterface/SFChooseIdentityPanel.h>
    24 
    25 @interface TCPEndpoint ()
    26 + (NSString*) describeCert: (SecCertificateRef)cert;
    27 + (NSString*) describeIdentity: (SecIdentityRef)identity;
    28 @end
    29 
    30 
    31 #define kListenerHost               @"localhost"
    32 #define kListenerPort               46353
    33 #define kSendInterval               0.5
    34 #define kNBatchedMessages           20
    35 #define kUseCompression             YES
    36 #define kUrgentEvery                4
    37 #define kListenerCloseAfter         50
    38 #define kClientAcceptCloseRequest   YES
    39 
    40 #define kListenerUsesSSL            YES     // Does the listener (server) use an SSL connection?
    41 #define kListenerRequiresClientCert YES     // Does the listener require clients to have an SSL cert?
    42 #define kClientRequiresSSL          YES     // Does the client require the listener to use SSL?
    43 #define kClientUsesSSLCert          YES     // Does the client use an SSL cert?
    44 
    45 
    46 static SecIdentityRef ChooseIdentity( NSString *prompt ) {
    47     NSMutableArray *identities = [NSMutableArray array];
    48     SecKeychainRef kc;
    49     SecKeychainCopyDefault(&kc);
    50     SecIdentitySearchRef search;
    51     SecIdentitySearchCreate(kc, CSSM_KEYUSE_ANY, &search);
    52     SecIdentityRef identity;
    53     while (SecIdentitySearchCopyNext(search, &identity) == noErr) {
    54         [identities addObject: (id)identity];
    55 		CFRelease( identity );
    56 	}
    57     CFRelease(search);
    58     Log(@"Found %u identities -- prompting '%@'", identities.count, prompt);
    59     if (identities.count > 0) {
    60         SFChooseIdentityPanel *panel = [SFChooseIdentityPanel sharedChooseIdentityPanel];
    61         if ([panel runModalForIdentities: identities message: prompt] == NSOKButton) {
    62             Log(@"Using SSL identity: %@", panel.identity);
    63             return panel.identity;
    64         }
    65     }
    66     return NULL;
    67 }
    68 
    69 static SecIdentityRef GetClientIdentity(void) {
    70     return ChooseIdentity(@"Choose an identity for the BLIP Client Test:");
    71 }
    72 
    73 static SecIdentityRef GetListenerIdentity(void) {
    74     return ChooseIdentity(@"Choose an identity for the BLIP Listener Test:");
    75 }
    76 
    77 
    78 #pragma mark -
    79 #pragma mark CLIENT TEST:
    80 
    81 
    82 @interface BLIPConnectionTester : NSObject <BLIPConnectionDelegate>
    83 {
    84     BLIPConnection *_conn;
    85     NSMutableDictionary *_pending;
    86 }
    87 
    88 @end
    89 
    90 
    91 @implementation BLIPConnectionTester
    92 
    93 - (id) init
    94 {
    95     self = [super init];
    96     if (self != nil) {
    97         Log(@"** INIT %@",self);
    98         _pending = [[NSMutableDictionary alloc] init];
    99         IPAddress *addr = [[[IPAddress alloc] initWithHostname: kListenerHost port: kListenerPort] autorelease];
   100         _conn = [[BLIPConnection alloc] initToAddress: addr];
   101         if( ! _conn ) {
   102             [self release];
   103             return nil;
   104         }
   105         if( kClientUsesSSLCert ) {
   106             [_conn setPeerToPeerIdentity: GetClientIdentity()];
   107         } else if( kClientRequiresSSL ) {
   108             _conn.SSLProperties = $mdict({kTCPPropertySSLAllowsAnyRoot, $true},
   109                                         {(id)kCFStreamSSLPeerName, [NSNull null]});
   110         }
   111         _conn.delegate = self;
   112         Log(@"** Opening connection...");
   113         [_conn open];
   114     }
   115     return self;
   116 }
   117 
   118 - (void) dealloc
   119 {
   120     Log(@"** %@ closing",self);
   121     [_conn close];
   122     [_conn release];
   123     [super dealloc];
   124 }
   125 
   126 - (void) sendAMessage
   127 {
   128     if( _conn.status==kTCP_Open || _conn.status==kTCP_Opening ) {
   129         if(_pending.count<100) {
   130             Log(@"** Sending a message that will fail to be handled...");
   131             BLIPRequest *q = [_conn requestWithBody: nil
   132                                          properties: $dict({@"Profile", @"BLIPTest/DontHandleMe"},
   133                                                            {@"User-Agent", @"BLIPConnectionTester"},
   134                                                            {@"Date", [[NSDate date] description]})];
   135             BLIPResponse *response = [q send];
   136             Assert(response);
   137             Assert(q.number>0);
   138             Assert(response.number==q.number);
   139             [_pending setObject: [NSNull null] forKey: $object(q.number)];
   140             response.onComplete = $target(self,responseArrived:);
   141             
   142             Log(@"** Sending another %i messages...", kNBatchedMessages);
   143             for( int i=0; i<kNBatchedMessages; i++ ) {
   144                 size_t size = random() % 32768;
   145                 NSMutableData *body = [NSMutableData dataWithLength: size];
   146                 UInt8 *bytes = body.mutableBytes;
   147                 for( size_t i=0; i<size; i++ )
   148                     bytes[i] = i % 256;
   149                 
   150                 q = [_conn requestWithBody: body
   151                                  properties: $dict({@"Profile", @"BLIPTest/EchoData"},
   152                                                    {@"Content-Type", @"application/octet-stream"},
   153                                                    {@"User-Agent", @"BLIPConnectionTester"},
   154                                                    {@"Date", [[NSDate date] description]},
   155                                                    {@"Size",$sprintf(@"%u",size)})];
   156                 Assert(q);
   157                 if( kUseCompression && (random()%2==1) )
   158                     q.compressed = YES;
   159                 if( random()%16 > 12 )
   160                     q.urgent = YES;
   161                 BLIPResponse *response = [q send];
   162                 Assert(response);
   163                 Assert(q.number>0);
   164                 Assert(response.number==q.number);
   165                 [_pending setObject: $object(size) forKey: $object(q.number)];
   166                 response.onComplete = $target(self,responseArrived:);
   167             }
   168         } else {
   169             Warn(@"There are %u pending messages; waiting for the listener to catch up...",_pending.count);
   170         }
   171         [self performSelector: @selector(sendAMessage) withObject: nil afterDelay: kSendInterval];
   172     }
   173 }
   174 
   175 - (void) responseArrived: (BLIPResponse*)response
   176 {
   177     Log(@"********** called responseArrived: %@",response);
   178 }
   179 
   180 - (void) connectionDidOpen: (TCPConnection*)connection
   181 {
   182     Log(@"** %@ didOpen",connection);
   183     [self sendAMessage];
   184 }
   185 - (BOOL) connection: (TCPConnection*)connection authorizeSSLPeer: (SecCertificateRef)peerCert
   186 {
   187     Log(@"** %@ authorizeSSLPeer: %@",self, [TCPEndpoint describeCert:peerCert]);
   188     return peerCert != nil;
   189 }
   190 - (void) connection: (TCPConnection*)connection failedToOpen: (NSError*)error
   191 {
   192     Warn(@"** %@ failedToOpen: %@",connection,error);
   193     CFRunLoopStop(CFRunLoopGetCurrent());
   194 }
   195 - (void) connectionDidClose: (TCPConnection*)connection
   196 {
   197     if (connection.error)
   198         Warn(@"** %@ didClose: %@", connection,connection.error);
   199     else
   200         Log(@"** %@ didClose", connection);
   201     setObj(&_conn,nil);
   202     [NSObject cancelPreviousPerformRequestsWithTarget: self];
   203     CFRunLoopStop(CFRunLoopGetCurrent());
   204 }
   205 - (BOOL) connection: (BLIPConnection*)connection receivedRequest: (BLIPRequest*)request
   206 {
   207     Log(@"***** %@ received %@",connection,request);
   208     [request respondWithData: request.body contentType: request.contentType];
   209     return YES;
   210 }
   211 
   212 - (void) connection: (BLIPConnection*)connection receivedResponse: (BLIPResponse*)response
   213 {
   214     Log(@"********** %@ received %@",connection,response);
   215     id sizeObj = [_pending objectForKey: $object(response.number)];
   216     Assert(sizeObj);
   217     
   218     if (sizeObj == [NSNull null]) {
   219         AssertEqual(response.error.domain, BLIPErrorDomain);
   220         AssertEq(response.error.code, kBLIPError_NotFound);
   221     } else {
   222         if( response.error )
   223             Warn(@"Got error response: %@",response.error);
   224         else {
   225             NSData *body = response.body;
   226             size_t size = body.length;
   227             Assert(size<32768);
   228             const UInt8 *bytes = body.bytes;
   229             for( size_t i=0; i<size; i++ )
   230                 AssertEq(bytes[i],i % 256);
   231             AssertEq(size,[sizeObj unsignedIntValue]);
   232         }
   233     }
   234     [_pending removeObjectForKey: $object(response.number)];
   235     Log(@"Now %u replies pending", _pending.count);
   236 }
   237 
   238 - (BOOL) connectionReceivedCloseRequest: (BLIPConnection*)connection
   239 {
   240     BOOL response = kClientAcceptCloseRequest;
   241     Log(@"***** %@ received a close request; returning %i",connection,response);
   242     return response;
   243 }
   244 
   245 
   246 @end
   247 
   248 
   249 TestCase(BLIPConnection) {
   250     SecKeychainSetUserInteractionAllowed(true);
   251     BLIPConnectionTester *tester = [[BLIPConnectionTester alloc] init];
   252     CAssert(tester);
   253     
   254     [[NSRunLoop currentRunLoop] run];
   255     
   256     Log(@"** Runloop stopped");
   257     [tester release];
   258 }
   259 
   260 
   261 
   262 
   263 #pragma mark LISTENER TEST:
   264 
   265 
   266 @interface BLIPTestListener : NSObject <TCPListenerDelegate, BLIPConnectionDelegate>
   267 {
   268     BLIPListener *_listener;
   269     int _nReceived;
   270 }
   271 
   272 @end
   273 
   274 
   275 @implementation BLIPTestListener
   276 
   277 - (id) init
   278 {
   279     self = [super init];
   280     if (self != nil) {
   281         _listener = [[BLIPListener alloc] initWithPort: kListenerPort];
   282         _listener.delegate = self;
   283         _listener.pickAvailablePort = YES;
   284         _listener.bonjourServiceType = @"_bliptest._tcp";
   285         if( kListenerUsesSSL ) {
   286             [_listener setPeerToPeerIdentity: GetListenerIdentity()];
   287             if (!kListenerRequiresClientCert)
   288                 [_listener setSSLProperty: $object(kTCPTryAuthenticate) 
   289                                    forKey: kTCPPropertySSLClientSideAuthentication];
   290         }
   291         Assert( [_listener open] );
   292         Log(@"%@ is listening...",self);
   293     }
   294     return self;
   295 }
   296 
   297 - (void) dealloc
   298 {
   299     Log(@"%@ closing",self);
   300     [_listener close];
   301     [_listener release];
   302     [super dealloc];
   303 }
   304 
   305 - (void) listener: (TCPListener*)listener didAcceptConnection: (TCPConnection*)connection
   306 {
   307     Log(@"** %@ accepted %@",self,connection);
   308     connection.delegate = self;
   309 }
   310 
   311 - (void) listener: (TCPListener*)listener failedToOpen: (NSError*)error
   312 {
   313     Log(@"** BLIPTestListener failed to open: %@",error);
   314 }
   315 
   316 - (void) listenerDidOpen: (TCPListener*)listener   {Log(@"** BLIPTestListener did open");}
   317 - (void) listenerDidClose: (TCPListener*)listener   {Log(@"** BLIPTestListener did close");}
   318 
   319 - (BOOL) listener: (TCPListener*)listener shouldAcceptConnectionFrom: (IPAddress*)address
   320 {
   321     Log(@"** %@ shouldAcceptConnectionFrom: %@",self,address);
   322     return YES;
   323 }
   324 
   325 
   326 - (void) connectionDidOpen: (TCPConnection*)connection
   327 {
   328     Log(@"** %@ didOpen [SSL=%@]",connection,connection.actualSecurityLevel);
   329     _nReceived = 0;
   330 }
   331 - (BOOL) connection: (TCPConnection*)connection authorizeSSLPeer: (SecCertificateRef)peerCert
   332 {
   333     Log(@"** %@ authorizeSSLPeer: %@",self, [TCPEndpoint describeCert:peerCert]);
   334     return peerCert != nil || ! kListenerRequiresClientCert;
   335 }
   336 - (void) connection: (TCPConnection*)connection failedToOpen: (NSError*)error
   337 {
   338     Log(@"** %@ failedToOpen: %@",connection,error);
   339 }
   340 - (void) connectionDidClose: (TCPConnection*)connection
   341 {
   342     if (connection.error)
   343         Warn(@"** %@ didClose: %@", connection,connection.error);
   344     else
   345         Log(@"** %@ didClose", connection);
   346     [connection release];
   347 }
   348 - (BOOL) connection: (BLIPConnection*)connection receivedRequest: (BLIPRequest*)request
   349 {
   350     Log(@"***** %@ received %@",connection,request);
   351     
   352     if ([request.profile isEqualToString: @"BLIPTest/EchoData"]) {
   353         NSData *body = request.body;
   354         size_t size = body.length;
   355         Assert(size<32768);
   356         const UInt8 *bytes = body.bytes;
   357         for( size_t i=0; i<size; i++ )
   358             AssertEq(bytes[i],i % 256);
   359         
   360         AssertEqual([request valueOfProperty: @"Content-Type"], @"application/octet-stream");
   361         Assert([request valueOfProperty: @"User-Agent"] != nil);
   362         AssertEq((size_t)[[request valueOfProperty: @"Size"] intValue], size);
   363 
   364         [request respondWithData: body contentType: request.contentType];
   365     } else if ([request.profile isEqualToString: @"BLIPTest/DontHandleMe"]) {
   366         // Deliberately don't handle this, to test unhandled request handling.
   367         return NO;
   368     } else {
   369         Assert(NO, @"Unknown profile in request %@", request);
   370     }
   371     
   372     if( ++ _nReceived == kListenerCloseAfter ) {
   373         Log(@"********** Closing BLIPTestListener after %i requests",_nReceived);
   374         [connection close];
   375     }
   376     return YES;
   377 }
   378 
   379 - (BOOL) connectionReceivedCloseRequest: (BLIPConnection*)connection;
   380 {
   381     Log(@"***** %@ received a close request",connection);
   382     return YES;
   383 }
   384 
   385 - (void) connection: (BLIPConnection*)connection closeRequestFailedWithError: (NSError*)error
   386 {
   387     Log(@"***** %@'s close request failed: %@",connection,error);
   388 }
   389 
   390 
   391 @end
   392 
   393 
   394 TestCase(BLIPListener) {
   395     EnableLogTo(BLIP,YES);
   396     EnableLogTo(PortMapper,YES);
   397     EnableLogTo(Bonjour,YES);
   398     SecKeychainSetUserInteractionAllowed(true);
   399     BLIPTestListener *listener = [[BLIPTestListener alloc] init];
   400     
   401     [[NSRunLoop currentRunLoop] run];
   402     
   403     [listener release];
   404 }
   405 
   406 
   407 #endif
   408 
   409 
   410 /*
   411  Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   412  
   413  Redistribution and use in source and binary forms, with or without modification, are permitted
   414  provided that the following conditions are met:
   415  
   416  * Redistributions of source code must retain the above copyright notice, this list of conditions
   417  and the following disclaimer.
   418  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   419  and the following disclaimer in the documentation and/or other materials provided with the
   420  distribution.
   421  
   422  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   423  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   424  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   425  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   426  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   427   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   428  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   429  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   430  */