LNXdev.com

Buffer Class(C++)

A General purpose data buffer for data serialization

This is a quite "narrow" raw data buffer implementation. The idea behind this one is that you can easily append and get data from an array of continuous bytes. It also makes use of 'malloc', 'realloc' and 'free' instead of the usual vector approach. 'new' and 'delete' are not used because there are some reallocation issues.

Writing

Writing data to the buffer is done with the overloaded Buffer::append() function. It supports std::string, character arrays, and unsigned fixed width integers. Buffer::write() completely empties the data set and writes from the beginning of the buffer. This function only supports strings and character arrays. See buffer.h for details.

Reading

Reading is done with a set of get_*() functions, and values are either returned, or in the case of Buffer::get_string(), written in a std::string passed by reference. Buffer::get_data() returns a pointer to constant data. You can also start reading from the beginning of the data set by using Buffer::rewind(). As always, the header file tells rest of the story (buffer.h).

Problems...

I made this class for a specific purpose and it does'nt support signed integers. So keep in mind that passing signed integers to the append() functions will result in an ambiquous call to an overloaded funtion. When appending unsigned integers (system specific), the result is undetermined (ie. propably not good). Limitations in this class are mostly due to the fact that it's designed to carry 'important' data between my sauna and the main building. And for that purpose, this class works just fine :)

Portability?

Only within Linux and Unix environments and the code should be platform independent. I have tested a previous version of this class with both 32-bit and 64-bit systems (Linux).

This class will not work in Windows environment.

FIXME:

There are some issues with this code that I'm going to change or fix in the near future. Few of them listed below:

For a more complete buffer(or byte array), you might want to check out the Boost libraries or Qt framework. Or improve on this one. The licence is open source (use it as you wish). Feedback is also appreciated (djwk@lnxdev.com).

Download source here: buffer.tar.gz

Compile the source code with g++

Terminal
$ g++ main.cpp buffer.cpp buffer.h -o buffer

The Main

Demonstrating functionality

// Buffer class examples
// Last edit: 2013-01-04
// By: Juha Wisakanto
// <djwk@lnxdev.com>

// This is not an exhaustive example.
// Not all functions are demonstrated here.

#include <iostream>
#include <stdint.h>
#include "buffer.h"

int main(void) {
  using namespace std;
  using namespace BUFFER;
  
  // initiate buffers used int his example
  Buffer b1, b2, b3, b4;
  
  cout << "use 'hexdump -C filename' to examine output files";
  cout << "in terminal." << endl;
  cout << "like this: 'hexdump -C b1.out' " << endl << endl;


  // appending strings to buffer and getting them back out again
  // b1
  cout << "writing strings to buffers and appending b2 in b1" << endl;
  string str = "Good ";
  cout << "str in b1: " << str << endl;
  b1.append(str);
  b1.write_to_file("b1.out");
  
  // b2
  str = "Pizza!";
  cout << "str in b2: " << str << endl;
  b2.append(str);
  b2.write_to_file("b2.out");
  
  unsigned int length;
  length = b1.size() + b2.size();
  b1 += b2;
  
  // and we are passing str by reference
  b1.get_string(str, length);
  cout << "b1 after appending: " << str << endl;
  b1.write_to_file("b1_again");

  cout << endl;
  // append uint8_t (unsigned 8-bit integer)
  cout << "append bytes to an empty buffer (b3): " << endl;
  uint8_t byte = 'A';
  cout << "append " << byte << endl;
  b3.append(byte);
  byte = ' ';
  cout << "append '" << byte << "' <- an invisible space" << endl;
  b3.append(byte);
  cout << "append b1 to b3" << endl;
  b3.append(b1);
  
  // cout b3 as a string
  length = b3.size();
  b3.rewind(); // remember to rewind before reading data
  b3.get_string(str, length);
  cout << "b3 str: " << str << endl;
  b3.write_to_file("b3.out");
  cout << endl;
  
  // let's serialize some fixed unsigned integers
  cout << "append fixed width integersn";
  uint8_t ui8 = 0xff;
  uint16_t ui16 = 0;
  uint32_t ui32 = 0xffffffff;
  uint64_t ui64 = 0;
  
  cout << "appending uint8_t, value: " << hex << (unsigned int)ui8 << endl;
  b4.append(ui8);
  cout << "appending uint16_t, value: " << hex << ui16 << endl;
  b4.append(ui16);
  cout << "appending uint32_t, value: " << hex << ui32 << endl;
  b4.append(ui32);
  cout << "appending uint64_t, value: " << hex << ui64 << endl;
  b4.append(ui64);
  
  // check this file with hexdump
  b4.write_to_file("b4.out");
  
  b4.rewind();
  uint8_t rslt8 = b4.get_uint8();
  uint16_t rslt16 = b4.get_uint16();
  uint32_t rslt32 = b4.get_uint32();
  uint64_t rslt64 = b4.get_uint64();
  
  // 'hexdump -C b4.out' should yield the same result as this
  cout << "values received from buffer:n";
  cout << "rslt8: " << hex << (unsigned int)rslt8 << endl;
  cout << "rslt16: " << hex << rslt16 << endl;
  cout << "rslt32: " << hex << rslt32 << endl;
  cout << "rslt64: " << hex << rslt64 << endl << endl;
  
  cout << "some error checking: n";
  cout << "buffer 4 status: ";
  // this should be good
  if(b4.is_good())
    cout << "goodn";
  else
    cout << "badn";
  
  cout << endl;
  // an intentional error (by forgetting to rewind)
  cout << "Lets provoke an error out of this thingn";
  cout << "by running the cursor out of bounds:n";
  rslt64 = b4.get_uint64();
  int errno;
  // results in error nr 4 (CURSOR_OUT_OF_BOUNDS)
  if((errno = b4.is_bad()))
    cout << "error in buffer 4, nr: " << errno << endl;
  
  cout << endl << "all done..." << endl << endl;

  return 0;
}

