# HG changeset patch # User Jens Alfke # Date 1211589456 25200 # Node ID 9d67172bb3232312f3b9fb8f3b1ea6f9c0bfa2cf First checkin after breaking out of Cloudy diff -r 000000000000 -r 9d67172bb323 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,9 @@ +syntax: glob +.DS_Store +build +.svn +(*) +*.pbxuser +*.perspectivev3 +*.mpkg +*.framework diff -r 000000000000 -r 9d67172bb323 BLIP/BLIP Overview.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIP Overview.txt Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,66 @@ +BLIP OVERVIEW +Jens Alfke +Preliminary Draft 1 -- 21 May 2008 + + +BLIP is a generic application-layer network protocol that runs atop TCP. It was inspired by BEEP (in fact BLIP stands for "BEEP-LIke Protocol") but is deliberately simpler and somewhat more limited. + +DATA MODEL + +BLIP lets the two peers on either end of a TCP socket send requests and responses to each other. +Each message and response is very much like a MIME body, as in email or HTTP: it consists of a blob of data of arbitrary length, plus a set of key/value pairs called "properties". The properties are mostly ignored by BLIP itself, but clients can use them for metadata about the body, and for delivery information (i.e. something like BEEP's "profiles".) + +Either peer can send a message at any time; there's no notion of "client" and "server" roles. Multiple messages can be transmitted simultaneously over the same connection, so a very long message does not block any other messages from being delivered. This means that message ordering is a bit looser than in BEEP or HTTP 1.1: the receiver will see the beginnings of messages in the same order in which the sender posted them, but they might not end in that same order. (For example, a long message will take longer to be delivered, so it may finish after messages that were begun after it.) + +The sender can indicate whether a message needs to be replied to; the response is tagged with the identity of the original message, to make it easy for the sender to recognize. This makes it straighforward to implement RPC-style (or REST-style) request/response interactions. (Replies cannot be replied to again, however.) + +A message can be flagged as "urgent". Urgent messages are pushed ahead in the outgoing queue and get a higher fraction of the available bandwidth. + +A message can be flagged as "compressed". This runs its body through the gzip algorithm, ideally making it faster to transmit. (Common markup-based data formats like XML and JSON compress extremely well, at ratios up to 10::1.) The message is decompressed on the receiving end, invisibly to client code. + +WIRE FORMAT + +All multi-byte numbers are encoded in network byte-order (big-endian). + +Each message is packed into a series of bytes consisting of the properties followed by the body. The properties are encoded as a 16-bit byte-count followed by a series of NUL-terminated C strings alternating keys and values. + +The message is then broken up into "frames", usually 4k to 12k bytes. Each frame is prefixed with a 12-byte header containing its length in bytes, message number, and some flags. + +Each of the two unidirectional TCP streams carries a sequence of these frames, and nothing else. If multiple messages are queued up at the sender, their frames will be interleaved, so that one message doesn't block the rest. The ordering is primarily round-robin, except that urgent messages are scheduled more often than regular ones; the scheduler tries to alternate urgent and regular frames, and lets the urgent frames be larger. It's rather like a thread scheduler, really. + +When one peer wants to close the connection, it finishes sending all pending frames and then closes its outgoing (write) stream. The other peer detects this and goes into closing mode as well, sending its pending frames and then closing the other stream, which closes the socket. On the other hand, if a peer's writing stream is closed unexpectedly, or its reading stream closes in mid-frame, this indicates a broken connection. + + Frame header: + UInt32 magic; // magic number (kBLIPFrameHeaderMagicNumber) + UInt32 number; // serial number of MSG (starts at 1) + BLIPMessageFlags flags; // encodes frame type, "more" flag, and other delivery options + UInt16 size; // total size of frame, _including_ this header + + Flags: + kBLIP_TypeMask = 0x000F, // bits reserved for storing message type + kBLIP_Compressed= 0x0010, // data is gzipped + kBLIP_Urgent = 0x0020, // please send sooner/faster + kBLIP_NoReply = 0x0040, // no RPY needed + kBLIP_MoreComing= 0x0080, // More frames of this message coming + // all other bits reserved + + Message types: + kBLIP_MSG = 0, // initiating message + kBLIP_RPY = 1, // response to a MSG + kBLIP_ERR = 2 // error response to a MSG + // values 3-15 reserved + + +LIMITATIONS + +Compared to BEEP, the BLIP protocol has: +* No channels. (It's as though every message/response were sent on a separate channel.) +* No ANS style responses (multiple answers) +* No proocol for "tuning" the session by negotiating encryption (SSL) or authentication (SASL, etc.) +* No negotiation of closing the connection (a peer can't veto a close) + +Some currently missing features that will likely be added are: +* Flow control (i.e. "windows" to throttle the sender so the listener can keep up) +* Ability to enforce one-at-a-time ordering of a set of requests and responses, so a message isn't sent until the previous message is complete (as in a BEEP channel) +* A more stream-like API for requests and responses, so their bodies can be sent and received incrementally. (The protocol and implementation already support this.) +* Ability to stop an incoming message partway through (e.g. to interrupt a file transfer) diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPConnection.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPConnection.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,83 @@ +// +// BLIPConnection.h +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "TCPConnection.h" +#import "TCPListener.h" +@class BLIPRequest, BLIPResponse, BLIPDispatcher; +@protocol BLIPConnectionDelegate; + + +/** Represents a connection to a peer, using the BLIP protocol over a TCP socket. + Outgoing connections are made simply by instantiating a BLIPConnection via -initToAddress:. + Incoming connections are usually set up by a BLIPListener and passed to the listener's + delegate. + Most of the API is inherited from TCPConnection. */ +@interface BLIPConnection : TCPConnection +{ + BLIPDispatcher *_dispatcher; +} + +/** The delegate object that will be called when the connection opens, closes or receives messages. */ +@property (assign) id delegate; + +@property (readonly) BLIPDispatcher *dispatcher; + +/** Creates an outgoing request, with no properties. + The body may be nil. + To send it, call -send. */ +- (BLIPRequest*) requestWithBody: (NSData*)body; + +/** Creates an outgoing request. + The body or properties may be nil. + To send it, call -send. */ +- (BLIPRequest*) requestWithBody: (NSData*)body + properties: (NSDictionary*)properies; + +/** Sends a request over this connection. + (Actually, it queues it to be sent; this method always returns immediately.) + Call this instead of calling -send on the request itself, if the request was created with + +[BLIPRequest requestWithBody:] and hasn't yet been assigned to any connection. + This method will assign it to this connection before sending it. + The request's matching response object will be returned, or nil if the request couldn't be sent. */ +- (BLIPResponse*) sendRequest: (BLIPRequest*)request; +@end + + + +/** The delegate messages that BLIPConnection will send, + in addition to the ones inherited from TCPConnectionDelegate. */ +@protocol BLIPConnectionDelegate + +/** Called when a BLIPRequest is received from the peer, if there is no BLIPDispatcher + rule to handle it. + The delegate should get the request's response object, fill in its data and properties + or error property, and send it. + If it doesn't explicitly send a response, a default empty one will be sent; + to prevent this, call -deferResponse on the request if you want to send a response later. */ +- (void) connection: (BLIPConnection*)connection receivedRequest: (BLIPRequest*)request; + +@optional +/** Called when a BLIPResponse (to one of your requests) is received from the peer. + This is called after the response object's onComplete target, if any, is invoked. */ +- (void) connection: (BLIPConnection*)connection receivedResponse: (BLIPResponse*)response; +@end + + + + +/** A "server" that listens on a TCP socket for incoming BLIP connections and creates + BLIPConnection instances to handle them. + Most of the API is inherited from TCPListener. */ +@interface BLIPListener : TCPListener +{ + BLIPDispatcher *_dispatcher; +} + +@property (readonly) BLIPDispatcher *dispatcher; + +@end diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPConnection.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPConnection.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,158 @@ +// +// BLIPConnection.m +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "BLIPConnection.h" +#import "BLIP_Internal.h" +#import "BLIPReader.h" +#import "BLIPWriter.h" +#import "BLIPDispatcher.h" + +#import "Logging.h" +#import "Test.h" +#import "ExceptionUtils.h" + + +NSString* const BLIPErrorDomain = @"BLIP"; + +NSError *BLIPMakeError( int errorCode, NSString *message, ... ) +{ + va_list args; + va_start(args,message); + message = [[NSString alloc] initWithFormat: message arguments: args]; + va_end(args); + LogTo(BLIP,@"BLIPError #%i: %@",errorCode,message); + NSDictionary *userInfo = [NSDictionary dictionaryWithObject: message + forKey: NSLocalizedDescriptionKey]; + [message release]; + return [NSError errorWithDomain: BLIPErrorDomain code: errorCode userInfo: userInfo]; +} + + + + +@implementation BLIPConnection + +- (void) dealloc +{ + [_dispatcher release]; + [super dealloc]; +} + +- (Class) readerClass {return [BLIPReader class];} +- (Class) writerClass {return [BLIPWriter class];} +- (id) delegate {return (id)_delegate;} +- (void) setDelegate: (id)delegate {_delegate = delegate;} + +- (BLIPDispatcher*) dispatcher +{ + if( ! _dispatcher ) { + _dispatcher = [[BLIPDispatcher alloc] init]; + _dispatcher.parent = ((BLIPListener*)self.server).dispatcher; + } + return _dispatcher; +} + + +- (void) _dispatchRequest: (BLIPRequest*)request +{ + LogTo(BLIP,@"Received all of %@",request.descriptionWithProperties); + @try{ + if( ! [self.dispatcher dispatchMessage: request] ) + [self tellDelegate: @selector(connection:receivedRequest:) withObject: request]; + if( ! request.noReply && ! request.repliedTo ) { + LogTo(BLIP,@"Returning default empty response to %@",request); + [request respondWithData: nil]; + } + }@catch( NSException *x ) { + MYReportException(x,@"Dispatching BLIP request"); + [request respondWithException: x]; + } +} + +- (void) _dispatchResponse: (BLIPResponse*)response +{ + LogTo(BLIP,@"Received all of %@",response); + [self tellDelegate: @selector(connection:receivedResponse:) withObject: response]; +} + + +- (BLIPRequest*) requestWithBody: (NSData*)body +{ + return [[[BLIPRequest alloc] _initWithConnection: self body: body properties: nil] autorelease]; +} + +- (BLIPRequest*) requestWithBody: (NSData*)body + properties: (NSDictionary*)properties +{ + return [[[BLIPRequest alloc] _initWithConnection: self body: body properties: properties] autorelease]; +} + +- (BLIPResponse*) sendRequest: (BLIPRequest*)request +{ + BLIPConnection *itsConnection = request.connection; + if( itsConnection==nil ) + request.connection = self; + else + Assert(itsConnection==self,@"%@ is already assigned to a different BLIPConnection",request); + return [request send]; +} + + +@end + + + + +@implementation BLIPListener + +- (id) initWithPort: (UInt16)port +{ + self = [super initWithPort: port]; + if (self != nil) { + self.connectionClass = [BLIPConnection class]; + } + return self; +} + +- (void) dealloc +{ + [_dispatcher release]; + [super dealloc]; +} + +- (BLIPDispatcher*) dispatcher +{ + if( ! _dispatcher ) + _dispatcher = [[BLIPDispatcher alloc] init]; + return _dispatcher; +} + +@end + + +/* + Copyright (c) 2008, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPDispatcher.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPDispatcher.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,28 @@ +// +// BLIPDispatcher.h +// MYNetwork +// +// Created by Jens Alfke on 5/15/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import +@class MYTarget, BLIPMessage; + + +@interface BLIPDispatcher : NSObject +{ + NSMutableArray *_predicates, *_targets; + BLIPDispatcher *_parent; +} + +@property (retain) BLIPDispatcher *parent; + +- (void) addTarget: (MYTarget*)target forPredicate: (NSPredicate*)predicate; +- (void) removeTarget: (MYTarget*)target; + +- (void) addTarget: (MYTarget*)target forValueOfProperty: (NSString*)value forKey: (NSString*)key; + +- (BOOL) dispatchMessage: (BLIPMessage*)message; + +@end diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPDispatcher.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPDispatcher.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,108 @@ +// +// BLIPDispatcher.m +// MYNetwork +// +// Created by Jens Alfke on 5/15/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "BLIPDispatcher.h" +#import "Target.h" +#import "BLIPRequest.h" +#import "BLIPProperties.h" + + +@implementation BLIPDispatcher + + +- (id) init +{ + self = [super init]; + if (self != nil) { + _targets = [[NSMutableArray alloc] init]; + _predicates = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void) dealloc +{ + [_targets release]; + [_predicates release]; + [_parent release]; + [super dealloc]; +} + + +@synthesize parent=_parent; + + +- (void) addTarget: (MYTarget*)target forPredicate: (NSPredicate*)predicate +{ + [_targets addObject: target]; + [_predicates addObject: predicate]; +} + + +- (void) removeTarget: (MYTarget*)target +{ + NSUInteger i = [_targets indexOfObject: target]; + if( i != NSNotFound ) { + [_targets removeObjectAtIndex: i]; + [_predicates removeObjectAtIndex: i]; + } +} + + +- (void) addTarget: (MYTarget*)target forValueOfProperty: (NSString*)value forKey: (NSString*)key +{ + [self addTarget: target + forPredicate: [NSComparisonPredicate predicateWithLeftExpression: [NSExpression expressionForKeyPath: key] + rightExpression: [NSExpression expressionForConstantValue: value] + modifier: NSDirectPredicateModifier + type: NSEqualToPredicateOperatorType + options: 0]]; +} + + +- (BOOL) dispatchMessage: (BLIPMessage*)message +{ + NSDictionary *properties = message.properties.allProperties; + NSUInteger n = _predicates.count; + for( NSUInteger i=0; i. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPMessage.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPMessage.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,109 @@ +// +// BLIPMessage.h +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import +@class BLIPConnection, BLIPProperties, BLIPMutableProperties; + + +/** NSError domain and codes for BLIP */ +extern NSString* const BLIPErrorDomain; +enum { + kBLIPError_BadData = 1, + kBLIPError_BadFrame, + kBLIPError_Disconnected, + kBLIPError_PeerNotAllowed, + + kBLIPError_Misc = 99, + + // errors returned in responses: + kBLIPError_BadRequest = 400, + kBLIPError_Forbidden = 403, + kBLIPError_NotFound = 404, + kBLIPError_BadRange = 416, + + kBLIPError_HandlerFailed = 501, + kBLIPError_Unspecified = 599 // peer didn't send any detailed error info +}; + + +/** Abstract superclass for both requests and responses. */ +@interface BLIPMessage : NSObject +{ + BLIPConnection *_connection; + UInt16 _flags; + UInt32 _number; + BLIPProperties *_properties; + NSData *_body; + NSMutableData *_encodedBody; + NSMutableData *_mutableBody; + BOOL _isMine, _isMutable, _sent, _propertiesAvailable, _complete; + SInt32 _bytesWritten; +}; + +/** The BLIPConnection associated with this message. */ +@property (readonly,retain) BLIPConnection *connection; + +/** This message's serial number in its connection. + A BLIPRequest's number is initially zero, then assigned when it's sent. + A BLIPResponse is automatically assigned the same number as the request it replies to. */ +@property (readonly) UInt32 number; + +/** Is this a message sent by me (as opposed to the peer)? */ +@property (readonly) BOOL isMine; + +/** Has this message been sent yet? (Only makes sense when isMe is set.) */ +@property (readonly) BOOL sent; + +/** Has enough of the message arrived to read its properies? */ +@property (readonly) BOOL propertiesAvailable; + +/** Has the entire message, including the body, arrived? */ +@property (readonly) BOOL complete; + +/** Should the message body be compressed using gzip? + This property can only be set before sending the message. */ +@property BOOL compressed; + +/** Should the message be sent ahead of normal-priority messages? + This property can only be set before sending the message. */ +@property BOOL urgent; + +/** Can this message be changed? (Only true for outgoing messages, before you send them.) */ +@property (readonly) BOOL isMutable; + +/** The message body, a blob of arbitrary data. */ +@property (copy) NSData *body; + +/** Appends data to the body. */ +- (void) addToBody: (NSData*)data; + +#pragma mark PROPERTIES: + +/** The message's properties, a dictionary-like object. */ +@property (readonly) BLIPProperties* properties; + +/** Mutable version of the message's properties; only available if this mesage is mutable. */ +@property (readonly) BLIPMutableProperties* mutableProperties; + +/** The value of the "Content-Type" property, which is by convention the MIME type of the body. */ +@property (copy) NSString *contentType; + +/** The value of the "Profile" property, which by convention identifies the purpose of the message. */ +@property (copy) NSString *profile; + +/** A shortcut to get the value of a property. */ +- (NSString*) valueOfProperty: (NSString*)property; + +/** A shortcut to set the value of a property. A nil value deletes that property. */ +- (void) setValue: (NSString*)value ofProperty: (NSString*)property; + +/** Similar to -description, but also shows the properties and their values. */ +@property (readonly) NSString* descriptionWithProperties; + + +@end diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPMessage.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPMessage.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,335 @@ +// +// BLIPMessage.m +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "BLIPMessage.h" +#import "BLIP_Internal.h" +#import "BLIPReader.h" +#import "BLIPWriter.h" + +#import "ExceptionUtils.h" +#import "Target.h" + +// From Google Toolbox For Mac +#import "GTMNSData+zlib.h" + + +@implementation BLIPMessage + + +- (id) _initWithConnection: (BLIPConnection*)connection + isMine: (BOOL)isMine + flags: (BLIPMessageFlags)flags + number: (UInt32)msgNo + body: (NSData*)body +{ + self = [super init]; + if (self != nil) { + _connection = [connection retain]; + _isMine = isMine; + _flags = flags; + _number = msgNo; + if( isMine ) { + _body = body.copy; + _properties = [[BLIPMutableProperties alloc] init]; + _propertiesAvailable = YES; + } else { + _encodedBody = body.mutableCopy; + } + LogTo(BLIPVerbose,@"INIT %@",self); + } + return self; +} + +- (void) dealloc +{ + LogTo(BLIPVerbose,@"DEALLOC %@",self); + [_properties release]; + [_encodedBody release]; + [_mutableBody release]; + [_body release]; + [_connection release]; + [super dealloc]; +} + + +- (NSString*) description +{ + NSUInteger length = (_body.length ?: _mutableBody.length) ?: _encodedBody.length; + NSMutableString *desc = [NSMutableString stringWithFormat: @"%@[#%u, %u bytes", + self.class,_number, length]; + if( _flags & kBLIP_Compressed ) { + if( _encodedBody && _encodedBody.length != length ) + [desc appendFormat: @" (%u gzipped)", _encodedBody.length]; + else + [desc appendString: @", gzipped"]; + } + if( _flags & kBLIP_Urgent ) + [desc appendString: @", urgent"]; + if( _flags & kBLIP_NoReply ) + [desc appendString: @", noreply"]; + [desc appendString: @"]"]; + return desc; +} + +- (NSString*) descriptionWithProperties +{ + NSMutableString *desc = (NSMutableString*)self.description; + [desc appendFormat: @" %@", self.properties.allProperties]; + return desc; +} + + +#pragma mark - +#pragma mark PROPERTIES & METADATA: + + +@synthesize connection=_connection, number=_number, isMine=_isMine, isMutable=_isMutable, + _bytesWritten, sent=_sent, propertiesAvailable=_propertiesAvailable, complete=_complete; + + +- (void) _setFlag: (BLIPMessageFlags)flag value: (BOOL)value +{ + Assert(_isMine && _isMutable); + if( value ) + _flags |= flag; + else + _flags &= ~flag; +} + +- (BOOL) compressed {return (_flags & kBLIP_Compressed) != 0;} +- (BOOL) urgent {return (_flags & kBLIP_Urgent) != 0;} +- (void) setCompressed: (BOOL)compressed {[self _setFlag: kBLIP_Compressed value: compressed];} +- (void) setUrgent: (BOOL)high {[self _setFlag: kBLIP_Urgent value: high];} + + +- (NSData*) body +{ + if( ! _body && _isMine ) + return [[_mutableBody copy] autorelease]; + else + return _body; +} + +- (void) setBody: (NSData*)body +{ + Assert(_isMine && _isMutable); + if( _mutableBody ) + [_mutableBody setData: body]; + else + _mutableBody = [body mutableCopy]; +} + +- (void) _addToBody: (NSData*)data +{ + if( data.length ) { + if( _mutableBody ) + [_mutableBody appendData: data]; + else + _mutableBody = [data mutableCopy]; + setObj(&_body,nil); + } +} + +- (void) addToBody: (NSData*)data +{ + Assert(_isMine && _isMutable); + [self _addToBody: data]; +} + + +- (BLIPProperties*) properties +{ + return _properties; +} + +- (BLIPMutableProperties*) mutableProperties +{ + Assert(_isMine && _isMutable); + return (BLIPMutableProperties*)_properties; +} + +- (NSString*) valueOfProperty: (NSString*)property +{ + return [_properties valueOfProperty: property]; +} + +- (void) setValue: (NSString*)value ofProperty: (NSString*)property +{ + [self.mutableProperties setValue: value ofProperty: property]; +} + +- (NSString*) contentType {return [_properties valueOfProperty: @"Content-Type"];} +- (void) setContentType: (NSString*)t {[self setValue: t ofProperty: @"Content-Type"];} +- (NSString*) profile {return [_properties valueOfProperty: @"Profile"];} +- (void) setProfile: (NSString*)p {[self setValue: p ofProperty: @"Profile"];} + + +#pragma mark - +#pragma mark I/O: + + +- (void) _encode +{ + Assert(_isMine && _isMutable); + _isMutable = NO; + + BLIPProperties *oldProps = _properties; + _properties = [oldProps copy]; + [oldProps release]; + + _encodedBody = [_properties.encodedData mutableCopy]; + Assert(_encodedBody.length>=2); + + NSData *body = _body ?: _mutableBody; + NSUInteger length = body.length; + if( length > 0 ) { + if( self.compressed ) { + body = [NSData gtm_dataByGzippingData: body compressionLevel: 5]; + LogTo(BLIPVerbose,@"Compressed %@ to %u bytes (%.0f%%)", self,body.length, + body.length*100.0/length); + } + [_encodedBody appendData: body]; + } +} + + +- (void) _assignedNumber: (UInt32)number +{ + Assert(_number==0,@"%@ has already been sent",self); + _number = number; + _isMutable = NO; +} + + +- (BOOL) _writeFrameTo: (BLIPWriter*)writer maxSize: (UInt16)maxSize +{ + Assert(_number!=0); + Assert(_isMine); + Assert(_encodedBody); + if( _bytesWritten==0 ) + LogTo(BLIP,@"Now sending %@",self); + ssize_t lengthToWrite = _encodedBody.length - _bytesWritten; + if( lengthToWrite <= 0 && _bytesWritten > 0 ) + return NO; // done + Assert(maxSize > sizeof(BLIPFrameHeader)); + maxSize -= sizeof(BLIPFrameHeader); + UInt16 flags = _flags; + if( lengthToWrite > maxSize ) { + lengthToWrite = maxSize; + flags |= kBLIP_MoreComing; + LogTo(BLIPVerbose,@"%@ pushing frame, bytes %u-%u", self, _bytesWritten, _bytesWritten+lengthToWrite); + } else { + flags &= ~kBLIP_MoreComing; + LogTo(BLIPVerbose,@"%@ pushing frame, bytes %u-%u (finished)", self, _bytesWritten, _bytesWritten+lengthToWrite); + } + + // First write the frame header: + BLIPFrameHeader header = { EndianU32_NtoB(kBLIPFrameHeaderMagicNumber), + EndianU32_NtoB(_number), + EndianU16_NtoB(flags), + EndianU16_NtoB(sizeof(BLIPFrameHeader) + lengthToWrite) }; + + [writer writeData: [NSData dataWithBytes: &header length: sizeof(header)]]; + + // Then write the body: + if( lengthToWrite > 0 ) { + [writer writeData: [NSData dataWithBytesNoCopy: (UInt8*)_encodedBody.bytes + _bytesWritten + length: lengthToWrite + freeWhenDone: NO]]; + _bytesWritten += lengthToWrite; + } + return (flags & kBLIP_MoreComing) != 0; +} + + +- (BOOL) _receivedFrameWithHeader: (const BLIPFrameHeader*)header body: (NSData*)body +{ + Assert(!_isMine); + AssertEq(header->number,_number); + Assert(_flags & kBLIP_MoreComing); + + BLIPMessageType frameType = (header->flags & kBLIP_TypeMask), curType = (_flags & kBLIP_TypeMask); + if( frameType != curType ) { + Assert(curType==kBLIP_RPY && frameType==kBLIP_ERR && _mutableBody.length==0, + @"Incoming frame's type %i doesn't match %@",frameType,self); + _flags = (_flags & ~kBLIP_TypeMask) | frameType; + } + + if( _encodedBody ) + [_encodedBody appendData: body]; + else + _encodedBody = [body mutableCopy]; + LogTo(BLIPVerbose,@"%@ rcvd bytes %u-%u", self, _encodedBody.length-body.length, _encodedBody.length); + + if( ! _properties ) { + // Try to extract the properties: + ssize_t usedLength; + setObj(&_properties, [BLIPProperties propertiesWithEncodedData: _encodedBody usedLength: &usedLength]); + if( _properties ) { + [_encodedBody replaceBytesInRange: NSMakeRange(0,usedLength) + withBytes: NULL length: 0]; + } else if( usedLength < 0 ) + return NO; + self.propertiesAvailable = YES; + } + + if( ! (header->flags & kBLIP_MoreComing) ) { + // After last frame, decode the data: + _flags &= ~kBLIP_MoreComing; + if( ! _properties ) + return NO; + unsigned encodedLength = _encodedBody.length; + if( self.compressed && encodedLength>0 ) { + _body = [[NSData gtm_dataByInflatingData: _encodedBody] copy]; + if( ! _body ) + return NO; + LogTo(BLIPVerbose,@"Uncompressed %@ from %u bytes (%.1fx)", self, encodedLength, + _body.length/(float)encodedLength); + } else { + _body = [_encodedBody copy]; + } + setObj(&_encodedBody,nil); + self.propertiesAvailable = self.complete = YES; + } + return YES; +} + + +- (void) _connectionClosed +{ + if( _isMine ) { + _bytesWritten = 0; + _flags |= kBLIP_MoreComing; + } +} + + +@end + + +/* + Copyright (c) 2008, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPProperties.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPProperties.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,61 @@ +// +// BLIPProperties.h +// MYNetwork +// +// Created by Jens Alfke on 5/13/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import + + +/** A key/value property store, like a set of MIME or RFC822 headers (but without the weird details). + It can be written to or read from a block of data; the data is binary, not the textual + format that MIME uses. */ +@interface BLIPProperties : NSObject + +/** Parse properties from a block of data. + On success, returns a Properties object and sets *usedLength to the number of bytes of + data consumed. + If the data doesn't contain the complete properties, returns nil and sets *usedLength to zero. + If the properties are syntactically invalid, returns nil and sets *usedLength to a negative number. +*/ ++ (BLIPProperties*) propertiesWithEncodedData: (NSData*)data + usedLength: (ssize_t*)usedLength; + +/** Returns an empty autoreleased instance. */ ++ (BLIPProperties*) properties; + +/** Property value lookup. (Case-sensitive, like NSDictionary, but unlike RFC822.) */ +- (NSString*) valueOfProperty: (NSString*)prop; + +/** Returns all the properties/values as a dictionary. */ +@property (readonly) NSDictionary* allProperties; + +/** The number of properties. */ +@property (readonly) NSUInteger count; + +/** The raw data representation of the properties. */ +@property (readonly) NSData *encodedData; + +@end + + + +/** Mutable subclass of BLIPProperties, used for creating new instances. */ +@interface BLIPMutableProperties : BLIPProperties +{ + NSMutableDictionary *_properties; +} + +/** Initializes a new instance, adding all the key/value pairs from the given NSDictionary. */ +- (id) initWithDictionary: (NSDictionary*)dict; + +/** Sets the value of a property. A nil value is allowed, and removes the property. */ +- (void) setValue: (NSString*)value ofProperty: (NSString*)prop; + +/** Sets the receiver's key/value pairs from the given NSDictionary. + All previously existing properties are removed first. */ +- (void) setAllProperties: (NSDictionary*)properties; + +@end \ No newline at end of file diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPProperties.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPProperties.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,409 @@ +// +// BLIPProperties.m +// MYNetwork +// +// Created by Jens Alfke on 5/13/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "BLIPProperties.h" + + +/** Common strings are abbreviated as single-byte strings in the packed form. + The ascii value of the single character minus one is the index into this table. */ +static const char* kAbbreviations[] = { + "Content-Type", + "Profile", + "Channel" + "Error-Code" + "Error-Domain", + "application/octet-stream", + "text/plain", + "text/xml", + "text/yaml", + "application/x-cloudy-signed+yaml", +}; +#define kNAbbreviations ((sizeof(kAbbreviations)/sizeof(const char*))) // cannot exceed 31! + + + +@interface BLIPPackedProperties : BLIPProperties +{ + NSData *_data; + int _count; + const char **_strings; + int _nStrings; +} + +@end + + + +// The base class just represents an immutable empty collection. +@implementation BLIPProperties + + ++ (BLIPProperties*) propertiesWithEncodedData: (NSData*)data usedLength: (ssize_t*)usedLength +{ + size_t available = data.length; + if( available < sizeof(UInt16) ) { + // Not enough available to read length: + *usedLength = 0; + return nil; + } + + // Read the length: + const char *bytes = data.bytes; + size_t length = EndianU16_BtoN( *(UInt16*)bytes ) + sizeof(UInt16); + if( length > available ) { + // Properties not complete yet. + *usedLength = 0; + return nil; + } + + // Complete -- try to create an object: + BLIPProperties *props; + if( length > sizeof(UInt16) ) + props = [[[BLIPPackedProperties alloc] initWithBytes: bytes length: length] autorelease]; + else + props = [BLIPProperties properties]; + + *usedLength = props ?length :-1; + return props; +} + + +- (id) copyWithZone: (NSZone*)zone +{ + return [self retain]; +} + +- (id) mutableCopyWithZone: (NSZone*)zone +{ + return [[BLIPMutableProperties allocWithZone: zone] initWithDictionary: self.allProperties]; +} + +- (BOOL) isEqual: (id)other +{ + return [other isKindOfClass: [BLIPProperties class]] + && [self.allProperties isEqual: [other allProperties]]; +} + +- (NSString*) valueOfProperty: (NSString*)prop {return nil;} +- (NSDictionary*) allProperties {return [NSDictionary dictionary];} +- (NSUInteger) count {return 0;} +- (NSUInteger) dataLength {return sizeof(UInt16);} + +- (NSData*) encodedData +{ + UInt16 len = 0; + return [NSData dataWithBytes: &len length: sizeof(len)]; +} + + ++ (BLIPProperties*) properties +{ + static BLIPProperties *sEmptyInstance; + if( ! sEmptyInstance ) + sEmptyInstance = [[self alloc] init]; + return sEmptyInstance; +} + + +@end + + + +/** Internal immutable subclass that keeps its contents in the packed data representation. */ +@implementation BLIPPackedProperties + + +- (id) initWithBytes: (const char*)bytes length: (size_t)length +{ + self = [super init]; + if (self != nil) { + // Copy data, then skip the length field: + _data = [[NSData alloc] initWithBytes: bytes length: length]; + bytes = (const char*)_data.bytes + sizeof(UInt16); + length -= sizeof(UInt16); + + if( bytes[length-1]!='\0' ) + goto fail; + + // The data consists of consecutive NUL-terminated strings, alternating key/value: + unsigned capacity = 0; + const char *end = bytes+length; + for( const char *str=bytes; str < end; str += strlen(str)+1, _nStrings++ ) { + if( _nStrings >= capacity ) { + capacity = capacity ?(2*capacity) :4; + _strings = realloc(_strings, capacity*sizeof(const char**)); + } + UInt8 first = (UInt8)str[0]; + if( first>'\0' && first<' ' && str[1]=='\0' ) { + // Single-control-character property string is an abbreviation: + if( first > kNAbbreviations ) + goto fail; + _strings[_nStrings] = kAbbreviations[first-1]; + } else + _strings[_nStrings] = str; + } + + // It's illegal for the data to end with a non-NUL or for there to be an odd number of strings: + if( (_nStrings & 1) ) + goto fail; + + return self; + + fail: + Warn(@"BLIPProperties: invalid data"); + [self release]; + return nil; + } + return self; +} + + +- (void) dealloc +{ + if( _strings ) free(_strings); + [_data release]; + [super dealloc]; +} + +- (id) copyWithZone: (NSZone*)zone +{ + return [self retain]; +} + +- (id) mutableCopyWithZone: (NSZone*)zone +{ + return [[BLIPMutableProperties allocWithZone: zone] initWithDictionary: self.allProperties]; +} + + +- (NSString*) valueOfProperty: (NSString*)prop +{ + const char *propStr = [prop UTF8String]; + Assert(propStr); + // Search in reverse order so that later values will take precedence over earlier ones. + for( int i=_nStrings-2; i>=0; i-=2 ) { + if( strcmp(propStr, _strings[i]) == 0 ) + return [NSString stringWithUTF8String: _strings[i+1]]; + } + return nil; +} + + +- (NSDictionary*) allProperties +{ + NSMutableDictionary *props = [NSMutableDictionary dictionaryWithCapacity: _nStrings/2]; + // Add values in forward order so that later ones will overwrite (take precedence over) + // earlier ones, which matches the behavior of -valueOfProperty. + // (However, note that unlike -valueOfProperty, this dictionary is case-sensitive!) + for( int i=0; i<_nStrings; i+=2 ) { + NSString *key = [[NSString alloc] initWithUTF8String: _strings[i]]; + NSString *value = [[NSString alloc] initWithUTF8String: _strings[i+1]]; + if( key && value ) + [props setObject: value forKey: key]; + [key release]; + [value release]; + } + return props; +} + + +- (NSUInteger) count {return _nStrings/2;} +- (NSData*) encodedData {return _data;} +- (NSUInteger) dataLength {return _data.length;} + + +@end + + + +/** Mutable subclass that stores its properties in an NSMutableDictionary. */ +@implementation BLIPMutableProperties + + ++ (BLIPProperties*) properties +{ + return [[self alloc] initWithDictionary: nil]; +} + +- (id) init +{ + self = [super init]; + if (self != nil) { + _properties = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (id) initWithDictionary: (NSDictionary*)dict +{ + self = [super init]; + if (self != nil) { + _properties = dict ?[dict mutableCopy] :[[NSMutableDictionary alloc] init]; + } + return self; +} + +- (id) initWithProperties: (BLIPProperties*)properties +{ + return [self initWithDictionary: [properties allProperties]]; +} + +- (void) dealloc +{ + [_properties release]; + [super dealloc]; +} + +- (id) copyWithZone: (NSZone*)zone +{ + ssize_t usedLength; + BLIPProperties *copy = [BLIPProperties propertiesWithEncodedData: self.encodedData usedLength: &usedLength]; + Assert(copy); + return [copy retain]; +} + + +- (NSString*) valueOfProperty: (NSString*)prop +{ + return [_properties objectForKey: prop]; +} + +- (NSDictionary*) allProperties +{ + return _properties; +} + +- (NSUInteger) count {return _properties.count;} + + +static void appendStr( NSMutableData *data, NSString *str ) { + const char *utf8 = [str UTF8String]; + size_t size = strlen(utf8)+1; + for( int i=0; i 0xFFFF ) + return nil; + *(UInt16*)[data mutableBytes] = EndianU16_NtoB((UInt16)length); + return data; +} + + +- (void) setValue: (NSString*)value ofProperty: (NSString*)prop +{ + Assert(prop.length>0); + if( value ) + [_properties setObject: value forKey: prop]; + else + [_properties removeObjectForKey: prop]; +} + + +- (void) setAllProperties: (NSDictionary*)properties +{ + if( properties.count ) { + for( id key in properties ) { + Assert([key isKindOfClass: [NSString class]]); + Assert([key length] > 0); + Assert([[properties objectForKey: key] isKindOfClass: [NSString class]]); + } + [_properties setDictionary: properties]; + } else + [_properties removeAllObjects]; +} + + +@end + + + + +TestCase(BLIPProperties) { + BLIPProperties *props; + + props = [BLIPProperties properties]; + CAssert(props); + CAssertEq(props.count,0); + Log(@"Empty properties:\n%@", props.allProperties); + NSData *data = props.encodedData; + Log(@"As data: %@", data); + CAssertEqual(data,[NSMutableData dataWithLength: 2]); + + BLIPMutableProperties *mprops = [props mutableCopy]; + Log(@"Mutable copy:\n%@", mprops.allProperties); + data = mprops.encodedData; + Log(@"As data: %@", data); + CAssertEqual(data,[NSMutableData dataWithLength: 2]); + + ssize_t used; + props = [BLIPProperties propertiesWithEncodedData: data usedLength: &used]; + CAssertEq(used,data.length); + CAssertEqual(props,mprops); + + [mprops setValue: @"Jens" ofProperty: @"First-Name"]; + [mprops setValue: @"Alfke" ofProperty: @"Last-Name"]; + [mprops setValue: @"" ofProperty: @"Empty-String"]; + [mprops setValue: @"Z" ofProperty: @"A"]; + Log(@"With properties:\n%@", mprops.allProperties); + data = mprops.encodedData; + Log(@"As data: %@", data); + + for( unsigned len=0; len. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPReader.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,27 @@ +// +// BLIPReader.h +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "TCPStream.h" +#import "BLIP_Internal.h" +@class BLIPResponse; + + +/** INTERNAL class that reads BLIP frames from the socket. */ +@interface BLIPReader : TCPReader +{ + BLIPFrameHeader _curHeader; + UInt32 _curBytesRead; + NSMutableData *_curBody; + + UInt32 _numQueriesReceived; + NSMutableDictionary *_pendingQueries, *_pendingReplies; +} + +- (void) _addPendingResponse: (BLIPResponse*)response; + +@end diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPReader.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPReader.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,263 @@ +// +// BLIPReader.m +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "BLIPReader.h" +#import "BLIP_Internal.h" +#import "BLIPWriter.h" +#import "BLIPDispatcher.h" + + +@interface BLIPReader () +- (BOOL) _receivedFrameWithHeader: (const BLIPFrameHeader*)header body: (NSData*)body; +@end + + +@implementation BLIPReader + + +#define _blipConn ((BLIPConnection*)_conn) + + +- (id) initWithConnection: (BLIPConnection*)conn stream: (NSStream*)stream +{ + self = [super initWithConnection: conn stream: stream]; + if (self != nil) { + _pendingQueries = [[NSMutableDictionary alloc] init]; + _pendingReplies = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void) dealloc +{ + [_pendingQueries release]; + [_pendingReplies release]; + [_curBody release]; + [super dealloc]; +} + +- (void) disconnect +{ + for( BLIPResponse *response in [_pendingReplies allValues] ) { + [response _connectionClosed]; + [_conn tellDelegate: @selector(connection:receivedResponse:) withObject: response]; + } + setObj(&_pendingReplies,nil); + [super disconnect]; +} + + +#pragma mark - +#pragma mark READING FRAMES: + + +- (NSString*) _validateHeader +{ + // Convert header to native byte order: + _curHeader.magic = EndianU32_BtoN(_curHeader.magic); + _curHeader.number= EndianU32_BtoN(_curHeader.number); + _curHeader.flags = EndianU16_BtoN(_curHeader.flags); + _curHeader.size = EndianU16_BtoN(_curHeader.size); + + if( _curHeader.magic != kBLIPFrameHeaderMagicNumber ) + return $sprintf(@"Incorrect magic number (%08X not %08X)", + _curHeader.magic,kBLIPFrameHeaderMagicNumber); + size_t bodyLength = _curHeader.size; + if( bodyLength < sizeof(BLIPFrameHeader) ) + return @"Length is impossibly short"; + bodyLength -= sizeof(BLIPFrameHeader); + _curBody = [[NSMutableData alloc] initWithLength: bodyLength]; + return nil; +} + + +- (void) _endCurFrame +{ + [self retain]; + [self _receivedFrameWithHeader: &_curHeader body: _curBody]; + memset(&_curHeader,0,sizeof(_curHeader)); + setObj(&_curBody,nil); + _curBytesRead = 0; + [self release]; +} + + +- (BOOL) isBusy +{ + return _curBytesRead > 0; +} + + +- (void) _canRead +{ + SInt32 headerLeft = sizeof(BLIPFrameHeader) - _curBytesRead; + if( headerLeft > 0 ) { + // Read (more of) the header: + NSInteger bytesRead = [(NSInputStream*)_stream read: (uint8_t*)&_curHeader +_curBytesRead + maxLength: headerLeft]; + if( bytesRead < 0 ) { + [self _gotError]; + } else { + _curBytesRead += bytesRead; + if( _curBytesRead < sizeof(BLIPFrameHeader) ) { + // Incomplete header: + LogTo(BLIPVerbose,@"%@ read %u bytes of header (%u left)", + self,bytesRead,sizeof(BLIPFrameHeader)-_curBytesRead); + } else { + // Finished reading the header! + headerLeft = 0; + NSString *err = [self _validateHeader]; + if( err ) { + Warn(@"%@ read bogus frame header: %@",self,err); + return (void)[self _gotError: BLIPMakeError(kBLIPError_BadData, @"%@", err)]; + } + LogTo(BLIPVerbose,@"%@: Read header; next is %u-byte body",self,_curBody.length); + + if( _curBody.length == 0 ) { + // Zero-byte body, so no need to wait for another read + [self _endCurFrame]; + } + } + } + + } else { + // Read (more of) the current frame's body: + SInt32 bodyRemaining = (SInt32)_curBody.length + headerLeft; + if( bodyRemaining > 0 ) { + uint8_t *dst = _curBody.mutableBytes; + dst += _curBody.length - bodyRemaining; + NSInteger bytesRead = [(NSInputStream*)_stream read: dst maxLength: bodyRemaining]; + if( bytesRead < 0 ) + return (void)[self _gotError]; + else if( bytesRead > 0 ) { + _curBytesRead += bytesRead; + bodyRemaining -= bytesRead; + LogTo(BLIPVerbose,@"%@: Read %u bytes of frame body (%u left)",self,bytesRead,bodyRemaining); + } + } + if( bodyRemaining==0 ) { + // Done reading this frame: give it to the Connection and reset my state + [self _endCurFrame]; + } + } +} + + +#pragma mark - +#pragma mark PROCESSING FRAMES: + + +- (void) _addPendingResponse: (BLIPResponse*)response +{ + [_pendingReplies setObject: response forKey: $object(response.number)]; +} + + +- (BOOL) _receivedFrameWithHeader: (const BLIPFrameHeader*)header body: (NSData*)body +{ + static const char* kTypeStrs[16] = {"MSG","RPY","ERR","3??","4??","5??","6??","7??"}; + BLIPMessageType type = header->flags & kBLIP_TypeMask; + LogTo(BLIPVerbose,@"%@ rcvd frame of %s #%u, length %u",self,kTypeStrs[type],header->number,body.length); + + id key = $object(header->number); + BOOL complete = ! (header->flags & kBLIP_MoreComing); + switch(type) { + case kBLIP_MSG: { + // Incoming request: + BLIPRequest *request = [_pendingQueries objectForKey: key]; + if( request ) { + // Continuation frame of a request: + if( complete ) { + [[request retain] autorelease]; + [_pendingQueries removeObjectForKey: key]; + } + } else if( header->number == _numQueriesReceived+1 ) { + // Next new request: + request = [[[BLIPRequest alloc] _initWithConnection: _blipConn + isMine: NO + flags: header->flags | kBLIP_MoreComing + number: header->number + body: nil] + autorelease]; + if( ! complete ) + [_pendingQueries setObject: request forKey: key]; + _numQueriesReceived++; + } else + return [self _gotError: BLIPMakeError(kBLIPError_BadFrame, + @"Received bad request frame #%u (next is #%u)", + header->number,_numQueriesReceived+1)]; + + if( ! [request _receivedFrameWithHeader: header body: body] ) + return [self _gotError: BLIPMakeError(kBLIPError_BadFrame, + @"Couldn't parse message frame")]; + + if( complete ) + [_blipConn _dispatchRequest: request]; + break; + } + + case kBLIP_RPY: + case kBLIP_ERR: { + BLIPResponse *response = [_pendingReplies objectForKey: key]; + if( response ) { + if( complete ) { + [[response retain] autorelease]; + [_pendingReplies removeObjectForKey: key]; + } + + if( ! [response _receivedFrameWithHeader: header body: body] ) { + return [self _gotError: BLIPMakeError(kBLIPError_BadFrame, + @"Couldn't parse response frame")]; + } else if( complete ) + [_blipConn _dispatchResponse: response]; + + } else { + if( header->number <= ((BLIPWriter*)self.writer).numQueriesSent ) + LogTo(BLIP,@"??? %@ got unexpected response frame to my msg #%u", + self,header->number); //benign + else + return [self _gotError: BLIPMakeError(kBLIPError_BadFrame, + @"Bogus message number %u in response", + header->number)]; + } + break; + } + + default: + // To leave room for future expansion, undefined message types are just ignored. + Log(@"??? %@ received header with unknown message type %i", self,type); + break; + } + return YES; +} + + +@end + + +/* + Copyright (c) 2008, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPRequest.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPRequest.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,101 @@ +// +// BLIPRequest.h +// MYNetwork +// +// Created by Jens Alfke on 5/22/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "BLIPMessage.h" +@class BLIPResponse, MYTarget; + + +/** A Request, or initiating message. */ +@interface BLIPRequest : BLIPMessage +{ + @private + BLIPResponse *_response; +} + +/** Creates an outgoing request. + The body may be nil. + The request is not associated with any BLIPConnection yet, so you must either set its + connection property before calling -send, or pass the request as a parameter to + -[BLIPConnection sendRequest:]. */ ++ (BLIPRequest*) requestWithBody: (NSData*)body; + +/** Creates an outgoing request. + The body or properties may be nil. + The request is not associated with any BLIPConnection yet, so you must either set its + connection property before calling -send, or pass the request as a parameter to + -[BLIPConnection sendRequest:]. */ ++ (BLIPRequest*) requestWithBody: (NSData*)body + properties: (NSDictionary*)properties; + +/** BLIPRequest extends the -connection property to be settable. + This allows a request to be created without a connection (i.e. before the connection is created). + It can later be sent by setting the connection property and calling -send. */ +@property (retain) BLIPConnection *connection; + +/** Sends this request over its connection. + (Actually, the connection queues it to be sent; this method always returns immediately.) + If this request has not been assigned to a connection, an exception will be raised. + Its matching response object will be returned, or nil if the request couldn't be sent. */ +- (BLIPResponse*) send; + +/** Does this request not need a response? + This property can only be set before sending the request. */ +@property BOOL noReply; + +/** Returns YES if you've replied to this request (by accessing its -response property.) */ +@property (readonly) BOOL repliedTo; + +/** The request's response. This can be accessed at any time, even before sending the request, + but the contents of the response won't be filled in until it arrives, of course. */ +@property (readonly) BLIPResponse *response; + +/** Call this when a request arrives, to indicate that you want to respond to it later. + It will prevent a default empty response from being sent upon return from the request handler. */ +- (void) deferResponse; + +/** Shortcut to respond to this request with the given data. */ +- (void) respondWithData: (NSData*)data; + +/** Shortcut to respond to this request with the given string (which will be encoded in UTF-8). */ +- (void) respondWithString: (NSString*)string; + +/** Shortcut to respond to this request with an error. */ +- (void) respondWithError: (NSError*)error; + +/** Shortcut to respond to this request with the given error code and message. + The BLIPErrorDomain is assumed. */ +- (void) respondWithErrorCode: (int)code message: (NSString*)message; //, ... __attribute__ ((format (__NSString__, 2,3)));; + +/** Shortcut to respond to this message with an error indicating that an exception occurred. */ +- (void) respondWithException: (NSException*)exception; + +@end + + + + +/** A reply to a BLIPRequest. */ +@interface BLIPResponse : BLIPMessage +{ + @private + NSError *_error; + MYTarget *_onComplete; +} + +/** Sends this response. */ +- (BOOL) send; + +/** The error returned by the peer, or nil if the response is successful. */ +@property (retain) NSError* error; + +/** Sets a target/action to be called when an incoming response is complete. + Use this on the response returned from -[BLIPRequest send], to be notified when the response is available. */ +@property (retain) MYTarget *onComplete; + + +@end diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPRequest.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPRequest.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,268 @@ +// +// BLIPRequest.m +// MYNetwork +// +// Created by Jens Alfke on 5/22/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "BLIPRequest.h" +#import "BLIP_Internal.h" +#import "BLIPWriter.h" +#import "BLIPReader.h" +#import "Target.h" +#import "ExceptionUtils.h" + + +@implementation BLIPRequest + + +- (id) _initWithConnection: (BLIPConnection*)connection + body: (NSData*)body + properties: (NSDictionary*)properties +{ + self = [self _initWithConnection: connection + isMine: YES + flags: kBLIP_MSG + number: 0 + body: body]; + if( self ) { + _isMutable = YES; + if( body ) + self.body = body; + if( properties ) + [self.mutableProperties setAllProperties: properties]; + } + return self; +} + ++ (BLIPRequest*) requestWithBody: (NSData*)body +{ + return [[[self alloc] _initWithConnection: nil body: body properties: nil] autorelease]; +} + ++ (BLIPRequest*) requestWithBody: (NSData*)body + properties: (NSDictionary*)properties +{ + return [[[self alloc] _initWithConnection: nil body: body properties: properties] autorelease]; +} + + +- (void) dealloc +{ + [_response release]; + [super dealloc]; +} + + +- (BOOL) noReply {return (_flags & kBLIP_NoReply) != 0;} +- (void) setNoReply: (BOOL)noReply {[self _setFlag: kBLIP_NoReply value: noReply];} +- (BLIPConnection*) connection {return _connection;} + +- (void) setConnection: (BLIPConnection*)conn +{ + Assert(_isMine && !_sent,@"Connection can only be set before sending"); + setObj(&_connection,conn); +} + + +- (BLIPResponse*) send +{ + Assert(_connection,@"%@ has no connection to send over",self); + Assert(!_sent,@"%@ was already sent",self); + [self _encode]; + BLIPResponse *response = self.response; + if( [(BLIPWriter*)_connection.writer sendRequest: self response: response] ) + self.sent = YES; + else + response = nil; + return response; +} + + +- (BLIPResponse*) response +{ + if( ! _response && ! self.noReply ) + _response = [[BLIPResponse alloc] _initWithRequest: self]; + return _response; +} + +- (void) deferResponse +{ + // This will allocate _response, causing -repliedTo to become YES, so BLIPConnection won't + // send an automatic empty response after the current request handler returns. + LogTo(BLIP,@"Deferring response to %@",self); + [self response]; +} + +- (BOOL) repliedTo +{ + return _response != nil; +} + +- (void) respondWithData: (NSData*)data {self.response.body = data; [self.response send];} +- (void) respondWithString: (NSString*)string {[self respondWithData: [string dataUsingEncoding: NSUTF8StringEncoding]];} +- (void) respondWithError: (NSError*)error {self.response.error = error; [self.response send];} + +- (void) respondWithErrorCode: (int)errorCode message: (NSString*)errorMessage +{ + [self respondWithError: BLIPMakeError(errorCode, @"%@",errorMessage)]; +} + +- (void) respondWithException: (NSException*)exception +{ + [self respondWithError: BLIPMakeError(kBLIPError_HandlerFailed, @"%@", exception.reason)]; +} + + +@end + + + + +#pragma mark - +@implementation BLIPResponse + +- (id) _initWithRequest: (BLIPRequest*)request +{ + Assert(request); + self = [super _initWithConnection: request.connection + isMine: !request.isMine + flags: kBLIP_RPY | kBLIP_MoreComing + number: request.number + body: nil]; + if (self != nil) { + if( _isMine ) { + _isMutable = YES; + if( request.urgent ) + _flags |= kBLIP_Urgent; + } else { + _flags |= kBLIP_MoreComing; + } + } + return self; +} + +- (void) dealloc +{ + [_error release]; + [_onComplete release]; + [super dealloc]; +} + + +- (NSError*) error +{ + if( ! (_flags & kBLIP_ERR) ) + return nil; + + NSMutableDictionary *userInfo = [[self.properties allProperties] mutableCopy]; + NSString *domain = [userInfo objectForKey: @"Error-Domain"]; + int code = [[userInfo objectForKey: @"Error-Code"] intValue]; + if( domain==nil || code==0 ) { + domain = BLIPErrorDomain; + if( code==0 ) + code = kBLIPError_Unspecified; + } + [userInfo removeObjectForKey: @"Error-Domain"]; + [userInfo removeObjectForKey: @"Error-Code"]; + return [NSError errorWithDomain: domain code: code userInfo: userInfo]; +} + +- (void) _setError: (NSError*)error +{ + _flags &= ~kBLIP_TypeMask; + if( error ) { + // Setting this stuff is a PITA because this object might be technically immutable, + // in which case the standard setters would barf if I called them. + _flags |= kBLIP_ERR; + setObj(&_body,nil); + setObj(&_mutableBody,nil); + + BLIPMutableProperties *errorProps = [self.properties mutableCopy]; + NSDictionary *userInfo = error.userInfo; + for( NSString *key in userInfo ) { + id value = $castIf(NSString,[userInfo objectForKey: key]); + if( value ) + [errorProps setValue: value ofProperty: key]; + } + [errorProps setValue: error.domain ofProperty: @"Error-Domain"]; + [errorProps setValue: $sprintf(@"%i",error.code) ofProperty: @"Error-Code"]; + setObj(&_properties,errorProps); + [errorProps release]; + + } else { + _flags |= kBLIP_RPY; + [self.mutableProperties setAllProperties: nil]; + } +} + +- (void) setError: (NSError*)error +{ + Assert(_isMine && _isMutable); + [self _setError: error]; +} + + +- (BOOL) send +{ + Assert(_connection,@"%@ has no connection to send over",self); + Assert(!_sent,@"%@ was already sent",self); + [self _encode]; + return (self.sent = [(BLIPWriter*)_connection.writer sendMessage: self]); +} + + +@synthesize onComplete=_onComplete; + + +- (void) setComplete: (BOOL)complete +{ + [super setComplete: complete]; + if( complete && _onComplete ) { + @try{ + [_onComplete invokeWithSender: self]; + }catchAndReport(@"BLIPResponse onComplete target"); + } +} + + +- (void) _connectionClosed +{ + [super _connectionClosed]; + if( !_isMine ) { + // Change incoming response to an error: + _isMutable = YES; + [_properties autorelease]; + _properties = [_properties mutableCopy]; + [self _setError: BLIPMakeError(kBLIPError_Disconnected, + @"Connection closed before response was received")]; + _isMutable = NO; + } +} + + +@end + + +/* + Copyright (c) 2008, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPTest.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPTest.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,350 @@ +// +// BLIPTest.m +// MYNetwork +// +// Created by Jens Alfke on 5/13/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#ifndef NDEBUG + + +#import "BLIPRequest.h" +#import "BLIPProperties.h" +#import "BLIPConnection.h" +#import "IPAddress.h" +#import "Target.h" + +#define HAVE_KEYCHAIN_FRAMEWORK 0 +#if HAVE_KEYCHAIN_FRAMEWORK +#import +#endif + +#define kListenerPort 46353 +#define kSendInterval 0.5 +#define kNBatchedMessages 20 +#define kUseCompression YES +#define kUrgentEvery 4 +#define kClientRequiresSSL NO +#define kClientUsesSSLCert NO +#define kListenerRequiresSSL NO +#define kListenerRequiresClientCert NO + + +static SecIdentityRef GetClientIdentity(void) { + return NULL; // Make this return a valid identity to test client-side certs +} + +static SecIdentityRef GetListenerIdentity(void) { + return NULL; // Make this return a valid identity to test client-side certs +} + + +#pragma mark - +#pragma mark CLIENT TEST: + + +@interface BLIPConnectionTester : NSObject +{ + BLIPConnection *_conn; + NSMutableDictionary *_pending; +} + +@end + + +@implementation BLIPConnectionTester + +- (id) init +{ + self = [super init]; + if (self != nil) { + Log(@"** INIT %@",self); + _pending = [[NSMutableDictionary alloc] init]; + IPAddress *addr = [[IPAddress alloc] initWithHostname: @"localhost" port: kListenerPort]; + _conn = [[BLIPConnection alloc] initToAddress: addr]; + if( ! _conn ) { + [self release]; + return nil; + } + if( kClientRequiresSSL ) { + _conn.SSLProperties = $mdict({kTCPPropertySSLAllowsAnyRoot, $true}); + if( kClientUsesSSLCert ) { + SecIdentityRef clientIdentity = GetClientIdentity(); + if( clientIdentity ) { + [_conn setSSLProperty: $array((id)clientIdentity) + forKey: kTCPPropertySSLCertificates]; + } + } + } + _conn.delegate = self; + Log(@"** Opening connection..."); + [_conn open]; + } + return self; +} + +- (void) dealloc +{ + Log(@"** %@ closing",self); + [_conn close]; + [_conn release]; + [super dealloc]; +} + +- (void) sendAMessage +{ + Log(@"** Sending another %i messages...", kNBatchedMessages); + for( int i=0; i 12 ) + q.urgent = YES; + BLIPResponse *response = [q send]; + Assert(response); + Assert(q.number>0); + Assert(response.number==q.number); + [_pending setObject: $object(size) forKey: $object(q.number)]; + response.onComplete = $target(self,responseArrived:); + } + [self performSelector: @selector(sendAMessage) withObject: nil afterDelay: kSendInterval]; +} + +- (void) responseArrived: (BLIPResponse*)response +{ + Log(@"********** called responseArrived: %@",response); +} + +- (void) connectionDidOpen: (TCPConnection*)connection +{ + Log(@"** %@ didOpen",connection); + [self sendAMessage]; +} +- (BOOL) connection: (TCPConnection*)connection authorizeSSLPeer: (SecCertificateRef)peerCert +{ +#if HAVE_KEYCHAIN_FRAMEWORK + Certificate *cert = peerCert ?[Certificate certificateWithCertificateRef: peerCert] :nil; + Log(@"** %@ authorizeSSLPeer: %@",self,cert); +#else + Log(@"** %@ authorizeSSLPeer: %@",self,peerCert); +#endif + return peerCert != nil; +} +- (void) connection: (TCPConnection*)connection failedToOpen: (NSError*)error +{ + Log(@"** %@ failedToOpen: %@",connection,error); + CFRunLoopStop(CFRunLoopGetCurrent()); +} +- (void) connectionDidClose: (TCPConnection*)connection +{ + Log(@"** %@ didClose",connection); + setObj(&_conn,nil); + [NSObject cancelPreviousPerformRequestsWithTarget: self]; + CFRunLoopStop(CFRunLoopGetCurrent()); +} +- (void) connection: (BLIPConnection*)connection receivedRequest: (BLIPRequest*)request +{ + Log(@"***** %@ received %@",connection,request); + [request respondWithData: request.body]; +} + +- (void) connection: (BLIPConnection*)connection receivedResponse: (BLIPResponse*)response +{ + Log(@"********** %@ received %@",connection,response); + NSNumber *sizeObj = [_pending objectForKey: $object(response.number)]; + + if( response.error ) + Warn(@"Got error response: %@",response.error); + else { + NSData *body = response.body; + size_t size = body.length; + Assert(size<32768); + const UInt8 *bytes = body.bytes; + for( size_t i=0; i +{ + BLIPListener *_listener; +} + +@end + + +@implementation BLIPTestListener + +- (id) init +{ + self = [super init]; + if (self != nil) { + _listener = [[BLIPListener alloc] initWithPort: kListenerPort]; + _listener.delegate = self; + _listener.pickAvailablePort = YES; + _listener.bonjourServiceType = @"_bliptest._tcp"; + if( kListenerRequiresSSL ) { + SecIdentityRef listenerIdentity = GetListenerIdentity(); + Assert(listenerIdentity); + _listener.SSLProperties = $mdict({kTCPPropertySSLCertificates, $array((id)listenerIdentity)}, + {kTCPPropertySSLAllowsAnyRoot,$true}, + {kTCPPropertySSLClientSideAuthentication, $object(kTryAuthenticate)}); + } + Assert( [_listener open] ); + Log(@"%@ is listening...",self); + } + return self; +} + +- (void) dealloc +{ + Log(@"%@ closing",self); + [_listener close]; + [_listener release]; + [super dealloc]; +} + +- (void) listener: (TCPListener*)listener didAcceptConnection: (TCPConnection*)connection +{ + Log(@"** %@ accepted %@",self,connection); + connection.delegate = self; +} + +- (void) listener: (TCPListener*)listener failedToOpen: (NSError*)error +{ + Log(@"** BLIPTestListener failed to open: %@",error); +} + +- (void) listenerDidOpen: (TCPListener*)listener {Log(@"** BLIPTestListener did open");} +- (void) listenerDidClose: (TCPListener*)listener {Log(@"** BLIPTestListener did close");} + +- (BOOL) listener: (TCPListener*)listener shouldAcceptConnectionFrom: (IPAddress*)address +{ + Log(@"** %@ shouldAcceptConnectionFrom: %@",self,address); + return YES; +} + + +- (void) connectionDidOpen: (TCPConnection*)connection +{ + Log(@"** %@ didOpen [SSL=%@]",connection,connection.actualSecurityLevel); +} +- (BOOL) connection: (TCPConnection*)connection authorizeSSLPeer: (SecCertificateRef)peerCert +{ +#if HAVE_KEYCHAIN_FRAMEWORK + Certificate *cert = peerCert ?[Certificate certificateWithCertificateRef: peerCert] :nil; + Log(@"** %@ authorizeSSLPeer: %@",connection,cert); +#else + Log(@"** %@ authorizeSSLPeer: %@",self,peerCert); +#endif + return peerCert != nil || ! kListenerRequiresClientCert; +} +- (void) connection: (TCPConnection*)connection failedToOpen: (NSError*)error +{ + Log(@"** %@ failedToOpen: %@",connection,error); +} +- (void) connectionDidClose: (TCPConnection*)connection +{ + Log(@"** %@ didClose",connection); + [connection release]; +} +- (void) connection: (BLIPConnection*)connection receivedRequest: (BLIPRequest*)request +{ + Log(@"***** %@ received %@",connection,request); + NSData *body = request.body; + size_t size = body.length; + Assert(size<32768); + const UInt8 *bytes = body.bytes; + for( size_t i=0; i. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPWriter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPWriter.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,24 @@ +// +// BLIPFrameWriter.h +// MYNetwork +// +// Created by Jens Alfke on 5/18/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "TCPWriter.h" +@class BLIPRequest, BLIPResponse, BLIPMessage; + + +@interface BLIPWriter : TCPWriter +{ + NSMutableArray *_outBox; + UInt32 _numQueriesSent; +} + +- (BOOL) sendRequest: (BLIPRequest*)request response: (BLIPResponse*)response; +- (BOOL) sendMessage: (BLIPMessage*)message; + +@property (readonly) UInt32 numQueriesSent; + +@end diff -r 000000000000 -r 9d67172bb323 BLIP/BLIPWriter.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIPWriter.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,150 @@ +// +// BLIPFrameWriter.m +// MYNetwork +// +// Created by Jens Alfke on 5/18/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "BLIPReader.h" +#import "BLIPWriter.h" +#import "BLIP_Internal.h" + + +#define kDefaultFrameSize 4096 + + +@implementation BLIPWriter + + +- (void) dealloc +{ + [_outBox release]; + [super dealloc]; +} + +- (void) disconnect +{ + [_outBox makeObjectsPerformSelector: @selector(_connectionClosed) withObject: nil]; + setObj(&_outBox,nil); + [super disconnect]; +} + +@synthesize numQueriesSent=_numQueriesSent; + + +- (BOOL) isBusy +{ + return _outBox.count>0 || [super isBusy]; +} + + +- (void) _queueMessage: (BLIPMessage*)msg isNew: (BOOL)isNew +{ + int n = _outBox.count, index; + if( msg.urgent && n > 1 ) { + // High-priority gets queued after the last existing high-priority message, + // leaving one regular-priority message in between if possible. + for( index=n-1; index>0; index-- ) { + BLIPMessage *otherMsg = [_outBox objectAtIndex: index]; + if( [otherMsg urgent] ) { + index = MIN(index+2, n); + break; + } else if( isNew && otherMsg._bytesWritten==0 ) { + // But have to keep message starts in order + index = index+1; + break; + } + } + if( index==0 ) + index = 1; + } else { + // Regular priority goes at the end of the queue: + index = n; + } + if( ! _outBox ) + _outBox = [[NSMutableArray alloc] init]; + [_outBox insertObject: msg atIndex: index]; + + if( isNew ) { + LogTo(BLIP,@"%@ queuing outgoing %@ at index %i",self,msg,index); + if( n==0 ) + [self queueIsEmpty]; + } +} + + +- (BOOL) sendMessage: (BLIPMessage*)message +{ + if( _shouldClose ) { + Warn(@"%@: Attempt to send a message after the connection has started closing",self); + return NO; + } + Assert(!message.sent,@"message has already been sent"); + [self _queueMessage: message isNew: YES]; + return YES; +} + + +- (BOOL) sendRequest: (BLIPRequest*)q response: (BLIPResponse*)response +{ + if( !_shouldClose ) { + [q _assignedNumber: ++_numQueriesSent]; + if( response ) { + [response _assignedNumber: _numQueriesSent]; + [(BLIPReader*)self.reader _addPendingResponse: response]; + } + } + return [self sendMessage: q]; +} + + +- (void) queueIsEmpty +{ + if( _outBox.count > 0 ) { + // Pop first message in queue: + BLIPMessage *msg = [[_outBox objectAtIndex: 0] retain]; + [_outBox removeObjectAtIndex: 0]; + + // As an optimization, allow message to send a big frame unless there's a higher-priority + // message right behind it: + size_t frameSize = kDefaultFrameSize; + if( msg.urgent || _outBox.count==0 || ! [[_outBox objectAtIndex: 0] urgent] ) + frameSize *= 4; + + if( [msg _writeFrameTo: self maxSize: frameSize] ) { + // add it back so it can send its next frame later: + [self _queueMessage: msg isNew: NO]; + } + [msg release]; + } else { + LogTo(BLIPVerbose,@"%@: no more work for writer",self); + } +} + + + +@end + + +/* + Copyright (c) 2008, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 BLIP/BLIP_Internal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/BLIP_Internal.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,86 @@ +// +// BLIP_Internal.h +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "BLIPConnection.h" +#import "BLIPRequest.h" +#import "BLIPProperties.h" +@class BLIPWriter; + + +/* Private declarations and APIs for BLIP implementation. Not for use by clients! */ + + +/* BLIP message types; encoded in each frame's header. */ +typedef enum { + kBLIP_MSG, // initiating message + kBLIP_RPY, // response to a MSG + kBLIP_ERR // error response to a MSG +} BLIPMessageType; + +/* Flag bits in a BLIP frame header */ +enum { + kBLIP_TypeMask = 0x000F, // bits reserved for storing message type + kBLIP_Compressed= 0x0010, // data is gzipped + kBLIP_Urgent = 0x0020, // please send sooner/faster + kBLIP_NoReply = 0x0040, // no RPY needed + kBLIP_MoreComing= 0x0080, // More frames coming (Applies only to individual frame) +}; +typedef UInt16 BLIPMessageFlags; + + +/** Header of a BLIP frame as sent across the wire. All fields are big-endian. */ +typedef struct { + UInt32 magic; // magic number (kBLIPFrameHeaderMagicNumber) + UInt32 number; // serial number of MSG + BLIPMessageFlags flags; // encodes frame type, "more" flag, and other delivery options + UInt16 size; // total size of frame, _including_ this header +} BLIPFrameHeader; + +#define kBLIPFrameHeaderMagicNumber 0x9B34F205 + + +NSError *BLIPMakeError( int errorCode, NSString *message, ... ) __attribute__ ((format (__NSString__, 2, 3))); + + +@interface BLIPConnection () +- (void) _dispatchRequest: (BLIPRequest*)request; +- (void) _dispatchResponse: (BLIPResponse*)response; +@end + + +@interface BLIPMessage () +@property BOOL sent, propertiesAvailable, complete; +- (void) _setFlag: (BLIPMessageFlags)flag value: (BOOL)value; +- (void) _encode; +@end + + +@interface BLIPMessage () +- (id) _initWithConnection: (BLIPConnection*)connection + isMine: (BOOL)isMine + flags: (BLIPMessageFlags)flags + number: (UInt32)msgNo + body: (NSData*)body; +- (BOOL) _writeFrameTo: (BLIPWriter*)writer maxSize: (UInt16)maxSize; +@property (readonly) SInt32 _bytesWritten; +- (void) _assignedNumber: (UInt32)number; +- (BOOL) _receivedFrameWithHeader: (const BLIPFrameHeader*)header body: (NSData*)body; +- (void) _connectionClosed; +@end + + +@interface BLIPRequest () +- (id) _initWithConnection: (BLIPConnection*)connection + body: (NSData*)body + properties: (NSDictionary*)properties; +@end + + +@interface BLIPResponse () +- (id) _initWithRequest: (BLIPRequest*)request; +@end diff -r 000000000000 -r 9d67172bb323 BLIP/runBLIPClient --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/runBLIPClient Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,3 @@ +#!/bin/csh + +build/Debug/MYNetwork Test_BLIPConnection -Log YES -LogBLIP YES $* diff -r 000000000000 -r 9d67172bb323 BLIP/runBLIPListener --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BLIP/runBLIPListener Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,3 @@ +#!/bin/csh + +build/Debug/MYNetwork Test_BLIPListener -Log YES -LogBLIP YES $* diff -r 000000000000 -r 9d67172bb323 IPAddress.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/IPAddress.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,112 @@ +// +// IPAddress.h +// MYNetwork +// +// Created by Jens Alfke on 1/4/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import + + +/** Represents an Internet Protocol address and port number (similar to a sockaddr_in.) + IPAddress itself only remembers the raw 32-bit IPv4 address; the subclass HostAddress + also remembers the DNS host-name. */ +@interface IPAddress : NSObject +{ + UInt32 _ipv4; // In network byte order (big-endian), just like struct in_addr + UInt16 _port; // native byte order +} + +/** Initializes an IPAddress from a host name (which may be a DNS name or dotted-quad numeric form) + and port number. + If the hostname is not in dotted-quad form, an instance of the subclass HostAddress will + be returned instead. */ +- (id) initWithHostname: (NSString*)hostname port: (UInt16)port; + +/** Initializes an IPAddress from a raw IPv4 address (in network byte order, i.e. big-endian) + and port number (in native byte order) */ +- (id) initWithIPv4: (UInt32)ipv4 port: (UInt16)port; + +/** Initializes an IPAddress from a raw IPv4 address (in network byte order, i.e. big-endian). + The port number defaults to zero. */ +- (id) initWithIPv4: (UInt32)ipv4; + +/** Initializes an IPAddress from a BSD struct sockaddr. */ +- (id) initWithSockAddr: (const struct sockaddr*)sockaddr; + +/** Returns the IP address of this host (with a socket number of zero.) + If multiple network interfaces are active, the main one's address is returned. */ ++ (IPAddress*) localAddress; + +/** Returns the address of the peer that an open socket is connected to. + (This calls getpeername.) */ ++ (IPAddress*) addressOfSocket: (CFSocketNativeHandle)socket; + +/** Returns YES if the two objects have the same IP address, ignoring port numbers. */ +- (BOOL) isSameHost: (IPAddress*)addr; + +/** The raw IPv4 address, in network (big-endian) byte order. */ +@property (readonly) UInt32 ipv4; // raw address in network byte order + +/** The address as a dotted-quad string, e.g. @"10.0.1.1". */ +@property (readonly) NSString* ipv4name; + +/** The address as a DNS hostname or else a dotted-quad string. + (IPAddress itself always returns dotted-quad; HostAddress returns the hostname it was + initialized with.) */ +@property (readonly) NSString* hostname; // dotted-quad string, or DNS name if I am a HostAddress + +/** The port number, or zero if none was specified, in native byte order. */ +@property (readonly) UInt16 port; + +/** Is this IP address in a designated private/local address range, such as 10.0.1.X? + If so, the address is not globally meaningful outside of the local subnet. */ +@property (readonly) BOOL isPrivate; // In a private/local addr range like 10.0.1.X? +@end + + + +/** A subclass of IPAddress that remembers the DNS hostname instead of a raw address. + An instance of HostAddress looks up its ipv4 address on the fly by calling gethostbyname. */ +@interface HostAddress : IPAddress +{ + NSString *_hostname; +} + +- (id) initWithHostname: (NSString*)hostname port: (UInt16)port; + +@end + + + +/** An IPAddress that can keep track of statistics on when it was last sucessfully used + and the number of successful attempts. This is useful when keeping a cache of recent + addresses for a peer that doesn't have a stable address. */ +@interface RecentAddress : IPAddress +{ + CFAbsoluteTime _lastSuccess; + UInt32 _successes; +} + +/** Initializes a RecentAddress from an IPAddress. (You can also initialize RecentAddress using + any inherited initializer method.) */ +- (id) initWithIPAddress: (IPAddress*)addr; + +/** The absolute time that -noteSuccess or -noteSeen was last called. */ +@property (readonly) CFAbsoluteTime lastSuccess; + +/** The number of times that -noteSuccess has been called. */ +@property (readonly) UInt32 successes; + +/** Call this to indicate that the address was successfully used to connect to the desired peer. + Returns YES if the state of the object has changed and it should be re-archived. */ +- (BOOL) noteSuccess; + +/** Call this to indicate that you have received evidence that this address is currently being + used by this peer. Unlike -noteSuccess it doesn't increment -successes, and only returns + YES (to indicate a persistent change) once every 18 hours (to avoid making the client + save its cache too often.) */ +- (BOOL) noteSeen; + +@end diff -r 000000000000 -r 9d67172bb323 IPAddress.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/IPAddress.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,376 @@ +// +// IPAddress.m +// MYNetwork +// +// Created by Jens Alfke on 1/4/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "IPAddress.h" +#import +#import +#import +#import +#import +#include + + +@implementation IPAddress + + ++ (UInt32) IPv4FromDottedQuadString: (NSString*)str +{ + Assert(str); + UInt32 ipv4 = 0; + NSScanner *scanner = [NSScanner scannerWithString: str]; + for( int i=0; i<4; i++ ) { + if( i>0 && ! [scanner scanString: @"." intoString: nil] ) + return 0; + NSInteger octet; + if( ! [scanner scanInteger: &octet] || octet<0 || octet>255 ) + return 0; + ipv4 = (ipv4<<8) | octet; + } + if( ! [scanner isAtEnd] ) + return 0; + return htonl(ipv4); +} + + +- (id) initWithHostname: (NSString*)hostname port: (UInt16)port +{ + Assert(hostname); + self = [super init]; + if (self != nil) { + _ipv4 = [[self class] IPv4FromDottedQuadString: hostname]; + if( ! _ipv4 ) { + [self release]; + return [[HostAddress alloc] initWithHostname: hostname port: port]; + } + _port = port; + } + return self; +} + + +- (id) initWithIPv4: (UInt32)ipv4 port: (UInt16)port +{ + self = [super init]; + if (self != nil) { + _ipv4 = ipv4; + _port = port; + } + return self; +} + +- (id) initWithIPv4: (UInt32)ipv4 +{ + return [self initWithIPv4: ipv4 port: 0]; +} + +- (id) initWithSockAddr: (const struct sockaddr*)sockaddr +{ + if( sockaddr->sa_family == AF_INET ) { + const struct sockaddr_in *addr_in = (const struct sockaddr_in*)sockaddr; + return [self initWithIPv4: addr_in->sin_addr.s_addr port: ntohs(addr_in->sin_port)]; + } else { + [self release]; + return nil; + } +} + ++ (IPAddress*) addressOfSocket: (CFSocketNativeHandle)socket +{ + uint8_t name[SOCK_MAXADDRLEN]; + socklen_t namelen = sizeof(name); + struct sockaddr *addr = (struct sockaddr*)name; + if (0 == getpeername(socket, addr, &namelen)) + return [[[self alloc] initWithSockAddr: addr] autorelease]; + else + return nil; +} + +- (id) copyWithZone: (NSZone*)zone +{ + return [self retain]; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + if( _ipv4 ) + [coder encodeInt32: _ipv4 forKey: @"ipv4"]; + if( _port ) + [coder encodeInt: _port forKey: @"port"]; +} + +- (id)initWithCoder:(NSCoder *)decoder +{ + self = [super init]; + if( self ) { + _ipv4 = [decoder decodeInt32ForKey: @"ipv4"]; + _port = [decoder decodeIntForKey: @"port"]; + } + return self; +} + + +@synthesize ipv4=_ipv4, port=_port; + +- (BOOL) isEqual: (IPAddress*)addr +{ + return [addr isKindOfClass: [IPAddress class]] && [self isSameHost: addr] && addr->_port==_port; +} + +- (BOOL) isSameHost: (IPAddress*)addr +{ + return addr && _ipv4==addr->_ipv4; +} + +- (NSUInteger) hash +{ + return _ipv4 ^ _port; +} + +- (NSString*) ipv4name +{ + UInt32 ipv4 = self.ipv4; + if( ipv4 != 0 ) { + const UInt8* b = (const UInt8*)&ipv4; + return [NSString stringWithFormat: @"%u.%u.%u.%u", + (unsigned)b[0],(unsigned)b[1],(unsigned)b[2],(unsigned)b[3]]; + } else + return nil; +} + +- (NSString*) hostname +{ + return [self ipv4name]; +} + +- (NSString*) description +{ + NSString *name = self.hostname ?: @"0.0.0.0"; + if( _port ) + name = [name stringByAppendingFormat: @":%hu",_port]; + return name; +} + + ++ (IPAddress*) localAddress +{ + // getifaddrs returns a linked list of interface entries; + // find the first active non-loopback interface with IPv4: + UInt32 address = 0; + struct ifaddrs *interfaces; + if( getifaddrs(&interfaces) == 0 ) { + struct ifaddrs *interface; + for( interface=interfaces; interface; interface=interface->ifa_next ) { + if( (interface->ifa_flags & IFF_UP) && ! (interface->ifa_flags & IFF_LOOPBACK) ) { + const struct sockaddr_in *addr = (const struct sockaddr_in*) interface->ifa_addr; + if( addr && addr->sin_family==AF_INET ) { + address = addr->sin_addr.s_addr; + break; + } + } + } + freeifaddrs(interfaces); + } + return [[[self alloc] initWithIPv4: address] autorelease]; +} + + +// Private IP address ranges. See RFC 3330. +static const struct {UInt32 mask, value;} const kPrivateRanges[] = { + {0xFF000000, 0x00000000}, // 0.x.x.x (hosts on "this" network) + {0xFF000000, 0x0A000000}, // 10.x.x.x (private address range) + {0xFF000000, 0x7F000000}, // 127.x.x.x (loopback) + {0xFFFF0000, 0xA9FE0000}, // 169.254.x.x (link-local self-configured addresses) + {0xFFF00000, 0xAC100000}, // 172.(16-31).x.x (private address range) + {0xFFFF0000, 0xC0A80000}, // 192.168.x.x (private address range) + {0,0} +}; + + +- (BOOL) isPrivate +{ + UInt32 address = ntohl(self.ipv4); + int i; + for( i=0; kPrivateRanges[i].mask; i++ ) + if( (address & kPrivateRanges[i].mask) == kPrivateRanges[i].value ) + return YES; + return NO; +} + + +@end + + + + + +@implementation HostAddress + + +- (id) initWithHostname: (NSString*)hostname port: (UInt16)port +{ + self = [super initWithIPv4: 0 port: port]; + if( self ) { + if( [hostname length]==0 ) { + [self release]; + return nil; + } + _hostname = [hostname copy]; + } + return self; +} + + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [super encodeWithCoder: coder]; + [coder encodeObject: _hostname forKey: @"host"]; +} + +- (id)initWithCoder:(NSCoder *)decoder +{ + self = [super initWithCoder: decoder]; + if( self ) { + _hostname = [[decoder decodeObjectForKey: @"host"] copy]; + } + return self; +} + +- (NSUInteger) hash +{ + return [_hostname hash] ^ _port; +} + + +- (NSString*) hostname {return _hostname;} + + +- (UInt32) ipv4 +{ + struct hostent *ent = gethostbyname(_hostname.UTF8String); + if( ! ent ) { + Log(@"HostAddress: DNS lookup failed for <%@>: %s", _hostname, hstrerror(h_errno)); + return 0; + } + return * (const in_addr_t*) ent->h_addr_list[0]; +} + + +- (BOOL) isSameHost: (IPAddress*)addr +{ + return [addr isKindOfClass: [HostAddress class]] && [_hostname caseInsensitiveCompare: addr.hostname]==0; +} + + +@end + + + + +@implementation RecentAddress + + +- (id) initWithIPAddress: (IPAddress*)addr +{ + return [super initWithIPv4: addr.ipv4 port: addr.port]; +} + + +@synthesize lastSuccess=_lastSuccess, successes=_successes; + +- (BOOL) noteSuccess +{ + if( _successes < 0xFFFF ) + _successes++; + _lastSuccess = CFAbsoluteTimeGetCurrent(); + return YES; +} + +- (BOOL) noteSeen +{ + CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); + BOOL significant = ( now-_lastSuccess >= 18*60*60 ); + _lastSuccess = now; + return significant; +} + + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [super encodeWithCoder: coder]; + [coder encodeDouble: _lastSuccess forKey: @"last"]; + [coder encodeInt: _successes forKey: @"succ"]; +} + +- (id)initWithCoder:(NSCoder *)decoder +{ + self = [super initWithCoder: decoder]; + if( self ) { + _lastSuccess = [decoder decodeDoubleForKey: @"last"]; + _successes = [decoder decodeIntForKey: @"succ"]; + } + return self; +} + + +@end + + + + + +#import "Test.h" + +TestCase(IPAddress) { + RequireTestCase(CollectionUtils); + IPAddress *addr = [[IPAddress alloc] initWithIPv4: htonl(0x0A0001FE) port: 8080]; + CAssertEq(addr.ipv4,htonl(0x0A0001FE)); + CAssertEq(addr.port,8080); + CAssertEqual(addr.hostname,@"10.0.1.254"); + CAssertEqual(addr.description,@"10.0.1.254:8080"); + CAssert(addr.isPrivate); + + addr = [[IPAddress alloc] initWithHostname: @"66.66.0.255" port: 123]; + CAssertEq(addr.class,[IPAddress class]); + CAssertEq(addr.ipv4,htonl(0x424200FF)); + CAssertEq(addr.port,123); + CAssertEqual(addr.hostname,@"66.66.0.255"); + CAssertEqual(addr.description,@"66.66.0.255:123"); + CAssert(!addr.isPrivate); + + addr = [[IPAddress alloc] initWithHostname: @"www.apple.com" port: 80]; + CAssertEq(addr.class,[HostAddress class]); + Log(@"www.apple.com = 0x%08X", addr.ipv4); + CAssertEq(addr.ipv4,htonl(0x1195A00A)); + CAssertEq(addr.port,80); + CAssertEqual(addr.hostname,@"www.apple.com"); + CAssertEqual(addr.description,@"www.apple.com:80"); + CAssert(!addr.isPrivate); +} + + +/* + Copyright (c) 2008, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 MYNetwork.xcodeproj/TemplateIcon.icns Binary file MYNetwork.xcodeproj/TemplateIcon.icns has changed diff -r 000000000000 -r 9d67172bb323 MYNetwork.xcodeproj/project.pbxproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MYNetwork.xcodeproj/project.pbxproj Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,379 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 270461130DE49030003D9D3F /* BLIPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 270460F40DE49030003D9D3F /* BLIPConnection.m */; }; + 270461140DE49030003D9D3F /* BLIPDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 270460F60DE49030003D9D3F /* BLIPDispatcher.m */; }; + 270461150DE49030003D9D3F /* BLIPMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 270460F90DE49030003D9D3F /* BLIPMessage.m */; }; + 270461160DE49030003D9D3F /* BLIPProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = 270460FB0DE49030003D9D3F /* BLIPProperties.m */; }; + 270461170DE49030003D9D3F /* BLIPReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 270460FD0DE49030003D9D3F /* BLIPReader.m */; }; + 270461180DE49030003D9D3F /* BLIPTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 270460FE0DE49030003D9D3F /* BLIPTest.m */; }; + 270461190DE49030003D9D3F /* BLIPWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 270461000DE49030003D9D3F /* BLIPWriter.m */; }; + 2704611A0DE49030003D9D3F /* IPAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = 270461020DE49030003D9D3F /* IPAddress.m */; }; + 2704611B0DE49030003D9D3F /* TCPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 2704610A0DE49030003D9D3F /* TCPConnection.m */; }; + 2704611C0DE49030003D9D3F /* TCPEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 2704610C0DE49030003D9D3F /* TCPEndpoint.m */; }; + 2704611D0DE49030003D9D3F /* TCPListener.m in Sources */ = {isa = PBXBuildFile; fileRef = 2704610E0DE49030003D9D3F /* TCPListener.m */; }; + 2704611E0DE49030003D9D3F /* TCPStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 270461100DE49030003D9D3F /* TCPStream.m */; }; + 2704611F0DE49030003D9D3F /* TCPWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 270461120DE49030003D9D3F /* TCPWriter.m */; }; + 2704612C0DE49088003D9D3F /* Test.m in Sources */ = {isa = PBXBuildFile; fileRef = 270461280DE49088003D9D3F /* Test.m */; }; + 2704612D0DE49088003D9D3F /* Logging.m in Sources */ = {isa = PBXBuildFile; fileRef = 2704612A0DE49088003D9D3F /* Logging.m */; }; + 270461370DE4918D003D9D3F /* ExceptionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 270461350DE4918D003D9D3F /* ExceptionUtils.m */; }; + 270461470DE491A6003D9D3F /* Target.m in Sources */ = {isa = PBXBuildFile; fileRef = 270461460DE491A6003D9D3F /* Target.m */; }; + 270461700DE492F3003D9D3F /* GTMNSData+zlib.m in Sources */ = {isa = PBXBuildFile; fileRef = 2704616F0DE492F3003D9D3F /* GTMNSData+zlib.m */; }; + 270461890DE49634003D9D3F /* CollectionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 270461870DE49634003D9D3F /* CollectionUtils.m */; }; + 2704618C0DE49652003D9D3F /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2704618B0DE49652003D9D3F /* libz.dylib */; }; + 270461920DE4975D003D9D3F /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 270461910DE4975C003D9D3F /* CoreServices.framework */; }; + 270462C20DE4A64B003D9D3F /* MYUtilitiesTest_main.m in Sources */ = {isa = PBXBuildFile; fileRef = 270462C10DE4A64B003D9D3F /* MYUtilitiesTest_main.m */; }; + 27D5EC070DE5FEDE00CD84FA /* BLIPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D5EC060DE5FEDE00CD84FA /* BLIPRequest.m */; }; + 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8DD76F9E0486AA7600D96B5E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 270460F30DE49030003D9D3F /* BLIPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BLIPConnection.h; sourceTree = ""; }; + 270460F40DE49030003D9D3F /* BLIPConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BLIPConnection.m; sourceTree = ""; }; + 270460F50DE49030003D9D3F /* BLIPDispatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BLIPDispatcher.h; sourceTree = ""; }; + 270460F60DE49030003D9D3F /* BLIPDispatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BLIPDispatcher.m; sourceTree = ""; }; + 270460F70DE49030003D9D3F /* BLIP_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BLIP_Internal.h; sourceTree = ""; }; + 270460F80DE49030003D9D3F /* BLIPMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BLIPMessage.h; sourceTree = ""; }; + 270460F90DE49030003D9D3F /* BLIPMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BLIPMessage.m; sourceTree = ""; }; + 270460FA0DE49030003D9D3F /* BLIPProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BLIPProperties.h; sourceTree = ""; }; + 270460FB0DE49030003D9D3F /* BLIPProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BLIPProperties.m; sourceTree = ""; }; + 270460FC0DE49030003D9D3F /* BLIPReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BLIPReader.h; sourceTree = ""; }; + 270460FD0DE49030003D9D3F /* BLIPReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BLIPReader.m; sourceTree = ""; }; + 270460FE0DE49030003D9D3F /* BLIPTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BLIPTest.m; sourceTree = ""; }; + 270460FF0DE49030003D9D3F /* BLIPWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BLIPWriter.h; sourceTree = ""; }; + 270461000DE49030003D9D3F /* BLIPWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BLIPWriter.m; sourceTree = ""; }; + 270461010DE49030003D9D3F /* IPAddress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IPAddress.h; sourceTree = ""; }; + 270461020DE49030003D9D3F /* IPAddress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IPAddress.m; sourceTree = ""; }; + 270461080DE49030003D9D3F /* TCP_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCP_Internal.h; sourceTree = ""; }; + 270461090DE49030003D9D3F /* TCPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPConnection.h; sourceTree = ""; }; + 2704610A0DE49030003D9D3F /* TCPConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCPConnection.m; sourceTree = ""; }; + 2704610B0DE49030003D9D3F /* TCPEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPEndpoint.h; sourceTree = ""; }; + 2704610C0DE49030003D9D3F /* TCPEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCPEndpoint.m; sourceTree = ""; }; + 2704610D0DE49030003D9D3F /* TCPListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPListener.h; sourceTree = ""; }; + 2704610E0DE49030003D9D3F /* TCPListener.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCPListener.m; sourceTree = ""; }; + 2704610F0DE49030003D9D3F /* TCPStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPStream.h; sourceTree = ""; }; + 270461100DE49030003D9D3F /* TCPStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCPStream.m; sourceTree = ""; }; + 270461110DE49030003D9D3F /* TCPWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPWriter.h; sourceTree = ""; }; + 270461120DE49030003D9D3F /* TCPWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCPWriter.m; sourceTree = ""; }; + 270461280DE49088003D9D3F /* Test.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Test.m; sourceTree = ""; }; + 270461290DE49088003D9D3F /* Test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Test.h; sourceTree = ""; }; + 2704612A0DE49088003D9D3F /* Logging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Logging.m; sourceTree = ""; }; + 2704612B0DE49088003D9D3F /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = ""; }; + 270461350DE4918D003D9D3F /* ExceptionUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExceptionUtils.m; sourceTree = ""; }; + 270461360DE4918D003D9D3F /* ExceptionUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExceptionUtils.h; sourceTree = ""; }; + 270461450DE491A6003D9D3F /* Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Target.h; sourceTree = ""; }; + 270461460DE491A6003D9D3F /* Target.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Target.m; sourceTree = ""; }; + 2704616E0DE492F3003D9D3F /* GTMNSData+zlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "GTMNSData+zlib.h"; path = "Foundation/GTMNSData+zlib.h"; sourceTree = ""; }; + 2704616F0DE492F3003D9D3F /* GTMNSData+zlib.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "GTMNSData+zlib.m"; path = "Foundation/GTMNSData+zlib.m"; sourceTree = ""; }; + 270461720DE49340003D9D3F /* MYNetwork */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = MYNetwork; sourceTree = BUILT_PRODUCTS_DIR; }; + 270461870DE49634003D9D3F /* CollectionUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CollectionUtils.m; sourceTree = ""; }; + 270461880DE49634003D9D3F /* CollectionUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CollectionUtils.h; sourceTree = ""; }; + 2704618B0DE49652003D9D3F /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = /usr/lib/libz.dylib; sourceTree = ""; }; + 270461910DE4975C003D9D3F /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = /System/Library/Frameworks/CoreServices.framework; sourceTree = ""; }; + 2704620E0DE4A221003D9D3F /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDefines.h; sourceTree = ""; }; + 270462C00DE4A639003D9D3F /* MYUtilities_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MYUtilities_Prefix.pch; sourceTree = ""; }; + 270462C10DE4A64B003D9D3F /* MYUtilitiesTest_main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MYUtilitiesTest_main.m; sourceTree = ""; }; + 270462C30DE4A65B003D9D3F /* BLIP Overview.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "BLIP Overview.txt"; path = "BLIP/BLIP Overview.txt"; sourceTree = ""; wrapsLines = 1; }; + 27D5EC050DE5FEDE00CD84FA /* BLIPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BLIPRequest.h; sourceTree = ""; }; + 27D5EC060DE5FEDE00CD84FA /* BLIPRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BLIPRequest.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8DD76F9B0486AA7600D96B5E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */, + 2704618C0DE49652003D9D3F /* libz.dylib in Frameworks */, + 270461920DE4975D003D9D3F /* CoreServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* MYNetwork */ = { + isa = PBXGroup; + children = ( + 270462C30DE4A65B003D9D3F /* BLIP Overview.txt */, + 270460F00DE49030003D9D3F /* MYNetwork */, + 270461220DE49055003D9D3F /* MYUtilities */, + 2704616D0DE492C9003D9D3F /* google-toolbox */, + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + ); + name = MYNetwork; + sourceTree = ""; + }; + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { + isa = PBXGroup; + children = ( + 270461910DE4975C003D9D3F /* CoreServices.framework */, + 08FB779EFE84155DC02AAC07 /* Foundation.framework */, + 2704618B0DE49652003D9D3F /* libz.dylib */, + ); + name = "External Frameworks and Libraries"; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 270461720DE49340003D9D3F /* MYNetwork */, + ); + name = Products; + sourceTree = ""; + }; + 270460F00DE49030003D9D3F /* MYNetwork */ = { + isa = PBXGroup; + children = ( + 270461010DE49030003D9D3F /* IPAddress.h */, + 270461020DE49030003D9D3F /* IPAddress.m */, + 270461070DE49030003D9D3F /* TCP */, + 270460F10DE49030003D9D3F /* BLIP */, + ); + name = MYNetwork; + sourceTree = ""; + }; + 270460F10DE49030003D9D3F /* BLIP */ = { + isa = PBXGroup; + children = ( + 270460F30DE49030003D9D3F /* BLIPConnection.h */, + 270460F40DE49030003D9D3F /* BLIPConnection.m */, + 270460F50DE49030003D9D3F /* BLIPDispatcher.h */, + 270460F60DE49030003D9D3F /* BLIPDispatcher.m */, + 270460F80DE49030003D9D3F /* BLIPMessage.h */, + 270460F90DE49030003D9D3F /* BLIPMessage.m */, + 27D5EC050DE5FEDE00CD84FA /* BLIPRequest.h */, + 27D5EC060DE5FEDE00CD84FA /* BLIPRequest.m */, + 270460FA0DE49030003D9D3F /* BLIPProperties.h */, + 270460FB0DE49030003D9D3F /* BLIPProperties.m */, + 270460FC0DE49030003D9D3F /* BLIPReader.h */, + 270460FD0DE49030003D9D3F /* BLIPReader.m */, + 270460FF0DE49030003D9D3F /* BLIPWriter.h */, + 270461000DE49030003D9D3F /* BLIPWriter.m */, + 270460FE0DE49030003D9D3F /* BLIPTest.m */, + 270460F70DE49030003D9D3F /* BLIP_Internal.h */, + ); + path = BLIP; + sourceTree = ""; + }; + 270461070DE49030003D9D3F /* TCP */ = { + isa = PBXGroup; + children = ( + 270461090DE49030003D9D3F /* TCPConnection.h */, + 2704610A0DE49030003D9D3F /* TCPConnection.m */, + 2704610B0DE49030003D9D3F /* TCPEndpoint.h */, + 2704610C0DE49030003D9D3F /* TCPEndpoint.m */, + 2704610D0DE49030003D9D3F /* TCPListener.h */, + 2704610E0DE49030003D9D3F /* TCPListener.m */, + 2704610F0DE49030003D9D3F /* TCPStream.h */, + 270461100DE49030003D9D3F /* TCPStream.m */, + 270461110DE49030003D9D3F /* TCPWriter.h */, + 270461120DE49030003D9D3F /* TCPWriter.m */, + 270461080DE49030003D9D3F /* TCP_Internal.h */, + ); + path = TCP; + sourceTree = ""; + }; + 270461220DE49055003D9D3F /* MYUtilities */ = { + isa = PBXGroup; + children = ( + 270462C10DE4A64B003D9D3F /* MYUtilitiesTest_main.m */, + 270462C00DE4A639003D9D3F /* MYUtilities_Prefix.pch */, + 270461880DE49634003D9D3F /* CollectionUtils.h */, + 270461870DE49634003D9D3F /* CollectionUtils.m */, + 270461360DE4918D003D9D3F /* ExceptionUtils.h */, + 270461350DE4918D003D9D3F /* ExceptionUtils.m */, + 2704612B0DE49088003D9D3F /* Logging.h */, + 2704612A0DE49088003D9D3F /* Logging.m */, + 270461450DE491A6003D9D3F /* Target.h */, + 270461460DE491A6003D9D3F /* Target.m */, + 270461290DE49088003D9D3F /* Test.h */, + 270461280DE49088003D9D3F /* Test.m */, + ); + name = MYUtilities; + path = ../MYUtilities; + sourceTree = ""; + }; + 2704616D0DE492C9003D9D3F /* google-toolbox */ = { + isa = PBXGroup; + children = ( + 2704620E0DE4A221003D9D3F /* GTMDefines.h */, + 2704616E0DE492F3003D9D3F /* GTMNSData+zlib.h */, + 2704616F0DE492F3003D9D3F /* GTMNSData+zlib.m */, + ); + name = "google-toolbox"; + sourceTree = "google-toolbox"; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8DD76F960486AA7600D96B5E /* MYNetwork */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "MYNetwork" */; + buildPhases = ( + 8DD76F990486AA7600D96B5E /* Sources */, + 8DD76F9B0486AA7600D96B5E /* Frameworks */, + 8DD76F9E0486AA7600D96B5E /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MYNetwork; + productInstallPath = "$(HOME)/bin"; + productName = MYNetwork; + productReference = 270461720DE49340003D9D3F /* MYNetwork */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "MYNetwork" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* MYNetwork */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8DD76F960486AA7600D96B5E /* MYNetwork */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 8DD76F990486AA7600D96B5E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 270461130DE49030003D9D3F /* BLIPConnection.m in Sources */, + 270461140DE49030003D9D3F /* BLIPDispatcher.m in Sources */, + 270461150DE49030003D9D3F /* BLIPMessage.m in Sources */, + 270461160DE49030003D9D3F /* BLIPProperties.m in Sources */, + 270461170DE49030003D9D3F /* BLIPReader.m in Sources */, + 270461180DE49030003D9D3F /* BLIPTest.m in Sources */, + 270461190DE49030003D9D3F /* BLIPWriter.m in Sources */, + 2704611A0DE49030003D9D3F /* IPAddress.m in Sources */, + 2704611B0DE49030003D9D3F /* TCPConnection.m in Sources */, + 2704611C0DE49030003D9D3F /* TCPEndpoint.m in Sources */, + 2704611D0DE49030003D9D3F /* TCPListener.m in Sources */, + 2704611E0DE49030003D9D3F /* TCPStream.m in Sources */, + 2704611F0DE49030003D9D3F /* TCPWriter.m in Sources */, + 2704612C0DE49088003D9D3F /* Test.m in Sources */, + 2704612D0DE49088003D9D3F /* Logging.m in Sources */, + 270461370DE4918D003D9D3F /* ExceptionUtils.m in Sources */, + 270461470DE491A6003D9D3F /* Target.m in Sources */, + 270461700DE492F3003D9D3F /* GTMNSData+zlib.m in Sources */, + 270461890DE49634003D9D3F /* CollectionUtils.m in Sources */, + 270462C20DE4A64B003D9D3F /* MYUtilitiesTest_main.m in Sources */, + 27D5EC070DE5FEDE00CD84FA /* BLIPRequest.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB927508733DD40010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = ../MYUtilities/MYUtilities_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = DEBUG; + INSTALL_PATH = /usr/local/bin; + PRODUCT_NAME = MYNetwork; + }; + name = Debug; + }; + 1DEB927608733DD40010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = MYNetwork_Prefix.pch; + INSTALL_PATH = /usr/local/bin; + PRODUCT_NAME = MYNetwork; + }; + name = Release; + }; + 1DEB927908733DD40010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; + PREBINDING = NO; + SDKROOT = macosx10.5; + }; + name = Debug; + }; + 1DEB927A08733DD40010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = macosx10.5; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "MYNetwork" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB927508733DD40010E9CD /* Debug */, + 1DEB927608733DD40010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "MYNetwork" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB927908733DD40010E9CD /* Debug */, + 1DEB927A08733DD40010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff -r 000000000000 -r 9d67172bb323 TCP/TCPConnection.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TCP/TCPConnection.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,124 @@ +// +// TCPConnection.h +// MYNetwork +// +// Created by Jens Alfke on 5/18/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "TCPEndpoint.h" +@class IPAddress; +@class TCPReader, TCPWriter, TCPListener; +@protocol TCPConnectionDelegate; + + +typedef enum { + kTCP_Disconnected = -1, + kTCP_Closed, + kTCP_Opening, + kTCP_Open, + kTCP_Closing +} TCPConnectionStatus; + + +/** A generic class that manages a TCP socket connection. + It creates a TCPReader and a TCPWriter to handle I/O. + TCPConnection itself mostly deals with SSL setup and opening/closing the socket. */ +@interface TCPConnection : TCPEndpoint +{ + @private + TCPListener *_server; + IPAddress *_address; + BOOL _isIncoming, _checkedPeerCert; + TCPConnectionStatus _status; + TCPReader *_reader; + TCPWriter *_writer; + NSError *_error; +} + +/** Initializes a TCPConnection to the given IP address. + Afer configuring settings, you should call -open to begin the connection. */ +- (id) initToAddress: (IPAddress*)address; + +/** Initializes a TCPConnection to the given IP address, binding to a specific outgoing port + number. (This is usually only necessary when attempting to tunnel through a NAT.) */ +- (id) initToAddress: (IPAddress*)address + localPort: (UInt16)localPort; + +/** Initializes a TCPConnection from an incoming TCP socket. + You don't usually need to call this; TCPListener does it automatically. */ +- (id) initIncomingFromSocket: (CFSocketNativeHandle)socket listener: (TCPListener*)listener; + +/** The delegate object that will be called when the connection opens, closes or receives messages. */ +@property (assign) id delegate; + +/** The certificate(s) of the connected peer, if this connection uses SSL. + The items in the array are SecCertificateRefs; use the Keychain API to work with them. */ +@property (readonly) NSArray *peerSSLCerts; + +/** Connection's current status */ +@property (readonly) TCPConnectionStatus status; + +/** Opens the connection. This happens asynchronously; wait for a delegate method to be called. + You don't need to open incoming connections received via a TCPListener. */ +- (void) open; + +/** Closes the connection, after waiting for all in-progress messages to be sent or received. + This happens asynchronously; wait for a delegate method to be called.*/ +- (void) close; + +/** Closes the connection, like -close, but if it hasn't closed by the time the timeout + expires, it will disconnect the socket. */ +- (void) closeWithTimeout: (NSTimeInterval)timeout; + +/** Closes all open TCPConnections. */ ++ (void) closeAllWithTimeout: (NSTimeInterval)timeout; + +/** Blocks until all open TCPConnections close. You should call +closeAllWithTimeout: first. */ ++ (void) waitTillAllClosed; + +/** The IP address of the other peer. */ +@property (readonly) IPAddress *address; + +/** The TCPListener that created this incoming connection, or nil */ +@property (readonly) TCPListener *server; + +/** Is this an incoming connection, received via a TCPListener? */ +@property (readonly) BOOL isIncoming; + +/** The fatal error, if any, + that caused the connection to fail to open or to disconnect unexpectedly. */ +@property (readonly) NSError *error; + +/** The actual security level of this connection. + Value is nil or one of the security level constants from NSStream.h, + such as NSStreamSocketSecurityLevelTLSv1. */ +@property (readonly) NSString* actualSecurityLevel; + + +@property (readonly) TCPReader *reader; +@property (readonly) TCPWriter *writer; + + +// protected: +- (Class) readerClass; +- (Class) writerClass; + +@end + + + +/** The delegate messages sent by TCPConnection. */ +@protocol TCPConnectionDelegate +@optional +/** Called after the connection successfully opens. */ +- (void) connectionDidOpen: (TCPConnection*)connection; +/** Called after the connection fails to open due to an error. */ +- (void) connection: (TCPConnection*)connection failedToOpen: (NSError*)error; +/** Called when the identity of the peer is known, if using an SSL connection and the SSL + settings say to check the peer's certificate. + This happens, if at all, after the -connectionDidOpen: call. */ +- (BOOL) connection: (TCPConnection*)connection authorizeSSLPeer: (SecCertificateRef)peerCert; +/** Called after the connection closes. */ +- (void) connectionDidClose: (TCPConnection*)connection; +@end diff -r 000000000000 -r 9d67172bb323 TCP/TCPConnection.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TCP/TCPConnection.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,365 @@ +// +// TCPConnection.m +// MYNetwork +// +// Created by Jens Alfke on 5/18/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "TCP_Internal.h" +#import "IPAddress.h" + +#import "ExceptionUtils.h" + + +NSString* const TCPErrorDomain = @"TCP"; + + +@interface TCPConnection () +@property TCPConnectionStatus status; +- (BOOL) _checkIfClosed; +- (void) _closed; +@end + + +@implementation TCPConnection + + +static NSMutableArray *sAllConnections; + + +- (Class) readerClass {return [TCPReader class];} +- (Class) writerClass {return [TCPWriter class];} + + +- (id) _initWithAddress: (IPAddress*)address + inputStream: (NSInputStream*)input + outputStream: (NSOutputStream*)output +{ + self = [super init]; + if (self != nil) { + if( !address || !input || !output ) { + LogTo(TCP,@"Failed to create %@: addr=%@, in=%@, out=%@", + self.class,address,input,output); + [self release]; + return nil; + } + _address = [address copy]; + _reader = [[[self readerClass] alloc] initWithConnection: self stream: input]; + _writer = [[[self writerClass] alloc] initWithConnection: self stream: output]; + LogTo(TCP,@"%@ initialized",self); + } + return self; +} + + + +- (id) initToAddress: (IPAddress*)address + localPort: (UInt16)localPort +{ + NSInputStream *input = nil; + NSOutputStream *output = nil; + [NSStream getStreamsToHost: [NSHost hostWithAddress: address.ipv4name] + port: address.port + inputStream: &input + outputStream: &output]; + return [self _initWithAddress: address inputStream: input outputStream: output]; + //FIX: Support localPort! +} + +- (id) initToAddress: (IPAddress*)address +{ + return [self initToAddress: address localPort: 0]; +} + + +- (id) initIncomingFromSocket: (CFSocketNativeHandle)socket + listener: (TCPListener*)listener +{ + CFReadStreamRef readStream = NULL; + CFWriteStreamRef writeStream = NULL; + CFStreamCreatePairWithSocket(kCFAllocatorDefault, socket, &readStream, &writeStream); + self = [self _initWithAddress: [IPAddress addressOfSocket: socket] + inputStream: (NSInputStream*)readStream + outputStream: (NSOutputStream*)writeStream]; + if( self ) { + _isIncoming = YES; + _server = [listener retain]; + CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + } + return self; +} + + +- (void) dealloc +{ + LogTo(TCP,@"DEALLOC %@",self); + [_reader release]; + [_writer release]; + [_address release]; + [super dealloc]; +} + + +- (NSString*) description +{ + return $sprintf(@"%@[%@ %@]",self.class,(_isIncoming ?@"from" :@"to"),_address); +} + + +@synthesize address=_address, isIncoming=_isIncoming, status=_status, delegate=_delegate, + reader=_reader, writer=_writer, server=_server; + + +- (NSError*) error +{ + return _error; +} + + +- (NSString*) actualSecurityLevel +{ + return _reader.securityLevel; + +} + +- (NSArray*) peerSSLCerts +{ + return _reader.peerSSLCerts ?: _writer.peerSSLCerts; +} + + +- (void) _setStreamProperty: (id)value forKey: (NSString*)key +{ + [_reader setProperty: value forKey: (CFStringRef)key]; + [_writer setProperty: value forKey: (CFStringRef)key]; +} + + +#pragma mark - +#pragma mark OPENING / CLOSING: + + +- (void) open +{ + if( _status<=kTCP_Closed && _reader ) { + _reader.SSLProperties = _sslProperties; + _writer.SSLProperties = _sslProperties; + [_reader open]; + [_writer open]; + if( ! [sAllConnections my_containsObjectIdenticalTo: self] ) + [sAllConnections addObject: self]; + self.status = kTCP_Opening; + } +} + + +- (void) disconnect +{ + if( _status > kTCP_Closed ) { + LogTo(TCP,@"%@ disconnecting",self); + [_writer disconnect]; + setObj(&_writer,nil); + [_reader disconnect]; + setObj(&_reader,nil); + self.status = kTCP_Disconnected; + } +} + + +- (void) close +{ + [self closeWithTimeout: 60.0]; +} + +- (void) closeWithTimeout: (NSTimeInterval)timeout +{ + if( _status == kTCP_Opening ) { + LogTo(TCP,@"%@ canceling open",self); + [self _closed]; + } else if( _status == kTCP_Open ) { + LogTo(TCP,@"%@ closing",self); + self.status = kTCP_Closing; + [self retain]; + [_reader close]; + [_writer close]; + if( ! [self _checkIfClosed] ) { + if( timeout <= 0.0 ) + [self disconnect]; + else if( timeout != INFINITY ) + [self performSelector: @selector(_closeTimeoutExpired) + withObject: nil afterDelay: timeout]; + } + [self release]; + } +} + +- (void) _closeTimeoutExpired +{ + if( _status==kTCP_Closing ) + [self disconnect]; +} + + +- (BOOL) _checkIfClosed +{ + if( _status == kTCP_Closing && _writer==nil && _reader==nil ) { + [self _closed]; + return YES; + } else + return NO; +} + + +// called by my streams when they close (after my -close is called) +- (void) _closed +{ + if( _status != kTCP_Closed && _status != kTCP_Disconnected ) { + LogTo(TCP,@"%@ is now closed",self); + self.status = (_status==kTCP_Closing ?kTCP_Closed :kTCP_Disconnected); + [self tellDelegate: @selector(connectionDidClose:) withObject: nil]; + } + [NSObject cancelPreviousPerformRequestsWithTarget: self + selector: @selector(_closeTimeoutExpired) + object: nil]; + [sAllConnections removeObjectIdenticalTo: self]; +} + + ++ (void) closeAllWithTimeout: (NSTimeInterval)timeout +{ + NSArray *connections = [sAllConnections copy]; + for( TCPConnection *conn in connections ) + [conn closeWithTimeout: timeout]; + [connections release]; +} + ++ (void) waitTillAllClosed +{ + while( sAllConnections.count ) { + if( ! [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode + beforeDate: [NSDate distantFuture]] ) + break; + } +} + + +#pragma mark - +#pragma mark STREAM CALLBACKS: + + +- (void) _streamOpened: (TCPStream*)stream +{ + if( _status==kTCP_Opening && _reader.isOpen && _writer.isOpen ) { + LogTo(TCP,@"%@ opened",self); + self.status = kTCP_Open; + [self tellDelegate: @selector(connectionDidOpen:) withObject: nil]; + } +} + + +- (BOOL) _streamPeerCertAvailable: (TCPStream*)stream +{ + BOOL allow = YES; + if( ! _checkedPeerCert ) { + @try{ + _checkedPeerCert = YES; + if( stream.securityLevel != nil ) { + NSArray *certs = stream.peerSSLCerts; + if( ! certs && ! _isIncoming ) + allow = NO; // Server MUST have a cert! + else { + SecCertificateRef cert = certs.count ?(SecCertificateRef)[certs objectAtIndex:0] :NULL; + LogTo(TCP,@"%@: Peer cert = %@",self,cert); + if( [_delegate respondsToSelector: @selector(connection:authorizeSSLPeer:)] ) + allow = [_delegate connection: self authorizeSSLPeer: cert]; + } + } + }@catch( NSException *x ) { + MYReportException(x,@"TCPConnection _streamPeerCertAvailable"); + _checkedPeerCert = NO; + allow = NO; + } + if( ! allow ) + [self _stream: stream + gotError: [NSError errorWithDomain: NSStreamSocketSSLErrorDomain + code: errSSLClosedAbort + userInfo: nil]]; + } + return allow; +} + + +- (void) _stream: (TCPStream*)stream gotError: (NSError*)error +{ + LogTo(TCP,@"%@ got %@ on %@",self,error,stream.class); + Assert(error); + setObj(&_error,error); + [_reader disconnect]; + setObj(&_reader,nil); + [_writer disconnect]; + setObj(&_writer,nil); + [self _closed]; +} + +- (void) _streamGotEOF: (TCPStream*)stream +{ + LogTo(TCP,@"%@ got EOF on %@",self,stream); + if( stream == _reader ) { + setObj(&_reader,nil); + // This is the expected way for he peer to initiate closing the connection. + if( _status==kTCP_Open ) { + [self closeWithTimeout: INFINITY]; + return; + } + } else if( stream == _writer ) { + setObj(&_writer,nil); + } + + if( _status == kTCP_Closing ) { + [self _checkIfClosed]; + } else { + [self _stream: stream + gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]]; + } +} + + +// Called after I called -close on a stream and it finished closing: +- (void) _streamClosed: (TCPStream*)stream +{ + LogTo(TCP,@"%@ finished closing %@",self,stream); + if( stream == _reader ) + setObj(&_reader,nil); + else if( stream == _writer ) + setObj(&_writer,nil); + if( !_reader.isOpen && !_writer.isOpen ) + [self _closed]; +} + + +@end + + +/* + Copyright (c) 2008, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 TCP/TCPEndpoint.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TCP/TCPEndpoint.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,42 @@ +// +// TCPEndpoint.h +// MYNetwork +// +// Created by Jens Alfke on 5/14/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import +#import + + +// SSL properties: +#define kTCPPropertySSLCertificates ((NSString*)kCFStreamSSLCertificates) +#define kTCPPropertySSLAllowsAnyRoot ((NSString*)kCFStreamSSLAllowsAnyRoot) +extern NSString* const kTCPPropertySSLClientSideAuthentication; // value is SSLAuthenticate enum + + +/** Abstract base class of TCPConnection and TCPListener. + Mostly just manages the SSL properties. */ +@interface TCPEndpoint : NSObject +{ + NSMutableDictionary *_sslProperties; + id _delegate; +} + +/** The desired security level. Use the security level constants from NSStream.h, + such as NSStreamSocketSecurityLevelNegotiatedSSL. */ +@property (copy) NSString *securityLevel; + +/** Detailed SSL settings. This is the same as CFStream's kCFStreamPropertySSLSettings + property. */ +@property (copy) NSMutableDictionary *SSLProperties; + +/** Shortcut to set a single SSL property. */ +- (void) setSSLProperty: (id)value + forKey: (NSString*)key; + +//protected: +- (void) tellDelegate: (SEL)selector withObject: (id)param; + +@end diff -r 000000000000 -r 9d67172bb323 TCP/TCPEndpoint.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TCP/TCPEndpoint.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,84 @@ +// +// BLIPEndpoint.m +// MYNetwork +// +// Created by Jens Alfke on 5/14/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "TCPEndpoint.h" + +#import "ExceptionUtils.h" + + +NSString* const kTCPPropertySSLClientSideAuthentication = @"kTCPPropertySSLClientSideAuthentication"; + + +@implementation TCPEndpoint + + +- (void) dealloc +{ + [_sslProperties release]; + [super dealloc]; +} + + +- (NSMutableDictionary*) SSLProperties {return _sslProperties;} + +- (void) setSSLProperties: (NSMutableDictionary*)props +{ + if( props != _sslProperties ) { + [_sslProperties release]; + _sslProperties = [props mutableCopy]; + } +} + +- (void) setSSLProperty: (id)value forKey: (NSString*)key +{ + if( value ) { + if( ! _sslProperties ) + _sslProperties = [[NSMutableDictionary alloc] init]; + [_sslProperties setObject: value forKey: key]; + } else + [_sslProperties removeObjectForKey: key]; +} + +- (NSString*) securityLevel {return [_sslProperties objectForKey: (id)kCFStreamSSLLevel];} +- (void) setSecurityLevel: (NSString*)level {[self setSSLProperty: level forKey: (id)kCFStreamSSLLevel];} + + +- (void) tellDelegate: (SEL)selector withObject: (id)param +{ + if( [_delegate respondsToSelector: selector] ) { + @try{ + [_delegate performSelector: selector withObject: self withObject: param]; + }catchAndReport(@"%@ delegate",self.class); + } +} + + +@end + + +/* + Copyright (c) 2008, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 TCP/TCPListener.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TCP/TCPListener.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,107 @@ +// +// TCPListener.m +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. + +#import "TCPEndpoint.h" +@class TCPConnection, IPAddress; +@protocol TCPListenerDelegate; + + +/** Generic TCP-based server that listens for incoming connections on a port. + For each incoming connection, it creates an instance of (a subclass of) the generic TCP + client class TCPClient. The -connectionClass property lets you customize which subclass + to use. + TCPListener supports Bonjour advertisements for the service, and automatic port renumbering + if there are conflicts. */ +@interface TCPListener : TCPEndpoint +{ + @private + uint16_t _port; + BOOL _pickAvailablePort; + BOOL _useIPv6; + CFSocketRef _ipv4socket; + CFSocketRef _ipv6socket; + + NSString *_bonjourServiceType, *_bonjourServiceName; + NSNetService *_netService; + NSDictionary *_bonjourTXTRecord; + BOOL _bonjourPublished; + NSInteger /*NSNetServicesError*/ _bonjourError; + + Class _connectionClass; +} + +/** Initializes a new TCPListener that will listen on the given port when opened. */ +- (id) initWithPort: (UInt16)port; + +/** The subclass of TCPConnection that will be instantiated. */ +@property Class connectionClass; + +@property (assign) id delegate; + +/** Should the server listen for IPv6 connections (on the same port number)? Defaults to NO. */ +@property BOOL useIPv6; + +/** The port number to listen on. + If the pickAvailablePort property is enabled, this value may be updated after the server opens + to reflect the actual port number being used. */ +@property uint16_t port; + +/** Should the server pick a higher port number if the desired port is already in use? + Defaults to NO. If enabled, the port number will be incremented until a free port is found. */ +@property BOOL pickAvailablePort; + +/** Opens the server. You must call this after configuring all desired properties (property + changes are ignored while the server is open.) */ +- (BOOL) open: (NSError **)error; + +- (BOOL) open; + +/** Closes the server. */ +- (void) close; + +/** Is the server currently open? */ +@property (readonly) BOOL isOpen; + + +#pragma mark BONJOUR: + +/** The Bonjour service type to advertise. Defaults to nil; setting it implicitly enables Bonjour. + The value should look like e.g. "_http._tcp."; for details, see the NSNetService documentation. */ +@property (copy) NSString *bonjourServiceType; + +/** The Bonjour service name to advertise. Defaults to nil, meaning that a default name will be + automatically generated if Bonjour is enabled (by setting -bonjourServiceType). */ +@property (copy) NSString *bonjourServiceName; + +/** The dictionary form of the Bonjour TXT record: metadata about the service that can be browsed + by peers. Changes to this dictionary will be pushed in near-real-time to interested peers. */ +@property (copy) NSDictionary *bonjourTXTRecord; + +/** Is this service currently published/advertised via Bonjour? */ +@property (readonly) BOOL bonjourPublished; + +/** Current error status of Bonjour service advertising. See NSNetServicesError for error codes. */ +@property (readonly) NSInteger /*NSNetServicesError*/ bonjourError; + + +@end + + + +#pragma mark - + +/** The delegate messages sent by TCPListener. */ +@protocol TCPListenerDelegate + +- (void) listener: (TCPListener*)listener didAcceptConnection: (TCPConnection*)connection; + +@optional +- (void) listenerDidOpen: (TCPListener*)listener; +- (void) listener: (TCPListener*)listener failedToOpen: (NSError*)error; +- (void) listenerDidClose: (TCPListener*)listener; +- (BOOL) listener: (TCPListener*)listener shouldAcceptConnectionFrom: (IPAddress*)address; +@end diff -r 000000000000 -r 9d67172bb323 TCP/TCPListener.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TCP/TCPListener.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,342 @@ +// +// TCPListener.m +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// Portions based on TCPServer class from Apple's "CocoaEcho" sample code. + +#import "TCPListener.h" +#import "TCPConnection.h" + +#import "ExceptionUtils.h" +#import "IPAddress.h" +#include + +#include +#include + + +static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, + CFDataRef address, const void *data, void *info); + +@interface TCPListener() +@property BOOL bonjourPublished; +@property NSInteger bonjourError; +- (void) _updateTXTRecord; +@end + + +@implementation TCPListener + + +- (id) initWithPort: (UInt16)port +{ + self = [super init]; + if (self != nil) { + _port = port; + } + return self; +} + + +- (void) dealloc +{ + [self close]; + LogTo(TCP,@"DEALLOC %@",self); + [super dealloc]; +} + + +@synthesize delegate=_delegate, port=_port, useIPv6=_useIPv6, + bonjourServiceType=_bonjourServiceType, bonjourServiceName=_bonjourServiceName, + bonjourPublished=_bonjourPublished, bonjourError=_bonjourError, + pickAvailablePort=_pickAvailablePort; + + +- (NSString*) description +{ + return $sprintf(@"%@[port %hu]",self.class,_port); +} + + +// Stores the last error from CFSocketCreate or CFSocketSetAddress into *ouError. +static void* getLastCFSocketError( NSError **outError ) { + if( outError ) + *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil]; + return NULL; +} + +// Closes a socket (if it's not already NULL), and returns NULL to assign to it. +static CFSocketRef closeSocket( CFSocketRef socket ) { + if( socket ) { + CFSocketInvalidate(socket); + CFRelease(socket); + } + return NULL; +} + +// opens a socket of a given protocol, either ipv4 or ipv6. +- (CFSocketRef) _openProtocol: (SInt32) protocolFamily + address: (struct sockaddr*)address + error: (NSError**)error +{ + CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL}; + CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, + kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt); + if( ! socket ) + return getLastCFSocketError(error); // CFSocketCreate leaves error code in errno + + int yes = 1; + setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)); + + NSData *addressData = [NSData dataWithBytes:address length:address->sa_len]; + if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) { + getLastCFSocketError(error); + return closeSocket(socket); + } + // set up the run loop source for the socket + CFRunLoopRef cfrl = CFRunLoopGetCurrent(); + CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0); + CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes); + CFRelease(source); + return socket; +} + +- (BOOL) _failedToOpen: (NSError*)error +{ + LogTo(TCP,@"%@ failed to open: %@",self,error); + [self tellDelegate: @selector(listener:failedToOpen:) withObject: error]; + return NO; +} + + +- (BOOL) open: (NSError**)outError +{ + // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us + do{ + struct sockaddr_in addr4; + memset(&addr4, 0, sizeof(addr4)); + addr4.sin_len = sizeof(addr4); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(_port); + addr4.sin_addr.s_addr = htonl(INADDR_ANY); + + NSError *error; + _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error]; + if( ! _ipv4socket ) { + if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) { + LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1); + self.port++; // try the next port + } else { + if( outError ) *outError = error; + return [self _failedToOpen: error]; + } + } + }while( ! _ipv4socket ); + + if (0 == _port) { + // now that the binding was successful, we get the port number + NSData *addr = [(NSData *)CFSocketCopyAddress(_ipv4socket) autorelease]; + const struct sockaddr_in *addr4 = addr.bytes; + self.port = ntohs(addr4->sin_port); + } + + if( _useIPv6 ) { + // set up the IPv6 endpoint + struct sockaddr_in6 addr6; + memset(&addr6, 0, sizeof(addr6)); + addr6.sin6_len = sizeof(addr6); + addr6.sin6_family = AF_INET6; + addr6.sin6_port = htons(_port); + memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr)); + + _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: outError]; + if( ! _ipv6socket ) { + _ipv4socket = closeSocket(_ipv4socket); + return [self _failedToOpen: *outError]; + } + } + + // Open Bonjour: + if( _bonjourServiceType && !_netService) { + // instantiate the NSNetService object that will advertise on our behalf. + _netService = [[NSNetService alloc] initWithDomain: @"local." + type: _bonjourServiceType + name: _bonjourServiceName ?:@"" + port: _port]; + if( _netService ) { + [_netService setDelegate:self]; + if( _bonjourTXTRecord ) + [self _updateTXTRecord]; + [_netService publish]; + } else { + self.bonjourError = -1; + Warn(@"%@: Failed to create NSNetService",self); + } + } + + LogTo(TCP,@"%@ is open",self); + [self tellDelegate: @selector(listenerDidOpen:) withObject: nil]; + return YES; +} + +- (BOOL) open +{ + return [self open: nil]; +} + + +- (void) close +{ + if( _ipv4socket ) { + if( _netService ) { + [_netService stop]; + [_netService release]; + _netService = nil; + self.bonjourPublished = NO; + } + self.bonjourError = 0; + + _ipv4socket = closeSocket(_ipv4socket); + _ipv6socket = closeSocket(_ipv6socket); + + LogTo(BLIP,@"%@ is closed",self); + [self tellDelegate: @selector(listenerDidClose:) withObject: nil]; + } +} + + +- (BOOL) isOpen +{ + return _ipv4socket != NULL; +} + + +#pragma mark - +#pragma mark ACCEPTING CONNECTIONS: + + +@synthesize connectionClass = _connectionClass; + + +- (BOOL) acceptConnection: (CFSocketNativeHandle)socket +{ + IPAddress *addr = [IPAddress addressOfSocket: socket]; + if( ! addr ) + return NO; + if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)] + && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] ) + return NO; + + Assert(_connectionClass); + TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket + listener: self]; + if( ! conn ) + return NO; + + if( _sslProperties ) { + conn.SSLProperties = _sslProperties; + [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer]; + } + [conn open]; + [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn]; + return YES; +} + + +static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) +{ + TCPListener *server = (TCPListener *)info; + if (kCFSocketAcceptCallBack == type) { + CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data; + BOOL accepted = NO; + @try{ + accepted = [server acceptConnection: nativeSocketHandle]; + }catchAndReport(@"TCPListenerAcceptCallBack"); + if( ! accepted ) + close(nativeSocketHandle); + } +} + + +#pragma mark - +#pragma mark BONJOUR: + + +- (NSDictionary*) bonjourTXTRecord +{ + return _bonjourTXTRecord; +} + +- (void) setBonjourTXTRecord: (NSDictionary*)txt +{ + if( ifSetObj(&_bonjourTXTRecord,txt) ) + [self _updateTXTRecord]; +} + +- (void) _updateTXTRecord +{ + if( _netService ) { + NSData *data; + if( _bonjourTXTRecord ) { + data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord]; + if( data ) + LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length); + else + Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord); + } else + data = nil; + [_netService setTXTRecordData: data]; + } +} + + +- (void)netServiceWillPublish:(NSNetService *)sender +{ + LogTo(BLIP,@"%@: Advertising %@",self,sender); + self.bonjourPublished = YES; +} + +- (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict +{ + self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue]; + LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError); + [_netService release]; + _netService = nil; +} + +- (void)netServiceDidStop:(NSNetService *)sender +{ + LogTo(BLIP,@"%@: Stopped advertising %@",self,sender); + self.bonjourPublished = NO; + [_netService release]; + _netService = nil; +} + + +@end + + + +/* + Copyright (c) 2008, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 TCP/TCPStream.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TCP/TCPStream.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,85 @@ +// +// TCPStream.h +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import +@class TCPConnection, TCPWriter; + + +/** INTERNAL abstract superclass for data streams, used by TCPConnection. */ +@interface TCPStream : NSObject +{ + TCPConnection *_conn; + NSStream *_stream; + BOOL _shouldClose; +} + +- (id) initWithConnection: (TCPConnection*)conn stream: (NSStream*)stream; + +/** The connection's security level as reported by the underlying CFStream. */ +@property (readonly) NSString *securityLevel; + +/** The SSL property dictionary for the CFStream. */ +@property (copy) NSDictionary* SSLProperties; + +/** The SSL certificate(s) of the peer, if any. */ +@property (readonly) NSArray *peerSSLCerts; + +/** Opens the stream. */ +- (void) open; + +/** Disconnects abruptly. */ +- (void) disconnect; + +/** Closes the stream politely, waiting until there's no data pending. */ +- (BOOL) close; + +/** Is the stream open? */ +@property (readonly) BOOL isOpen; + +/** Does the stream have pending data to read or write, that prevents it from closing? */ +@property (readonly) BOOL isBusy; + +/** Generic accessor for CFStream/NSStream properties. */ +- (id) propertyForKey: (CFStringRef)cfStreamProperty; + +/** Generic accessor for CFStream/NSStream properties. */ +- (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty; + +@end + + +/** Input stream for a TCPConnection. */ +@interface TCPReader : TCPStream +/** The connection's TCPWriter. */ +@property (readonly) TCPWriter *writer; +@end + + + +@interface TCPStream (Protected) +/** Called when the stream opens. */ +- (void) _opened; + +/** Called when the stream has bytes available to read. */ +- (void) _canRead; + +/** Called when the stream has space available in its output buffer to write to. */ +- (void) _canWrite; + +/** Called when the underlying stream closes due to the socket closing. */ +- (void) _gotEOF; + +/** Call this if a read/write call returns -1 to report an error; + it will look up the error from the NSStream and call gotError: with it. + This method always returns NO, so you can "return [self _gotError]". */ +- (BOOL) _gotError; + +/** Signals a fatal error to the TCPConnection. + This method always returns NO, so you can "return [self _gotError: e]". */ +- (BOOL) _gotError: (NSError*)error; +@end diff -r 000000000000 -r 9d67172bb323 TCP/TCPStream.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TCP/TCPStream.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,290 @@ +// +// TCPStream.m +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "TCPStream.h" +#import "TCP_Internal.h" + + +extern const CFStringRef _kCFStreamPropertySSLClientSideAuthentication; // in CFNetwork + +static NSError* fixStreamError( NSError *error ); + + +@implementation TCPStream + + +- (id) initWithConnection: (TCPConnection*)conn stream: (NSStream*)stream +{ + self = [super init]; + if (self != nil) { + _conn = [conn retain]; + _stream = [stream retain]; + _stream.delegate = self; + [_stream scheduleInRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes]; + LogTo(TCPVerbose,@"%@ initialized; status=%i", self,_stream.streamStatus); + } + return self; +} + + +- (void) dealloc +{ + LogTo(TCP,@"DEALLOC %@",self); + if( _stream ) + [self disconnect]; + [super dealloc]; +} + + +- (id) propertyForKey: (CFStringRef)cfStreamProperty +{ + return nil; // abstract -- overridden by TCPReader and TCPWriter +} + +- (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty +{ // abstract -- overridden by TCPReader and TCPWriter +} + + +#pragma mark - +#pragma mark SSL: + + +- (NSString*) securityLevel {return [_stream propertyForKey: NSStreamSocketSecurityLevelKey];} + +- (NSDictionary*) SSLProperties {return [self propertyForKey: kCFStreamPropertySSLSettings];} + +- (void) setSSLProperties: (NSDictionary*)p +{ + LogTo(TCPVerbose,@"%@ SSL settings := %@",self,p); + [self setProperty: p forKey: kCFStreamPropertySSLSettings]; + + id clientAuth = [p objectForKey: kTCPPropertySSLClientSideAuthentication]; + if( clientAuth ) + [self setProperty: clientAuth forKey: _kCFStreamPropertySSLClientSideAuthentication]; +} + +- (NSArray*) peerSSLCerts +{ + Assert(self.isOpen); + return [self propertyForKey: kCFStreamPropertySSLPeerCertificates]; +} + + +#pragma mark - +#pragma mark OPENING/CLOSING: + + +- (void) open +{ + Assert(_stream); + AssertEq(_stream.streamStatus,NSStreamStatusNotOpen); + LogTo(TCP,@"Opening %@",self); + [_stream open]; +} + + +- (void) disconnect +{ + if( _stream ) { + LogTo(TCP,@"Disconnect %@",self); + _stream.delegate = nil; + [_stream close]; + setObj(&_stream,nil); + } + setObj(&_conn,nil); +} + + +- (BOOL) close +{ + if( self.isBusy ) { + _shouldClose = YES; + return NO; + } else { + LogTo(TCP,@"Closing %@",self); + [[self retain] autorelease]; // don't let myself be dealloced in the midst of this + [_conn _streamClosed: self]; // have to do this before disconnect + [self disconnect]; + return YES; + } +} + + +- (BOOL) isOpen +{ + NSStreamStatus status = _stream.streamStatus; + return status >= NSStreamStatusOpen && status < NSStreamStatusAtEnd; +} + +- (BOOL) isBusy +{ + return NO; // abstract +} + + +- (void) _opened +{ + [_conn _streamOpened: self]; +} + +- (void) _canRead +{ + // abstract +} + +- (void) _canWrite +{ + // abstract +} + +- (void) _gotEOF +{ + if( self.isBusy ) + [self _gotError: [NSError errorWithDomain: NSPOSIXErrorDomain code: ECONNRESET userInfo: nil]]; + else { + [self retain]; + [_conn _streamGotEOF: self]; + [self disconnect]; + [self release]; + } +} + +- (BOOL) _gotError: (NSError*)error +{ + [_conn _stream: self gotError: fixStreamError(error)]; + return NO; +} + +- (BOOL) _gotError +{ + NSError *error = _stream.streamError; + if( ! error ) + error = [NSError errorWithDomain: NSPOSIXErrorDomain code: EIO userInfo: nil]; //fallback + return [self _gotError: error]; +} + + +- (void) stream: (NSStream*)stream handleEvent: (NSStreamEvent)streamEvent +{ + [[self retain] autorelease]; + switch(streamEvent) { + case NSStreamEventOpenCompleted: + LogTo(TCPVerbose,@"%@ opened",self); + [self _opened]; + break; + case NSStreamEventHasBytesAvailable: + if( ! [_conn _streamPeerCertAvailable: self] ) + return; + LogTo(TCPVerbose,@"%@ can read",self); + [self _canRead]; + break; + case NSStreamEventHasSpaceAvailable: + if( ! [_conn _streamPeerCertAvailable: self] ) + return; + LogTo(TCPVerbose,@"%@ can write",self); + [self _canWrite]; + break; + case NSStreamEventErrorOccurred: + LogTo(TCPVerbose,@"%@ got error",self); + [self _gotError]; + break; + case NSStreamEventEndEncountered: + LogTo(TCPVerbose,@"%@ got EOF",self); + [self _gotEOF]; + break; + default: + Warn(@"%@: unknown NSStreamEvent %i",self,streamEvent); + break; + } + + // If I was previously asked to close, try again in case I'm no longer busy + if( _shouldClose ) + [self close]; +} + + +@end + + + + +@implementation TCPReader + + +- (TCPWriter*) writer +{ + return _conn.writer; +} + + +- (id) propertyForKey: (CFStringRef)cfStreamProperty +{ + CFTypeRef value = CFReadStreamCopyProperty((CFReadStreamRef)_stream,cfStreamProperty); + return [(id)CFMakeCollectable(value) autorelease]; +} + +- (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty +{ + if( ! CFReadStreamSetProperty((CFReadStreamRef)_stream,cfStreamProperty,(CFTypeRef)value) ) + Warn(@"%@ didn't accept property '%@'", self,cfStreamProperty); +} + + +@end + + + + +static NSError* fixStreamError( NSError *error ) +{ + // NSStream incorrectly returns SSL errors without the correct error domain: + if( $equal(error.domain,@"NSUnknownErrorDomain") ) { + int code = error.code; + if( -9899 <= code && code <= -9800 ) { + NSMutableDictionary *userInfo = error.userInfo.mutableCopy; + if( ! [userInfo objectForKey: NSLocalizedFailureReasonErrorKey] ) { + // look up error message: + NSBundle *secBundle = [NSBundle bundleWithPath: @"/System/Library/Frameworks/Security.framework"]; + NSString *message = [secBundle localizedStringForKey: $sprintf(@"%i",code) + value: nil + table: @"SecErrorMessages"]; + if( message ) { + if( ! userInfo ) userInfo = $mdict(); + [userInfo setObject: message forKey: NSLocalizedFailureReasonErrorKey]; + } + } + error = [NSError errorWithDomain: NSStreamSocketSSLErrorDomain + code: code userInfo: userInfo]; + } else + Warn(@"NSStream returned error with unknown domain: %@",error); + } + return error; +} + +/* + Copyright (c) 2008, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 TCP/TCPWriter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TCP/TCPWriter.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,34 @@ +// +// TCPWriter.h +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "TCPStream.h" + + +/** INTERNAL class that writes a queue of arbitrary data blobs to the socket. */ +@interface TCPWriter : TCPStream +{ + NSMutableArray *_queue; + NSData *_currentData; + SInt32 _currentDataPos; +} + +/** The connection's TCPReader. */ +@property (readonly) TCPReader *reader; + +/** Schedules data to be written to the socket. + Always returns immediately; the bytes won't actually be sent until there's room. */ +- (void) writeData: (NSData*)data; + +//protected: + +/** Will be called when the internal queue of data to be written is empty. + Subclasses should override this and call -writeData: to refill the queue, + if possible. */ +- (void) queueIsEmpty; + +@end diff -r 000000000000 -r 9d67172bb323 TCP/TCPWriter.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TCP/TCPWriter.m Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,115 @@ +// +// TCPWriter.m +// MYNetwork +// +// Created by Jens Alfke on 5/10/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + +#import "TCPWriter.h" +#import "TCP_Internal.h" + + +@implementation TCPWriter + + +- (void) dealloc +{ + [_queue release]; + [_currentData release]; + [super dealloc]; +} + + +- (TCPReader*) reader +{ + return _conn.reader; +} + + +- (id) propertyForKey: (CFStringRef)cfStreamProperty +{ + CFTypeRef value = CFWriteStreamCopyProperty((CFWriteStreamRef)_stream,cfStreamProperty); + return [(id)CFMakeCollectable(value) autorelease]; +} + +- (void) setProperty: (id)value forKey: (CFStringRef)cfStreamProperty +{ + if( ! CFWriteStreamSetProperty((CFWriteStreamRef)_stream,cfStreamProperty,(CFTypeRef)value) ) + Warn(@"%@ didn't accept property '%@'", self,cfStreamProperty); +} + + +- (BOOL) isBusy +{ + return _currentData || _queue.count > 0; +} + + +- (void) writeData: (NSData*)data +{ + if( !_queue ) + _queue = [[NSMutableArray alloc] init]; + [_queue addObject: data]; + if( _queue.count==1 && ((NSOutputStream*)_stream).hasSpaceAvailable ) + [self _canWrite]; +} + + +- (void) _canWrite +{ + if( ! _currentData ) { + if( _queue.count==0 ) { + [self queueIsEmpty]; // this may call -writeData, which will call _canWrite again + return; + } + _currentData = [[_queue objectAtIndex: 0] retain]; + _currentDataPos = 0; + [_queue removeObjectAtIndex: 0]; + } + + const uint8_t* src = _currentData.bytes; + src += _currentDataPos; + NSInteger len = _currentData.length - _currentDataPos; + NSInteger written = [(NSOutputStream*)_stream write: src maxLength: len]; + if( written < 0 ) + [self _gotError]; + else if( written < len ) { + LogTo(TCPVerbose,@"%@ wrote %i bytes (of %u)", self,written,len); + _currentDataPos += written; + } else { + LogTo(TCPVerbose,@"%@ wrote %i bytes", self,written); + setObj(&_currentData,nil); + } +} + + +- (void) queueIsEmpty +{ +} + + +@end + + +/* + Copyright (c) 2008, Jens Alfke . All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted + provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff -r 000000000000 -r 9d67172bb323 TCP/TCP_Internal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TCP/TCP_Internal.h Fri May 23 17:37:36 2008 -0700 @@ -0,0 +1,25 @@ +// +// TCP_Internal.h +// MYNetwork +// +// Created by Jens Alfke on 5/18/08. +// Copyright 2008 Jens Alfke. All rights reserved. +// + + +#import "TCPWriter.h" +#import "TCPConnection.h" +#import "TCPListener.h" + +/* Private declarations and APIs for TCP client/server implementation. */ + + + +@interface TCPConnection () +- (void) _setStreamProperty: (id)value forKey: (NSString*)key; +- (void) _streamOpened: (TCPStream*)stream; +- (BOOL) _streamPeerCertAvailable: (TCPStream*)stream; +- (void) _stream: (TCPStream*)stream gotError: (NSError*)error; +- (void) _streamGotEOF: (TCPStream*)stream; +- (void) _streamClosed: (TCPStream*)stream; +@end