added endianness support

This commit is contained in:
fraillt
2017-07-24 16:08:33 +03:00
parent 014b072b96
commit 4ad65f8384
28 changed files with 1081 additions and 669 deletions

39
CHANGELOG.md Normal file
View File

@@ -0,0 +1,39 @@
<a name="2.0.0"></a>
# [2.0.0](https://github.com/fraillt/bitsery/compare/v1.1.1...v2.0.0) (2017-07-25)
### Features
* Endianness support, default network configuration is *little endian*
* added user extensible function **ext**, to work with objects that require different serialization/deserialization path (e.g. pointers)
* **optional** extension (for *ext* function), to work with *std::optional* types
### Bug Fixes
* *align* method fixed in *BufferReader*
### Other notes
* file structure changed, added *details* folder.
* no longer support for implicit size converions for all functions (*value*, *array*, *container*), instead added helper functions with specific size, to avoid typing *s.template value<1>...* within serialization function body
* changed parameters order for all functions that use custom function (lambda)
* *BufferReader* and *BufferWriter* is now alias types for real types *BasicBufferReader/Writer&lt;DefaultConfig&gt;* (*DefaultConfig* is defined in *common.h*)
<a name="1.1.1"></a>
# [1.1.1](https://github.com/fraillt/bitsery/compare/v1.0.0...v1.1.1) (2017-02-23)
### Notes
* changed folder structure
* added more BufferReader constructors
# 1.0.0 (2017-02-22)
### Features
Serialization functions:
* **value** - primitive types (ints, enums, floats)
* **container** - dynamic size containers
* **array** - fixed size containers
* **text** - for c-array and std::string
* **range** - compresion for primitive types (e.g. int between [255..512] will take up 8bits
* **substitution** - default value from list (e.g. 4d vector, that is most of the time equals to [0,0,0,1] can store only 1bit)
* **boolBit**/**boolByte** - serialize bool, as 1bit or 1byte.

View File

@@ -1,23 +1,12 @@
cmake_minimum_required(VERSION 3.2)
set(PROJECT_NAME bitsery)
set(TEST_PROJECT_NAME ${PROJECT_NAME}_tests)
project(${PROJECT_NAME})
project(bitsery)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -g -O0 -fprofile-arcs -ftest-coverage")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/ext)
if(CMAKE_COMPILER_IS_GNUCXX)
#include(CodeCoverage)
#setup_target_for_coverage(${PROJECT_NAME}_coverage ${TEST_PROJECT_NAME} coverage)
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(examples)
#add tests
enable_testing()
add_subdirectory(tests)

View File

@@ -4,23 +4,22 @@ Header only C++ binary serialization library.
It is designed around the networking requirements for multiplayer real-time fast paced games as first person shooters.
All cross-platform requirements are enforced at compile time, so serialized data do not store any run-time type information and is as small as possible.
## Status
## Features
**bitsery** is in pre-release state and is looking for your feedback.
It has basic features, serialize arithmetic types, enums, containers and text, and few advanced features like value ranges and default values (substitution), but is still missing functions for delta changes and geometry compression.
> Current version do not handle Big/Little Endianness.
> Error handling on deserialization is not tested.
**bitsery** is looking for your feedback.
* Has configurable endianess support.
* Can serialize all common types: arithmetic types, enums, containers and text.
* Has advanced features like value ranges and default values.
* Is extensible, for types that requires different serialization and deserialization logic (e.g. pointers)
* Has error checking on deserialization, and asserts on serialization runtime errors.
## Example
```cpp
#include <iostream>
#include <vector>
#include <bitsery/bitsery.h>
enum class MyEnum { V1,V2,V3 };
enum class MyEnum:uint16_t { V1,V2,V3 };
struct MyStruct {
int i;
uint32_t i;
MyEnum e;
std::vector<float> fs;
};
@@ -28,10 +27,10 @@ struct MyStruct {
//define how object should be serialized/deserialized
SERIALIZE(MyStruct) {
return s.
value(o.i).
value(o.e).
container(o.fs, 100);
}
value4(o.i).
value2(o.e).
container4(o.fs, 10);
};
using namespace bitsery;
@@ -62,12 +61,16 @@ int main() {
//deserialize same object, can also be invoked like this: serialize(des, data)
des.object(res);
//check is equal
std::cout << "is equal: " << (data.fs == res.fs && data.i == res.i && data.e == res.e) << std::endl;
assert(data.fs == res.fs && data.i == res.i && data.e == res.e);
}
```
## Todo list
> Support wider range for underlying buffer value type for BufferWriter and BufferReader (currently must be unsigned 1byte, e.g. uint8_t).
> Delta serialization and deserialization is in progress.
## Platforms
This library was tested on

View File

@@ -25,14 +25,11 @@ cmake_minimum_required(VERSION 3.2)
include_directories(${CMAKE_SOURCE_DIR}/include)
file(GLOB EXAMPLE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
file(GLOB ExampleFiles ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
FOREACH(EXAMPLE ${EXAMPLE_FILES})
get_filename_component(EXAMPLE_NAME ${EXAMPLE} NAME_WE)
add_executable(${EXAMPLE_NAME} ${EXAMPLE})
set_property(TARGET ${EXAMPLE_NAME} PROPERTY CXX_STANDARD 14)
set_property(TARGET ${EXAMPLE_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)
FOREACH(ExampleFile ${ExampleFiles})
get_filename_component(ExampleName ${ExampleFile} NAME_WE)
add_executable(${ExampleName} ${ExampleFile})
ENDFOREACH()

View File

@@ -20,14 +20,11 @@
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#include <iostream>
#include <vector>
#include <bitsery/bitsery.h>
enum class MyEnum { V1,V2,V3 };
enum class MyEnum:uint16_t { V1,V2,V3 };
struct MyStruct {
int i;
uint32_t i;
MyEnum e;
std::vector<float> fs;
};
@@ -36,9 +33,9 @@ struct MyStruct {
SERIALIZE(MyStruct) {
return s.
value4(o.i).
value4(o.e).
container4(o.fs, 100);
}
value2(o.e).
container4(o.fs, 10);
};
using namespace bitsery;
@@ -69,7 +66,5 @@ int main() {
//deserialize same object, can also be invoked like this: serialize(des, data)
des.object(res);
//check is equal
std::cout << "is equal: " << (data.fs == res.fs && data.i == res.i && data.e == res.e) << std::endl;
assert(data.fs == res.fs && data.i == res.i && data.e == res.e);
}

View File

@@ -146,7 +146,8 @@ FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname)
# Capturing lcov counters and generating report
COMMAND ${LCOV_PATH} --directory . --capture --output-file ${coverage_info}
COMMAND ${LCOV_PATH} --remove ${coverage_info} 'tests/*' '/usr/*' --output-file ${coverage_cleaned}
#extract only /include/bitsery directory
COMMAND ${LCOV_PATH} --extract ${coverage_info} '*include/bitsery*' --output-file ${coverage_cleaned}
COMMAND ${GENHTML_PATH} -o ${_outputname} ${coverage_cleaned}
COMMAND ${CMAKE_COMMAND} -E remove ${coverage_info} ${coverage_cleaned}

18
ext/LinkTestLIb.cmake Normal file
View File

@@ -0,0 +1,18 @@
function(LinkTestLib TargetName)
add_dependencies(${TargetName} googletest)
if(NOT WIN32 OR MINGW)
FOREACH(LibName ${GTestLinkLibNames})
target_link_libraries(${TargetName} ${GTestLibsDir}/lib${LibName}.a )
ENDFOREACH()
else()
FOREACH(LibName ${GTestLinkLibNames})
target_link_libraries(${TargetName}
debug ${GTestLibsDir}/DebugLibs/${CMAKE_FIND_LIBRARY_PREFIXES}${LibName}${CMAKE_FIND_LIBRARY_SUFFIXES}
optimized ${GTestLibsDir}/ReleaseLibs/${CMAKE_FIND_LIBRARY_PREFIXES}${LibName}${CMAKE_FIND_LIBRARY_SUFFIXES})
ENDFOREACH()
endif()
target_link_libraries(${TargetName} ${CMAKE_THREAD_LIBS_INIT})
endfunction(LinkTestLib)

View File

@@ -24,32 +24,32 @@ cmake_minimum_required(VERSION 3.2)
project(gtest_builder C CXX)
include(ExternalProject)
set(GTEST_FORCE_SHARED_CRT ON)
set(GTEST_DISABLE_PTHREADS OFF)
set(ForceSharedCrt ON)
set(DisablePThreads OFF)
if(MINGW)
set(GTEST_DISABLE_PTHREADS ON)
set(DisablePThreads ON)
endif()
if (${USE_GMOCK})
if (${UseGMock})
message("use gmock")
set(BUILD_ARGS -DBUILD_GTEST=OFF -DBUILD_GMOCK=ON)
set(BuildArgs -DBUILD_GTEST=OFF -DBUILD_GMOCK=ON)
else ()
message("use gtest only")
set(BUILD_ARGS -DBUILD_GTEST=ON -DBUILD_GMOCK=OFF)
set(BuildArgs -DBUILD_GTEST=ON -DBUILD_GMOCK=OFF)
endif()
if (WIN32 AND NOT MINGW)
set(BUILD_ARGS ${BUILD_ARGS}
set(BuildArgs ${BuildArgs}
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:PATH=DebugLibs
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=ReleaseLibs)
endif()
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
CMAKE_ARGS ${BUILD_ARGS}
-Dgtest_force_shared_crt=${GTEST_FORCE_SHARED_CRT}
-Dgtest_disable_pthreads=${GTEST_DISABLE_PTHREADS}
CMAKE_ARGS ${BuildArgs}
-Dgtest_force_shared_crt=${ForceSharedCrt}
-Dgtest_disable_pthreads=${DisablePThreads}
PREFIX "${CMAKE_CURRENT_BINARY_DIR}"
# disable update command
UPDATE_COMMAND ""
@@ -60,20 +60,20 @@ ExternalProject_Add(googletest
#export variables
ExternalProject_Get_Property(googletest source_dir)
ExternalProject_Get_Property(googletest binary_dir)
if (${USE_GMOCK})
if (${UseGMock})
# need to include both googletest and googlemock
set(GTEST_INCLUDE_DIRS ${source_dir}/googlemock/include ${source_dir}/googletest/include PARENT_SCOPE)
set(GTEST_LIBS_DIR ${binary_dir}/googlemock PARENT_SCOPE)
set(GTEST_LIBNAME gmock PARENT_SCOPE)
set(GTEST_MAIN_LIBNAME gmock_main PARENT_SCOPE)
set(GTEST_LINK_LIBNAMES gmock_main PARENT_SCOPE)
set(GTestIncludeDirs ${source_dir}/googlemock/include ${source_dir}/googletest/include PARENT_SCOPE)
set(GTestLibsDir ${binary_dir}/googlemock PARENT_SCOPE)
set(GTestLibName gmock PARENT_SCOPE)
set(GTestMainLibName gmock_main PARENT_SCOPE)
set(GTestLinkLibNames gmock_main PARENT_SCOPE)
else()
set(GTEST_INCLUDE_DIRS ${source_dir}/googletest/include PARENT_SCOPE)
set(GTEST_LIBS_DIR ${binary_dir}/googletest PARENT_SCOPE)
set(GTEST_LIBNAME gtest PARENT_SCOPE)
set(GTEST_MAIN_LIBNAME gtest_main PARENT_SCOPE)
set(GTestIncludeDirs ${source_dir}/googletest/include PARENT_SCOPE)
set(GTestLibsDir ${binary_dir}/googletest PARENT_SCOPE)
set(GTestLibName gtest PARENT_SCOPE)
set(GTestMainLibName gtest_main PARENT_SCOPE)
# need to include both libs gtest and gtest_main
set(GTEST_LINK_LIBNAMES gtest gtest_main PARENT_SCOPE)
set(GTestLinkLibNames gtest gtest_main PARENT_SCOPE)
endif()

View File

@@ -24,9 +24,9 @@
#ifndef BITSERY_BITSERY_H
#define BITSERY_BITSERY_H
#define BITSERY_MAJOR_VERSION 1
#define BITSERY_MINOR_VERSION 1
#define BITSERY_PATCH_VERSION 1
#define BITSERY_MAJOR_VERSION 2
#define BITSERY_MINOR_VERSION 0
#define BITSERY_PATCH_VERSION 0
#define BITSERY_QUOTE_MACRO(name) #name
#define BITSERY_BUILD_VERSION_STR(major,minor, patch) \

View File

@@ -27,32 +27,37 @@
#include "common.h"
#include <cassert>
#include <algorithm>
#include <vector>
namespace bitsery {
struct BufferReader {
template <typename Config>
struct BasicBufferReader {
using ValueType = typename Config::BufferValueType;
using ScratchType = typename Config::BufferScrathType;
using value_type = uint8_t;
BufferReader(const std::vector<uint8_t> &buf) : _pos{buf.data()}, _end{buf.data() + buf.size()} {
}
BufferReader(const uint8_t* data, size_t size) : _pos{data}, _end{data + size}
BasicBufferReader(const ValueType* data, size_t size) : _pos{data}, _end{data + size}
{
static_assert(std::is_unsigned<ValueType>(), "Config::BufferValueType must be unsigned");
static_assert(std::is_unsigned<ScratchType>(), "Config::BufferScrathType must be unsigned");
static_assert(sizeof(ValueType)*2 == sizeof(ScratchType), "ScratchType must be 2x bigger than value type");
static_assert(sizeof(ValueType) == 1, "currently only supported BufferValueType is 1 byte");
}
explicit BasicBufferReader(const std::vector<ValueType> &buf) : BasicBufferReader(buf.data(), buf.size()) {
}
template <size_t N>
BufferReader(const uint8_t (&data)[N]): _pos{data}, _end{data + N}
explicit BasicBufferReader(const ValueType (&data)[N]): BasicBufferReader(data, N)
{
}
BufferReader(const BufferReader&) = delete;
BufferReader& operator=(const BufferReader& ) = delete;
BufferReader(BufferReader&&) noexcept = default;
BufferReader& operator=(BufferReader&&) noexcept = default;
~BufferReader() noexcept = default;
BasicBufferReader(const BasicBufferReader&) = delete;
BasicBufferReader& operator=(const BasicBufferReader& ) = delete;
BasicBufferReader(BasicBufferReader&&) noexcept = default;
BasicBufferReader& operator=(BasicBufferReader&&) noexcept = default;
~BasicBufferReader() noexcept = default;
template<size_t SIZE, typename T>
@@ -62,7 +67,7 @@ namespace bitsery {
using UT = typename std::make_unsigned<T>::type;
return !m_scratch
? directRead(&v, 1)
: readBits(reinterpret_cast<UT &>(v), BITS_SIZE<T>);
: readBits(reinterpret_cast<UT &>(v), details::BITS_SIZE<T>);
}
template<size_t SIZE, typename T>
@@ -70,16 +75,15 @@ namespace bitsery {
static_assert(std::is_integral<T>(), "");
static_assert(sizeof(T) == SIZE, "");
if (!m_scratchBits) {
if (!m_scratchBits)
return directRead(buf, count);
} else {
using UT = typename std::make_unsigned<T>::type;
//todo improve implementation
const auto end = buf + count;
for (auto it = buf; it != end; ++it) {
if (!readBits(reinterpret_cast<UT &>(*it), BITS_SIZE<T>))
return false;
}
using UT = typename std::make_unsigned<T>::type;
//todo improve implementation
const auto end = buf + count;
for (auto it = buf; it != end; ++it) {
if (!readBits(reinterpret_cast<UT &>(*it), details::BITS_SIZE<T>))
return false;
}
return true;
}
@@ -88,7 +92,6 @@ namespace bitsery {
template<typename T>
bool readBits(T &v, size_t bitsCount) {
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
assert(bitsCount <= BITS_SIZE<T>);
const auto bytesRequired = bitsCount > m_scratchBits
? ((bitsCount - 1 - m_scratchBits) >> 3) + 1u
@@ -101,8 +104,8 @@ namespace bitsery {
bool align() {
if (m_scratchBits) {
SCRATCH_TYPE tmp{};
readBitsInternal(tmp, BITS_SIZE<value_type> - m_scratchBits);
ScratchType tmp{};
readBitsInternal(tmp, m_scratchBits);
return tmp == 0;
}
return true;
@@ -113,8 +116,8 @@ namespace bitsery {
}
private:
const value_type* _pos;
const value_type* _end;
const ValueType* _pos;
const ValueType* _end;
template<typename T>
bool directRead(T *v, size_t count) {
@@ -122,28 +125,39 @@ namespace bitsery {
const auto bytesCount = sizeof(T) * count;
if (static_cast<size_t>(std::distance(_pos, _end)) < bytesCount)
return false;
std::copy_n(_pos, bytesCount, reinterpret_cast<value_type *>(v));
//read from buffer, to data ptr,
std::copy_n(_pos, bytesCount, reinterpret_cast<ValueType *>(v));
std::advance(_pos, bytesCount);
//swap each byte if nessesarry
_swapDataBits(v, count, std::integral_constant<bool,
Config::NetworkEndianness != details::getSystemEndianness()>{});
return true;
}
template<typename T>
void _swapDataBits(T *v, size_t count, std::true_type) {
std::for_each(v, std::next(v, count), [this](T& v) { v = details::swap(v); });
}
template<typename T>
void _swapDataBits(T *v, size_t count, std::false_type) {
//empty function because no swap is required
}
template<typename T>
void readBitsInternal(T &v, size_t size) {
auto bitsLeft = size;
T res{};
while (bitsLeft > 0) {
auto bits = std::min(bitsLeft, BITS_SIZE<value_type>);
auto bits = std::min(bitsLeft, details::BITS_SIZE<ValueType>);
if (m_scratchBits < bits) {
value_type tmp;
std::copy_n(_pos, 1, reinterpret_cast<value_type *>(&tmp));
std::advance(_pos, 1);
m_scratch |= static_cast<SCRATCH_TYPE>(tmp) << m_scratchBits;
m_scratchBits += BITS_SIZE<value_type>;
ValueType tmp;
directRead(&tmp, 1);
m_scratch |= static_cast<ScratchType>(tmp) << m_scratchBits;
m_scratchBits += details::BITS_SIZE<ValueType>;
}
auto shiftedRes =
static_cast<T>(m_scratch & ((static_cast<SCRATCH_TYPE>(1) << bits) - 1)) << (size - bitsLeft);
static_cast<T>(m_scratch & ((static_cast<ScratchType>(1) << bits) - 1)) << (size - bitsLeft);
res |= shiftedRes;
m_scratch >>= bits;
m_scratchBits -= bits;
@@ -152,13 +166,12 @@ namespace bitsery {
v = res;
}
using SCRATCH_TYPE = typename BIGGER_TYPE<value_type>::type;
SCRATCH_TYPE m_scratch{};
ScratchType m_scratch{};
size_t m_scratchBits{}; ///< Number of bits currently in the scratch buffer. If the user wants to read more bits than this, we have to go fetch another dword from memory.
};
//helper type
using BufferReader = BasicBufferReader<DefaultConfig>;
}
#endif //BITSERY_BUFFER_READER_H

View File

@@ -29,6 +29,8 @@
#include <cassert>
#include <algorithm>
#include <iterator>
#include <utility>
#include <vector>
namespace bitsery {
@@ -38,13 +40,13 @@ namespace bitsery {
void writeBytes(const T &) {
static_assert(std::is_integral<T>(), "");
static_assert(sizeof(T) == SIZE, "");
_bitsCount += BITS_SIZE<T>;
_bitsCount += details::BITS_SIZE<T>;
}
template<typename T>
void writeBits(const T &, size_t bitsCount) {
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
assert(bitsCount <= BITS_SIZE<T>);
assert(bitsCount <= details::BITS_SIZE<T>);
_bitsCount += bitsCount;
}
@@ -52,7 +54,7 @@ namespace bitsery {
void writeBuffer(const T *, size_t count) {
static_assert(std::is_integral<T>(), "");
static_assert(sizeof(T) == SIZE, "");
_bitsCount += BITS_SIZE<T> * count;
_bitsCount += details::BITS_SIZE<T> * count;
}
//get size in bytes
@@ -65,30 +67,34 @@ namespace bitsery {
};
template <typename Config>
struct BasicBufferWriter {
using ValueType = typename Config::BufferValueType;
using ScratchType = typename Config::BufferScrathType;
struct BufferWriter {
using value_type = uint8_t;
explicit BufferWriter(std::vector<uint8_t> &buffer) : _buf{buffer}, _outIt{std::back_inserter(buffer)} {
static_assert(std::is_unsigned<value_type>::value, "");
explicit BasicBufferWriter(std::vector<ValueType> &buffer) : _outIt{std::back_inserter(buffer)} {
static_assert(std::is_unsigned<ValueType>(), "Config::BufferValueType must be unsigned");
static_assert(std::is_unsigned<ScratchType>(), "Config::BufferScrathType must be unsigned");
static_assert(sizeof(ValueType)*2 == sizeof(ScratchType), "ScratchType must be 2x bigger than value type");
static_assert(sizeof(ValueType) == 1, "currently only supported BufferValueType is 1 byte");
}
BufferWriter(const BufferWriter&) = delete;
BufferWriter& operator=(const BufferWriter& ) = delete;
BufferWriter(BufferWriter&&) noexcept = default;
BufferWriter& operator=(BufferWriter&&) noexcept = default;
~BufferWriter() noexcept = default;
BasicBufferWriter(const BasicBufferWriter&) = delete;
BasicBufferWriter& operator=(const BasicBufferWriter& ) = delete;
BasicBufferWriter(BasicBufferWriter&&) noexcept = default;
BasicBufferWriter& operator=(BasicBufferWriter&&) noexcept = default;
~BasicBufferWriter() noexcept = default;
template<size_t SIZE, typename T>
void writeBytes(const T &v) {
static_assert(std::is_integral<T>(), "");
static_assert(sizeof(T) == SIZE, "");
if (!m_scratchBits) {
if (!_scratchBits) {
directWrite(&v, 1);
} else {
using UT = typename std::make_unsigned<T>::type;
writeBits(reinterpret_cast<const UT &>(v), BITS_SIZE<T>);
writeBits(reinterpret_cast<const UT &>(v), details::BITS_SIZE<T>);
}
}
@@ -96,36 +102,36 @@ namespace bitsery {
void writeBuffer(const T *buf, size_t count) {
static_assert(std::is_integral<T>(), "");
static_assert(sizeof(T) == SIZE, "");
if (!m_scratchBits) {
if (!_scratchBits) {
directWrite(buf, count);
} else {
using UT = typename std::make_unsigned<T>::type;
//todo improve implementation
const auto end = buf + count;
for (auto it = buf; it != end; ++it)
writeBits(reinterpret_cast<const UT &>(*it), BITS_SIZE<T>);
writeBits(reinterpret_cast<const UT &>(*it), details::BITS_SIZE<T>);
}
}
template<typename T>
void writeBits(const T &v, size_t bitsCount) {
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
assert(bitsCount <= BITS_SIZE<T>);
assert(0 < bitsCount && bitsCount <= details::BITS_SIZE<T>);
assert(v <= ((1ULL << bitsCount) - 1));
writeBitsInternal(v, bitsCount);
}
void align() {
if (m_scratchBits)
writeBitsInternal(value_type{}, BITS_SIZE<value_type> - m_scratchBits);
if (_scratchBits)
writeBitsInternal(ValueType{}, details::BITS_SIZE<ValueType> - _scratchBits);
}
void flush() {
if (m_scratchBits) {
auto tmp = static_cast<value_type>( m_scratch & bufTypeMask );
if (_scratchBits) {
auto tmp = static_cast<ValueType>( _scratch & _MASK );
directWrite(&tmp, 1);
m_scratch >>= m_scratchBits;
m_scratchBits -= m_scratchBits;
_scratch >>= _scratchBits;
_scratchBits -= _scratchBits;
}
}
@@ -133,57 +139,68 @@ namespace bitsery {
private:
template<typename T>
void directWrite(const T *v, size_t count) {
const auto bytesSize = sizeof(T) * count;
const auto pos = _buf.size();
_buf.resize(pos + bytesSize);
std::copy_n(reinterpret_cast<const value_type *>(v), bytesSize, _buf.data() + pos);
void directWrite(T&& v, size_t count) {
_directWriteSwapTag(std::forward<T>(v), count, std::integral_constant<bool,
Config::NetworkEndianness != details::getSystemEndianness()>{});
}
template<typename T>
void _directWriteSwapTag(const T *v, size_t count, std::true_type) {
std::for_each(v, std::next(v, count), [this](const T& v) {
const auto res = details::swap(v);
std::copy_n(reinterpret_cast<const ValueType*>(&res), sizeof(T), _outIt);
});
}
template<typename T>
void _directWriteSwapTag(const T *v, size_t count, std::false_type) {
std::copy_n(reinterpret_cast<const ValueType*>(v), count * sizeof(T), _outIt);
}
template<typename T>
void writeBitsInternal(const T &v, size_t size) {
constexpr size_t valueSize = details::BITS_SIZE<ValueType>;
auto value = v;
auto bitsLeft = size;
while (bitsLeft > 0) {
auto bits = std::min(bitsLeft, BITS_SIZE<value_type>);
m_scratch |= static_cast<SCRATCH_TYPE>( value ) << m_scratchBits;
m_scratchBits += bits;
if (m_scratchBits >= BITS_SIZE<value_type>) {
auto tmp = static_cast<value_type>(m_scratch & bufTypeMask);
auto bits = std::min(bitsLeft, valueSize);
_scratch |= static_cast<ScratchType>( value ) << _scratchBits;
_scratchBits += bits;
if (_scratchBits >= valueSize) {
auto tmp = static_cast<ValueType>(_scratch & _MASK);
directWrite(&tmp, 1);
m_scratch >>= BITS_SIZE<value_type>;
m_scratchBits -= BITS_SIZE<value_type>;
_scratch >>= valueSize;
_scratchBits -= valueSize;
value >>= BITS_SIZE<value_type>;
value >>= valueSize;
}
bitsLeft -= bits;
}
}
void writeBitsInternal(const value_type &v, size_t size) {
//overload for ValueType, for better performance
void writeBitsInternal(const ValueType &v, size_t size) {
if (size > 0) {
m_scratch |= static_cast<SCRATCH_TYPE>( v ) << m_scratchBits;
m_scratchBits += size;
if (m_scratchBits >= BITS_SIZE<value_type>) {
auto tmp = static_cast<value_type>(m_scratch & bufTypeMask);
_scratch |= static_cast<ScratchType>( v ) << _scratchBits;
_scratchBits += size;
if (_scratchBits >= details::BITS_SIZE<ValueType>) {
auto tmp = static_cast<ValueType>(_scratch & _MASK);
directWrite(&tmp, 1);
m_scratch >>= BITS_SIZE<value_type>;
m_scratchBits -= BITS_SIZE<value_type>;
_scratch >>= details::BITS_SIZE<ValueType>;
_scratchBits -= details::BITS_SIZE<ValueType>;
}
}
}
const value_type bufTypeMask = 0xFF;
using SCRATCH_TYPE = typename BIGGER_TYPE<value_type>::type;
std::vector<value_type> &_buf;
std::back_insert_iterator<std::vector<value_type>> _outIt;
SCRATCH_TYPE m_scratch{};
size_t m_scratchBits{};
//size_t _bufSize{};
const ValueType _MASK = std::numeric_limits<ValueType>::max();
std::back_insert_iterator<std::vector<ValueType>> _outIt;
ScratchType _scratch{};
size_t _scratchBits{};
};
//helper type
using BufferWriter = BasicBufferWriter<DefaultConfig>;
}
#endif //BITSERY_BUFFER_WRITER_H

View File

@@ -21,215 +21,27 @@
//SOFTWARE.
#ifndef BITSERY_COMMON_H
#define BITSERY_COMMON_H
#include <cstdint>
#include "details/buffer_common.h"
namespace bitsery {
template<typename T>
constexpr size_t BITS_SIZE = sizeof(T) << 3;
template<typename T>
struct BIGGER_TYPE {
struct DefaultConfig {
static constexpr EndiannessType NetworkEndianness = EndiannessType::LittleEndian;
using BufferValueType = uint8_t;
using BufferScrathType = uint16_t;
};
template<>
struct BIGGER_TYPE<uint8_t> {
typedef uint16_t type;
};
template<>
struct BIGGER_TYPE<uint16_t> {
typedef uint32_t type;
};
template<>
struct BIGGER_TYPE<uint32_t> {
typedef uint64_t type;
};
template<>
struct BIGGER_TYPE<int8_t> {
typedef int16_t type;
};
template<>
struct BIGGER_TYPE<int16_t> {
typedef int32_t type;
};
template<>
struct BIGGER_TYPE<int32_t> {
typedef int64_t type;
};
template<>
struct BIGGER_TYPE<char> {
typedef int16_t type;
};
template<typename T, typename Enable = void>
struct SAME_SIZE_UNSIGNED_TYPE {
typedef std::make_unsigned_t<T> type;
};
template<typename T>
struct SAME_SIZE_UNSIGNED_TYPE<T, typename std::enable_if<std::is_enum<T>::value>::type> {
typedef std::make_unsigned_t<std::underlying_type_t<T>> type;
};
template<typename T>
struct SAME_SIZE_UNSIGNED_TYPE<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
typedef std::conditional_t<std::is_same<T, float>::value, uint32_t, uint64_t> type;
};
template<typename T>
using SAME_SIZE_UNSIGNED = typename SAME_SIZE_UNSIGNED_TYPE<T>::type;
template<size_t SIZE>
struct ProcessAnyType {
template<typename S, typename T>
static void serialize(S &s, T &&v) {
s.template value<SIZE>(std::forward<T>(v));
}
};
template<>
struct ProcessAnyType<0> {
template<typename S, typename T>
static void serialize(S &s, T &&v) {
s.object(std::forward<T>(v));
}
};
/*
* serializer macro, serialize function specialization that accepts T& and const T&
*/
#define SERIALIZE(ObjectType) \
template <typename S, typename T, typename std::enable_if<std::is_same<T, ObjectType>::value || std::is_same<T, const ObjectType>::value>::type* = nullptr> \
S& serialize(S& s, T& o)
/*
* range functions
*/
template<typename T>
constexpr size_t calcRequiredBits(T min, T max) {
size_t res{};
for (auto diff = max - min; diff > 0; diff >>= 1)
++res;
return res;
}
template<typename T, typename Enable = void>
struct RangeSpec {
constexpr RangeSpec(T minValue, T maxValue)
: min{minValue},
max{maxValue},
bitsRequired{calcRequiredBits(min, max)} {
}
const T min;
const T max;
const size_t bitsRequired;
};
template<typename T>
struct RangeSpec<T, typename std::enable_if<std::is_enum<T>::value>::type> {
constexpr RangeSpec(T minValue, T maxValue) :
min{minValue},
max{maxValue},
bitsRequired{calcRequiredBits(
static_cast<std::underlying_type_t<T>>(min),
static_cast<std::underlying_type_t<T>>(max))} {
}
const T min;
const T max;
const size_t bitsRequired;
};
//this class is used to make default RangeSpec float specialization always prefer constructor with precision
struct BitsConstraint {
explicit constexpr BitsConstraint(size_t bits) : value{bits} {}
const size_t value;
};
template<typename T>
struct RangeSpec<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
constexpr RangeSpec(T minValue, T maxValue, BitsConstraint bits) :
min{minValue},
max{maxValue},
bitsRequired{bits.value} {
}
constexpr RangeSpec(T minValue, T maxValue, T precision) :
min{minValue},
max{maxValue},
bitsRequired{calcRequiredBits<SAME_SIZE_UNSIGNED<T>>({}, ((max - min) / precision))} {
}
const T min;
const T max;
const size_t bitsRequired;
};
template<typename T, typename std::enable_if<std::is_arithmetic<T>::value>::type * = nullptr>
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
return !(r.min > v || v > r.max);
}
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
using VT = std::underlying_type_t<T>;
return !(static_cast<VT>(r.min) > static_cast<VT>(v)
|| static_cast<VT>(v) > static_cast<VT>(r.max));
}
/*
* delta functions
*/
class ObjectMemoryPosition {
public:
template<typename T>
ObjectMemoryPosition(const T &oldObj, const T &newObj)
:ObjectMemoryPosition{reinterpret_cast<const char *>(&oldObj), reinterpret_cast<const char *>(&newObj),
sizeof(T)} {
}
template<typename T>
bool isFieldsEquals(const T &newObjField) {
return *getOldObjectField(newObjField) == newObjField;
}
template<typename T>
const T *getOldObjectField(const T &field) {
auto offset = reinterpret_cast<const char *>(&field) - newObj;
return reinterpret_cast<const T *>(oldObj + offset);
}
private:
ObjectMemoryPosition(const char *objOld, const char *objNew, size_t)
: oldObj{objOld},
newObj{objNew} {
}
const char *oldObj;
const char *newObj;
};
}
#endif //BITSERY_COMMON_H