Definition

buffer.h

// Buffer class definition
// Last edit: 2013-01-03
// By: Juha Wisakanto
// <djwk@lnxdev.com>

#ifndef BUFFER_H_
#define BUFFER_H_
#include <string>
#include <stdint.h> // fixed width integers

namespace BUFFER {
  class Buffer {
    private:
      // buffer size in bytes
      unsigned int m_size;
      
      // pointer to data
      char *m_data;
      
      // cursor for reading data from buffer
      char *m_cursor;

      // cursor position as integer for overflow detection
      // this cannot be greater than m_size
      unsigned int m_cursor_position;
      enum Errors {
	NONE = 0,
	INTEGER_OVERFLOW = 1,
	MEMORY_OVERFLOW = 2,
	MEMORY_ALLOCATION = 3,
	CURSOR_OUT_OF_BOUNDS = 4
      };
      
      // error indicator (as listed above)
      int m_error;
      
      // extend buffer size
      char *m_extend(const unsigned int bytes);
      
      // check bounds with m_request_bytes before moving cursor
      // or writing data
      bool m_request_bytes(const unsigned int bytes);
      
      // cursor movements (using m_request_bytes above)
      void m_cursor_forward(const unsigned int bytes);
      void m_cursor_reverse(const unsigned int bytes);
      bool m_cursor_begin();
      
      void m_show_status();

    public:
      Buffer();
      ~Buffer();
      Buffer(const Buffer &b); // explicit copy contructor
      Buffer(char *c, unsigned int bytes);
      
      // operator overloads
      Buffer &operator=(const Buffer &b); // explicit assignment operator
      Buffer &operator+=(const Buffer &b);
      const Buffer operator+(const Buffer &b) const;
      
      // write() completely empties the buffer 
      void write(const char *in_data, unsigned int bytes);
      void write(std::string &in_string);
      
      // all integers are converted to big endian
      void append(Buffer &append);
      void append(const void *in_data, unsigned int bytes);
      void append(const std::string &in_string);
      
      // append unsigned integers
      void append(const uint8_t in);
      void append(const uint16_t in);
      void append(const uint32_t in);
      void append(const uint64_t in);

      // get unsigned integers
      uint8_t get_uint8();
      uint16_t get_uint16();
      uint32_t get_uint32();
      uint64_t get_uint64();

