view C++/Pack.h @ 303:c019f194475a

MFS
author David Demelier <markand@malikania.fr>
date Sat, 15 Nov 2014 13:13:31 +0100
parents 2935e07ddb88
children
line wrap: on
line source

/*
 * Pack.h -- binary data serialization
 *
 * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#ifndef _PACK_H_
#define _PACK_H_

#include <cstdint>
#include <fstream>
#include <memory>
#include <sstream>
#include <string>

/**
 * @class Pack
 * @brief Serialize binary data to files
 *
 * This class write and read binary data from files. It currently
 * support:
 *	uint8_t,
 *	uint16_t,
 *	uint32_t,
 *	uint64_t
 */
class Pack {
private:
	template <typename T>
	struct IsContainer {
		using Yes	= char [1];
		using No	= char [2];

		template <typename U>
		static constexpr Yes &test(typename U::value_type *);

		template <typename U>
		static constexpr No &test(...);

		static constexpr const bool value = sizeof (test<T>(0)) == sizeof (Yes);
	};

	friend class PackWriter;
	friend class PackReader;

public:
	/**
	 * @enum Endian
	 * @brief Endian mode
	 */
	enum Endian {
		Little,		//! Little endian
		Big		//! Big endian
	};

public:
	/**
	 * Host system endian mode.
	 */
	static const Endian mode;

	/**
	 * @struct TypeInfo
	 * @brief Type information
	 *
	 * Used for conversions.
	 */
	template <typename T>
	struct TypeInfo {
		static constexpr const bool convertible{false};
		static constexpr const bool serializable{false};
	};

	/**
	 * Helper to mark a specialization convertible.
	 *
	 * Already done for:
	 *	uint8_t
	 *	uint16_t
	 *	uint32_t
	 *	uint64_t
	 *
	 * The specialization must have the following function:
	 *
	 *	static void convert(T &value) noexcept
	 */
	struct Convertible {
		static constexpr const bool convertible{true};
	};

	/**
	 * Helper to mark a specialization serializable.
	 *
	 * The specialisation must have the following functions:
	 *
	 *	static void serialize(PackWriter &writer, const T &)
	 *	static void unserialize(PackReader &reader, T &)
	 */
	struct Serializable {
		static constexpr const bool serializable{true};
	};

	/**
	 * Convert data inplace.
	 *
	 * @param value the value
	 */
	template <typename T>
	static inline void convert(T &value) noexcept
	{
		static_assert(TypeInfo<T>::convertible, "unsupported type");

		TypeInfo<T>::convert(value);
	}
};

/**
 * @class PackReader
 * @brief Base abstract reader class
 */
class PackReader {
protected:
	Pack::Endian m_endian;

	PackReader(Pack::Endian endian);

	virtual std::istream &stream() = 0;

public:
	/**
	 * Default destructor.
	 */
	virtual ~PackReader() = default;

	/**
	 * Read a primitive convertible type.
	 *
	 * @param value the value destination
	 * @return *this
	 */
	template <typename T, typename std::enable_if<Pack::TypeInfo<T>::convertible>::type * = nullptr>
	PackReader &operator>>(T &value)
	{
		stream().read(reinterpret_cast<char *>(&value), sizeof (T));

		if (m_endian != Pack::mode)
			Pack::convert(value);
				
		return *this;
	}

	/**
	 * Read a serializable type.
	 *
	 * @param value the value destination
	 * @return *this
	 */
	template <typename T, typename std::enable_if<Pack::TypeInfo<T>::serializable>::type * = nullptr>
	PackReader &operator>>(T &value)
	{
		Pack::TypeInfo<T>::unserialize(*this, value);

		return *this;
	}

	/**
	 * Read an array.
	 *
	 * This operator is a little bit tricky because you don't  know in
	 * advance how much data you want to read. Because of that, this
	 * function looks the capacity of the container and reads that number
	 * of data.
	 *
	 * Because it looks for capacity, you can't use a container which
	 * already have some data, they will be overriden.
	 *
	 * If this is a concern, you should roll your own loop to fill up
	 * your container.
	 *
	 * @param container the container (all previous data will be lost)
	 * @return *this
	 */
	template <typename T, typename std::enable_if<Pack::IsContainer<T>::value>::type * = nullptr>
	PackReader &operator>>(T &container)
	{
		typename T::value_type v;

		T copy;

		for (size_t i = 0; i < container.capacity(); ++i) {
			(*this) >> v;
			copy.push_back(v);
		}

		container = std::move(copy);

		return *this;
	}
};

/**
 * @class PackWriter
 * @brief Base abstract writer class
 */
