root/cygnal/libamf/buffer.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. hex2digit
  2. hex2mem
  3. hexify
  4. hexify
  5. hexify
  6. init
  7. copy
  8. append
  9. remove
  10. remove
  11. remove
  12. clear
  13. resize
  14. resize
  15. dump
  16. corrupt
  17. corrupt

// 
//   Copyright (C) 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#ifdef HAVE_CONFIG_H
#include "gnashconfig.h"
#endif

#include <boost/cstdint.hpp>
#include <iostream>
#include <boost/random/uniform_int.hpp>
#include <boost/random/mersenne_twister.hpp>

#include "buffer.h"
#include "amf.h"
#include "log.h"
//#include "network.h"
#include "GnashException.h"


/// \namespace cygnal
///
/// This namespace is for all the AMF specific classes in libamf.
namespace cygnal
{

/// \brief Convert a Hex digit into it's decimal value.
///
/// @param digit The digit as a hex value
///
/// @return The byte as a decimal value.
boost::uint8_t
Buffer::hex2digit (boost::uint8_t digit)
{  
    if (digit == 0)
        return 0;
    
    if (digit >= '0' && digit <= '9')
        return digit - '0';
    if (digit >= 'a' && digit <= 'f')
        return digit - 'a' + 10;
    if (digit >= 'A' && digit <= 'F')
        return digit - 'A' + 10;
    
    // shouldn't ever get this far
    return -1;
}

/// \brief Encode a Buffer from a hex string.
///
/// @param str A hex string, ex... "00 03 05 0a"
///
/// @return A reference to a Buffer in host endian format. This is
///             primary used only for testing to create binary data
///             from an easy to read and edit format.
Buffer &
Buffer::hex2mem(const std::string &str)
{
//    GNASH_REPORT_FUNCTION;
    size_t count = str.size();
    size_t size = (count/3) + 4;
    boost::uint8_t ch = 0;
    
    boost::uint8_t *ptr = const_cast<boost::uint8_t *>(reinterpret_cast<const boost::uint8_t *>(str.c_str()));
    boost::uint8_t *end = ptr + count;

    init(size);
    
    for (size_t i=0; ptr<end; i++) {
        if (*ptr == ' ') {      // skip spaces.
            ptr++;
            continue;
        }
        ch = hex2digit(*ptr++) << 4;
        ch |= hex2digit(*ptr++);
        *this += ch;
            i++;
    }
    resize(size);
    
    return *this;
}

// Convert each byte into its hex representation
std::string
Buffer::hexify ()
{
    return gnash::hexify(_data.get(), allocated(), false);
}

std::string
Buffer::hexify (bool ascii)
{
    return gnash::hexify(_data.get(), allocated(), ascii);
}

std::string
Buffer::hexify (cygnal::Buffer &buf, bool ascii)
{
    return gnash::hexify(buf.reference(), buf.allocated(), ascii);
}

/// \brief Initialize a block of memory for this buffer.
///             This should only be used internally by the Buffer
///             class.
///
/// @param nbytes The total size to allocate memory for.
///
/// @return A reference to the initialized Buffer.
Buffer &
Buffer::init(size_t size)
{
//    GNASH_REPORT_FUNCTION;
    if (!_data) {
        _data.reset(new boost::uint8_t[size]);
        _seekptr = _data.get();
    }
    _seekptr = _data.get();
    _nbytes = size;

    clear();// FIXME; this is a perforance hit, but aids in debugging
#ifdef USE_STATS_BUFFERS
    clock_gettime (CLOCK_REALTIME, &_stamp);
#endif
    
    return *this;
}

/// \brief Create a new Buffer with the default size
Buffer::Buffer() 
    : _seekptr(0)
{
//    GNASH_REPORT_FUNCTION;
    _nbytes = cygnal::NETBUFSIZE;
    init(cygnal::NETBUFSIZE);
}
    
/// \brief Create a new Buffer with a size other than the default
Buffer::Buffer(size_t nbytes)
    : _seekptr(0)
{
//    GNASH_REPORT_FUNCTION;
    _nbytes = nbytes;
    init(_nbytes);
}

/// \brief Create a new Buffer with a hex string.
///             This is primary used only for testing to create binary
///             data from an easy to read and edit format.
/// @param str A hex string, ex... "00 03 05 0a"
Buffer::Buffer(const std::string &str)
{
//    GNASH_REPORT_FUNCTION;
    hex2mem(str);
}

/// Delete the memory allocated for this Buffer
Buffer::~Buffer()
{
//    GNASH_REPORT_FUNCTION;
    if (_data) {
#ifdef USE_STATS_BUFFERS
        struct timespec now;
        clock_gettime (CLOCK_REALTIME, &now);
        log_debug("Buffer %x (%d) stayed in queue for %f seconds",
                  (void *)_data.get(), _nbytes,
                  (float)((now.tv_sec - _stamp.tv_sec) + ((now.tv_nsec - _stamp.tv_nsec)/1e9)));
#endif
        _seekptr = 0;
        _nbytes = 0;
    }
}

/// \brief Copy data into the buffer.
///             This overwrites all data, and resets the seek ptr.
///
/// @param data A pointer to the raw bytes to copy into the
///             buffer.
/// 
/// @param nbytes The number of bytes to copy.
///             
/// @return A reference to a Buffer.
Buffer &
Buffer::copy(boost::uint8_t *data, size_t nbytes)
{    
//    GNASH_REPORT_FUNCTION;
    if (_data) {
        if (_nbytes >= nbytes) {
            std::copy(data, data + nbytes, _data.get());
            _seekptr = _data.get() + nbytes;
        } else {
            boost::format msg("Not enough storage was allocated to hold the "
                              "copied data! Needs %1%, only has %2% bytes");
            msg % nbytes % _nbytes;
            throw gnash::GnashException(msg.str());
        }
    }
    return *this;
}

/// \brief Append data to existing data in the buffer.
///
/// @param data A pointer to the raw bytes to append to the
///             buffer.
/// 
/// @param nbytes The number of bytes to append.
///             
/// @return A reference to a Buffer.
Buffer &
Buffer::append(boost::uint8_t *data, size_t nbytes)
{
//    GNASH_REPORT_FUNCTION;
    if (_data) {
        if (spaceLeft() >= nbytes) {
            std::copy(data, data + nbytes, _seekptr);
            _seekptr += nbytes;
        } else {
            boost::format msg("Not enough storage was allocated to hold the "
                              "appended data! Needs %1%, only has %2% bytes");
            msg % nbytes % spaceLeft();
            throw gnash::GnashException(msg.str());
        }
    }

    return *this;
}

/// \brief Append a Buffer class to existing data in the buffer.
///
/// @param buf A Buffer class containing the data to append.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator+=(Buffer &buf)
{
// //    GNASH_REPORT_FUNCTION;
    append(buf.reference(), buf.allocated());
    return *this;
}

/// \brief Copy a AMF0 type into the buffer.
///             This overwrites all data, and resets the seek ptr.
///
/// @param type An AMF0 type. 
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator+=(cygnal::Element::amf0_type_e type)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t nb = static_cast<boost::uint8_t>(type);
    
    return operator+=(nb);
}

/// \brief Append a byte to existing data in the buffer.
///
/// @param byte A single byte.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator+=(char byte)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t nb = static_cast<boost::uint8_t>(byte);
    return operator+=(nb);
}

/// \brief Append a boolean to existing data in the buffer.
///
/// @param type A boolean. 
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator+=(bool flag)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t nb = static_cast<boost::uint8_t>(flag);
    return operator+=(nb);
}

/// \brief Append a byte to existing data in the buffer.
///
/// @param byte A single byte.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator+=(boost::uint8_t byte)
{
//    GNASH_REPORT_FUNCTION;
    if ((_seekptr + 1) <= (_data.get() + _nbytes)) {
        *_seekptr = byte;
        _seekptr += sizeof(boost::uint8_t);
    }
    return *this;
}

/// \brief Append a string to existing data in the buffer.
///
/// @param str A string containing ASCII data to copy into the
///             buffer.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator+=(const char *str)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t *ptr = const_cast<boost::uint8_t *>(reinterpret_cast<const boost::uint8_t *>(str));
    return append(ptr, strlen(str));
    
}

/// \brief Append a string to existing data in the buffer.
///
/// @param str A string containing ASCII data to copy into the
///             buffer.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator+=(const std::string &str)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t *ptr = const_cast<boost::uint8_t *>(reinterpret_cast<const boost::uint8_t *>(str.c_str()));
    return append(ptr, str.size());
    
}

