/*=--------------------------------------------------------------------------=* * NetBuffer.cpp -- Implementation of the BNetBuffer class. * * Written by S.T. Mansfield (thephantom@mac.com) * * Remarks: * * This is basically a fancy-schmancy FIFO stack manager. Thusly, it is * considered an error if data is not popped off a particular instance's * stack in the order it was pushed on. * * We could possibly implement this class as a thin wrapper around the * BMessage class, but I'm not sure what kind of payload the latter class * brings to the party, so we'll do our own stack management. We also * cannot be sure that BMessage behaves as described above down the road. * * Never use hot wax to soothe enraged lobsters. *=--------------------------------------------------------------------------=* * Copyright (c) 2002, The OpenBeOS project. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *=--------------------------------------------------------------------------=* */ #include #include #include #include /* * Parametric ctor initialization list defaults. */ #define CTOR_INIT_LIST \ BArchivable( ), \ m_init( B_NO_INIT ), \ fData( NULL ), \ fDataSize( 0 ), \ fStackSize( 0 ), \ fCapacity( 0 ) /* * Markers that surround a record in an instance's stack. */ #define DATA_START_MARKER 0x01c0ffee #define DATA_END_MARKER 0xbeefeeee /* BNetBuffer *=--------------------------------------------------------------------------=* * Purpose: * Class ctor. * * Input parameter: * size : Initial size of buffer (default is zero). * * Remarks: * This bad boy doubles up as the defalt ctor. */ BNetBuffer::BNetBuffer( size_t size ) : CTOR_INIT_LIST { m_init = ( resize( size ) == B_OK ) ? B_OK : B_NO_INIT; } /* BNetBuffer *=--------------------------------------------------------------------------=* * Purpose: * Class copy ctor. * * Input parameter: * refparam : Instance to copy. */ BNetBuffer::BNetBuffer( const BNetBuffer& refparam ) : CTOR_INIT_LIST { m_init = ( clone( refparam ) == B_OK ) ? B_OK : B_NO_INIT; } /* BNetBuffer *=--------------------------------------------------------------------------=* * Purpose: * Ctor to instantiate from serialized data (BMessage). * * Input parameter: * archive : Serialized object to instantiate from. */ BNetBuffer::BNetBuffer( BMessage* archive ) : CTOR_INIT_LIST { const unsigned char* msgDataPtr; ssize_t msgNBytes; int32 dataSize; int32 stackSize; int32 capacity; if ( archive->FindInt32( "bnbuff_datasize", &dataSize ) != B_OK ) { return; } if ( archive->FindInt32( "bnbuff_stacksize", &stackSize ) != B_OK ) { return; } if ( archive->FindInt32( "bnbuff_capacity", &capacity ) != B_OK ) { return; } if ( archive->FindData( "bnbuff_stack", B_RAW_TYPE, ( const void** )&msgDataPtr, &msgNBytes ) != B_OK ) { return; } if ( capacity ) { if ( resize( capacity, true ) != B_OK ) { return; } memcpy( fData, msgDataPtr, stackSize ); } fDataSize = dataSize; fStackSize = stackSize; fCapacity = capacity; m_init = B_OK; } /* ~BNetBuffer *=--------------------------------------------------------------------------=* * Purpose: * Class dtor. */ BNetBuffer::~BNetBuffer( void ) { if ( fData != NULL ) { delete fData; fData = NULL; } fDataSize = fStackSize = fCapacity = 0; m_init = B_NO_INIT; } /* operator= *=--------------------------------------------------------------------------=* * Purpose: * Class' assignment operator. * * Input parameter: * refparam : Instance to assign from. */ BNetBuffer& BNetBuffer::operator=( const BNetBuffer& refparam ) { m_init = clone( refparam ); return *this; } /* InitCheck *=--------------------------------------------------------------------------=* * Purpose: * Determine whether or not this instance is properly initialized. * * Returns: * B_OK if this instance is initialized, B_ERROR if not. */ status_t BNetBuffer::InitCheck( void ) { return ( m_init == B_OK ) ? B_OK : B_ERROR; } /* Archive *=--------------------------------------------------------------------------=* * Purpose: * Serialize this instance into the passed BMessage parameter. * * Input parameter: * deep : [ignored] default==true. * * Output parameter: * into : BMessage object to serialize into. * * Returns: * B_OK/BERROR on success/failure. Returns B_NO_INIT if instance not * properly initialized. */ status_t BNetBuffer::Archive( BMessage* into, bool deep ) const { if ( m_init != B_OK ) { return B_NO_INIT; } if ( into->AddInt32( "bnbuff_datasize", fDataSize ) ) { return B_ERROR; } if ( into->AddInt32( "bnbuff_stacksize", fStackSize ) ) { return B_ERROR; } if ( into->AddInt32( "bnbuff_capacity", fCapacity ) ) { return B_ERROR; } if ( into->AddData( "bnbuff_stack", B_RAW_TYPE, fData, fStackSize ) != B_OK ) //STM: Should we store the *whole* stack instead?......^^^^^^^^^^fCapacity { return B_ERROR; } return B_OK; } /* Instantiate *=--------------------------------------------------------------------------=* * Purpose: * Un-serialize and instantiate from the passed BMessage parameter. * * Input parameter: * archive : Archived BMessage object for (de)serialization. * * Returns: * NULL if a BNetBuffer instance can not be initialized, otherwise * a new BNetBuffer object instantiated from the BMessage parameter. */ BArchivable* BNetBuffer::Instantiate( BMessage* archive ) { if ( !validate_instantiation( archive, "BNetBuffer" ) ) { return NULL; } BNetBuffer* bnb = new BNetBuffer( archive ); if ( bnb == NULL ) { return NULL; } if ( bnb->InitCheck( ) != B_OK ) { delete bnb; return NULL; } return bnb; } /* AppendXXX *=--------------------------------------------------------------------------=* * Purpose: * Append some data to this buffer (think PUSHing onto a FIFO stack). * * Returns: * B_OK/B_ERROR for success/failure. B_NO_INIT if instance not properly * initialized or stuck in some unknown state during construction. * * Remarks: * * Where appropriate numeric data such as (u)int16, et al, are converted * to network byte order during storage, no need to do this in advance. */ status_t BNetBuffer::AppendInt8( int8 Data ) { return dpush( B_INT8_TYPE, sizeof( int8 ), &Data ); } status_t BNetBuffer::AppendUint8( uint8 Data ) { return dpush( B_UINT8_TYPE, sizeof( uint8 ), &Data ); } status_t BNetBuffer::AppendInt16( int16 Data ) { int16 locData = B_HOST_TO_BENDIAN_INT16( Data ); return dpush( B_INT16_TYPE, sizeof( int16 ), &locData ); } status_t BNetBuffer::AppendUint16( uint16 Data ) { uint16 locData = B_HOST_TO_BENDIAN_INT16( Data ); return dpush( B_UINT16_TYPE, sizeof( uint16 ), &locData ); } status_t BNetBuffer::AppendInt32( int32 Data ) { int32 locData = B_HOST_TO_BENDIAN_INT32( Data ); return dpush( B_INT32_TYPE, sizeof( int32 ), &locData ); } status_t BNetBuffer::AppendUint32( uint32 Data ) { uint32 locData = B_HOST_TO_BENDIAN_INT32( Data ); return dpush( B_UINT32_TYPE, sizeof( uint32 ), &locData ); } status_t BNetBuffer::AppendInt64( int64 Data ) { int64 locData = B_HOST_TO_BENDIAN_INT64( Data ); return dpush( B_INT64_TYPE, sizeof( int64 ), &locData ); } status_t BNetBuffer::AppendUint64( uint64 Data ) { uint64 locData = B_HOST_TO_BENDIAN_INT64( Data ); return dpush( B_UINT64_TYPE, sizeof( uint64 ), &locData ); } status_t BNetBuffer::AppendFloat( float Data ) { return dpush( B_FLOAT_TYPE, sizeof( float ), &Data ); } status_t BNetBuffer::AppendDouble( double Data ) { return dpush( B_DOUBLE_TYPE, sizeof( double ), &Data ); } status_t BNetBuffer::AppendString( const char* Data ) { return dpush( B_STRING_TYPE, strlen( Data ), Data ); } status_t BNetBuffer::AppendData( const void* Data, size_t Length ) { return dpush( B_RAW_TYPE, Length, Data ); } status_t BNetBuffer::AppendMessage( const BMessage& Msg ) { status_t result; ssize_t msgLen; char* msgData; msgLen = Msg.FlattenedSize( ); if ( msgLen == 0 ) { //STM: It's possible, but SHOULD we store an empty BMessage? //STM: Is an empty BMessage instance even legit in Be/OBOS? return B_ERROR; } if ( ( msgData = new char[msgLen] ) == NULL ) { return B_ERROR; //STM: B_NO_MEM??? } // Don't get tripped on this, just trying to err on the side of caution. result = B_ERROR; if ( Msg.Flatten( msgData, msgLen ) == B_OK ) { result = dpush( B_MESSAGE_TYPE, msgLen, msgData ); } delete msgData; return result; } /* RemoveXXXX *=--------------------------------------------------------------------------=* * Purpose: * Remove some data from this buffer, think POPping from a FIFO stack. * * Returns: * B_OK/B_ERROR for success/failure. B_NO_INIT if instance not properly * initialized or stuck in some unknown state during construction. * * Remarks: * Where appropriate numeric data such as (u)int16, et al, are converted * from network byte order. No need to do this ipso post facto. */ status_t BNetBuffer::RemoveInt8( int8& Data ) { return dpop( B_INT8_TYPE, sizeof( int8 ), &Data ); } status_t BNetBuffer::RemoveUint8( uint8& Data ) { return dpop( B_UINT8_TYPE, sizeof( uint8 ), &Data ); } status_t BNetBuffer::RemoveInt16( int16& Data ) { // dpop will convert this bad boy from network to host byte order. return dpop( B_INT16_TYPE, sizeof( int16 ), &Data ); } status_t BNetBuffer::RemoveUint16( uint16& Data ) { // dpop will convert this beast from network to host byte order. return dpop( B_UINT16_TYPE, sizeof( uint16 ), &Data ); } status_t BNetBuffer::RemoveInt32( int32& Data ) { // dpop will convert this thang from network to host byte order. return dpop( B_INT32_TYPE, sizeof( int32 ), &Data ); } status_t BNetBuffer::RemoveUint32( uint32& Data ) { // dpop will convert this variable from network to host byte order. return dpop( B_UINT32_TYPE, sizeof( uint32 ), &Data ); } status_t BNetBuffer::RemoveInt64( int64& Data ) { // dpop will convert this bag-o-bits from network to host byte order. return dpop( B_INT64_TYPE, sizeof( int64 ), &Data ); } status_t BNetBuffer::RemoveUint64( uint64& Data ) { // dpop will convert this construct from network to host byte order. return dpop( B_UINT64_TYPE, sizeof( uint64 ), &Data ); } status_t BNetBuffer::RemoveFloat( float& Data ) { return dpop( B_FLOAT_TYPE, sizeof( float ), &Data ); } status_t BNetBuffer::RemoveDouble( double& Data ) { return dpop( B_DOUBLE_TYPE, sizeof( double ), &Data ); } status_t BNetBuffer::RemoveString( char* Data, size_t Length ) { return dpop( B_STRING_TYPE, Length, Data ); } status_t BNetBuffer::RemoveData( void* Data, size_t Length ) { return dpop( B_RAW_TYPE, Length, Data ); } status_t BNetBuffer::RemoveMessage( BMessage& message ) { status_t result; int32 v; unsigned char* stackPtr = fData; char* msgData; /* * We have to cheat a little bit here and peek ahead of dpop so we can * get at the size parameter. Perform basic checks along the way so we * can make sure that we really get to the size parameter and not some * bogus value. Redundant, but necessary. */ v = *( int32 * )stackPtr; if ( v != DATA_START_MARKER ) { return B_ERROR; } stackPtr += sizeof( int32 ); v = *( int32 * )stackPtr; if ( v != B_MESSAGE_TYPE ) { return B_ERROR; } // Snarf the data size. stackPtr += sizeof( int32 ); v = *( int32 * )stackPtr; if ( ( msgData = new char[v] ) == NULL ) { return B_ERROR; //STM: B_NO_MEM??? } if ( dpop( B_MESSAGE_TYPE, v, msgData ) != B_OK ) { delete msgData; return B_ERROR; } result = message.Unflatten( msgData ); delete msgData; return result; } /* Data *=--------------------------------------------------------------------------=* * Purpose: * Class accessor. * * Returns: * Pointer to class' data suitable for those functions and methods that * expect some kind of "generic" pointer. * * Remarks: * * This is WEAK and WRONG, as it allows non-members to directly * manipulate an instance's member data without the usual checks in * place. At the very least the return value should be const. Ideally, * this method should not exist. */ unsigned char* BNetBuffer::Data( void ) const { // Have I mentioned that this is a Really Bad Thing to do? return fData; // In case I missed it: returning a non-const pointer to our member data // is not a good thing. Caveat emptor. } /* Size *=--------------------------------------------------------------------------=* * Purpose: * Class accessor. * * Returns: * How many bytes of data are contained. This *does not* include the * overhead of the stack control variables (delineators and size). */ size_t BNetBuffer::Size( void ) const { return fDataSize; } /* BytesRemaining *=--------------------------------------------------------------------------=* * Purpose: * Class accessor ...erm... sort of. * * Returns: * How much capacity this instance has left to store data. * * Remarks: * This method is essentially meaningless as the class' storage capacity * will grow (or shrink) as necessary (that's an obfuscated way to say * "dynamically re-allocated" boys and girls). Do not rely on this * particular method in normal use as it refers to a moving target. */ size_t BNetBuffer::BytesRemaining( void ) const { return ( fCapacity - fStackSize ); } /* clone *=--------------------------------------------------------------------------=* * Purpose: * Private copy helper method. * * Input parameter: * RefParam: Instance to clone. * * Returns: * status_t indicating success or point of failure (see resize()). * B_NO_INIT if RefParam not properly initialized. */ status_t BNetBuffer::clone( const BNetBuffer& RefParam ) { if ( !RefParam.m_init ) { return B_NO_INIT; } if ( resize( RefParam.fCapacity, true ) != B_OK ) { return B_ERROR; } fDataSize = RefParam.fDataSize; fStackSize = RefParam.fStackSize; memcpy( fData, RefParam.fData, fStackSize ); return B_OK; } /* dpop *=--------------------------------------------------------------------------=* * Purpose: * Pop some data from our stack. * * Input parameters: * Type : Expected data type for this record. * Length : Expected data size for this record. * * Output parameter: * Data : Recepticle for popped data. You are responsible for * insuring that this is large enough to hold your * intended data. If this parameter is NULL, you'll get * back a B_ERROR. If the space allocated that this * parameter points to is not sufficient to contain the * intended data, you'll likely over-run something else * in user-space and piss off a few folks in the process. * * Returns: * B_OK/B_ERROR on success/failure. * B_NO_INIT if this instance not properly initialized. */ status_t BNetBuffer::dpop( int32 Type, int32 Length, void* Data ) { if ( m_init != B_OK ) { return B_NO_INIT; } if ( Data == NULL ) { return B_ERROR; } unsigned char* stackPtr = fData; unsigned char* userDataPtr; int32 int32size = sizeof( int32 ); // <-- Performance: called a lot here. int32 recordSize = ( ( int32size * 4 ) + Length ); int32 tmp; // Validate the start marker. tmp = *( int32 * )stackPtr; if ( tmp != (int32) DATA_START_MARKER ) { return B_ERROR; } // Validate the data type. stackPtr += int32size; tmp = *( int32 * )stackPtr; if ( tmp != Type ) { return B_ERROR; } // Validate the data size. stackPtr += int32size; tmp = *( int32 * )stackPtr; if ( tmp != Length ) { return B_ERROR; } // Stash a pointer to the contained user data, used when we really "pop." stackPtr += int32size; userDataPtr = stackPtr; // Validate the end marker. stackPtr += Length; tmp = *( int32 * )stackPtr; if ( tmp != (int32) DATA_END_MARKER ) { return B_ERROR; } // Point to the next entry in the stack. stackPtr += int32size; // Extract the contained data. memcpy( Data, userDataPtr, Length ); // Convert from network byte order if necessary. switch ( Type ) { case B_INT16_TYPE: case B_UINT16_TYPE: *( uint16 * )Data = B_BENDIAN_TO_HOST_INT16( *( uint16 * )Data ); break; case B_INT32_TYPE: case B_UINT32_TYPE: *( uint32 * )Data = B_BENDIAN_TO_HOST_INT32( *( uint32 * )Data ); break; case B_INT64_TYPE: case B_UINT64_TYPE: *( uint64 * )Data = B_BENDIAN_TO_HOST_INT64( *( uint64 * )Data ); break; default: break; } // Condense the stack. fStackSize -= recordSize; memmove( fData, stackPtr, fStackSize ); // Zero out the unused area of the stack where we memmove'd from. memset( &fData[fStackSize], 0, ( fCapacity - fStackSize ) ); // Update the extents. fCapacity += recordSize; fDataSize -= Length; return B_OK; } /* dpush *=--------------------------------------------------------------------------=* * Purpose: * Push some data onto our buffer. * * Input parameters: * Type : Data type for this record. * Length : Data length for this record. * Data : Pointer to data to store. NO-OP (B_ERROR) if NULL. * * Returns: * B_OK/B_ERROR on success/failure. * B_NO_INIT if this instance not properly initialized. * * Remarks: * * No need to worry about host-to-network byte ordering conversion -- * since this is a /private/ helper said conversion, where applicable, * takes place in our caller (see AddIntXX if you haven't already passed * them on your way down to this humble helper). */ status_t BNetBuffer::dpush( int32 Type, int32 Length, const void* Data ) { if ( m_init != B_OK ) { return B_NO_INIT; } if ( Data == NULL ) { return B_ERROR; } int32 int32size = sizeof( int32 ); // <--Performance: called a lot here. int32 recordSize = ( ( int32size * 4 ) + Length ); if ( resize( fCapacity + recordSize ) != B_OK ) { return B_ERROR; } unsigned char* stackPtr = &fData[fStackSize]; *( int32 * )stackPtr = DATA_START_MARKER; stackPtr += int32size; *( int32 * )stackPtr = Type; stackPtr += int32size; *( int32 * )stackPtr = Length; stackPtr += int32size; memcpy( stackPtr, Data, Length ); stackPtr += Length; *( int32 * )stackPtr = DATA_END_MARKER; // Groovy! All stored, dot our t's and cross our i's. fDataSize += Length; fStackSize += recordSize; return B_OK; } /* resize *=--------------------------------------------------------------------------=* * Purpose: * Grow the internal data buffer to accommodate the requested size. * * //STM: What about shrinking the buffer to conserve resources? * * Input parameter: * NewSize : New size of data buffer. Duh. * RegenStack : If true, do not try to preserve the existing stack. * Default is false (preserve existing data). * * Returns: * B_OK: Success. * B_NO_MEMORY: if we cannot allocate memory for the data buffer. * * Remarks: * * In keeping with the "contract programming" development model: this is * a "safe copy" method in that the class' existing member data will not * be disturbed in the event of failure. When our caller gets back a * success status (B_OK), said caller is guaranteed that all is well. * Laterally, in the event this method returns failure, the caller is * guaranteed that the class' existing member data is left untouched, * thereby preserving the existing instance's integrity. * * * Buffer size is adjusted on a 16-byte granularity to keep from beating * the bleep out of the memory pool (can you say "fragmentation" boys and * girls?). To tweak the granularity, change the GRANULARITY token at the * beginning of this method. For best results, GRANULARITY should be a * power of two, thankyouverymuch. * * * 'RegenStack' and its intented behavior is a bit of a double-negative. * May have to change this to eliminate said double negative if it proves * to be too confusing. */ status_t BNetBuffer::resize( int32 NewSize, bool RegenStack ) { #define GRANULARITY 16 unsigned char* newData; int32 newCapacity; newCapacity = ( ( ( NewSize / GRANULARITY ) + 1 ) * GRANULARITY ); // Don't waste cycles or thrash the memory pool if we don't need to... if ( newCapacity <= fCapacity ) { return B_OK; } /* STM: If we decide to shrink as well as grow, then replace above with: if ( newCapacity == fCapacity ) { return B_OK; } */ newData = new unsigned char[newCapacity]; if ( newData == NULL ) { return B_NO_MEMORY; } // Paranoid? Pffffsh. You bet. :-) memset( newData, 0, newCapacity ); if ( fData != NULL ) { if ( !RegenStack ) { memcpy( newData, fData, fStackSize ); } delete fData; } fData = newData; fCapacity = newCapacity; return B_OK; #undef GRANULARITY } /* Reserved methods, for future evolution */ void BNetBuffer::_ReservedBNetBufferFBCCruft1() { } void BNetBuffer::_ReservedBNetBufferFBCCruft2() { } void BNetBuffer::_ReservedBNetBufferFBCCruft3() { } void BNetBuffer::_ReservedBNetBufferFBCCruft4() { } void BNetBuffer::_ReservedBNetBufferFBCCruft5() { } void BNetBuffer::_ReservedBNetBufferFBCCruft6() { } /*=------------------------------------------------------------------- End -=*/