View File

@@ -40,7 +40,7 @@ namespace bitsery {
_reader{r},
_oldObj{oldObj},
_newObj{newObj},
_objMemPos(std::deque<ObjectMemoryPosition>(1, ObjectMemoryPosition{oldObj, newObj})),
_objMemPos(std::deque<details::ObjectMemoryPosition>(1, details::ObjectMemoryPosition{oldObj, newObj})),
_isNewElement{false} {
};
@@ -134,7 +134,7 @@ namespace bitsery {
const TObj &_oldObj;
const TObj &_newObj;
std::stack<ObjectMemoryPosition> _objMemPos;
std::stack<details::ObjectMemoryPosition> _objMemPos;
bool _isNewElement;
template<typename T>
@@ -173,7 +173,7 @@ namespace bitsery {
*p = *pOld;
--offset;
} else {
_objMemPos.emplace(ObjectMemoryPosition{*pOld, *p});
_objMemPos.emplace(details::ObjectMemoryPosition{*pOld, *p});
fnc(*this, *p);
_objMemPos.pop();
offset = readIndexOffset();

View File

@@ -40,7 +40,7 @@ namespace bitsery {
_writter{w},
_oldObj{oldObj},
_newObj{newObj},
_objMemPos(std::deque<ObjectMemoryPosition>(1, ObjectMemoryPosition{oldObj, newObj})),
_objMemPos(std::deque<details::ObjectMemoryPosition>(1, details::ObjectMemoryPosition{oldObj, newObj})),
_isNewElement{false} {
};
@@ -132,7 +132,7 @@ namespace bitsery {
Writter &_writter;
const TObj &_oldObj;
const TObj &_newObj;
std::stack<ObjectMemoryPosition> _objMemPos;
std::stack<details::ObjectMemoryPosition> _objMemPos;
bool _isNewElement;
template<typename T>
@@ -170,7 +170,7 @@ namespace bitsery {
auto lastChanged = begin;
while (misMatch.first != oldEnd && misMatch.second != end) {
writeIndexOffset(std::distance(lastChanged, misMatch.second));
_objMemPos.emplace(ObjectMemoryPosition{*misMatch.first, *misMatch.second});
_objMemPos.emplace(details::ObjectMemoryPosition{*misMatch.first, *misMatch.second});
fnc(*this, *misMatch.second);
_objMemPos.pop();
++misMatch.first;
@@ -183,7 +183,7 @@ namespace bitsery {
writeIndexOffset(std::distance(lastChanged, end));
//write old elements
for (auto pOld = misMatch.first; p != end && pOld != oldEnd; ++p, ++pOld) {
_objMemPos.emplace(ObjectMemoryPosition{*pOld, *p});
_objMemPos.emplace(details::ObjectMemoryPosition{*pOld, *p});
fnc(*this, *p);
_objMemPos.pop();
}

View File

@@ -25,33 +25,12 @@
#define BITSERY_DESERIALIZER_H
#include "common.h"
#include <array>
#include "details/serialization_common.h"
#include <utility>
#include <string>
namespace bitsery {
/*
* functions for range
*/
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void setRangeValue(T& v, const RangeSpec<T>& r) {
v += r.min;
};
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
void setRangeValue(T& v, const RangeSpec<T>& r) {
using VT = std::underlying_type_t<T>;
reinterpret_cast<VT&>(v) += static_cast<VT>(r.min);
};
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void setRangeValue(T& v, const RangeSpec<T>& r) {
using UIT = SAME_SIZE_UNSIGNED<T>;
const auto intRep = reinterpret_cast<UIT&>(v);
const UIT maxUint = (static_cast<UIT>(1) << r.bitsRequired) - 1;
v = r.min + (static_cast<T>(intRep) / maxUint) * (r.max - r.min);
};
template<typename Reader>
class Deserializer {
@@ -63,6 +42,7 @@ namespace bitsery {
return serialize(*this, std::forward<T>(obj));
}
//in c++17 change "class" to typename
template <template <typename> class Extension, typename TValue, typename Fnc>
Deserializer& ext(TValue& v, Fnc&& fnc) {
static_assert(!std::is_const<TValue>(), "");
@@ -79,7 +59,7 @@ namespace bitsery {
Deserializer& value(T& v) {
static_assert(std::numeric_limits<T>::is_iec559, "");
if (_isValid) {
_isValid = _reader.template readBytes<VSIZE>(reinterpret_cast<SAME_SIZE_UNSIGNED<T>&>(v));
_isValid = _reader.template readBytes<VSIZE>(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T>&>(v));
}
return *this;
}
@@ -132,10 +112,10 @@ namespace bitsery {
template <typename T>
Deserializer& range(T& v, const RangeSpec<T>& range) {
if (_isValid) {
_isValid = _reader.template readBits(reinterpret_cast<SAME_SIZE_UNSIGNED<T>&>(v), range.bitsRequired);
setRangeValue(v, range);
_isValid = _reader.readBits(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T>&>(v), range.bitsRequired);
details::setRangeValue(v, range);
if (_isValid)
_isValid = isRangeValid(v, range);
_isValid = details::isRangeValid(v, range);
}
return *this;
}
@@ -192,15 +172,7 @@ namespace bitsery {
readSize(size, maxSize);
if (_isValid) {
str.resize(size);
if (size) {
//if (std::is_const<decltype(std::declval<std::basic_string<T>>().data())>::value) {
std::vector<T> buf(size);
_isValid = _reader.template readBuffer<VSIZE>(buf.data(), size);
str.assign(buf.data(), size);
//} else {
//_isValid = _reader.template readBuffer<VSIZE>(str.data(), size);
//}
}
procContainer<VSIZE>(std::begin(str), std::end(str), std::true_type{});
}
return *this;
}
@@ -210,7 +182,9 @@ namespace bitsery {
size_t size;
readSize(size, N-1);
if (_isValid) {
_isValid = _reader.template readBuffer<VSIZE>(str, size);
auto first = std::begin(str);
procContainer<VSIZE>(first, std::next(first, size), std::true_type{});
//null-terminated string
str[size] = {};
}
return *this;
@@ -226,11 +200,7 @@ namespace bitsery {
readSize(size, maxSize);
if (_isValid) {
obj.resize(size);
for (auto& v:obj) {
if (_isValid)
fnc(*this, v);
}
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
}
return *this;
}
@@ -241,10 +211,7 @@ namespace bitsery {
readSize(size, maxSize);
if (_isValid) {
obj.resize(size);
for (auto& v: obj) {
if (_isValid)
value<VSIZE>(v);
}
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
}
return *this;
}
@@ -255,10 +222,7 @@ namespace bitsery {
readSize(size, maxSize);
if (_isValid) {
obj.resize(size);
for (auto& v: obj) {
if (_isValid)
object(v);
}
procContainer(std::begin(obj), std::end(obj));
}
return *this;
}
@@ -271,27 +235,19 @@ namespace bitsery {
template<typename T, size_t N, typename Fnc>
Deserializer& array(std::array<T,N> &arr, Fnc && fnc) {
for (auto& v: arr)
if (_isValid)
fnc(*this, v);
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
return *this;
}
template<size_t VSIZE, typename T, size_t N>
Deserializer& array(std::array<T,N> &arr) {
for (auto& v: arr) {
if (_isValid)
value<VSIZE>(v);
}
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
return *this;
}
template<typename T, size_t N>
Deserializer& array(std::array<T,N> &arr) {
for (auto& v: arr) {
if (_isValid)
object(v);
}
procContainer(std::begin(arr), std::end(arr));
return *this;
}
@@ -299,23 +255,19 @@ namespace bitsery {
template<typename T, size_t N, typename Fnc>
Deserializer& array(T (&arr)[N], Fnc&& fnc) {
T* end = arr + N;
for (T* it= arr; it != end; ++it) {
if (_isValid)
fnc(*this, *it);
}
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
return *this;
}
template<size_t VSIZE, typename T, size_t N>
Deserializer& array(T (&arr)[N]) {
procCArray<VSIZE>(arr);
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
return *this;
}
template<typename T, size_t N>
Deserializer& array(T (&arr)[N]) {
procCArray<0>(arr);
procContainer(std::begin(arr), std::end(arr));
return *this;
}
bool isValid() const {
@@ -390,45 +342,34 @@ namespace bitsery {
}
}
template< size_t VSIZE, typename TIterator>
void procContainerValues(TIterator begin, TIterator end) {
for (auto it = begin; it != end; ++it) {
if (_isValid)
value<VSIZE>(*it);
}
//process value types
//false_type means that we must process all elements individually
template<size_t VSIZE, typename It>
void procContainer(It first, It last, std::false_type) {
for (;_isValid && first != last; ++first)
value<VSIZE>(*first);
};
template<typename TIterator>
void procContainerValues(TIterator begin, TIterator end) {
for (auto it = begin; it != end; ++it) {
if (_isValid)
object(*it);
}
//process value types
//true_type means, that we can copy whole buffer
template<size_t VSIZE, typename It>
void procContainer(It first, It last, std::true_type) {
if (_isValid && first != last)
_isValid = _reader.template readBuffer<VSIZE>(&(*first), std::distance(first, last));
};
template <size_t VSIZE, typename T>
void procContainer(T&& obj) {
//todo could be improved for arithmetic types in contiguous containers (std::vector, std::array) (keep in mind std::vector<bool> specialization)
for (auto& v: obj)
if (_isValid)
ProcessAnyType<VSIZE>::serialize(*this, v);
//process by calling functions
template<typename It, typename Fnc>
void procContainer(It first, It last, Fnc fnc) {
for (;_isValid && first != last; ++first)
fnc(*this, *first);
};
template <size_t VSIZE, typename T, size_t N>
void procCArray(T (&arr)[N]) {
//todo could be improved for arithmetic types
T* end = arr + N;
for (T* it = arr; it != end; ++it)
if (_isValid)
ProcessAnyType<VSIZE>::serialize(*this, *it);
};
template <typename Iterator, typename Fnc>
void procCont(Iterator begin, Iterator end, Fnc&& fnc) {
for (Iterator it = begin; it != end; ++it)
if (_isValid)
fnc(*it);
//process object types
template<typename It>
void procContainer(It first, It last) {
for (;_isValid && first != last; ++first)
object(*first);
};
};

View File

@@ -0,0 +1,100 @@
//MIT License
//
//Copyright (c) 2017 Mindaugas Vinkelis
//
//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.
#ifndef BITSERY_BUFFER_COMMON_H
#define BITSERY_BUFFER_COMMON_H
#include <type_traits>
#include <cstddef>
#include <cstdint>
namespace bitsery {
/*
* endianess
*/
enum class EndiannessType {
LittleEndian,
BigEndian
};
namespace details {
template<typename T>
constexpr size_t BITS_SIZE = sizeof(T) << 3;
//add swap functions to class, to avoid compilation warning about unused functions
struct swapImpl {
static uint64_t exec(uint64_t value) {
#ifdef __GNUC__
return __builtin_bswap64( value );
#else
value = ( value & 0x00000000FFFFFFFF ) << 32 | ( value & 0xFFFFFFFF00000000 ) >> 32;
value = ( value & 0x0000FFFF0000FFFF ) << 16 | ( value & 0xFFFF0000FFFF0000 ) >> 16;
value = ( value & 0x00FF00FF00FF00FF ) << 8 | ( value & 0xFF00FF00FF00FF00 ) >> 8;
return value;
#endif
}
static uint32_t exec(uint32_t value)
{
#ifdef __GNUC__
return __builtin_bswap32( value );
#else
return ( value & 0x000000ff ) << 24 | ( value & 0x0000ff00 ) << 8 | ( value & 0x00ff0000 ) >> 8 | ( value & 0xff000000 ) >> 24;
#endif
}
static uint16_t exec(uint16_t value)
{
return ( value & 0x00ff ) << 8 | ( value & 0xff00 ) >> 8;
}
static uint8_t exec(uint8_t value) {
return value;
}
};
template <typename TValue>
TValue swap(TValue value) {
constexpr size_t TSize = sizeof(TValue);
using UT = typename std::conditional<TSize == 1, uint8_t,
typename std::conditional<TSize == 2, uint16_t,
typename std::conditional<TSize == 4, uint32_t , uint64_t>::type>::type>::type;
return swapImpl::exec(static_cast<UT>(value));
}
//add test data in separate struct, because some compilers only support constexpr functions with return-only body
struct EndiannessTestData {
static constexpr uint32_t _sample4Bytes = 0x01020304;
static constexpr uint8_t _sample1stByte = (const uint8_t&)_sample4Bytes;
};
constexpr EndiannessType getSystemEndianness() {
static_assert(EndiannessTestData::_sample1stByte == 0x04 || EndiannessTestData::_sample1stByte == 0x01, "system must be either little or big endian");
return EndiannessTestData::_sample1stByte == 0x04 ? EndiannessType::LittleEndian : EndiannessType::BigEndian;
}
}
}
#endif //BITSERY_BUFFER_COMMON_H

View File

@@ -0,0 +1,239 @@
//MIT License
//
//Copyright (c) 2017 Mindaugas Vinkelis
//
//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.
#ifndef BITSERY_SERIALIZATION_COMMON_H
#define BITSERY_SERIALIZATION_COMMON_H
#include <cstdint>
#include <type_traits>
#include <array>
namespace bitsery {
namespace details {
template<typename T, typename Enable = void>
struct SAME_SIZE_UNSIGNED_TYPE {
typedef std::make_unsigned_t<T> type;
};
template<typename T>
struct SAME_SIZE_UNSIGNED_TYPE<T, typename std::enable_if<std::is_enum<T>::value>::type> {
typedef std::make_unsigned_t<std::underlying_type_t<T>> type;
};
template<typename T>
struct SAME_SIZE_UNSIGNED_TYPE<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
typedef std::conditional_t<std::is_same<T, float>::value, uint32_t, uint64_t> type;
};
template<typename T>
using SAME_SIZE_UNSIGNED = typename SAME_SIZE_UNSIGNED_TYPE<T>::type;
template <typename T>
constexpr size_t getSize(T v, size_t s) {
return v > 0 ? getSize(v / 2, s + 1) : s;
}
template<typename T>
constexpr size_t calcRequiredBits(T min, T max) {
//call recursive function, because some compilers only support constexpr functions with return-only body
return getSize(max - min, 0);
}
}
/*
* range functions in bitsery namespace because these are used by user
*/
template<typename T, typename Enable = void>
struct RangeSpec {
constexpr RangeSpec(T minValue, T maxValue)
: min{minValue},
max{maxValue},
bitsRequired{details::calcRequiredBits(min, max)} {
}
const T min;
const T max;
const size_t bitsRequired;
};
template<typename T>
struct RangeSpec<T, typename std::enable_if<std::is_enum<T>::value>::type> {
constexpr RangeSpec(T minValue, T maxValue) :
min{minValue},
max{maxValue},
bitsRequired{details::calcRequiredBits(
static_cast<std::underlying_type_t<T>>(min),
static_cast<std::underlying_type_t<T>>(max))} {
}
const T min;
const T max;
const size_t bitsRequired;
};
//this class is used to make default RangeSpec float specialization always prefer constructor with precision
struct BitsConstraint {
explicit constexpr BitsConstraint(size_t bits) : value{bits} {}
const size_t value;
};
template<typename T>
struct RangeSpec<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
constexpr RangeSpec(T minValue, T maxValue, BitsConstraint bits) :
min{minValue},
max{maxValue},
bitsRequired{bits.value} {
}
constexpr RangeSpec(T minValue, T maxValue, T precision) :
min{minValue},
max{maxValue},
bitsRequired{details::calcRequiredBits<details::SAME_SIZE_UNSIGNED<T>>({}, ((max - min) / precision))} {
}
const T min;
const T max;
const size_t bitsRequired;
};
namespace details {
/*
* functions for range
*/
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
auto getRangeValue(const T &v, const RangeSpec<T> &r) {
return static_cast<SAME_SIZE_UNSIGNED<T>>(v - r.min);
};
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
auto getRangeValue(const T &v, const RangeSpec<T> &r) {
return static_cast<SAME_SIZE_UNSIGNED<T>>(v) - static_cast<SAME_SIZE_UNSIGNED<T>>(r.min);
};
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
auto getRangeValue(const T &v, const RangeSpec<T> &r) {
using VT = SAME_SIZE_UNSIGNED<T>;
const VT maxUint = (static_cast<VT>(1) << r.bitsRequired) - 1;
const auto ratio = (v - r.min) / (r.max - r.min);
return static_cast<VT>(ratio * maxUint);
};
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void setRangeValue(T& v, const RangeSpec<T>& r) {
v += r.min;
};
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
void setRangeValue(T& v, const RangeSpec<T>& r) {
using VT = std::underlying_type_t<T>;
reinterpret_cast<VT&>(v) += static_cast<VT>(r.min);
};
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void setRangeValue(T& v, const RangeSpec<T>& r) {
using UIT = SAME_SIZE_UNSIGNED<T>;
const auto intRep = reinterpret_cast<UIT&>(v);
const UIT maxUint = (static_cast<UIT>(1) << r.bitsRequired) - 1;
v = r.min + (static_cast<T>(intRep) / maxUint) * (r.max - r.min);
};
template<typename T, typename std::enable_if<std::is_arithmetic<T>::value>::type * = nullptr>
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
return !(r.min > v || v > r.max);
}
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
using VT = std::underlying_type_t<T>;
return !(static_cast<VT>(r.min) > static_cast<VT>(v)
|| static_cast<VT>(v) > static_cast<VT>(r.max));
}
/*
* functions for substitution
*/
template<typename T, size_t N>
size_t findSubstitutionIndex(const T &v, const std::array<T, N> &defValues) {
auto index{1u};
for (auto &d:defValues) {
if (d == v)
return index;
++index;
}
return 0u;
};
/*
* delta functions
*/
class ObjectMemoryPosition {
public:
template<typename T>
ObjectMemoryPosition(const T &oldObj, const T &newObj)
:ObjectMemoryPosition{reinterpret_cast<const char *>(&oldObj), reinterpret_cast<const char *>(&newObj),
sizeof(T)} {
}
template<typename T>
bool isFieldsEquals(const T &newObjField) {
return *getOldObjectField(newObjField) == newObjField;
}
template<typename T>
const T *getOldObjectField(const T &field) {
auto offset = reinterpret_cast<const char *>(&field) - newObj;
return reinterpret_cast<const T *>(oldObj + offset);
}
private:
ObjectMemoryPosition(const char *objOld, const char *objNew, size_t)
: oldObj{objOld},
newObj{objNew} {
}
const char *oldObj;
const char *newObj;
};
}
}
#endif //BITSERY_SERIALIZATION_COMMON_H

View File

@@ -26,50 +26,13 @@
#define BITSERY_SERIALIZER_H
#include "common.h"
#include "details/serialization_common.h"
#include <cassert>
#include <array>
#include <string>
namespace bitsery {
/*
* functions for range
*/
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
auto getRangeValue(const T &v, const RangeSpec<T> &r) {
return static_cast<SAME_SIZE_UNSIGNED<T>>(v - r.min);
};
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
auto getRangeValue(const T &v, const RangeSpec<T> &r) {
return static_cast<SAME_SIZE_UNSIGNED<T>>(v) - static_cast<SAME_SIZE_UNSIGNED<T>>(r.min);
};
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
auto getRangeValue(const T &v, const RangeSpec<T> &r) {
using VT = SAME_SIZE_UNSIGNED<T>;
const VT maxUint = (static_cast<VT>(1) << r.bitsRequired) - 1;
const auto ratio = (v - r.min) / (r.max - r.min);
return static_cast<VT>(ratio * maxUint);
};
/*
* functions for substitution
*/
template<typename T, size_t N>
size_t findSubstitutionIndex(const T &v, const std::array<T, N> &defValues) {
auto index{1u};
for (auto &d:defValues) {
if (d == v)
return index;
++index;
}
return 0u;
};
template<typename Writter>
class Serializer {
public:
@@ -80,6 +43,7 @@ namespace bitsery {
return serialize(*this, obj);
}
//in c++17 change "class" to typename
template <template <typename> class Extension, typename TValue, typename Fnc>
Serializer& ext(const TValue& v, Fnc&& fnc ) {
Extension<const TValue> ext{v};
@@ -95,7 +59,7 @@ namespace bitsery {
template<size_t VSIZE, typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
Serializer& value(const T &v) {
static_assert(std::numeric_limits<T>::is_iec559, "");
_writter.template writeBytes<VSIZE>(reinterpret_cast<const SAME_SIZE_UNSIGNED<T> &>(v));
_writter.template writeBytes<VSIZE>(reinterpret_cast<const details::SAME_SIZE_UNSIGNED<T> &>(v));
return *this;
}
@@ -116,7 +80,7 @@ namespace bitsery {
*/
Serializer& boolBit(bool v) {
_writter.template writeBits(static_cast<unsigned char>(v ? 1 : 0), 1);
_writter.writeBits(static_cast<unsigned char>(v ? 1 : 0), 1);
return *this;
}
@@ -131,8 +95,8 @@ namespace bitsery {
template<typename T>
Serializer& range(const T &v, const RangeSpec<T> &range) {
assert(isRangeValid(v, range));
_writter.template writeBits(getRangeValue(v, range), range.bitsRequired);
assert(details::isRangeValid(v, range));
_writter.template writeBits<decltype(details::getRangeValue(v, range))>(details::getRangeValue(v, range), range.bitsRequired);
return *this;
}
@@ -141,7 +105,7 @@ namespace bitsery {
*/
template<typename T, size_t N, typename Fnc>
Serializer& substitution(const T &v, const std::array<T, N> &expectedValues, Fnc &&fnc) {
auto index = findSubstitutionIndex(v, expectedValues);
auto index = details::findSubstitutionIndex(v, expectedValues);
range(index, {{}, N +1});
if (!index)
fnc(*this, v);
@@ -150,7 +114,7 @@ namespace bitsery {
template<size_t VSIZE, typename T, size_t N>
Serializer& substitution(const T &v, const std::array<T, N> &expectedValues) {
auto index = findSubstitutionIndex(v, expectedValues);
auto index = details::findSubstitutionIndex(v, expectedValues);
range(index, {{}, N +1});
if (!index)
value<VSIZE>(v);
@@ -159,7 +123,7 @@ namespace bitsery {
template<typename T, size_t N>
Serializer& substitution(const T &v, const std::array<T, N> &expectedValues) {
auto index = findSubstitutionIndex(v, expectedValues);
auto index = details::findSubstitutionIndex(v, expectedValues);
range(index, {{}, N +1});
if (!index)
object(v);
@@ -173,13 +137,19 @@ namespace bitsery {
template<size_t VSIZE, typename T>
Serializer& text(const std::basic_string<T> &str, size_t maxSize) {
assert(str.size() <= maxSize);
procText<VSIZE>(str.data(), str.size());
auto first = std::begin(str);
auto last = std::end(str);
writeSize(std::distance(first, last));
procContainer<VSIZE>(first, last, std::true_type{});
return *this;
}
template<size_t VSIZE, typename T, size_t N>
Serializer& text(const T (&str)[N]) {
procText<VSIZE>(str, std::min(std::char_traits<T>::length(str), N - 1));
auto first = std::begin(str);
auto last = std::next(first, std::min(std::char_traits<T>::length(str), N - 1));
writeSize(std::distance(first, last));
procContainer<VSIZE>(first, last, std::true_type{});
return *this;
}
@@ -191,8 +161,7 @@ namespace bitsery {
Serializer& container(const T &obj, size_t maxSize, Fnc &&fnc) {
assert(obj.size() <= maxSize);
writeSize(obj.size());
for (auto &v: obj)
fnc(*this, v);
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
return *this;
}
@@ -201,7 +170,8 @@ namespace bitsery {
static_assert(VSIZE > 0, "");
assert(obj.size() <= maxSize);
writeSize(obj.size());
procContainer<VSIZE>(obj);
//todo optimisation is possible for contigous containers, but currently there is no compile-time check for this
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
return *this;
}
@@ -209,7 +179,7 @@ namespace bitsery {
Serializer& container(const T &obj, size_t maxSize) {
assert(obj.size() <= maxSize);
writeSize(obj.size());
procContainer<0>(obj);
procContainer(std::begin(obj), std::end(obj));
return *this;
}
@@ -221,21 +191,20 @@ namespace bitsery {
template<typename T, size_t N, typename Fnc>
Serializer& array(const std::array<T, N> &arr, Fnc &&fnc) {
for (auto &v: arr)
fnc(*this, v);
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
return *this;
}
template<size_t VSIZE, typename T, size_t N>
Serializer& array(const std::array<T, N> &arr) {
static_assert(VSIZE > 0, "");
procContainer<VSIZE>(arr);
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
return *this;
}
template<typename T, size_t N>
Serializer& array(const std::array<T, N> &arr) {
procContainer<0>(arr);
procContainer(std::begin(arr), std::end(arr));
return *this;
}
@@ -243,22 +212,20 @@ namespace bitsery {
template<typename T, size_t N, typename Fnc>
Serializer& array(const T (&arr)[N], Fnc &&fnc) {
const T *end = arr + N;
for (const T *tmp = arr; tmp != end; ++tmp)
fnc(*this, *tmp);
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
return *this;
}
template<size_t VSIZE, typename T, size_t N>
Serializer& array(const T (&arr)[N]) {
static_assert(VSIZE > 0, "");
procCArray<VSIZE>(arr);
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
return *this;
}
template<typename T, size_t N>
Serializer& array(const T (&arr)[N]) {
procCArray<0>(arr);
procContainer(std::begin(arr), std::end(arr));
return *this;
}
@@ -329,27 +296,36 @@ namespace bitsery {
}
}
template<size_t VSIZE, typename T>
void procContainer(T &&obj) {
//todo could be improved for arithmetic types in contiguous containers (std::vector, std::array) (keep in mind std::vector<bool> specialization)
for (auto &v: obj)
ProcessAnyType<VSIZE>::serialize(*this, v);
//process value types
//false_type means that we must process all elements individually
template<size_t VSIZE, typename It>
void procContainer(It first, It last, std::false_type) {
for (;first != last; ++first)
value<VSIZE>(*first);
};
template<size_t VSIZE, typename T, size_t N>
void procCArray(T (&arr)[N]) {
//todo could be improved for arithmetic types
const T *end = arr + N;
for (const T *it = arr; it != end; ++it)
ProcessAnyType<VSIZE>::serialize(*this, *it);
//process value types
//true_type means, that we can copy whole buffer
template<size_t VSIZE, typename It>
void procContainer(It first, It last, std::true_type) {
if (first != last)
_writter.template writeBuffer<VSIZE>(&(*first), std::distance(first, last));
};
//process by calling functions
template<typename It, typename Fnc>
void procContainer(It first, It last, Fnc fnc) {
for (;first != last; ++first)
fnc(*this, *first);
};
//process object types
template<typename It>
void procContainer(It first, It last) {
for (;first != last; ++first)
object(*first);
};
template<size_t VSIZE, typename T>
void procText(const T *str, size_t size) {
writeSize(size);
if (size)
_writter.template writeBuffer<VSIZE>(str, size);
}
};
}

View File

@@ -21,77 +21,54 @@
#SOFTWARE.
cmake_minimum_required(VERSION 3.2)
project(${TEST_PROJECT_NAME} C CXX)
set(TestProjectName bitsery_tests)
project(${TestProjectName} C CXX)
find_package(Threads REQUIRED)
#add googletest external project
#USE_GMOCK enable gmock
#exports variables GTEST_INCLUDE_DIRS, GTEST_LIBS_DIR, GTEST_LIBNAME, GTEST_MAIN_LIBNAME
set(EXT_PROJECTS_DIR ${CMAKE_SOURCE_DIR}/ext)
set(USE_GMOCK ON)
add_subdirectory(${EXT_PROJECTS_DIR}/gtest ${CMAKE_BINARY_DIR}/gtest)
set(ExtCMakeFilesDir ${CMAKE_SOURCE_DIR}/ext)
set(UseGMock ON)
add_subdirectory(${ExtCMakeFilesDir}/gtest ${CMAKE_BINARY_DIR}/gtest)
#this helps idea to know which files are actually used
file(GLOB_RECURSE IncludeHeaders ${CMAKE_SOURCE_DIR}/include/bitsery/*.h)
# set common include folder for module
include_directories(SYSTEM ${GTEST_INCLUDE_DIRS})
include_directories(SYSTEM ${GTestIncludeDirs})
include_directories(${CMAKE_SOURCE_DIR}/include)
file(GLOB TEST_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
file(GLOB TestSourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
FOREACH(TEST_PROJECT_FILE ${TEST_SRC_FILES})
get_filename_component(SEPARATE_TEST_NAME ${TEST_PROJECT_FILE} NAME_WE)
set(SEPARATE_TEST_NAME TEST_${SEPARATE_TEST_NAME})
add_executable(${SEPARATE_TEST_NAME} ${TEST_PROJECT_FILE})
add_dependencies(${SEPARATE_TEST_NAME} googletest)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
message(WARNING "extension tests for optional is disable for VS, because VS currenty doesn't have <optional>")
list(REMOVE_ITEM TestSourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/serialization_ext_optional.cpp)
endif()
set_property(TARGET ${SEPARATE_TEST_NAME} PROPERTY CXX_STANDARD 14)
set_property(TARGET ${SEPARATE_TEST_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)
include(${ExtCMakeFilesDir}/LinkTestLib.cmake)
FOREACH(TestFile ${TestSourceFiles})
get_filename_component(TestName ${TestFile} NAME_WE)
set(TestName TEST_${TestName})
add_executable(${TestName} ${TestFile} ${IncludeHeaders})
LinkTestLib(${TestName})
if(NOT WIN32 OR MINGW)
FOREACH(LIBNAME ${GTEST_LINK_LIBNAMES})
target_link_libraries(${SEPARATE_TEST_NAME} ${GTEST_LIBS_DIR}/lib${LIBNAME}.a )
ENDFOREACH()
else()
FOREACH(LIBNAME ${GTEST_LINK_LIBNAMES})
target_link_libraries(${SEPARATE_TEST_NAME}
debug ${GTEST_LIBS_DIR}/DebugLibs/${CMAKE_FIND_LIBRARY_PREFIXES}${LIBNAME}${CMAKE_FIND_LIBRARY_SUFFIXES}
optimized ${GTEST_LIBS_DIR}/ReleaseLibs/${CMAKE_FIND_LIBRARY_PREFIXES}${LIBNAME}${CMAKE_FIND_LIBRARY_SUFFIXES})
ENDFOREACH()
endif()
target_link_libraries(${SEPARATE_TEST_NAME} ${CMAKE_THREAD_LIBS_INIT})
add_test(NAME ${SEPARATE_TEST_NAME} COMMAND $<TARGET_FILE:${SEPARATE_TEST_NAME}>)
add_test(NAME ${TestName} COMMAND $<TARGET_FILE:${TestName}>)
ENDFOREACH()
#all in one tests for code coverage
add_executable(${TEST_PROJECT_NAME} ${TEST_SRC_FILES})
add_dependencies(${TEST_PROJECT_NAME} googletest)
add_executable(${TestProjectName} ${TestSourceFiles})
set_property(TARGET ${TEST_PROJECT_NAME} PROPERTY CXX_STANDARD 14)
set_property(TARGET ${TEST_PROJECT_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)
if(NOT WIN32 OR MINGW)
FOREACH(LIBNAME ${GTEST_LINK_LIBNAMES})
target_link_libraries(${TEST_PROJECT_NAME} ${GTEST_LIBS_DIR}/lib${LIBNAME}.a )
ENDFOREACH()
else()
FOREACH(LIBNAME ${GTEST_LINK_LIBNAMES})
target_link_libraries(${TEST_PROJECT_NAME}
debug ${GTEST_LIBS_DIR}/DebugLibs/${CMAKE_FIND_LIBRARY_PREFIXES}${LIBNAME}${CMAKE_FIND_LIBRARY_SUFFIXES}
optimized ${GTEST_LIBS_DIR}/ReleaseLibs/${CMAKE_FIND_LIBRARY_PREFIXES}${LIBNAME}${CMAKE_FIND_LIBRARY_SUFFIXES})
ENDFOREACH()
if(CMAKE_COMPILER_IS_GNUCXX)
include(${ExtCMakeFilesDir}/CodeCoverage.cmake)
target_compile_options(${TestProjectName} PUBLIC -O0 -fprofile-arcs -ftest-coverage)
target_link_libraries(${TestProjectName} -O0 -fprofile-arcs -ftest-coverage)
setup_target_for_coverage(tests_coverage ${TestProjectName} coverage)
endif()
target_link_libraries(${TEST_PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})
LinkTestLib(${TestProjectName})

View File

@@ -24,13 +24,13 @@
#include <gmock/gmock.h>
#include <bitsery/buffer_writer.h>
#include <bitsery/buffer_reader.h>
#include <list>
#include <bitset>
#include <bitsery/details/serialization_common.h>
using testing::Eq;
using testing::ContainerEq;
using bitsery::BufferWriter;
using bitsery::BufferReader;
using Buffer = std::vector<bitsery::DefaultConfig::BufferValueType>;
struct IntegralUnsignedTypes {
uint32_t a;
@@ -40,23 +40,29 @@ struct IntegralUnsignedTypes {
uint64_t e;
};
template <typename T>
constexpr size_t getBits(T v) {
return bitsery::details::calcRequiredBits<T>({}, v);
};
TEST(BufferBitsOperations, WriteAndReadBits) {
//setup data
IntegralUnsignedTypes data;
data.a = 485454;//bits 19
data.b = 45978;//bits 16
data.c = 0;//bits 1
data.d = 36;//bits 6
data.e = 479845648946;//bits 39
constexpr IntegralUnsignedTypes data{
485454,//bits 19
45978,//bits 16
0,//bits 1
36,//bits 6
479845648946//bits 39
};
constexpr size_t aBITS = 21;
constexpr size_t bBITS = 16;
constexpr size_t cBITS = 5;
constexpr size_t dBITS = 7;
constexpr size_t eBITS = 40;
constexpr size_t aBITS = getBits(data.a) + 2;
constexpr size_t bBITS = getBits(data.b) + 0;
constexpr size_t cBITS = getBits(data.c) + 2;
constexpr size_t dBITS = getBits(data.d) + 1;
constexpr size_t eBITS = getBits(data.e) + 8;
//create and write to buffer
std::vector<uint8_t> buf;
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(data.a, aBITS);
@@ -87,7 +93,7 @@ TEST(BufferBitsOperations, WriteAndReadBits) {
TEST(BufferBitsOperations, WhenFinishedFlushWriter) {
std::vector<uint8_t> buf;
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(3u, 2);
@@ -101,7 +107,7 @@ TEST(BufferBitsOperations, BufferSizeIsCountedPerByteNotPerBit) {
//setup data
//create and write to buffer
std::vector<uint8_t> buf;
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(7u,3);
@@ -110,7 +116,7 @@ TEST(BufferBitsOperations, BufferSizeIsCountedPerByteNotPerBit) {
//read from buffer
BufferReader br{buf};
unsigned tmp;
uint16_t tmp;
EXPECT_THAT(br.readBits(tmp,4), Eq(true));
EXPECT_THAT(br.readBits(tmp,2), Eq(true));
EXPECT_THAT(br.readBits(tmp,2), Eq(true));
@@ -126,11 +132,44 @@ TEST(BufferBitsOperations, BufferSizeIsCountedPerByteNotPerBit) {
EXPECT_THAT(br2.readBits(tmp,9), Eq(false));
}
TEST(BufferBitsOperations, ConsecutiveCallsToAlignHasNoEffect) {
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(3u, 2);
//3 calls to align after 1st data
bw.align();
bw.align();
bw.align();
bw.writeBits(7u, 3);
//1 call to align after 2nd data
bw.align();
bw.writeBits(15u, 4);
bw.flush();
unsigned char tmp;
BufferReader br{buf};
EXPECT_THAT(br.readBits(tmp,2), Eq(true));
EXPECT_THAT(tmp, Eq(3u));
EXPECT_THAT(br.align(), Eq(true));
EXPECT_THAT(br.readBits(tmp,3), Eq(true));
EXPECT_THAT(tmp, Eq(7u));
EXPECT_THAT(br.align(), Eq(true));
EXPECT_THAT(br.align(), Eq(true));
EXPECT_THAT(br.align(), Eq(true));
EXPECT_THAT(br.readBits(tmp,4), Eq(true));
EXPECT_THAT(tmp, Eq(15u));
}
TEST(BufferBitsOperations, WhenAlignedFlushHasNoEffect) {
//setup data
//create and write to buffer
std::vector<uint8_t> buf;
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(3u, 2);
@@ -140,11 +179,12 @@ TEST(BufferBitsOperations, WhenAlignedFlushHasNoEffect) {
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
}
TEST(BufferBitsOperations, AlignMustWriteZerosBits) {
//setup data
//create and write to buffer
std::vector<uint8_t> buf;
Buffer buf;
BufferWriter bw{buf};
//write 2 bits and align

View File

@@ -31,6 +31,7 @@ using testing::Eq;
using testing::ContainerEq;
using bitsery::BufferWriter;
using bitsery::BufferReader;
using Buffer = std::vector<bitsery::DefaultConfig::BufferValueType>;
struct IntegralTypes {
int64_t a;
@@ -68,7 +69,7 @@ TEST(BufferBytesOperations, WriteAndReadBytes) {
//setup data
auto data =getInitializedIntegralTypes();
//create and write to buffer
std::vector<uint8_t> buf{};
Buffer buf{};
BufferWriter bw{buf};
writeIntegralTypesToBuffer(bw, data);
@@ -98,7 +99,7 @@ TEST(BufferBytesOperations, BufferReaderUsingDataPlusSizeCtor) {
//setup data
auto data =getInitializedIntegralTypes();
//create and write to buffer
std::vector<uint8_t> buf{};
Buffer buf{};
BufferWriter bw{buf};
writeIntegralTypesToBuffer(bw, data);
@@ -127,7 +128,7 @@ TEST(BufferBytesOperations, BufferReaderUsingCArrayCtor) {
//setup data
auto data =getInitializedIntegralTypes();
//create and write to buffer
std::vector<uint8_t> buf{};
Buffer buf{};
BufferWriter bw{buf};
writeIntegralTypesToBuffer(bw, data);
@@ -161,7 +162,7 @@ TEST(BufferBytesOperations, ReadReturnsFalseIfNotEnoughBufferSize) {
uint8_t a = 111;
//create and write to buffer
std::vector<uint8_t> buf{};
Buffer buf{};
BufferWriter bw{buf};
bw.writeBytes<1>(a);
@@ -190,7 +191,7 @@ TEST(BufferBytesOperations, ReadIsCompletedWhenAllBytesAreRead) {
data.d = 200;
//create and write to buffer
std::vector<uint8_t> buf{};
Buffer buf{};
BufferWriter bw{buf};
bw.writeBytes<4>(data.b);
@@ -217,3 +218,57 @@ TEST(BufferBytesOperations, ReadIsCompletedWhenAllBytesAreRead) {
EXPECT_THAT(br1.isCompleted(), Eq(true));
}
TEST(BufferBytesOperations, ReadWriteBufferFncCanAcceptSignedData) {
//setup data
constexpr size_t DATA_SIZE = 3;
int16_t src[DATA_SIZE] {54,-4877,30067};
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
bw.writeBuffer<2>(src, DATA_SIZE);
bw.flush();
//read from buffer
BufferReader br1{buf};
int16_t dst[DATA_SIZE]{};
EXPECT_THAT(br1.readBuffer<2>(dst, DATA_SIZE), Eq(true));
EXPECT_THAT(dst, ContainerEq(src));
//read more than available
BufferReader br2{buf};
int16_t dstMore[DATA_SIZE+1]{};
EXPECT_THAT(br2.readBuffer<2>(dstMore, DATA_SIZE+1), Eq(false));
}
TEST(BufferBytesOperations, ReadWriteBufferCanWorkOnUnalignedData) {
//setup data
constexpr size_t DATA_SIZE = 3;
int16_t src[DATA_SIZE] {54,-4877,30067};
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
bw.writeBits(15u, 4);
bw.writeBuffer<2>(src, DATA_SIZE);
bw.writeBits(12u, 4);
bw.flush();
EXPECT_THAT(buf.size(), Eq(sizeof(src) + 1));
//read from buffer
BufferReader br1{buf};
int16_t dst[DATA_SIZE]{};
uint8_t tmp{};
br1.readBits(tmp, 4);
EXPECT_THAT(tmp, Eq(15));
EXPECT_THAT(br1.readBuffer<2>(dst, DATA_SIZE), Eq(true));
EXPECT_THAT(dst, ContainerEq(src));
br1.readBits(tmp, 4);
EXPECT_THAT(tmp, Eq(12));
//read more than available
BufferReader br2{buf};
br2.readBits(tmp, 4);
int16_t dstMore[DATA_SIZE+1]{};
EXPECT_THAT(tmp, Eq(15));
EXPECT_THAT(br2.readBuffer<2>(dstMore, DATA_SIZE+1), Eq(false));
}

183
tests/buffer_endianness.cpp Normal file
View File

@@ -0,0 +1,183 @@
//MIT License
//
//Copyright (c) 2017 Mindaugas Vinkelis
//
//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 <gmock/gmock.h>
#include <bitsery/buffer_writer.h>
#include <bitsery/buffer_reader.h>
#include <bitsery/details/serialization_common.h>
using testing::Eq;
using testing::ContainerEq;
using bitsery::EndiannessType;
using bitsery::DefaultConfig;
using Buffer = std::vector<bitsery::DefaultConfig::BufferValueType>;
constexpr EndiannessType getInverseEndianness(EndiannessType e) {
return e == EndiannessType::LittleEndian
? EndiannessType::BigEndian
: EndiannessType::LittleEndian;
}
struct InverseEndiannessConfig {
static constexpr bitsery::EndiannessType NetworkEndianness = getInverseEndianness(DefaultConfig::NetworkEndianness);
using BufferValueType = DefaultConfig::BufferValueType;
using BufferScrathType = DefaultConfig::BufferScrathType;
};
struct IntegralTypes {
int64_t a;
uint32_t b;
int16_t c;
uint8_t d;
int8_t e;
};
TEST(BufferEndianness, WhenWriteBytesThenBytesAreSwapped) {
//fill initial values
IntegralTypes src{};
src.a = 0x1122334455667788;
src.b = 0xBBCCDDEE;
src.c = 0xCCDD;
src.d = 0xDD;
src.e = 0xEE;
//fill expected result after swap
IntegralTypes resInv{};
resInv.a = 0x8877665544332211;
resInv.b = 0xEEDDCCBB;
resInv.c = 0xDDCC;
resInv.d = 0xDD;
resInv.e = 0xEE;
//create and write to buffer
Buffer buf{};
bitsery::BasicBufferWriter<DefaultConfig> bw{buf};
bw.writeBytes<8>(src.a);
bw.writeBytes<4>(src.b);
bw.writeBytes<2>(src.c);
bw.writeBytes<1>(src.d);
bw.writeBytes<1>(src.e);
bw.flush();
//read from buffer using inverse endianness config
bitsery::BasicBufferReader<InverseEndiannessConfig> br{buf};
IntegralTypes res{};
br.readBytes<8>(res.a);
br.readBytes<4>(res.b);
br.readBytes<2>(res.c);
br.readBytes<1>(res.d);
br.readBytes<1>(res.e);
//check results
EXPECT_THAT(res.a, Eq(resInv.a));
EXPECT_THAT(res.b, Eq(resInv.b));
EXPECT_THAT(res.c, Eq(resInv.c));
EXPECT_THAT(res.d, Eq(resInv.d));
EXPECT_THAT(res.e, Eq(resInv.e));
}
TEST(BufferEndianness, WhenWriteBuffer1ByteValuesThenEndiannessIsIgnored) {
//fill initial values
constexpr size_t SIZE = 4;
uint8_t src[SIZE] = {0xAA, 0xBB, 0xCC, 0xDD};
uint8_t res[SIZE] = {};
//create and write to buffer
Buffer buf{};
bitsery::BasicBufferWriter<DefaultConfig> bw{buf};
bw.writeBuffer<1>(src, SIZE);
bw.flush();
//read from buffer using inverse endianness config
bitsery::BasicBufferReader<InverseEndiannessConfig> br{buf};
br.readBuffer<1>(res, SIZE);
//result is identical, because we write separate values, of size 1byte, that requires no swapping
//check results
EXPECT_THAT(res, ContainerEq(src));
}
TEST(BufferEndianness, WhenWriteBufferMoreThan1ByteValuesThenValuesAreSwapped) {
//fill initial values
constexpr size_t SIZE = 4;
uint16_t src[SIZE] = {0xAA00, 0xBB11, 0xCC22, 0xDD33};
uint16_t resInv[SIZE] = {0x00AA, 0x11BB, 0x22CC, 0x33DD};
uint16_t res[SIZE] = {};
//create and write to buffer
Buffer buf{};
bitsery::BasicBufferWriter<DefaultConfig> bw{buf};
bw.writeBuffer<2>(src, SIZE);
bw.flush();
//read from buffer using inverse endianness config
bitsery::BasicBufferReader<InverseEndiannessConfig> br{buf};
br.readBuffer<2>(res, SIZE);
//result is identical, because we write separate values, of size 1byte, that requires no swapping
//check results
EXPECT_THAT(res, ContainerEq(resInv));
}
template <typename T>
constexpr size_t getBits(T v) {
return bitsery::details::calcRequiredBits<T>({}, v);
};
struct IntegralUnsignedTypes {
uint64_t a;
uint32_t b;
uint16_t c;
uint8_t d;
};
TEST(BufferEndianness, WhenBufferValueTypeIs1ByteThenBitOperationsIsNotAffectedByEndianness) {
//fill initial values
static_assert(sizeof(DefaultConfig::BufferValueType) == 1, "currently only 1 byte size, value size is supported");
//fill initial values
constexpr IntegralUnsignedTypes src {
0x0000334455667788,//bits 19
0x00CCDDEE,//bits 16
0x00DD,//bits 1
0x0F,//bits 6
};
constexpr size_t aBITS = getBits(src.a) + 8;
constexpr size_t bBITS = getBits(src.b) + 0;
constexpr size_t cBITS = getBits(src.c) + 5;
constexpr size_t dBITS = getBits(src.d) + 2;
//create and write to buffer
Buffer buf{};
bitsery::BasicBufferWriter<DefaultConfig> bw{buf};
bw.writeBits(src.a, aBITS);
bw.writeBits(src.b, bBITS);
bw.writeBits(src.c, cBITS);
bw.writeBits(src.d, dBITS);
bw.flush();
//read from buffer using inverse endianness config
bitsery::BasicBufferReader<InverseEndiannessConfig> br{buf};
IntegralUnsignedTypes res{};
br.readBits(res.a, aBITS);
br.readBits(res.b, bBITS);
br.readBits(res.c, cBITS);
br.readBits(res.d, dBITS);
//check results
EXPECT_THAT(res.a, Eq(src.a));
EXPECT_THAT(res.b, Eq(src.b));
EXPECT_THAT(res.c, Eq(src.c));
EXPECT_THAT(res.d, Eq(src.d));
}

View File

@@ -31,11 +31,41 @@
using testing::ContainerEq;
using testing::Eq;
/*
* overload to get container of types
*/
template <typename Container>
Container getFilledContainer() {
return {1,2,3,4,5,78,456,8,54};
}
template <>
std::vector<MyStruct1> getFilledContainer<std::vector<MyStruct1>>() {
return {
{0,1},
{2,3},
{4,5},
{6,7},
{8,9},
{11,34},
{5134,1532}
};
}
template <>
std::list<MyStruct2> getFilledContainer<std::list<MyStruct2>>() {
return {
{MyStruct2::V1, {0,1}} ,
{MyStruct2::V3, {-45,45}}
};
}
/*
* start testing session
*/
template <typename T>
class SerializeContainerArthmeticTypes:public testing::Test {
public:
@@ -107,26 +137,6 @@ public:
}
};
template <>
std::vector<MyStruct1> getFilledContainer<std::vector<MyStruct1>>() {
return {
{0,1},
{2,3},
{4,5},
{6,7},
{8,9},
{11,34},
{5134,1532}
};
}
template <>
std::list<MyStruct2> getFilledContainer<std::list<MyStruct2>>() {
return {
{MyStruct2::V1, {0,1}} ,
{MyStruct2::V3, {-45,45}}
};
}
using SequenceContainersWithCompositeTypes = ::testing::Types<
std::vector<MyStruct1>,

View File

@@ -23,12 +23,15 @@
#include <gmock/gmock.h>
#include "serialization_test_utils.h"
#include <experimental/optional>
namespace std {
template <typename T>
using optional = experimental::optional<T>;
};
#if __cplusplus > 201402L
# include<optional>
#else
# include <experimental/optional>
namespace std {
template <typename T>
using optional = experimental::optional<T>;
}
#endif
#include <bitsery/ext/optional.h>
@@ -81,5 +84,3 @@ TEST(SerializeExtensionOptional, OptionalHasValue) {
EXPECT_THAT(t1.value(), Eq(r1.value()));
}

View File

@@ -157,7 +157,7 @@ TEST(DeltaSerializer, GeneralConceptTest) {
yNew.vx[1].s = "bla";
yNew.vx.push_back(X{ 3 });
std::vector<uint8_t> buf;
std::vector<bitsery::DefaultConfig::BufferValueType> buf;
bitsery::BufferWriter bw{ buf };
bitsery::DeltaSerializer<bitsery::BufferWriter, Y> ser(bw, y, yNew);
serialize(ser, yNew);

View File

@@ -29,17 +29,17 @@ using bitsery::BitsConstraint;
TEST(SerializeRange, RequiredBitsIsConstexpr) {
constexpr RangeSpec<int> r1{0, 31};
static_assert(r1.bitsRequired == 5);
static_assert(r1.bitsRequired == 5, "r1.bitsRequired == 5");
constexpr RangeSpec<MyEnumClass> r2{MyEnumClass::E1, MyEnumClass::E4};
static_assert(r2.bitsRequired == 2);
static_assert(r2.bitsRequired == 2, "r2.bitsRequired == 2");
constexpr RangeSpec<double> r3{-1.0,1.0, BitsConstraint{5u}};
//EXPECT_THAT(r1.bitsRequired, Eq(5));
static_assert(r3.bitsRequired == 5);
static_assert(r3.bitsRequired == 5, "r3.bitsRequired == 5");
constexpr RangeSpec<float> r4{-1.0f,1.0f, 0.01f};
static_assert(r4.bitsRequired == 8);
static_assert(r4.bitsRequired == 8, "r4.bitsRequired == 8");
}
@@ -133,7 +133,7 @@ TEST(SerializeRange, FloatUsingBitsSizeConstraint1) {
ctx.createDeserializer().range(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, ::testing::FloatNear(t1, (max - min) / (static_cast<bitsery::SAME_SIZE_UNSIGNED<float>>(1) << bits)));
EXPECT_THAT(res1, ::testing::FloatNear(t1, (max - min) / (static_cast<bitsery::details::SAME_SIZE_UNSIGNED<float>>(1) << bits)));
}
TEST(SerializeRange, DoubleUsingBitsSizeConstraint2) {
@@ -150,5 +150,5 @@ TEST(SerializeRange, DoubleUsingBitsSizeConstraint2) {
ctx.createDeserializer().range(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(7));
EXPECT_THAT(res1, ::testing::DoubleNear(t1, (max - min) / (static_cast<bitsery::SAME_SIZE_UNSIGNED<double>>(1) << bits)));
EXPECT_THAT(res1, ::testing::DoubleNear(t1, (max - min) / (static_cast<bitsery::details::SAME_SIZE_UNSIGNED<double>>(1) << bits)));
}

View File

@@ -27,6 +27,11 @@
#include <bitsery/bitsery.h>
#include <memory>
/*
* define some types for testing
*/
struct MyStruct1 {
MyStruct1(int v1, int v2):i1{v1}, i2{v2} {}
MyStruct1():MyStruct1{0,0} {}
@@ -70,8 +75,9 @@ SERIALIZE(MyStruct2) {
object(o.s1);
}
class SerializationContext {
std::vector<uint8_t> buf{};
std::vector<bitsery::DefaultConfig::BufferValueType> buf{};
std::unique_ptr<bitsery::BufferWriter> bw;
std::unique_ptr<bitsery::BufferReader> br;
public:
@@ -83,7 +89,7 @@ public:
size_t getBufferSize() const {
return buf.size();
}
//since all containers .size() method returns size_t, it cannot be dirrectly serialized, because size_t is platform dependant
//since all containers .size() method returns size_t, it cannot be directly serialized, because size_t is platform dependant
//this function returns number of bytes writen to buffer, when reading/writing size of container
static size_t containerSizeSerializedBytesCount(size_t elemsCount) {
if (elemsCount < 0x80u)

View File

@@ -86,7 +86,7 @@ TEST(SerializeValues, ValueSizeOverload2Byte) {
}
TEST(SerializeValues, ValueSizeOverload4Byte) {
float v{54.498};
float v{54.498f};
float res;
constexpr size_t TSIZE = sizeof(v);