/// \brief Append a double to existing data in the buffer.
///
/// @param num A numeric double value.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator+=(double num)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t *ptr = reinterpret_cast<boost::uint8_t *>(&num);
    return append(ptr, AMF0_NUMBER_SIZE);
}

/// \brief Append a short to existing data in the buffer.
/// 
/// @param num A numeric short value.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator+=(boost::uint16_t length)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t *ptr = reinterpret_cast<boost::uint8_t *>(&length);
    return append(ptr, sizeof(boost::uint16_t));
}

/// \brief Append an integer to existing data in the buffer.
/// 
/// @param num A numeric integer value.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator+=(boost::uint32_t length)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t *ptr = reinterpret_cast<boost::uint8_t *>(&length);
    return append(ptr, sizeof(boost::uint32_t));
}

/// \brief Append a Buffer class to existing data in the buffer.
///
/// @param buf A Buffer class containing the data to append.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator+=(boost::shared_ptr<Buffer> &buf)
{
//    GNASH_REPORT_FUNCTION;
    append(buf->reference(), buf->allocated());
    return *this;
}

/// \brief Append a Buffer class to existing data in the buffer.
///
/// @param buf A Buffer class containing the data to append.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator=(Buffer &buf)
{
//    GNASH_REPORT_FUNCTION;
    if (buf.size() != _nbytes) {
        resize(buf.size());
    }
    copy(buf.reference(), buf.size());

    return *this;
}

/// \brief Copy a string into the buffer.
///             This overwrites all data, and resets the seek ptr.
///
/// @param str A string containing ASCII data to copy into the
///             buffer.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator=(const std::string &str)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t *ptr = const_cast<boost::uint8_t *>(reinterpret_cast<const boost::uint8_t *>(str.c_str()));
    return copy(ptr, str.size());
}

Buffer &
Buffer::operator=(const char *str)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t *ptr = const_cast<boost::uint8_t *>(reinterpret_cast<const boost::uint8_t *>(str));
    return copy(ptr, strlen(str));
}

/// \brief Copy a double into the buffer.
///             This overwrites all data, and resets the seek ptr.
///
/// @param num A numeric double value.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator=(double num)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t *ptr = reinterpret_cast<boost::uint8_t *>(&num);
    return copy(ptr, AMF0_NUMBER_SIZE);
}

/// \brief Copy a short into the buffer.
///             This overwrites all data, and resets the seek ptr.
///
/// @param num A numeric short value.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator=(boost::uint16_t length)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t *ptr = reinterpret_cast<boost::uint8_t *>(&length);
    return copy(ptr, sizeof(boost::uint16_t));
}

/// \brief Copy a AMF0 type into the buffer.
///             This overwrites all data, and resets the seek ptr.
///
/// @param type An AMF0 type. 
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator=(cygnal::Element::amf0_type_e type)
{
    boost::uint8_t nb = static_cast<boost::uint8_t>(type);
    return operator=(nb);
}

/// Copy a boolean into the buffer. This overwrites all data, and
///             resets the seek ptr.
///
/// @param flag A boolean. 
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator=(bool flag)
{
    boost::uint8_t nb = static_cast<boost::uint8_t>(flag);
    return operator=(nb);
}

/// \brief Copy a byte into the buffer.
///             This overwrites all data, and resets the seek ptr.
///
/// @param byte A single byte.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator=(boost::uint8_t byte)
{
//    GNASH__FUNCTION;
   
    return copy(&byte, 1);
}

