jens@0: //
jens@0: // BLIPMessage.m
jens@0: // MYNetwork
jens@0: //
jens@0: // Created by Jens Alfke on 5/10/08.
jens@0: // Copyright 2008 Jens Alfke. All rights reserved.
jens@0: //
jens@0:
jens@0: #import "BLIPMessage.h"
jens@0: #import "BLIP_Internal.h"
jens@0: #import "BLIPReader.h"
jens@0: #import "BLIPWriter.h"
jens@0:
jens@1: #import "Logging.h"
jens@1: #import "Test.h"
jens@0: #import "ExceptionUtils.h"
jens@0: #import "Target.h"
jens@0:
jens@0: // From Google Toolbox For Mac
jens@0: #import "GTMNSData+zlib.h"
jens@0:
jens@0:
jens@0: @implementation BLIPMessage
jens@0:
jens@0:
jens@0: - (id) _initWithConnection: (BLIPConnection*)connection
jens@0: isMine: (BOOL)isMine
jens@0: flags: (BLIPMessageFlags)flags
jens@0: number: (UInt32)msgNo
jens@0: body: (NSData*)body
jens@0: {
jens@0: self = [super init];
jens@0: if (self != nil) {
jens@0: _connection = [connection retain];
jens@0: _isMine = isMine;
jens@0: _flags = flags;
jens@0: _number = msgNo;
jens@0: if( isMine ) {
jens@0: _body = body.copy;
jens@0: _properties = [[BLIPMutableProperties alloc] init];
jens@0: _propertiesAvailable = YES;
jens@0: } else {
jens@0: _encodedBody = body.mutableCopy;
jens@0: }
jens@0: LogTo(BLIPVerbose,@"INIT %@",self);
jens@0: }
jens@0: return self;
jens@0: }
jens@0:
jens@0: - (void) dealloc
jens@0: {
jens@0: LogTo(BLIPVerbose,@"DEALLOC %@",self);
jens@0: [_properties release];
jens@0: [_encodedBody release];
jens@0: [_mutableBody release];
jens@0: [_body release];
jens@0: [_connection release];
jens@0: [super dealloc];
jens@0: }
jens@0:
jens@0:
jens@0: - (NSString*) description
jens@0: {
jens@0: NSUInteger length = (_body.length ?: _mutableBody.length) ?: _encodedBody.length;
jens@0: NSMutableString *desc = [NSMutableString stringWithFormat: @"%@[#%u, %u bytes",
jens@0: self.class,_number, length];
jens@0: if( _flags & kBLIP_Compressed ) {
jens@0: if( _encodedBody && _encodedBody.length != length )
jens@0: [desc appendFormat: @" (%u gzipped)", _encodedBody.length];
jens@0: else
jens@0: [desc appendString: @", gzipped"];
jens@0: }
jens@0: if( _flags & kBLIP_Urgent )
jens@0: [desc appendString: @", urgent"];
jens@0: if( _flags & kBLIP_NoReply )
jens@0: [desc appendString: @", noreply"];
jens@0: [desc appendString: @"]"];
jens@0: return desc;
jens@0: }
jens@0:
jens@0: - (NSString*) descriptionWithProperties
jens@0: {
jens@0: NSMutableString *desc = (NSMutableString*)self.description;
jens@0: [desc appendFormat: @" %@", self.properties.allProperties];
jens@0: return desc;
jens@0: }
jens@0:
jens@0:
jens@0: #pragma mark -
jens@0: #pragma mark PROPERTIES & METADATA:
jens@0:
jens@0:
jens@0: @synthesize connection=_connection, number=_number, isMine=_isMine, isMutable=_isMutable,
jens@0: _bytesWritten, sent=_sent, propertiesAvailable=_propertiesAvailable, complete=_complete;
jens@0:
jens@0:
jens@0: - (void) _setFlag: (BLIPMessageFlags)flag value: (BOOL)value
jens@0: {
jens@0: Assert(_isMine && _isMutable);
jens@0: if( value )
jens@0: _flags |= flag;
jens@0: else
jens@0: _flags &= ~flag;
jens@0: }
jens@0:
jens@0: - (BOOL) compressed {return (_flags & kBLIP_Compressed) != 0;}
jens@0: - (BOOL) urgent {return (_flags & kBLIP_Urgent) != 0;}
jens@0: - (void) setCompressed: (BOOL)compressed {[self _setFlag: kBLIP_Compressed value: compressed];}
jens@0: - (void) setUrgent: (BOOL)high {[self _setFlag: kBLIP_Urgent value: high];}
jens@0:
jens@0:
jens@0: - (NSData*) body
jens@0: {
jens@0: if( ! _body && _isMine )
jens@0: return [[_mutableBody copy] autorelease];
jens@0: else
jens@0: return _body;
jens@0: }
jens@0:
jens@0: - (void) setBody: (NSData*)body
jens@0: {
jens@0: Assert(_isMine && _isMutable);
jens@0: if( _mutableBody )
jens@0: [_mutableBody setData: body];
jens@0: else
jens@0: _mutableBody = [body mutableCopy];
jens@0: }
jens@0:
jens@0: - (void) _addToBody: (NSData*)data
jens@0: {
jens@0: if( data.length ) {
jens@0: if( _mutableBody )
jens@0: [_mutableBody appendData: data];
jens@0: else
jens@0: _mutableBody = [data mutableCopy];
jens@0: setObj(&_body,nil);
jens@0: }
jens@0: }
jens@0:
jens@0: - (void) addToBody: (NSData*)data
jens@0: {
jens@0: Assert(_isMine && _isMutable);
jens@0: [self _addToBody: data];
jens@0: }
jens@0:
jens@0:
jens@3: - (NSString*) bodyString
jens@3: {
jens@3: NSData *body = self.body;
jens@3: if( body )
jens@3: return [[[NSString alloc] initWithData: body encoding: NSUTF8StringEncoding] autorelease];
jens@3: else
jens@3: return nil;
jens@3: }
jens@3:
jens@3: - (void) setBodyString: (NSString*)string
jens@3: {
jens@3: self.body = [string dataUsingEncoding: NSUTF8StringEncoding];
jens@3: self.contentType = @"text/plain; charset=UTF-8";
jens@3: }
jens@3:
jens@3:
jens@0: - (BLIPProperties*) properties
jens@0: {
jens@0: return _properties;
jens@0: }
jens@0:
jens@0: - (BLIPMutableProperties*) mutableProperties
jens@0: {
jens@0: Assert(_isMine && _isMutable);
jens@0: return (BLIPMutableProperties*)_properties;
jens@0: }
jens@0:
jens@0: - (NSString*) valueOfProperty: (NSString*)property
jens@0: {
jens@0: return [_properties valueOfProperty: property];
jens@0: }
jens@0:
jens@0: - (void) setValue: (NSString*)value ofProperty: (NSString*)property
jens@0: {
jens@0: [self.mutableProperties setValue: value ofProperty: property];
jens@0: }
jens@0:
jens@0: - (NSString*) contentType {return [_properties valueOfProperty: @"Content-Type"];}
jens@0: - (void) setContentType: (NSString*)t {[self setValue: t ofProperty: @"Content-Type"];}
jens@0: - (NSString*) profile {return [_properties valueOfProperty: @"Profile"];}
jens@0: - (void) setProfile: (NSString*)p {[self setValue: p ofProperty: @"Profile"];}
jens@0:
jens@0:
jens@0: #pragma mark -
jens@0: #pragma mark I/O:
jens@0:
jens@0:
jens@0: - (void) _encode
jens@0: {
jens@0: Assert(_isMine && _isMutable);
jens@0: _isMutable = NO;
jens@0:
jens@0: BLIPProperties *oldProps = _properties;
jens@0: _properties = [oldProps copy];
jens@0: [oldProps release];
jens@0:
jens@0: _encodedBody = [_properties.encodedData mutableCopy];
jens@0: Assert(_encodedBody.length>=2);
jens@0:
jens@0: NSData *body = _body ?: _mutableBody;
jens@0: NSUInteger length = body.length;
jens@0: if( length > 0 ) {
jens@0: if( self.compressed ) {
jens@0: body = [NSData gtm_dataByGzippingData: body compressionLevel: 5];
jens@0: LogTo(BLIPVerbose,@"Compressed %@ to %u bytes (%.0f%%)", self,body.length,
jens@0: body.length*100.0/length);
jens@0: }
jens@0: [_encodedBody appendData: body];
jens@0: }
jens@0: }
jens@0:
jens@0:
jens@0: - (void) _assignedNumber: (UInt32)number
jens@0: {
jens@0: Assert(_number==0,@"%@ has already been sent",self);
jens@0: _number = number;
jens@0: _isMutable = NO;
jens@0: }
jens@0:
jens@0:
jens@0: - (BOOL) _writeFrameTo: (BLIPWriter*)writer maxSize: (UInt16)maxSize
jens@0: {
jens@0: Assert(_number!=0);
jens@0: Assert(_isMine);
jens@0: Assert(_encodedBody);
jens@0: if( _bytesWritten==0 )
jens@0: LogTo(BLIP,@"Now sending %@",self);
jens@0: ssize_t lengthToWrite = _encodedBody.length - _bytesWritten;
jens@0: if( lengthToWrite <= 0 && _bytesWritten > 0 )
jens@0: return NO; // done
jens@0: Assert(maxSize > sizeof(BLIPFrameHeader));
jens@0: maxSize -= sizeof(BLIPFrameHeader);
jens@0: UInt16 flags = _flags;
jens@0: if( lengthToWrite > maxSize ) {
jens@0: lengthToWrite = maxSize;
jens@0: flags |= kBLIP_MoreComing;
jens@0: LogTo(BLIPVerbose,@"%@ pushing frame, bytes %u-%u", self, _bytesWritten, _bytesWritten+lengthToWrite);
jens@0: } else {
jens@0: flags &= ~kBLIP_MoreComing;
jens@0: LogTo(BLIPVerbose,@"%@ pushing frame, bytes %u-%u (finished)", self, _bytesWritten, _bytesWritten+lengthToWrite);
jens@0: }
jens@0:
jens@0: // First write the frame header:
jens@0: BLIPFrameHeader header = { EndianU32_NtoB(kBLIPFrameHeaderMagicNumber),
jens@0: EndianU32_NtoB(_number),
jens@0: EndianU16_NtoB(flags),
jens@0: EndianU16_NtoB(sizeof(BLIPFrameHeader) + lengthToWrite) };
jens@0:
jens@0: [writer writeData: [NSData dataWithBytes: &header length: sizeof(header)]];
jens@0:
jens@0: // Then write the body:
jens@0: if( lengthToWrite > 0 ) {
jens@0: [writer writeData: [NSData dataWithBytesNoCopy: (UInt8*)_encodedBody.bytes + _bytesWritten
jens@0: length: lengthToWrite
jens@0: freeWhenDone: NO]];
jens@0: _bytesWritten += lengthToWrite;
jens@0: }
jens@0: return (flags & kBLIP_MoreComing) != 0;
jens@0: }
jens@0:
jens@0:
jens@0: - (BOOL) _receivedFrameWithHeader: (const BLIPFrameHeader*)header body: (NSData*)body
jens@0: {
jens@0: Assert(!_isMine);
jens@0: AssertEq(header->number,_number);
jens@0: Assert(_flags & kBLIP_MoreComing);
jens@0:
jens@0: BLIPMessageType frameType = (header->flags & kBLIP_TypeMask), curType = (_flags & kBLIP_TypeMask);
jens@0: if( frameType != curType ) {
jens@0: Assert(curType==kBLIP_RPY && frameType==kBLIP_ERR && _mutableBody.length==0,
jens@0: @"Incoming frame's type %i doesn't match %@",frameType,self);
jens@0: _flags = (_flags & ~kBLIP_TypeMask) | frameType;
jens@0: }
jens@0:
jens@0: if( _encodedBody )
jens@0: [_encodedBody appendData: body];
jens@0: else
jens@0: _encodedBody = [body mutableCopy];
jens@0: LogTo(BLIPVerbose,@"%@ rcvd bytes %u-%u", self, _encodedBody.length-body.length, _encodedBody.length);
jens@0:
jens@0: if( ! _properties ) {
jens@0: // Try to extract the properties:
jens@0: ssize_t usedLength;
jens@0: setObj(&_properties, [BLIPProperties propertiesWithEncodedData: _encodedBody usedLength: &usedLength]);
jens@0: if( _properties ) {
jens@0: [_encodedBody replaceBytesInRange: NSMakeRange(0,usedLength)
jens@0: withBytes: NULL length: 0];
jens@0: } else if( usedLength < 0 )
jens@0: return NO;
jens@0: self.propertiesAvailable = YES;
jens@0: }
jens@0:
jens@0: if( ! (header->flags & kBLIP_MoreComing) ) {
jens@0: // After last frame, decode the data:
jens@0: _flags &= ~kBLIP_MoreComing;
jens@0: if( ! _properties )
jens@0: return NO;
jens@0: unsigned encodedLength = _encodedBody.length;
jens@0: if( self.compressed && encodedLength>0 ) {
jens@0: _body = [[NSData gtm_dataByInflatingData: _encodedBody] copy];
jens@0: if( ! _body )
jens@0: return NO;
jens@0: LogTo(BLIPVerbose,@"Uncompressed %@ from %u bytes (%.1fx)", self, encodedLength,
jens@0: _body.length/(float)encodedLength);
jens@0: } else {
jens@0: _body = [_encodedBody copy];
jens@0: }
jens@0: setObj(&_encodedBody,nil);
jens@0: self.propertiesAvailable = self.complete = YES;
jens@0: }
jens@0: return YES;
jens@0: }
jens@0:
jens@0:
jens@0: - (void) _connectionClosed
jens@0: {
jens@0: if( _isMine ) {
jens@0: _bytesWritten = 0;
jens@0: _flags |= kBLIP_MoreComing;
jens@0: }
jens@0: }
jens@0:
jens@0:
jens@0: @end
jens@0:
jens@0:
jens@0: /*
jens@0: Copyright (c) 2008, Jens Alfke . All rights reserved.
jens@0:
jens@0: Redistribution and use in source and binary forms, with or without modification, are permitted
jens@0: provided that the following conditions are met:
jens@0:
jens@0: * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@0: and the following disclaimer.
jens@0: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@0: and the following disclaimer in the documentation and/or other materials provided with the
jens@0: distribution.
jens@0:
jens@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@0: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
jens@0: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@0: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@0: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
jens@0: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
jens@0: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
jens@0: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@0: */