      // return pointer to constant data
      // ( to the beginning of the data )
      const char *get_data();
      
      bool get_data(void *out, unsigned int bytes);
      bool get_string(std::string &str, unsigned int bytes);
      
      // "rewind" cursor back to the beginning
      bool rewind();

      unsigned int size();
      unsigned int cursor_position();
      
      // this is mostly used for debugging the byte array(buffer)
      void write_to_file(std::string filename);
      
      // good if no errors?
      bool is_good();
      
      // bad if other than zero
      int is_bad();
  };
}

#endif

Implementation

buffer.cpp

// Buffer class implementation
// Last edit: 2013-01-03
// By: Juha Wisakanto
// <djwk@lnxdev.com>

#include <iostream>
#include <fstream>
#include <cstring>
#include <cstdlib>
#include <endian.h>
#include "buffer.h"

using namespace BUFFER;

// increase buffer size and return pointer to last empty byte
char *Buffer::m_extend(const unsigned int bytes) { 
  unsigned int tot_size = m_size + bytes;
  char *temp;
  if((temp = (char *) realloc(m_data, tot_size))) {
    m_data = temp;
    m_cursor = &m_data[m_cursor_position];
    temp = &m_data[m_size];
    m_size = tot_size;
  }
  else {
    temp = 0;
    m_error = MEMORY_ALLOCATION;
  }
  return temp;
}

bool Buffer::m_request_bytes(const unsigned int bytes) {
  if(m_size - m_cursor_position >= bytes)
    return true;
  else
    return false;
}

void Buffer::m_cursor_forward(const unsigned int bytes) {
  if(m_cursor_position + bytes <= m_size) {
    m_cursor_position += bytes;
    m_cursor = &m_data[m_cursor_position];
  }
  else
    m_error = CURSOR_OUT_OF_BOUNDS;
}

void Buffer::m_cursor_reverse(const unsigned int bytes) {
  if(m_cursor_position >= bytes) {
    m_cursor_position -= bytes;
    m_cursor = &m_data[m_cursor_position];
  }
  else
    m_error = CURSOR_OUT_OF_BOUNDS;
}

bool Buffer::m_cursor_begin() {
  if(m_data != 0) {
    m_cursor_position = 0;
    m_cursor = &m_data[m_cursor_position];
    return true;
  }
  else
    return false;
}

void Buffer::m_show_status() {
  using std::cout;
  using std::endl;
  cout << "buffer size: " << m_size << " bytes" << endl;
  cout << "buffer error: " << m_error << endl;
}

Buffer::Buffer()
{
  m_data = (char *)malloc(0);
  m_size = 0;
  m_error = NONE;
  m_cursor = m_data;
  m_cursor_position = 0;
}

Buffer::~Buffer()
{
  free(m_data);
}

Buffer::Buffer(const Buffer &b)
{
  m_size = b.m_size;
  m_data = (char *)malloc(m_size);
  memcpy(m_data, b.m_data, m_size);
  m_error = b.m_error;
  m_cursor = m_data;
}

Buffer::Buffer(char *c, unsigned int bytes)
{
  m_data = (char *)malloc(bytes);
  m_size = bytes;
  m_data = (char *)memcpy(m_data, c, bytes);
  m_error = NONE;
  m_cursor = m_data;
}

Buffer &Buffer::operator=(const Buffer &b)
{
  if(this == &b)
    return *this;
  free(m_data);
  m_data = (char *)malloc(b.m_size);
  m_size = b.m_size;
  m_cursor = b.m_cursor;
  m_cursor_position = b.m_cursor_position;
  m_error = b.m_error;
  memcpy(m_data, b.m_data, b.m_size);
  return *this;
}

Buffer &Buffer::operator+=(const Buffer &b)
{
  unsigned int tot_size = m_size + b.m_size;
  m_data = (char *) realloc(m_data, tot_size);
  char *ptr = &m_data[m_size];
  memcpy(ptr, b.m_data, b.m_size);
  m_size = tot_size;
  return *this;
}

const Buffer Buffer::operator+(const Buffer &b) const
{
  Buffer result = *this;
  result += b;
  return result;
}