/// \brief Copy a byte into the buffer.
///             This overwrites all data, and resets the seek ptr.
///
/// @param byte A pointer to a single byte.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator=(boost::uint8_t *data)
{
//    GNASH_REPORT_FUNCTION;
    if (data) {
        _data.reset(data);
    } else {
        throw gnash::ParserException("Passing invalid pointer!");
    }
    return *this;
}

/// \brief Copy a Buffer class into the buffer.
///             This overwrites all data, and resets the seek ptr.
///
/// @param buf A Buffer class containing the data to copy.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::operator=(boost::shared_ptr<Buffer> &buf)
{
//    GNASH_REPORT_FUNCTION;
    copy(buf->reference(), buf->size());
    return *this;
}

/// \brief Test equivalance against another Buffer.
///             This compares all the data on the current Buffer with
///             the supplied one, so it can be a performance hit. This
///             is primarily only used for testing purposes.
///
/// @param buf A reference to a Buffer.
///
/// @return A boolean true if the Buffers are indentical.
bool
Buffer::operator==(Buffer &buf)
{
//    GNASH_REPORT_FUNCTION;
     if (buf.size() == _nbytes){
         if (memcmp(buf.reference(), _data.get(), _nbytes) == 0) {
             return true;
         }
     }
     return false;
}

/// \brief Drop a byte without resizing.
///             This will remove the byte from the Buffer, and then
///             move the remaining data to be in the correct
///             location. This resets the seek pointer.
///
/// @param byte The byte to remove from the buffer.
///
/// @return A real pointer to the base address of the buffer.
boost::uint8_t *
Buffer::remove(boost::uint8_t c)
{
//    GNASH_REPORT_FUNCTION;
    boost::uint8_t *start = std::find(begin(), end(), c);

//    log_debug("Byte is at %x", (void *)start);
    
    if (start == 0) {
        return 0;
    }
    
    std::copy(start + 1, end(), start);
    *(end() - 1) = 0;
    _seekptr--;

    return _data.get();
}

/// \brief Drop a byte without resizing.
///             This will remove the byte from the Buffer, and then
///             move the remaining data to be in the correct
///             location. This resets the seek pointer.
///
/// @param start The location of the byte to remove from the
///             Buffer
///
/// @return A real pointer to the base address of the Buffer.
boost::uint8_t *
Buffer::remove(int start)
{
//    GNASH_REPORT_FUNCTION;
    std::copy((_data.get() + start + 1), end(), (_data.get() + start)),
//    *end() = 0;
    _seekptr--;
    return _data.get();
}

/// \brief Drop bytes without resizing.
///             This will remove the bytes from the Buffer, and then
///             move the remaining data to be in the correct
///             location. This resets the seek pointer.
///
/// @param index The location of the byte to start removing data
///             from the Buffer. This is an numerical value, not a
///             pointer.
///
/// @param start The location of the byte to remove from the
///             Buffer
/// @param range The amount of bytes to remove from the Buffer.
///
/// @return A real pointer to the base address of the Buffer.
boost::uint8_t *
Buffer::remove(int start, int range)
{
//    GNASH_REPORT_FUNCTION;
    std::copy((_data.get() + range + 1), end(), (_data.get() + start)),
//    *end() = 0;
        _seekptr -= range;
    
    return _data.get();
}

/// \brief Clear the contents of the buffer by setting all the bytes to
///             zeros.
///
/// @return nothing
void
Buffer::clear()
{
//    GNASH_REPORT_FUNCTION;
    if (_data) {
        memset(_data.get(), 0, _nbytes);
    }
    _seekptr = _data.get();
}

/// \brief Resize the buffer that holds the data.
///             The new size of the current data is based on the
///             current amount of data within the allocated memory.
///             This is used to make a Buffer the same size as
///             the existing data, and to truncate the unsed portion
///             of the Buffer when copying to the new memory
///             location.
/// 
/// @return A reference to a Buffer.
Buffer &
Buffer::resize()
{
//    GNASH_REPORT_FUNCTION;
    return resize(_seekptr - _data.get());
}