class PackWriter {
protected:
	Pack::Endian m_endian;

	PackWriter(Pack::Endian endian);

	virtual std::ostream &stream() = 0;

public:
	/**
	 * Default destructor.
	 */
	virtual ~PackWriter() = default;

	/**
	 * Write a convertible type to the stream.
	 *
	 * @param value the value
	 * @return *this
	 */
	template <typename T, typename std::enable_if<Pack::TypeInfo<T>::convertible>::type * = nullptr>
	PackWriter &operator<<(T value)
	{
		if (m_endian != Pack::mode)
			Pack::convert(value);

		stream().write(reinterpret_cast<const char *>(&value), sizeof (T));
				
		return *this;
	}

	/**
	 * Write a serializable type to the stream.
	 *
	 * @param value the value
	 * @return *this
	 */
	template <typename T, typename std::enable_if<Pack::TypeInfo<T>::serializable>::type * = nullptr>
	PackWriter &operator<<(const T &value)
	{
		Pack::TypeInfo<T>::serialize(*this, value);

		return *this;
	}

	/**
	 * Write a container to the stream.
	 *
	 * @param container the container
	 * @return *this
	 */
	template <typename T, typename std::enable_if<Pack::IsContainer<T>::value>::type * = nullptr>
	PackWriter &operator<<(const T &container)
	{
		for (const auto &v : container)
			(*this) << v;

		return *this;
	}
};

/**
 * @class PackFileReader
 * @brief Extract binary data from a file
 */
class PackFileReader : public PackReader {
private:
	std::ifstream m_in;

protected:
	std::istream &stream() override;

public:
	/**
	 * Read a file.
	 *
	 * @param path the path
	 * @param endian the endian requested
	 */
	PackFileReader(const std::string &path, Pack::Endian endian);
};

/**
 * @class PackStringReader
 * @brief Extract binary data from a string
 */
class PackStringReader : public PackReader {
private:
	std::istringstream m_in;

	std::istream &stream() override;

public:
	/**
	 * Read a string.
	 *
	 * @param input the input string
	 * @param endian the endian requested
	 */
	PackStringReader(std::string input, Pack::Endian endian);
};

/**
 * @class PackFileWriter
 * @brief Write binary data to a string
 */
class PackFileWriter : public PackWriter {
private:
	std::ofstream m_out;

protected:
	std::ostream &stream() override;

public:
	/**
	 * Write to a file.
	 *
	 * @param path the path
	 * @param endian the endian requested
	 */
	PackFileWriter(const std::string &path, Pack::Endian endian);
};

/**
 * @class PackStringWriter
 * @brief Write binary data to a string
 */
class PackStringWriter : public PackWriter {
private:
	std::ostringstream m_out;

	std::ostream &stream() override;

public:
	/**
	 * Write to a string
	 *
	 * @param endian the endian requested
	 */
	PackStringWriter(Pack::Endian endian);

	/**
	 * The current buffer. Returns a copy of the string.
	 *
	 * @return the string
	 */
	std::string buffer() const;
};

template <>
struct Pack::TypeInfo<uint8_t> : public Pack::Convertible {
	static inline void convert(uint8_t &) noexcept
	{
		// uint8_t are endian independent
	}
};

template <>
struct Pack::TypeInfo<uint16_t> : public Pack::Convertible {
	static inline void convert(uint16_t &v)
	{
		v = (((v >> 8) & 0x00FFL) | ((v << 8) & 0xFF00L));
	}
};

template <>
struct Pack::TypeInfo<uint32_t> : public Pack::Convertible {
	static inline void convert(uint32_t &v)
	{
		v = ((((v) >> 24) & 0x000000FFL)
		    | (((v) >> 8)  & 0x0000FF00L)
		    | (((v) << 8)  & 0x00FF0000L)
		    | (((v) << 24) & 0xFF000000L));
	}
};

template <>
struct Pack::TypeInfo<uint64_t> : public Pack::Convertible {
	static inline void convert(uint64_t &v)
	{
		v = ((((v) & 0xff00000000000000ull) >> 56)
			| (((v) & 0x00ff000000000000ull) >> 40)
			| (((v) & 0x0000ff0000000000ull) >> 24)
			| (((v) & 0x000000ff00000000ull) >> 8 )
			| (((v) & 0x00000000ff000000ull) << 8 )
			| (((v) & 0x0000000000ff0000ull) << 24)
			| (((v) & 0x000000000000ff00ull) << 40)
			| (((v) & 0x00000000000000ffull) << 56));
	}
};

#endif // !_PACK_H_