void Buffer::append(Buffer &appending)
{
  *this += appending;
}

void Buffer::write(const char *in_data, unsigned int bytes)
{
  free(m_data);
  m_size = bytes;
  m_data = (char *)malloc(bytes);
  m_data = (char *)memcpy(m_data, in_data, bytes);
}

void Buffer::write(std::string &in_string) {
  Buffer::write(in_string.c_str(), in_string.size());
}

void Buffer::append(const void *in_data, unsigned int bytes) {
  char *pointer = m_extend(bytes);
  memcpy(pointer, in_data, bytes);
}

void Buffer::append(const std::string &in_string) {
  unsigned int size = in_string.size();
  char *in = 0;
  in = (char *)malloc(size);
  memcpy(in, in_string.c_str(), size);
  Buffer::append(in, size);
  free(in);
}

// unsigned integers
// also converts to network-byte-order, big-endian
void Buffer::append(const uint8_t in)
{
  char *pointer = m_extend(1);
  memcpy(pointer, &in, 1);
}

void Buffer::append(const uint16_t in)
{
  char *pointer;
  uint16_t in_be = htobe16(in);
  pointer = m_extend(2);
  memcpy(pointer, &in_be, 2);
}

void Buffer::append(const uint32_t in)
{
  char *pointer;
  uint32_t in_be = htobe32(in);
  pointer = m_extend(4);
  memcpy(pointer, &in_be, 4);
}

void Buffer::append(const uint64_t in)
{ 
  char *pointer;
  uint64_t in_be = htobe64(in);
  pointer = m_extend(8);
  memcpy(pointer, &in_be, 8);
}

// get unsigned integers from buffer
// and convert back to host-byte-order
uint8_t Buffer::get_uint8()
{
  uint8_t out = 0;
  memcpy(&out, m_cursor, 1);
  m_cursor_forward(1);
  return out;
}

uint16_t Buffer::get_uint16()
{
  uint16_t out = 0;
  memcpy(&out, m_cursor, 2);
  m_cursor_forward(2);
  out = be16toh(out);
  return out;
}

uint32_t Buffer::get_uint32()
{
  uint32_t out = 0;
  memcpy(&out, m_cursor, 4);
  m_cursor_forward(4);
  out = be32toh(out);
  return out;
}

uint64_t Buffer::get_uint64()
{
  uint64_t out = 0;
  memcpy(&out, m_cursor, 8);
  m_cursor_forward(8);
  out = be64toh(out);
  return out;
}

const char *Buffer::get_data()
{
  return m_data;
}

bool Buffer::get_data(void *out, unsigned int bytes)
{
  if(m_request_bytes(bytes)) {
    memcpy(out, m_cursor, bytes);
    m_cursor_forward(bytes);
    return true;
  }
  else
    return false;
}

bool Buffer::get_string(std::string &str, unsigned int bytes) 
{
  if(m_request_bytes(bytes)) {
    char *get = (char *)malloc(bytes + 1);
    memcpy(get, m_cursor, bytes);
    get[bytes] = '�';
    str = get;
    m_cursor_forward(bytes);
    free(get);
    return true;
  }
  else {
    m_error = MEMORY_OVERFLOW;
    return false;
  }
}

bool Buffer::rewind() {
  if(m_cursor_begin())
    return true;
  else
    return false;
}

unsigned int Buffer::size() {
  return m_size;
}

unsigned int Buffer::cursor_position() {
  return m_cursor_position;
}

void Buffer::write_to_file(std::string filename)
{
  std::cout << "Writing file (" << filename << " " << m_size << " bytes): ";
  std::fstream output;
  output.open(filename.c_str(), std::ios::out | std::ios::binary);
  if(output.is_open()) {
    output.write(m_data, m_size);
    std::cout << "ok.n";
  }
  else {
    std::cout << "failed to open filen";
    if(output.fail())
    std::cout << "fail or bad bit is setn";
  }
  output.close();
}

bool Buffer::is_good() {
  if(m_error)
    return false;
  else
    return true;
}

int Buffer::is_bad() {
  return m_error;
}


syntax highlighted by Google-Code-Prettify