/// \brief Resize the buffer that holds the data.
///             If the size is larger than the existing data, the data
///             is copied into the new region. If the size is smaller
///             than the existing data, the remaining data is
///             truncated when copying to the new memory location.
///
/// @param nbyte The size to resize the Buffer to.
///
/// @return A reference to a Buffer.
Buffer &
Buffer::resize(size_t size)
{
//    GNASH_REPORT_FUNCTION;
    boost::scoped_array<boost::uint8_t> tmp;

    // If there is no size, don't do anything
    if (size == 0) {
        return *this;
    }
    
    // If we don't have any data yet in this buffer, resizing is cheap, as
    // we don't havce to copy any data.
    if (_seekptr == _data.get()) {
        _data.reset(new boost::uint8_t[size]);
        _nbytes= size;
        return *this;
    }
    
    if (_nbytes == 0) {
        return init(size);
    } else {
        // Don't bother to resize without really changing anything
        if (size == _nbytes) {
            return *this;
        }

        // check the sizes. If we had data read using ->reference(), the seekptr isn't
        // increased, so in these cases we just copy al lthe data blindly, as it's
        // better than loosing data.
        size_t used = 0;
        if (_seekptr != _data.get()) {
            used = _seekptr - _data.get();
        } else {
            if (size < _nbytes) {
                used = size;
            } else {
                used = _nbytes;
            }
        }
        
        
        // Copy the existing data into the new block of memory. The data
        // held currently is moved to the temporary array, and then gets
        // deleted when this method returns.
        // We loose data if we resize smaller than the data currently held.
        if (size < used) {
            gnash::log_error("cygnal::Buffer::resize(%d): Truncating data (%d bytes) while resizing!", size, used - size);
            used = size;
        }
        boost::uint8_t *newptr = new boost::uint8_t[size];
        std::copy(_data.get(), _data.get() + used, newptr);
        _data.reset(newptr);
        
        // Make the seekptr point into the new space with the correct offset
        _seekptr = _data.get() + used;

        // Adjust the size
        _nbytes = size;
    }
    
    return *this;
}

///  \brief Dump the internal data of this class in a human readable form.
///             This should only be used for debugging purposes.
void
Buffer::dump(std::ostream& os) const
{
    os << "Buffer is " << _seekptr-_data.get() << "/" << _nbytes << " bytes: ";
     // Skip in-memory address " at " << (void *)_data.get() << endl;
    if (_nbytes > 0) {
        const size_t bytes = _seekptr - _data.get();
        os << gnash::hexify((unsigned char *)_data.get(), bytes, false)
       << std::endl;
        os << gnash::hexify((unsigned char *)_data.get(), bytes, true)
           << std::endl;
    } else {
        os << "ERROR: Buffer size out of range!" << std::endl;
    }
}

/// \brief Corrupt a buffer with random errors.
///             This is used only for testing to make sure we can cleanly
///             handle corruption of the packets.
///
/// @param factor A divisor to adjust how many errors are created.
///
/// @return nothing
int
Buffer::corrupt()
{
    return corrupt(10);
}

int
Buffer::corrupt(int factor)
{
    boost::mt19937 seed;
    // Pick the number of errors to create based on the Buffer's data size
    boost::uniform_int<> errs(1, (_nbytes/factor));
    int errors = errs(seed);
    gnash::log_debug("Creating %d errors in the buffer", errors);
    
    for (int i=0; i<errors; i++) {
        // find a location someplace within the file.
        boost::uniform_int<> location(0, _nbytes);
        int pos = location(seed);
        
//      log_debug("Creating error at %d in the buffer", pos);
        // Create a random new value for the byte
        boost::uniform_int<> shift(1, 256);
        int newval = shift(seed);
        // stomp the old value for our new one.
        _data[pos] = newval;
    }

    return errors;
}

} // end of amf namespace

// local Variables:
// mode: C++
// indent-tabs-mode: t
// End:

/* [<][>][^][v][top][bottom][index][help] */