Electively serialize to/from XML or binary in C++

Binary Deserialization

Serialization based on binary streams of data can become quite sophisticated, but I decided to go for a relatively easy approach which implies that the size of the data is known a priori. For deserialization, this is pretty simple – for example, just take the size of the file -, so we’ll start with that.

#ifndef VTE_CORE_BINARYDESERIALIZER_H
#define VTE_CORE_BINARYDESERIALIZER_H
#include <core/Types.h>
#include "Deserializer.h"
namespace vte
{
namespace core
{
class BinaryDeserializer : public Deserializer
{
public:
	BinaryDeserializer( u8* buffer, u32 bufferSize );
	~BinaryDeserializer();
	void beginAttribute( const std::string& name )
	{
	}
	u32 readNumberOfElements();
	void readParameter( const std::string& name, std::string& value );
	void readParameter( const std::string& name, s32& value, std::string& readable );
	void readParameter( const std::string& name, bool& value );
	void readParameter( const std::string& name, u32& value );
	void readParameter( const std::string& name, f32& value );
	void readParameter( const std::string& name, vec3& value );
	void readParameter( const std::string& name, irr::video::SColorf& value );
	void endAttribute()
	{
	}
private:
	u8* buffer;
	u32 bufferSize;
	u32 pointer;
	bool overrun;
};
} // namespace core
} // namespace vte
#endif

As you can see, beginAttribute and endAttribute have empty implementations since they are not required for binary deserialization.
The constructor takes a pointer to the data buffer and the buffer size which are saved in the buffer and bufferSize attributes. Moreover, it keeps track of its current reading position in pointer and whether a buffer overrun has occurred in overrun.

vte::core::BinaryDeserializer::BinaryDeserializer( u8* buffer, u32 bufferSize )
:	buffer( buffer ),
	bufferSize( bufferSize ),
	pointer( 0 ),
	overrun( false )
{
}
vte::core::BinaryDeserializer::~BinaryDeserializer()
{
}

readNumberOfElements first checks whether an overrun has already occurred or will occur if it attempts to read the next four bytes from the buffer. If possible, it reads four bytes into a variable of type u32 (which is a signed int) and pushes the pointer four bytes further.
This principle repeats for all data types which are read using the readParameter methods. The version for string attributes first reads the length of the string and then the string data into a temporary buffer to eventually create a string object; the version for bool reads a single byte which is expected to has a value of either 0 for “false” or anything else for “true”.

u32 vte::core::BinaryDeserializer::readNumberOfElements()
{
	VTE_ASSERT( !overrun );
	if( overrun || pointer + 4 > bufferSize )
	{
		overrun = true;
		return 0;
	}
	u32 value;
	memcpy( &value, (void*)( buffer + pointer ), 4 );
	pointer += 4;
	return value;
}
void vte::core::BinaryDeserializer::readParameter( const std::string& name, std::string& value )
{
	VTE_ASSERT( !overrun );
	u32 len = readNumberOfElements();
	if( overrun || pointer + len > bufferSize )
	{
		overrun = true;
		return;
	}
	if( len > 0 )
	{
		value.resize( len );
		memcpy( (void*) value.data(), (void*)( buffer + pointer ), len );
	}
	pointer += len;
}
void vte::core::BinaryDeserializer::readParameter( const std::string& name, bool& value )
{
	VTE_ASSERT( !overrun );
	if( overrun || pointer + 1 > bufferSize )
	{
		overrun = true;
		return;
	}
	value = buffer[ pointer ] == 0 ? false : true;
	pointer++;
}

4 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *