added -growable- function

This commit is contained in:
fraillt
2017-09-08 16:22:54 +03:00
parent cba80774e4
commit 2f8ae0075c
38 changed files with 1723 additions and 1264 deletions

View File

@@ -2,19 +2,38 @@
### Features
* added **SERIALIZE_FRIEND** macro to be able to serialize private struct fields.
* new function **growable**, that allows to have forward/backward compatability within this functions serialization flow. It only allows to append new data at the end of to existing flow without breaking old consumers.
* old consumer: correctly read old interfce and ignore new data.
* new consumer: get defaults (zero values) for new fields, when reading old data.
* new **SERIALIZE_FRIEND** macro that enables to serialize private object fields.
* friendly static_assert message when serializing **object**, that doesn't have **serialize** function defined.
* added **custom** function to override default behaviour for **object** serialization.
* renamed function **ext** to **extension** and changed its interface, to make it more easy to extend.
* added **object** overload, that invokes user function/lambda with object. It is the same as calling user function directly, but makes more consistent API.
* Serializer/Deserializer now have optional *context* parameter, that might be required in some specific serialization cases.
* improved serialization performance: added support for fixed size buffer for best performance.
* improved performance for reading/writing container sizes.
* added new method to BufferReader **getError** which returns on of three values: NO_ERROR, BUFFER_OVERFLOW, INVALID_BUFFER_DATA, also added setError method, that is only used by Deserializer.
### Breaking changes
* container and text sizes representation changed, to allow much faster size reads/writes for small values.
* renamed functions:
* **ext** to **extend** and changed its interface, to make it more easy to extend.
* alias functions that write bytes directly no has *b* (meaning bytes) at the end of the name eg. *value4* now is *value4b*.
* **substitution** changed to **entropy**, to sound more familiar to *entropy encoding* term.
* now all serializer/deserializer functions return void, to avoid undefined behaviour for functions parameters evaluation when using method chaining. There was no benefits apart from *nicer* syntax, but could have undefined behaviour when building complex serialization flows.
* removed **array** and added fixed sizes overloads for **container**.
* changed BufferWriter/Reader behaviour:
* added *FixedBufferSize* config bool parameter for *BufferWriter* for better serializer performance (more than 50% improvement). Default config is resizable buffer (*std::vector<uint8_t>*).
* after serialization, call *getWrittenRange* to get valid range written to buffer, because BufferWritter for resizable buffer now always resize to *capacity* to avoid using *back_insert_iterator* for better performance.
* BufferReader only has constructors with iterators (range).
* BufferReader has constructor with iterators (range), and raw value type pointers (begin, end).
* removed **isValid** method from Deserializer, only BufferReader/Writer store states.
* renamed BufferReader **isCompleted** to **isCompletedSuccessfully**, that returns true only when there is no errors and buffer is fully read.
### Bug fixes
* **readBytes** was reading in aligned mode, when scratch value from previous bit operations was zero.
<a name="2.0.1"></a>
# [2.0.1](https://github.com/fraillt/bitsery/compare/v2.0.0...v2.0.1) (2017-08-12)

26
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,26 @@
# Contributing to Bitsery
So you want to contribute something to Bitsery? That's great! Contributions are always very much appreciated, whether it's a bug fix, a new feature - or just a fix to the documentation.
However, to make sure the process of accepting patches goes smoothly, you should try to follow these few simple guidelines when
you contribute:
1. Fork the repository.
2. Create new branch based on the *master* branch (`git checkout -b your_branch master`). If your contribution is a bug fix, you should name your branch `bugfix/xxx`; for a feature, it should be `feature/xxx`. Otherwise, just use your good judgment. Consistent naming of branches is appreciated since it makes the output of `git branch` easier to understand with a single glance.
3. Do your modifications on that branch. Except for special cases, your contribution should include proper unit tests and documentation.
4. Make sure your modifications did not break anything by building, running tests and checking code coverage (test coverage should not be less than 100%):
```shell
mkdir build
cd build
cmake ..
make
ctest
make tests_coverage
x-www-browser ./coverage/index.html
```
5. Commit your changes, and push to your fork (`git push origin your_branch`). Commit message should be one line short description. When applicable, please squash adjacent *wip* commits into a single *logical* commit.
6. Open a pull request against Bitsery *master* branch. Currently ongoing development is on *master*. At some point an integration branch will be set-up, and pull-requests should target that, but for now its all against master. You may see feature branches come and go, too.
## Style guide
Just use your own judgment and stick to the style of the surrounding code.

View File

@@ -4,84 +4,32 @@
[![Join the chat at https://gitter.im/bitsery/Lobby](https://badges.gitter.im/bitsery/Lobby.svg)](https://gitter.im/bitsery/Lobby)
Header only C++ binary serialization library.
It is designed around the networking requirements for multiplayer real-time fast paced games as first person shooters.
It is designed around the networking requirements for real-time data delivery, especially for games.
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.
All cross-platform requirements are enforced at compile time, so serialized data do not store any meta-data information and is as small as possible.
> **bitsery** is looking for your feedback on [gitter](https://gitter.im/bitsery/Lobby)
## Features
* Cross-platform compatible
* Cross-platform compatible.
* Optimized for speed and space.
* No code generation required: no IDL or metadata, use your types directly.
* 2-in-1 declarative control flow, same code for serialization and deserialization.
* Provides forward/backward compatibility for your types.
* Allows fine-grained serialization control using advanced serialization techniques like value ranges, entrophy encoding etc...
* Extendable for types that requires different serialization and deserialization logic (e.g. pointers, or geometry compression).
* Configurable endianess support.
* Advanced serialization features like value ranges and entrophy encoding.
* Easy to extend for types that requires different serialization and deserialization logic (e.g. pointers, or geometry compression).
* No exceptions. Error checking at runtime for deserialization, and asserts on serialization.
## Why to use bitsery
Read more about the "why" in library [motivation](doc/design/README.md) section.
## How to use it
This documentation comprises these parts:
* [Tutorial](doc/tutorial/README.md) - getting started.
* [Reference section](doc/README.md) - all the details.
## Example
```cpp
#include <bitsery/bitsery.h>
enum class MyEnum:uint16_t { V1,V2,V3 };
struct MyStruct {
uint32_t i;
MyEnum e;
std::vector<float> fs;
};
//define how object should be serialized/deserialized
SERIALIZE(MyStruct) {
s.value4(o.i);
s.value2(o.e);
s.container4(o.fs, 10);
};
using namespace bitsery;
int main() {
//set some random data
MyStruct data{8941, MyEnum::V2, {15.0f, -8.5f, 0.045f}};
MyStruct res{};
//create serializer
//1) create buffer to store data
std::vector<uint8_t> buffer;
//2) create buffer writer that is able to write bytes or bits to buffer
BufferWriter bw{buffer};
//3) create serializer
Serializer<BufferWriter> ser{bw};
//serialize object, can also be invoked like this: serialize(ser, data)
ser.object(data);
//flush to buffer, before creating buffer reader
bw.flush();
//create deserializer
//1) create buffer reader
BufferReader br{bw.getWrittenRange()};
//2) create deserializer
Deserializer<BufferReader> des{br};
//deserialize same object, can also be invoked like this: serialize(des, data)
des.object(res);
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.
## Requirements
## Platforms

View File

@@ -2,34 +2,50 @@ To get the most out of **Bitsery**, start with the [tutorial](tutorial/README.md
Once you're familiar with the library consider the following reference material.
Library design:
* [*valueN* instead of *value*](design/function_n.md)
* [fundamental types](design/fundamental_types.md)
* [serializer/deserializer functions overloads](design/function_overload.md)
* [extending library functionality](design/extensions.md)
* [errors handling](design/errors.md)
* `valueNb instead of value`
* `fundamental types`
* `serializer/deserializer functions overloads`
* `extending library functionality`
* `errors handling`
* `forward/backward compatibility via growable`
Serializer/Deserializer functions (alphabetical order):
* [align](fnc_array.md)
* [array](fnc_array.md)
* [boolBit/Byte](fnc_bool.md)
* [container](fnc_container.md)
* [custom](fnc_custom.md)
* [extension](fnc_extension.md)
* [isValid](reference/fnc_is_valid.md)
* [object](fnc_object.md)
* [range](fnc_range.md)
* [substitution](fnc_substitution.md)
* [text](fnc_text.md)
* [value](fnc_value.md)
Core Serializer/Deserializer functions (alphabetical order):
* `boolByte`
* `container`
* `object`
* `text`
* `value`
Advanced Serializer/Deserializer functions (alphabetical order):
* `align`
* `boolBit`
* `entropy`
* `extend`
* `growable`
* `range`
BasicBufferWriter/Reader functions:
* [writeBits](bb_write_bits.md)
* `writeBits/readBits`
* `writeBytes/readBytes`
* `writeBuffer/readBuffer`
* `align`
* `beginSession/endSession`
* `flush (writer only)`
* `setError (reader only)`
* `getError (reader only)`
* `isCompletedSuccessfully (reader only)`
Tips and tricks:
* if you're getting static assert "please define 'serialize' function", most likely it is because your SERIALIZE function is not defined in same namespace as object.
Advanced topics:
Limitations:
* max **text** or **container** size can be 2^(n-2) (where n = sizeof(std::size_t) * 8) for 32-bit systems it is 1073741823 (0x3FFFFFF).
* when using **growable** serialized buffer cannot be greater than 2^(n-2) (where n = sizeof(std::size_t) * 8).
Other:
* [Why Bitsery?](why-bitsery.md)
* [Contributing](../CONTRIBUTING.md)
* [Change log](../CHANGELOG.md)

27
doc/design/README.md Normal file
View File

@@ -0,0 +1,27 @@
## Motivation
Inspiration to create **bitsery** came mainly because there aren't any good alternatives for C++.
Most well-known serialization libraries are *too fat* and tries to solve too many things by supporting multiple data formats (binary, json, xml) and multiple languages (C++, C#, Javascript, etc..) while in the process becomes hard to use, are memory or/and speed inefficient.
The best alternative that I was able to find is [flatbuffers](https://google.github.io/flatbuffers/).
It is fast, memory efficient, and [comparing with other alternatives](https://google.github.io/flatbuffers/flatbuffers_benchmarks.html) looks like *de facto* choice for games.
While Flatbuffers is designed with multiple programming languages support, bitsery is designed specifically for C++.
## A word about JSON
People use C++ because they want speed and memory efficiency, and JSON is not on the list of efficient serialization format.
Although JSON is very readable and very convenient when used together with dynamically typed languages (such as JavaScript).
When serializing data from statically typed languages, however, JSON not only has the obvious drawback of runtime inefficiency, but also forces you to write more code to access data (counterintuitively) due to its dynamic-typing serialization system.
It's also a text format,- human readable, but space inefficient.
Adding optional support for JSON doesn't come for free either.
When there is no multi-language support, we no longer require IDL(interface definition language) to define schemas so we could have consistent interface across multiple languages.
When we no longer have code generation, it becomes imposibble to support JSON *for free* without defining additional metadata, because C++ doesn't have a reflection system (although static reflection was proposed to standard recently).
So, to avoid unnecessary library complexity it is best to forget about JSON, and stick with what machines and C++ is good at,- binary format.
Bitsery is a result of what you can get, when you sacrifice multi-language support and JSON format, but take other *goodies*.
# Bitsery
*todo*

View File

@@ -0,0 +1,6 @@
*document in progress*
* NO_ERROR,
* BUFFER_OVERFLOW,
* INVALID_BUFFER_DATA
* write what happens when data is corrupted
* how growable effect error codes when deserializing old data format.

View File

@@ -0,0 +1,7 @@
### BufferReader.isCompletedSuccessfully()
Returns true when buffer was fully read, and there was no [errors](buf_get_error.md).
If buffer contains multiple serialized objects and you want to know deserialization state for each object, then use [getError](buf_get_error.md) after reading each object.
Use this function when buffer contains one object, or you know that you read all data.

View File

@@ -0,0 +1,3 @@
*document in progress*
* explain why *value* and *object* is fundamental functions.
* write about **growable** and **customize**

View File

@@ -88,10 +88,10 @@ struct Player {
using namespace bitsery;
void serialize(Serializer<BufferWriter>& s, const Player& data) {
s.value4(data.pos.x);
s.value4(data.pos.y);
s.value4(data.pos.z);
s.text1(data.name);
s.value4b(data.pos.x);
s.value4b(data.pos.y);
s.value4b(data.pos.z);
s.text1b(data.name);
}
int main() {
@@ -127,7 +127,7 @@ Let's look at the code, how we did this.
There are three distinct parts that participate in serialization process.
* Buffer - container that we store our serialized data, in our case vector<uint8_t>.
* BufferWriter - resposible for writing bytes and bits to *Buffer*, it also makes sure that it is portable across Little and Big endian systems.
* Serializer - extendable interface that converts any type to bytes or bits, and use *BufferWriter* to write them. Serializer interface also ensures that code is portable at compile time. This means, that if your serialization code compiles on other platform, it will be 100% correct.
* Serializer - extendable interface that converts any type to bytes or bits, and use *BufferWriter* to write them. Serializer object does not store any state, it only forwards all calls to BufferWritter, further more it ensures that code is portable at compile time. This means, that if your serialization code compiles on other platform, it will be 100% correct.
```cpp
std::vector<uint8_t> buf;
BufferWriter bw{buf};
@@ -135,20 +135,20 @@ There are three distinct parts that participate in serialization process.
```
Serialization function is very readable, and explicitly express intent what and how to serialize:
* *value4* serialize [fundamental type](../design/fundamental_types.md) (ints, floats, chars, enums) of 4 bytes.
* *text1* effectively serialize text, and underlying text type is 1byte per letter.
* *value4b* serialize [fundamental type](../design/fundamental_types.md) (ints, floats, chars, enums) of 4 bytes.
* *text1b* effectively serialize text, and underlying text type is 1byte per letter.
```cpp
s.value4(data.pos.x);
s.value4(data.pos.y);
s.value4(data.pos.z);
s.text1(data.name);
s.value4b(data.pos.x);
s.value4b(data.pos.y);
s.value4b(data.pos.z);
s.text1b(data.name);
```
> learn more about why you need to write [value4 instead of value](../design/function_n.md).
> learn more about why you need to write [value4b instead of value](../design/function_n.md).
Finally, before getting serialized data you must *flush* BufferWriter, it writes any remaining bits to buffer. In our case it is not required, because we only worked with whole bytes, but it is good practice to always use it after finishing serialization.
Finally, before getting serialized data you must *flush* BufferWriter, it writes any remaining bits to buffer and additional data for types that require forward/backward compatibility. In our case it is not required, because we only worked with whole bytes, but it is good practice to always use it after finishing serialization.
To actually get written data you must call *getWrittenRange*, it return begin/end iterators to our buffer (*std::vector<uint8_t> buf*), for performance reasons BufferWritter always resizes underlying buffer to *capacity* so it could use containers iterator to update data, instead of back_insert_iterator to insert data.

View File

@@ -9,10 +9,10 @@ So one way to make this happen is to have *Serializer/Deserializer* as template
```cpp
template <typename S>
void serialize(S& s, Player& o) {
s.value4(o.pos.x);
s.value4(o.pos.y);
s.value4(o.pos.z);
s.text1(o.name);
s.value4b(o.pos.x);
s.value4b(o.pos.y);
s.value4b(o.pos.z);
s.text1b(o.name);
}
```
@@ -53,10 +53,10 @@ struct Player {
using namespace bitsery;
SERIALIZE(Player) {
s.value4(o.pos.x);
s.value4(o.pos.y);
s.value4(o.pos.z);
s.text1(o.name);
s.value4b(o.pos.x);
s.value4b(o.pos.y);
s.value4b(o.pos.z);
s.text1b(o.name);
}
Player createData() {
@@ -86,8 +86,7 @@ int main() {
serialize(des, res);
std::cout << "deserializer state: " << des.isValid() << std::endl
<< "buffer completed: " << br.isCompleted() << std::endl
std::cout << "buffer completed successfully: " << br.isCompletedSuccessfully() << std::endl
<< "pos equals: " << (res.pos == data.pos) << std::endl
<< "name equals: " << (strcmp(res.name, data.name) == 0);
return 0;
@@ -95,8 +94,7 @@ int main() {
```
```bash
deserializer state: 1
buffer completed: 1
buffer completed successfully: 1
pos equals: 1
name equals: 1
```
@@ -105,14 +103,14 @@ We created *Deserializer* and modified *serialize* function to accept *Serialize
Deserialization is very similar as serialization, it also consists of three separate components:
* Buffer - container that we read data from, in our case *vector<uint8_t>*.
* BufferReader - reads bytes and bits from *Buffer*, it also makes sure that it is portable across Little and Big endian systems.
* Deserializer - same interface as *Serializer* that use *BufferReader* to read bits and bytes, and convert to specific type. Deserializer also checks for errors at runtime, because data might come from untrusted source and can terminate program with buffer-overflow or segmentation fault if we are not careful.
* BufferReader - reads bytes and bits from *Buffer*, it makes sure that it is portable across Little and Big endian systems and also checks for errors at runtime, because data might come from untrusted source and can terminate program with buffer-overflow or segmentation fault if we are not careful.
* Deserializer - same interface as *Serializer* but forward all data to *BufferReader* to read bits and bytes.
Since deserialization involves error checking there are two additional functions to check if everything is correct after deserialization.
* [BufferReader.isCompleted()](../reference/buf_is_completed.md) - returns true, if whole buffer was read during deserialization.
* [Deserializer.isValid()](../reference/fnc_is_valid.md) - returns true, if there was no errors during deserialization.
* [BufferReader.isCompletedSuccessfully()](../reference/buf_is_completed_successfully.md) - returns true, if whole buffer was read during deserialization and no errors was found.
* [BufferReader.getError()](../reference/buf_get_error.md) - returns current buffer reader state. Useful when buffer contains more than one object, and you want to check each objects deserialization state separately.
One thing to note about BufferReader is that it doesn't have constructor that accepts buffer directly. Instead it only accepts begin/end iterators, because it needs to know precise data buffer length, to correctly use *isComplete* function.
One thing to note about BufferReader is that it doesn't have constructor that accepts buffer directly. Instead it only accepts begin/end iterators, because it needs to know precise data buffer length, to correctly use *isCompleteSuccessfully* function.
```cpp
BufferReader br{bw.getWrittenRange()};

View File

@@ -9,9 +9,9 @@ struct MyStruct {
//define how object should be serialized/deserialized
SERIALIZE(MyStruct) {
s.value4(o.i);
s.value2(o.e);
s.container4(o.fs, 10);
s.value4b(o.i);
s.value2b(o.e);
s.container4b(o.fs, 10);
};
using namespace bitsery;

View File

@@ -24,8 +24,8 @@
#ifndef BITSERY_BITSERY_H
#define BITSERY_BITSERY_H
#define BITSERY_MAJOR_VERSION 2
#define BITSERY_MINOR_VERSION 1
#define BITSERY_MAJOR_VERSION 3
#define BITSERY_MINOR_VERSION 0
#define BITSERY_PATCH_VERSION 0
#define BITSERY_QUOTE_MACRO(name) #name

View File

@@ -27,18 +27,23 @@
#include "common.h"
#include <algorithm>
#include <cstring>
namespace bitsery {
template<typename Config>
struct BasicBufferReader {
using BufferType = typename Config::BufferType;
using ValueType = typename BufferType::value_type;
using IteratorType = typename BufferType::iterator;
using BufferIteratorType = typename BufferType::iterator;
using ScratchType = typename details::SCRATCH_TYPE<ValueType>::type;
BasicBufferReader(IteratorType begin, IteratorType end)
:_pos{begin}, _end{end} {
BasicBufferReader(ValueType* begin, ValueType* end)
:_pos{begin},
_end{end},
_session{*this, _pos, _end}
{
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),
@@ -46,8 +51,13 @@ namespace bitsery {
static_assert(sizeof(ValueType) == 1, "currently only supported BufferValueType is 1 byte");
}
BasicBufferReader(BufferRange<IteratorType> range)
:BasicBufferReader(range.begin(), range.end()) {}
BasicBufferReader(BufferRange<BufferIteratorType> range)
:BasicBufferReader(std::addressof(*range.begin()), std::addressof(*range.end())) {
static_assert(std::is_same<
typename std::iterator_traits<BufferIteratorType>::iterator_category,
std::random_access_iterator_tag>::value,
"BufferReader only accepts random access iterators");
}
BasicBufferReader(const BasicBufferReader &) = delete;
@@ -61,82 +71,115 @@ namespace bitsery {
template<size_t SIZE, typename T>
bool readBytes(T &v) {
void readBytes(T &v) {
static_assert(std::is_integral<T>(), "");
static_assert(sizeof(T) == SIZE, "");
using UT = typename std::make_unsigned<T>::type;
return !m_scratch
? directRead(&v, 1)
: readBits(reinterpret_cast<UT &>(v), details::BITS_SIZE<T>);
if (!m_scratchBits)
directRead(&v, 1);
else
readBits(reinterpret_cast<UT &>(v), details::BITS_SIZE<T>);
}
template<size_t SIZE, typename T>
bool readBuffer(T *buf, size_t count) {
void readBuffer(T *buf, size_t count) {
static_assert(std::is_integral<T>(), "");
static_assert(sizeof(T) == SIZE, "");
if (!m_scratchBits)
return directRead(buf, count);
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;
if (!m_scratchBits) {
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)
readBits(reinterpret_cast<UT &>(*it), details::BITS_SIZE<T>);
}
return true;
}
template<typename T>
bool readBits(T &v, size_t bitsCount) {
void readBits(T &v, size_t bitsCount) {
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
const auto bytesRequired = bitsCount > m_scratchBits
? ((bitsCount - 1 - m_scratchBits) >> 3) + 1u
: 0u;
if (static_cast<size_t>(std::distance(_pos, _end)) < bytesRequired)
return false;
readBitsInternal(v, bitsCount);
return true;
}
bool align() {
void align() {
if (m_scratchBits) {
ScratchType tmp{};
readBitsInternal(tmp, m_scratchBits);
return tmp == 0;
if (tmp)
setError(BufferReaderError::INVALID_BUFFER_DATA);
}
return true;
}
bool isCompleted() const {
return _pos == _end;
bool isCompletedSuccessfully() const {
return _pos == _end && !_session.hasActiveSessions();
}
BufferReaderError getError() const {
auto res = std::distance(_end, _pos);
if (res > 0) {
auto err = static_cast<BufferReaderError>(res);
if (_session.hasActiveSessions() && err == BufferReaderError::BUFFER_OVERFLOW)
return BufferReaderError::NO_ERROR;
return err;
}
return BufferReaderError::NO_ERROR;
}
void setError(BufferReaderError error) {
_end = _pos;
//to avoid creating temporary for error state, mark an error by passing _pos after the _end
std::advance(_pos, static_cast<size_t>(error));
}
void beginSession() {
align();
if (getError() != BufferReaderError::INVALID_BUFFER_DATA) {
_session.begin();
}
}
void endSession() {
align();
if (getError() != BufferReaderError::INVALID_BUFFER_DATA) {
_session.end();
}
}
private:
IteratorType _pos;
IteratorType _end;
ValueType* _pos;
ValueType* _end;
details::BufferSessionsReader<BasicBufferReader<Config>, ValueType*> _session;
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.
template<typename T>
bool directRead(T *v, size_t count) {
void directRead(T *v, size_t count) {
static_assert(!std::is_const<T>::value, "");
const auto bytesCount = sizeof(T) * count;
if (static_cast<size_t>(std::distance(_pos, _end)) < bytesCount)
return false;
//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;
if (std::distance(_pos, _end) >= static_cast<typename BufferType::difference_type>(bytesCount)) {
std::memcpy(reinterpret_cast<ValueType *>(v), _pos, bytesCount);
_pos += bytesCount;
//swap each byte if nessesarry
_swapDataBits(v, count, std::integral_constant<bool,
Config::NetworkEndianness != details::getSystemEndianness()>{});
} else {
//set everything to zeros
std::memset(v, 0, bytesCount);
if (getError() == BufferReaderError::NO_ERROR)
setError(BufferReaderError::BUFFER_OVERFLOW);
}
}
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); });
std::for_each(v, std::next(v, count), [this](T &x) { x = details::swap(x); });
}
template<typename T>
@@ -166,9 +209,6 @@ namespace bitsery {
v = res;
}
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>;

View File

@@ -55,11 +55,31 @@ namespace bitsery {
}
void align() {
_bitsCount += 8 - (_bitsCount % 8);
auto _scratch = (_bitsCount % 8);
_bitsCount += (8 - _scratch) % 8;
}
void flush() {
_bitsCount += (8 - (_bitsCount % 8)) % 8;
align();
//flush sessions count
if (_sessionsBytesCount > 0) {
auto sessionsDataSizeBytesCount = (_sessionsBytesCount < 0x8000u ? 2 : 4);
_bitsCount += (_sessionsBytesCount + sessionsDataSizeBytesCount) * 8;
_sessionsBytesCount = 0;
}
}
void beginSession() {
}
void endSession() {
auto endPos = getWrittenBytesCount();
details::writeSize(*this, endPos);
auto sessionEndBytesCount = getWrittenBytesCount() - endPos;
//remove written bytes, because we'll write them at the end
_bitsCount -= sessionEndBytesCount * 8;
_sessionsBytesCount += sessionEndBytesCount;
}
//get size in bytes
@@ -69,7 +89,7 @@ namespace bitsery {
private:
size_t _bitsCount{};
size_t _sessionsBytesCount{};
};
template<typename Config>
@@ -79,7 +99,9 @@ namespace bitsery {
using ScratchType = typename details::SCRATCH_TYPE<ValueType>::type;
using BufferContext = details::WriteBufferContext<BufferType, Config::FixedBufferSize>;
explicit BasicBufferWriter(BufferType &buffer) : _bufferContext{buffer} {
explicit BasicBufferWriter(BufferType &buffer)
: _bufferContext{buffer}
{
static_assert(std::is_unsigned<ValueType>(), "Config::BufferType::value_type must be unsigned");
static_assert(std::is_unsigned<ScratchType>(), "Config::BufferScrathType must be unsigned");
static_assert(sizeof(ValueType) * 2 == sizeof(ScratchType),
@@ -134,23 +156,29 @@ namespace bitsery {
}
void align() {
if (_scratchBits)
writeBitsInternal(ValueType{}, details::BITS_SIZE<ValueType> - _scratchBits);
writeBitsInternal(ValueType{}, (details::BITS_SIZE<ValueType> - _scratchBits) % 8);
}
void flush() {
if (_scratchBits) {
auto tmp = static_cast<ValueType>( _scratch & _MASK );
directWrite(&tmp, 1);
_scratch >>= _scratchBits;
_scratchBits -= _scratchBits;
}
align();
_session.flushSessions(*this);
}
BufferRange<typename BufferType::iterator> getWrittenRange() const {
return _bufferContext.getWrittenRange();
}
void beginSession() {
align();
_session.begin();
}
void endSession() {
align();
auto range = _bufferContext.getWrittenRange();
_session.end(static_cast<size_t>(std::distance(range.begin(), range.end())));
}
private:
template<typename T>
@@ -212,6 +240,7 @@ namespace bitsery {
BufferContext _bufferContext;
ScratchType _scratch{};
size_t _scratchBits{};
details::BufferSessionsWriter _session{};
};
//helper type

View File

@@ -27,8 +27,6 @@
#include "details/buffer_common.h"
#include <vector>
#include <list>
namespace bitsery {
struct DefaultConfig {

View File

@@ -1,220 +0,0 @@
//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_DELTADESERIALIZER_H
#define BITSERY_DELTADESERIALIZER_H
#include <array>
#include <stack>
#include <algorithm>
#include "deserializer.h"
namespace bitsery {
template<typename Reader, typename TObj>
class DeltaDeserializer {
public:
DeltaDeserializer(Reader &r, const TObj &oldObj, const TObj &newObj)
: _deserializer{r},
_reader{r},
_oldObj{oldObj},
_newObj{newObj},
_objMemPos(std::deque<details::ObjectMemoryPosition>(1, details::ObjectMemoryPosition{oldObj, newObj})),
_isNewElement{false} {
};
template<size_t SIZE = 0, typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
DeltaDeserializer &value(T &v) {
if (getChangedState(v)) {
constexpr size_t ValueSize = SIZE == 0 ? sizeof(T) : SIZE;
_reader.template readBytes<ValueSize>(v);
}
return *this;
}
template<typename T>
DeltaDeserializer &object(T &&obj) {
if (getChangedState(obj))
serialize(*this, std::forward<T>(obj));
return *this;
}
template<size_t VSIZE = 1, typename T>
DeltaDeserializer &text(std::basic_string<T> &str, size_t maxSize) {
if (getChangedState(str)) {
_deserializer.template text<VSIZE>(str, maxSize);
}
return *this;
}
template<size_t VSIZE = 1, typename T, size_t N>
DeltaDeserializer &text(T (&str)[N]) {
if (getChangedState(str)) {
_deserializer.template text<VSIZE>(str);
}
return *this;
}
template<typename T, size_t N, typename Fnc>
DeltaDeserializer &array(std::array<T, N> &arr, Fnc &&fnc) {
if (getChangedState(arr)) {
if (!_isNewElement) {
const auto old = *_objMemPos.top().getOldObjectField(arr);
processContainer(std::begin(old), std::end(old), std::begin(arr), std::end(arr), fnc);
} else {
for (auto &v:arr)
fnc(*this, v);
}
}
return *this;
}
template<typename T, size_t N, typename Fnc>
DeltaDeserializer &array(T (&arr)[N], Fnc &&fnc) {
if (getChangedState(arr)) {
if (!_isNewElement) {
const auto old = *_objMemPos.top().getOldObjectField(arr);
T *tmp = arr;
processContainer(old, old + N, tmp, tmp + N, fnc);
} else {
T *tmp = arr;
for (auto i = 0u; i < N; ++i, ++tmp)
fnc(*this, *tmp);
}
}
return *this;
}
template<typename T, typename Fnc>
DeltaDeserializer &container(T &obj, size_t maxSize, Fnc &&fnc) {
if (getChangedState(obj)) {
size_t newSize{};
_reader.readBits(newSize, 32);
if (!_isNewElement) {
auto old = *_objMemPos.top().getOldObjectField(obj);
if (old.size() != newSize)
obj.resize(newSize);
processContainer(std::begin(old), std::end(old), std::begin(obj), std::end(obj),
std::forward<Fnc>(fnc));
} else {
obj.resize(newSize);
for (auto &v:obj)
fnc(*this, v);
}
}
return *this;
}
private:
Deserializer <Reader> _deserializer;
Reader &_reader;
const TObj &_oldObj;
const TObj &_newObj;
std::stack<details::ObjectMemoryPosition> _objMemPos;
bool _isNewElement;
template<typename T>
bool getChangedState(T &obj) {
if (!_isNewElement) {
if (!readChangedState()) {
obj = *_objMemPos.top().getOldObjectField(obj);
return false;
}
}
return true;
}
template<typename T, size_t N>
bool getChangedState(T (&arr)[N]) {
if (!_isNewElement) {
if (!readChangedState()) {
auto old = *_objMemPos.top().getOldObjectField(arr);
auto end = arr + N;
auto pOld = old;
for (auto p = arr; p != end; ++p, ++pOld)
*p = *pOld;
return false;
}
}
return true;
}
template<typename TConstIt, typename TIt, typename Fnc>
bool processContainer(TConstIt oldBegin, TConstIt oldEnd, TIt begin, TIt end, Fnc &&fnc) {
auto offset = readIndexOffset();
auto p = begin;
auto pOld = oldBegin;
for (; p != end && pOld != oldEnd; ++p, ++pOld) {
if (offset) {
*p = *pOld;
--offset;
} else {
_objMemPos.emplace(details::ObjectMemoryPosition{*pOld, *p});
fnc(*this, *p);
_objMemPos.pop();
offset = readIndexOffset();
}
}
if (offset != 0 && pOld != oldEnd)
return false;
_isNewElement = true;
for (; p != end; ++p, --offset)
fnc(*this, *p);
_isNewElement = false;
return offset == 0;
}
bool readChangedState() {
unsigned char res{};
_reader.readBits(res, 1);
return res;
}
size_t readIndexOffset() {
//special case, if items are updated sequentialy
unsigned char tmp{};
_reader.readBits(tmp, 1);
if (tmp) {
return 0u;
} else {
size_t res{};
_reader.readBits(tmp, 1);
if (tmp > 0)
_reader.readBits(res, 4);
else
_reader.readBits(res, 32);
return res;
}
}
};
}
#endif //BITSERY_DELTADESERIALIZER_H

View File

@@ -1,220 +0,0 @@
//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_DELTASERIALIZER_H
#define BITSERY_DELTASERIALIZER_H
#include <array>
#include <stack>
#include <algorithm>
#include "serializer.h"
namespace bitsery {
template<typename Writter, typename TObj>
class DeltaSerializer {
public:
DeltaSerializer(Writter &w, const TObj &oldObj, const TObj &newObj)
: _serializer{w},
_writter{w},
_oldObj{oldObj},
_newObj{newObj},
_objMemPos(std::deque<details::ObjectMemoryPosition>(1, details::ObjectMemoryPosition{oldObj, newObj})),
_isNewElement{false} {
};
template<size_t SIZE = 0, typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
DeltaSerializer &value(const T &v) {
if (setChangedState(v)) {
constexpr size_t ValueSize = SIZE == 0 ? sizeof(T) : SIZE;
_writter.template writeBytes<ValueSize>(v);
}
return *this;
}
template<typename T>
DeltaSerializer &object(T &&obj) {
if (setChangedState(obj)) {
serialize(*this, std::forward<T>(obj));
}
return *this;
}
template<size_t VSIZE = 1, typename T>
DeltaSerializer &text(const std::basic_string<T> &str, size_t maxSize) {
if (setChangedState(str)) {
_serializer.template text<VSIZE>(str, maxSize);
}
return *this;
}
template<size_t VSIZE = 1, typename T, size_t N>
DeltaSerializer &text(const T (&str)[N]) {
if (setChangedState(str)) {
_serializer.template text<VSIZE>(str);
}
return *this;
}
template<typename T, size_t N, typename Fnc>
DeltaSerializer &array(const std::array<T, N> &arr, Fnc &&fnc) {
if (setChangedState(arr)) {
if (!_isNewElement) {
const auto &old = *_objMemPos.top().getOldObjectField(arr);
processContainer(std::begin(old), std::end(old), std::begin(arr), std::end(arr), fnc);
} else {
for (auto &v:arr)
fnc(*this, v);
}
}
return *this;
}
template<typename T, size_t N, typename Fnc>
DeltaSerializer &array(const T (&arr)[N], Fnc &&fnc) {
if (setChangedState(arr)) {
if (!_isNewElement) {
auto old = *_objMemPos.top().getOldObjectField(arr);
const T *tmp = arr;
processContainer(old, old + N, tmp, tmp + N, fnc);
} else {
const T *tmp = arr;
for (auto i = 0u; i < N; ++i, ++tmp)
fnc(*this, *tmp);
}
}
return *this;
}
template<typename T, typename Fnc>
DeltaSerializer &container(T &&obj, size_t maxSize, Fnc &&fnc) {
if (setChangedState(obj)) {
_writter.writeBits(obj.size(), 32);
if (!_isNewElement) {
auto old = *_objMemPos.top().getOldObjectField(obj);
processContainer(std::begin(old), std::end(old), std::begin(obj), std::end(obj),
std::forward<Fnc>(fnc));
} else {
for (auto &v:obj)
fnc(*this, v);
}
}
return *this;
}
private:
Serializer <Writter> _serializer;
Writter &_writter;
const TObj &_oldObj;
const TObj &_newObj;
std::stack<details::ObjectMemoryPosition> _objMemPos;
bool _isNewElement;
template<typename T>
bool setChangedState(const T &obj) {
if (!_isNewElement) {
auto res = !_objMemPos.top().isFieldsEquals(obj);
writeChangedState(res);
return res;
}
return true;
}
template<typename T, size_t N>
bool setChangedState(const T (&arr)[N]) {
if (!_isNewElement) {
auto old = *_objMemPos.top().getOldObjectField(arr);
auto end = arr + N;
bool changed{};
for (auto p = arr, pOld = old; p != end; ++p, ++pOld) {
if (!(*p == *pOld)) {
changed = true;
break;
}
}
writeChangedState(changed);
return changed;
}
return true;
}
template<typename T, typename Fnc>
void processContainer(const T oldBegin, const T oldEnd, const T begin, const T end, Fnc &&fnc) {
auto misMatch = std::mismatch(oldBegin, oldEnd, begin, end);
auto lastChanged = begin;
while (misMatch.first != oldEnd && misMatch.second != end) {
writeIndexOffset(std::distance(lastChanged, misMatch.second));
_objMemPos.emplace(details::ObjectMemoryPosition{*misMatch.first, *misMatch.second});
fnc(*this, *misMatch.second);
_objMemPos.pop();
++misMatch.first;
++misMatch.second;
lastChanged = misMatch.second;
misMatch = std::mismatch(misMatch.first, oldEnd, misMatch.second, end);
}
auto p = misMatch.second;
//write items left
writeIndexOffset(std::distance(lastChanged, end));
//write old elements
for (auto pOld = misMatch.first; p != end && pOld != oldEnd; ++p, ++pOld) {
_objMemPos.emplace(details::ObjectMemoryPosition{*pOld, *p});
fnc(*this, *p);
_objMemPos.pop();
}
//write new elements
_isNewElement = true;
for (; p != end; ++p)
fnc(*this, *p);
_isNewElement = false;
}
void writeChangedState(bool state) {
_writter.writeBits(state ? 1u : 0u, 1);
}
void writeIndexOffset(const size_t offset) {
//special case, if items are updated sequentialy
if (offset == 0) {
_writter.writeBits(1u, 1);
} else {
_writter.writeBits(0u, 1);
auto smallOffset = offset < 16;
_writter.writeBits(smallOffset ? 1u : 0u, 1);
if (smallOffset)
_writter.writeBits(offset, 4);
else
_writter.writeBits(offset, 32);
}
}
};
}
#endif //BITSERY_DELTASERIALIZER_H

View File

@@ -35,7 +35,15 @@ namespace bitsery {
template<typename Reader>
class Deserializer {
public:
Deserializer(Reader &r) : _reader{r}, _isValid{true} {};
Deserializer(Reader &r, void* context = nullptr) : _reader{r}, _context{context} {};
/*
* get serialization context.
* this is optional, but might be required for some specific deserialization flows.
*/
void* getContext() {
return _context;
}
/*
* object function
@@ -43,10 +51,14 @@ namespace bitsery {
template<typename T>
void object(T &&obj) {
if (_isValid)
details::SerializeFunction<Deserializer, T>::invoke(*this, std::forward<T>(obj));
details::SerializeFunction<Deserializer, T>::invoke(*this, std::forward<T>(obj));
}
template<typename T, typename Fnc>
void object(T &&obj, Fnc &&fnc) {
fnc(*this, std::forward<T>(obj));
};
/*
* value overloads
*/
@@ -54,53 +66,48 @@ namespace bitsery {
template<size_t VSIZE, typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
void value(T &v) {
static_assert(std::numeric_limits<T>::is_iec559, "");
if (_isValid)
_isValid = _reader.template readBytes<VSIZE>(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T> &>(v));
_reader.template readBytes<VSIZE>(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T> &>(v));
}
template<size_t VSIZE, typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
void value(T &v) {
using UT = std::underlying_type_t<T>;
if (_isValid)
_isValid = _reader.template readBytes<VSIZE>(reinterpret_cast<UT &>(v));
_reader.template readBytes<VSIZE>(reinterpret_cast<UT &>(v));
}
template<size_t VSIZE, typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
void value(T &v) {
if (_isValid)
_isValid = _reader.template readBytes<VSIZE>(v);
_reader.template readBytes<VSIZE>(v);
}
/*
* custom function
* growable function
*/
template<typename T, typename Fnc>
void custom(T &&obj, Fnc &&fnc) {
if (_isValid)
fnc(*this, std::forward<T>(obj));
template <typename T, typename Fnc>
void growable(T&& obj, Fnc&& fnc) {
_reader.beginSession();
fnc(*this, std::forward<T>(obj));
_reader.endSession();
};
/*
* extension functions
* extend functions
*/
template<typename T, typename Ext, typename Fnc>
void extension(T &obj, Ext &&ext, Fnc &&fnc) {
if (_isValid)
ext.deserialize(obj, *this, std::forward<Fnc>(fnc));
void extend(T &obj, Ext &&ext, Fnc &&fnc) {
ext.deserialize(*this, _reader, obj, std::forward<Fnc>(fnc));
};
template<size_t VSIZE, typename T, typename Ext>
void extension(T &obj, Ext &&ext) {
if (_isValid)
ext.deserialize(obj, *this, [](auto &s, auto &v) { s.template value<VSIZE>(v); });
void extend(T &obj, Ext &&ext) {
ext.deserialize(*this, _reader, obj, [](auto &s, auto &v) { s.template value<VSIZE>(v); });
};
template<typename T, typename Ext>
void extension(T &obj, Ext &&ext) {
if (_isValid)
ext.deserialize(obj, *this, [](auto &s, auto &v) { s.object(v); });
void extend(T &obj, Ext &&ext) {
ext.deserialize(*this, _reader, obj, [](auto &s, auto &v) { s.object(v); });
};
/*
@@ -108,21 +115,17 @@ namespace bitsery {
*/
void boolBit(bool &v) {
if (_isValid) {
unsigned char tmp;
_isValid = _reader.readBits(tmp, 1);
v = tmp == 1;
}
uint8_t tmp{};
_reader.readBits(tmp, 1);
v = tmp == 1;
}
void boolByte(bool &v) {
if (_isValid) {
unsigned char tmp;
_isValid = _reader.template readBytes<1>(tmp);
if (_isValid)
_isValid = tmp < 2;
v = tmp == 1;
}
unsigned char tmp;
_reader.template readBytes<1>(tmp);
if (tmp > 1)
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
v = tmp == 1;
}
/*
@@ -131,51 +134,45 @@ namespace bitsery {
template<typename T>
void range(T &v, const RangeSpec<T> &range) {
if (_isValid) {
_isValid = _reader.readBits(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T> &>(v), range.bitsRequired);
details::setRangeValue(v, range);
if (_isValid)
_isValid = details::isRangeValid(v, range);
_reader.readBits(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T> &>(v), range.bitsRequired);
details::setRangeValue(v, range);
if (!details::isRangeValid(v, range)) {
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
v = range.min;
}
}
/*
* substitution overloads
* entropy overloads
*/
template<typename T, size_t N, typename Fnc>
void substitution(T &v, const std::array<T, N> &expectedValues, Fnc &&fnc) {
void entropy(T &v, const T (&expectedValues)[N], Fnc &&fnc) {
size_t index;
range(index, {{}, N + 1});
if (_isValid) {
if (index)
v = expectedValues[index - 1];
else
fnc(*this, v);
}
if (index)
v = expectedValues[index - 1];
else
fnc(*this, v);
};
template<size_t VSIZE, typename T, size_t N>
void substitution(T &v, const std::array<T, N> &expectedValues) {
void entropy(T &v, const T (&expectedValues)[N]) {
size_t index;
range(index, {{}, N + 1});
if (_isValid) {
if (index)
v = expectedValues[index - 1];
else
value<VSIZE>(v);
}
if (index)
v = expectedValues[index - 1];
else
value<VSIZE>(v);
};
template<typename T, size_t N>
void substitution(T &v, const std::array<T, N> &expectedValues) {
void entropy(T &v, const T (&expectedValues)[N]) {
size_t index;
range(index, {{}, N + 1});
if (_isValid) {
if (index)
v = expectedValues[index - 1];
else
object(v);
}
if (index)
v = expectedValues[index - 1];
else
object(v);
};
/*
@@ -183,96 +180,96 @@ namespace bitsery {
*/
template<size_t VSIZE, typename T>
void text(std::basic_string<T> &str, size_t maxSize) {
void text(T &str, size_t maxSize) {
size_t size;
readSize(size, maxSize);
if (_isValid) {
str.resize(size);
procContainer<VSIZE>(std::begin(str), std::end(str), std::true_type{});
}
str.resize(size);
procContainer<VSIZE>(std::begin(str), std::end(str), std::true_type{});
}
template<size_t VSIZE, typename T, size_t N>
void text(T (&str)[N]) {
size_t size;
readSize(size, N - 1);
if (_isValid) {
auto first = std::begin(str);
procContainer<VSIZE>(first, std::next(first, size), std::true_type{});
//null-terminated string
str[size] = {};
}
auto first = std::begin(str);
procContainer<VSIZE>(first, std::next(first, size), std::true_type{});
//null-terminated string
str[size] = {};
}
/*
* container overloads
*/
//dynamic size containers
template<typename T, typename Fnc>
void container(T &&obj, size_t maxSize, Fnc &&fnc) {
static_assert(details::IsResizable<T>::value,
"use container(const T&) overload without `maxSize` for static containers");
decltype(obj.size()) size{};
readSize(size, maxSize);
if (_isValid) {
obj.resize(size);
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
}
obj.resize(size);
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
}
template<size_t VSIZE, typename T>
void container(T &obj, size_t maxSize) {
static_assert(details::IsResizable<T>::value,
"use container(const T&) overload without `maxSize` for static containers");
decltype(obj.size()) size{};
readSize(size, maxSize);
if (_isValid) {
obj.resize(size);
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
}
obj.resize(size);
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
}
template<typename T>
void container(T &obj, size_t maxSize) {
static_assert(details::IsResizable<T>::value,
"use container(const T&) overload without `maxSize` for static containers");
decltype(obj.size()) size{};
readSize(size, maxSize);
if (_isValid) {
obj.resize(size);
procContainer(std::begin(obj), std::end(obj));
}
obj.resize(size);
procContainer(std::begin(obj), std::end(obj));
}
//fixed size containers
template<typename T, typename Fnc, typename std::enable_if<!std::is_integral<Fnc>::value>::type * = nullptr>
void container(T &&obj, Fnc &&fnc) {
static_assert(!details::IsResizable<T>::value,
"use container(T&, size_t, Fnc) overload with `maxSize` for dynamic containers");
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
}
/*
* array overloads (fixed size array (std::array, and c-style array))
*/
//std::array overloads
template<typename T, size_t N, typename Fnc>
void array(std::array<T, N> &arr, Fnc &&fnc) {
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
template<size_t VSIZE, typename T>
void container(T &obj) {
static_assert(!details::IsResizable<T>::value,
"use container(T&, size_t) overload with `maxSize` for dynamic containers");
static_assert(VSIZE > 0);
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
}
template<size_t VSIZE, typename T, size_t N>
void array(std::array<T, N> &arr) {
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
}
template<typename T, size_t N>
void array(std::array<T, N> &arr) {
procContainer(std::begin(arr), std::end(arr));
template<typename T>
void container(T &obj) {
static_assert(!details::IsResizable<T>::value,
"use container(T&, size_t) overload with `maxSize` for dynamic containers");
procContainer(std::begin(obj), std::end(obj));
}
//c-style array overloads
template<typename T, size_t N, typename Fnc>
void array(T (&arr)[N], Fnc &&fnc) {
void container(T (&arr)[N], Fnc &&fnc) {
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
}
template<size_t VSIZE, typename T, size_t N>
void array(T (&arr)[N]) {
void container(T (&arr)[N]) {
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
}
template<typename T, size_t N>
void array(T (&arr)[N]) {
void container(T (&arr)[N]) {
procContainer(std::begin(arr), std::end(arr));
}
@@ -280,128 +277,108 @@ namespace bitsery {
_reader.align();
}
bool isValid() const {
return _isValid;
}
//overloads for functions with explicit type size
template<typename T>
void value1(T &&v) { value<1>(std::forward<T>(v)); }
void value1b(T &&v) { value<1>(std::forward<T>(v)); }
template<typename T>
void value2(T &&v) { value<2>(std::forward<T>(v)); }
void value2b(T &&v) { value<2>(std::forward<T>(v)); }
template<typename T>
void value4(T &&v) { value<4>(std::forward<T>(v)); }
void value4b(T &&v) { value<4>(std::forward<T>(v)); }
template<typename T>
void value8(T &&v) { value<8>(std::forward<T>(v)); }
void value8b(T &&v) { value<8>(std::forward<T>(v)); }
template<typename T, typename Ext>
void extension1(T &v, Ext &&ext) { extension<1>(v, std::forward<Ext>(ext)); };
void extend1b(T &v, Ext &&ext) { extend<1>(v, std::forward<Ext>(ext)); };
template<typename T, typename Ext>
void extension2(T &v, Ext &&ext) { extension<2>(v, std::forward<Ext>(ext)); };
void extend2b(T &v, Ext &&ext) { extend<2>(v, std::forward<Ext>(ext)); };
template<typename T, typename Ext>
void extension4(T &v, Ext &&ext) { extension<4>(v, std::forward<Ext>(ext)); };
void extend4b(T &v, Ext &&ext) { extend<4>(v, std::forward<Ext>(ext)); };
template<typename T, typename Ext>
void extension8(T &v, Ext &&ext) { extension<8>(v, std::forward<Ext>(ext)); };
void extend8b(T &v, Ext &&ext) { extend<8>(v, std::forward<Ext>(ext)); };
template<typename T, size_t N>
void substitution1(T &v, const std::array<T, N> &expectedValues) { substitution<1>(v, expectedValues); };
void entropy1b(T &v, const T (&expectedValues)[N]) { entropy<1>(v, expectedValues); };
template<typename T, size_t N>
void substitution2(T &v, const std::array<T, N> &expectedValues) { substitution<2>(v, expectedValues); };
void entropy2b(T &v, const T (&expectedValues)[N]) { entropy<2>(v, expectedValues); };
template<typename T, size_t N>
void substitution4(T &v, const std::array<T, N> &expectedValues) { substitution<4>(v, expectedValues); };
void entropy4b(T &v, const T (&expectedValues)[N]) { entropy<4>(v, expectedValues); };
template<typename T, size_t N>
void substitution8(T &v, const std::array<T, N> &expectedValues) { substitution<8>(v, expectedValues); };
void entropy8b(T &v, const T (&expectedValues)[N]) { entropy<8>(v, expectedValues); };
template<typename T>
void text1(std::basic_string<T> &str, size_t maxSize) { text<1>(str, maxSize); }
void text1b(T &str, size_t maxSize) { text<1>(str, maxSize); }
template<typename T>
void text2(std::basic_string<T> &str, size_t maxSize) { text<2>(str, maxSize); }
void text2b(T &str, size_t maxSize) { text<2>(str, maxSize); }
template<typename T>
void text4(std::basic_string<T> &str, size_t maxSize) { text<4>(str, maxSize); }
void text4b(T &str, size_t maxSize) { text<4>(str, maxSize); }
template<typename T, size_t N>
void text1(T (&str)[N]) { text<1>(str); }
void text1b(T (&str)[N]) { text<1>(str); }
template<typename T, size_t N>
void text2(T (&str)[N]) { text<2>(str); }
void text2b(T (&str)[N]) { text<2>(str); }
template<typename T, size_t N>
void text4(T (&str)[N]) { text<4>(str); }
void text4b(T (&str)[N]) { text<4>(str); }
template<typename T>
void container1(T &&obj, size_t maxSize) { container<1>(std::forward<T>(obj), maxSize); }
void container1b(T &&obj, size_t maxSize) { container<1>(std::forward<T>(obj), maxSize); }
template<typename T>
void container2(T &&obj, size_t maxSize) { container<2>(std::forward<T>(obj), maxSize); }
void container2b(T &&obj, size_t maxSize) { container<2>(std::forward<T>(obj), maxSize); }
template<typename T>
void container4(T &&obj, size_t maxSize) { container<4>(std::forward<T>(obj), maxSize); }
void container4b(T &&obj, size_t maxSize) { container<4>(std::forward<T>(obj), maxSize); }
template<typename T>
void container8(T &&obj, size_t maxSize) { container<8>(std::forward<T>(obj), maxSize); }
void container8b(T &&obj, size_t maxSize) { container<8>(std::forward<T>(obj), maxSize); }
template<typename T>
void container1b(T &&obj) { container<1>(std::forward<T>(obj)); }
template<typename T>
void container2b(T &&obj) { container<2>(std::forward<T>(obj)); }
template<typename T>
void container4b(T &&obj) { container<4>(std::forward<T>(obj)); }
template<typename T>
void container8b(T &&obj) { container<8>(std::forward<T>(obj)); }
template<typename T, size_t N>
void array1(std::array<T, N> &arr) { array<1>(arr); }
void container1b(T (&arr)[N]) { container<1>(arr); }
template<typename T, size_t N>
void array2(std::array<T, N> &arr) { array<2>(arr); }
void container2b(T (&arr)[N]) { container<2>(arr); }
template<typename T, size_t N>
void array4(std::array<T, N> &arr) { array<4>(arr); }
void container4b(T (&arr)[N]) { container<4>(arr); }
template<typename T, size_t N>
void array8(std::array<T, N> &arr) { array<8>(arr); }
template<typename T, size_t N>
void array1(T (&arr)[N]) { array<1>(arr); }
template<typename T, size_t N>
void array2(T (&arr)[N]) { array<2>(arr); }
template<typename T, size_t N>
void array4(T (&arr)[N]) { array<4>(arr); }
template<typename T, size_t N>
void array8(T (&arr)[N]) { array<8>(arr); }
void container8b(T (&arr)[N]) { container<8>(arr); }
private:
Reader &_reader;
bool _isValid;
void* _context;
void readSize(size_t &size, size_t maxSize) {
size = {};
if (_isValid) {
unsigned char firstBit;
_isValid = _reader.readBits(firstBit, 1);
if (_isValid) {
if (firstBit) {
_isValid = _reader.readBits(size, 7);
} else {
unsigned char secondBit;
_isValid = _reader.readBits(secondBit, 1);
if (_isValid) {
if (secondBit) {
_isValid = _reader.readBits(size, 14);
} else {
_isValid = _reader.readBits(size, 30);
}
}
}
}
if (_isValid)
_isValid = size <= maxSize;
details::readSize(_reader, size);
if (size > maxSize) {
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
size = {};
}
}
@@ -409,7 +386,7 @@ namespace bitsery {
//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)
for (; first != last; ++first)
value<VSIZE>(*first);
};
@@ -417,21 +394,21 @@ namespace bitsery {
//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));
if (first != last)
_reader.template readBuffer<VSIZE>(&(*first), std::distance(first, last));
};
//process by calling functions
template<typename It, typename Fnc>
void procContainer(It first, It last, Fnc fnc) {
for (; _isValid && first != last; ++first)
for (; first != last; ++first)
fnc(*this, *first);
};
//process object types
template<typename It>
void procContainer(It first, It last) {
for (; _isValid && first != last; ++first)
for (; first != last; ++first)
object(*first);
};

View File

@@ -0,0 +1,73 @@
//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_DETAILS_BOTH_COMMON_H
#define BITSERY_DETAILS_BOTH_COMMON_H
#include <cstdint>
#include <cstddef>
namespace bitsery {
namespace details {
/*
* size read/write functions
*/
template <typename Reader>
void readSize(Reader& r, size_t& size) {
uint8_t hb{};
r.template readBytes<1>(hb);
if (hb < 0x80u) {
size = hb;
} else {
uint8_t lb{};
r.template readBytes<1>(lb);
if (hb & 0x40u) {
uint16_t lw{};
r.template readBytes<2>(lw);
size = ((((hb & 0x3Fu) << 8) | lb) << 16) | lw;
} else {
size = ((hb & 0x7Fu) << 8) | lb;
}
}
}
template <typename Writter>
void writeSize(Writter& w, const size_t size) {
if (size < 0x80u) {
w.template writeBytes<1>(static_cast<uint8_t>(size));
} else {
if (size < 0x4000u) {
w.template writeBytes<1>(static_cast<uint8_t>((size >> 8) | 0x80u));
w.template writeBytes<1>(static_cast<uint8_t>(size));
} else {
assert(size < 0x40000000u);
w.template writeBytes<1>(static_cast<uint8_t>((size >> 24) | 0xC0u));
w.template writeBytes<1>(static_cast<uint8_t>(size >> 16));
w.template writeBytes<2>(static_cast<uint16_t>(size));
}
}
}
}
}
#endif //BITSERY_DETAILS_BOTH_COMMON_H

View File

@@ -20,15 +20,17 @@
//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
#ifndef BITSERY_DETAILS_BUFFER_COMMON_H
#define BITSERY_DETAILS_BUFFER_COMMON_H
#include <type_traits>
#include <cstddef>
#include <cstdint>
#include <algorithm>
#include <utility>
#include <cassert>
#include <vector>
#include <stack>
#include <cstring>
#include "both_common.h"
namespace bitsery {
@@ -47,6 +49,11 @@ namespace bitsery {
I end() const { return this->second; }
};
enum class BufferReaderError {
NO_ERROR,
BUFFER_OVERFLOW,
INVALID_BUFFER_DATA
};
namespace details {
template<typename T>
@@ -124,36 +131,218 @@ namespace bitsery {
using type = uint64_t;
};
// struct BufferSessionInfo {
// size_t depth;
// size_t offset;
// };
class BufferSessionsWriter {
public:
void begin() {
//write position
_sessionIndex.push(_sessions.size());
_sessions.emplace_back(0);
}
void end(size_t pos) {
assert(!_sessionIndex.empty());
//change position to session end
auto sessionIt = std::next(std::begin(_sessions), _sessionIndex.top());
_sessionIndex.pop();
*sessionIt = pos;
}
template <typename TWriter>
void flushSessions(TWriter& writer) {
if (_sessions.size()) {
assert(_sessionIndex.empty());
auto range = writer.getWrittenRange();
auto dataSize = static_cast<size_t>(std::distance(range.begin(), range.end()));
for(auto& s:_sessions) {
details::writeSize(writer, s);
}
_sessions.clear();
range = writer.getWrittenRange();
auto totalSize = static_cast<size_t>(std::distance(range.begin(), range.end()));
//write offset where actual data ends
auto sessionsOffset = totalSize - dataSize + 2;//2 bytes for offset data
if (sessionsOffset < 0x8000u) {
writer.template writeBytes<2>(static_cast<uint16_t>(sessionsOffset));
} else {
//size doesnt fit in 2 bytes, write 4 bytes instead
sessionsOffset+=2;
uint16_t low = static_cast<uint16_t>(sessionsOffset);
//mark most significant bit, that size is 4 bytes
uint16_t high = static_cast<uint16_t>(0x8000u | (sessionsOffset >> 16));
writer.template writeBytes<2>(low);
writer.template writeBytes<2>(high);
}
}
}
private:
std::vector<size_t> _sessions{};
std::stack<size_t> _sessionIndex;
};
template <typename TReader, typename TIterator>
struct BufferSessionsReader {
TIterator _bufBegin;
BufferSessionsReader(TReader& r, TIterator& begin, TIterator& end)
:_reader{r},
_pos{begin},
_end{end}
{
_bufBegin = begin;
}
void begin() {
if (_sessions.empty())
initializeSessions();
//save end position for current session
_sessionsStack.push(_end);
if (_nextSessionIt != std::end(_sessions)) {
if (std::distance(_pos, _end) > 0) {
//set end position for new session
auto newEnd = std::next(_bufBegin, *_nextSessionIt);
if (std::distance(newEnd, _end) < 0)
{
//new session cannot end further than current end
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
return;
}
_end = newEnd;
++_nextSessionIt;
}
//if we reached the end, means that there is no more data to read, hence there is no more sessions to advance to
} else {
//there is no data to read anymore
//pos == end or buffer overflow while session is active
if (!(_pos == _end || _reader.getError() == BufferReaderError::NO_ERROR)) {
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
}
}
}
void end() {
if (!_sessionsStack.empty()) {
//move position to the end of session
//can additionaly be checked for session data versioning
//_pos == _end : same versions
//distance(_pos,_end) > 0: reading newer version
//getError() == BUFFER_OVERFLOW: reading older version
auto dist = std::distance(_pos, _end);
if (dist > 0) {
//newer version might have some inner sessions, try to find the one after current ends
auto currPos = static_cast<size_t>(std::distance(_bufBegin, _end));
for (; _nextSessionIt != std::end(_sessions); ++_nextSessionIt) {
if (*_nextSessionIt > currPos)
break;
}
}
_pos = _end;
//restore end position
_end = _sessionsStack.top();
_sessionsStack.pop();
}
}
bool hasActiveSessions() const {
return _sessionsStack.size() > 0;
}
private:
TReader& _reader;
TIterator& _pos;
TIterator& _end;
std::vector<size_t> _sessions{};
std::vector<size_t>::iterator _nextSessionIt{};
std::stack<TIterator> _sessionsStack{};
void initializeSessions() {
//save current position
auto currPos = _pos;
auto bufferSizeLeft = std::distance(_pos, _end);
//read size
if (bufferSizeLeft < 2) {
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
return;
}
auto endSessionsSizesIt = std::next(_end, -2);
_pos = endSessionsSizesIt;
size_t sessionsOffset{};
uint16_t high;
_reader.template readBytes<2>(high);
if (high >= 0x8000u) {
if (bufferSizeLeft < 4) {
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
return;
}
endSessionsSizesIt = std::next(endSessionsSizesIt, -2);
_pos = endSessionsSizesIt;
uint16_t low;
_reader.template readBytes<2>(low);
//mask out last bit
high &= 0x7FFFu;
sessionsOffset = static_cast<size_t>((high << 16) | low);
} else
sessionsOffset = high;
if (static_cast<size_t>(bufferSizeLeft) < sessionsOffset) {
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
return;
}
//we can initialy resizes to this value, and we'll shrink it after reading
//read session sizes
auto sessionsIt = std::back_inserter(_sessions);
_pos = std::next(_end, -sessionsOffset);
while (std::distance(_pos, endSessionsSizesIt) > 0) {
//todo try to read into iterator directly
size_t size;
details::readSize(_reader, size);
*sessionsIt++ = size;
}
_sessions.shrink_to_fit();
//set iterators to data
_pos = currPos;
_end = std::next(_end, -sessionsOffset);
_nextSessionIt = std::begin(_sessions);//set before first session;
}
};
template<typename Buffer, bool isFixed>
class WriteBufferContext {
};
template<typename Buffer>
class WriteBufferContext<Buffer, true> {
class WriteBufferContext<Buffer, true>{
public:
using ValueType = typename Buffer::value_type;
using IteratorType = typename Buffer::iterator;
explicit WriteBufferContext(Buffer &buffer)
: _buffer{buffer},
_outIt{buffer.begin()},
_end{buffer.end()}
_outIt{std::addressof(*std::begin(buffer))},
_end{std::addressof(*std::end(buffer))}
{
}
void write(const ValueType *data, size_t size) {
assert(std::distance(_outIt, _end) >= static_cast<typename Buffer::difference_type>(size));
_outIt = std::copy_n(data, size, _outIt);
memcpy(_outIt, data, size);
_outIt += size;
}
BufferRange<IteratorType> getWrittenRange() const {
return BufferRange<IteratorType>{_buffer.begin(), _outIt};
auto begin = std::begin(_buffer);
return BufferRange<IteratorType>{begin, std::next(begin, _outIt - std::addressof(*begin))};
}
private:
Buffer &_buffer;
IteratorType _outIt;
IteratorType _end;
ValueType* _outIt;
ValueType* _end;
};
template<typename Buffer>
@@ -169,11 +358,12 @@ namespace bitsery {
}
void write(const ValueType *data, size_t size) {
if (std::distance(_outIt, _end) >= static_cast<typename Buffer::difference_type>(size)) {
_outIt = std::copy_n(data, size, _outIt);
if ((_end - _outIt) >= static_cast<typename Buffer::difference_type>(size)) {
std::memcpy(_outIt, data, size);
_outIt += size;
} else {
//get current position before invalidating iterators
auto pos = std::distance(_buffer.begin(), _outIt);
auto pos = std::distance(std::addressof(*std::begin(_buffer)), _outIt);
//make dummy call to back insert iterator to resize buffer
*(std::back_insert_iterator<Buffer>(_buffer)) = {};
resizeToCapacity(pos);
@@ -182,24 +372,26 @@ namespace bitsery {
}
BufferRange<IteratorType> getWrittenRange() const {
return BufferRange<IteratorType>{_buffer.begin(), _outIt};
auto begin = std::begin(_buffer);
return BufferRange<IteratorType>{begin, std::next(begin, _outIt - std::addressof(*begin))};
}
private:
void resizeToCapacity(typename Buffer::difference_type writePos) {
if (_buffer.capacity() != _buffer.size()) {
_buffer.resize(_buffer.capacity());
}
_end = _buffer.end();
_outIt = std::next(_buffer.begin(), writePos);
_end = std::addressof(*std::end(_buffer));
_outIt = std::addressof(*std::next(std::begin(_buffer), writePos));
}
Buffer &_buffer;
IteratorType _outIt;
IteratorType _end;
ValueType* _outIt;
ValueType* _end;
};
}
}
#endif //BITSERY_BUFFER_COMMON_H
#endif //BITSERY_DETAILS_BUFFER_COMMON_H

View File

@@ -20,12 +20,12 @@
//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
#ifndef BITSERY_DETAILS_SERIALIZATION_COMMON_H
#define BITSERY_DETAILS_SERIALIZATION_COMMON_H
#include <cstdint>
#include <type_traits>
#include <array>
#include "both_common.h"
namespace bitsery {
@@ -60,8 +60,20 @@ namespace bitsery {
return getSize(max - min, 0);
}
template <typename T, typename = int>
struct IsResizable : std::false_type {};
template <typename T>
struct IsResizable <T, decltype((void)std::declval<T>().resize(1u), 0)> : std::true_type {};
}
/*
* serialization/deserialization context
*/
struct Context {
void* getCustomPtr();
};
/*
* range functions in bitsery namespace because these are used by user
*/
@@ -138,7 +150,8 @@ namespace bitsery {
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);
using VT = SAME_SIZE_UNSIGNED<T>;
return static_cast<VT>(static_cast<VT>(v) - static_cast<VT>(r.min));
};
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
@@ -181,11 +194,11 @@ namespace bitsery {
}
/*
* functions for substitution
* functions for entropy encoding
*/
template<typename T, size_t N>
size_t findSubstitutionIndex(const T &v, const std::array<T, N> &defValues) {
size_t findEntropyIndex(const T &v, const T (&defValues)[N]) {
auto index{1u};
for (auto &d:defValues) {
if (d == v)
@@ -215,6 +228,7 @@ namespace bitsery {
}
};
/*
* delta functions
*/
@@ -256,4 +270,4 @@ namespace bitsery {
}
#endif //BITSERY_SERIALIZATION_COMMON_H
#endif //BITSERY_DETAILS_SERIALIZATION_COMMON_H

View File

@@ -48,16 +48,16 @@ namespace bitsery {
static_assert(std::is_default_constructible<TVal>::value, "");
};
template<typename T, typename Ser, typename Fnc>
void serialize(const T &obj, Ser &ser, Fnc &&fnc) const {
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &ser, Writer &, const T &obj, Fnc &&fnc) const {
assertType<T>();
ser.boolByte(static_cast<bool>(obj));
if (obj)
fnc(ser, *obj);
}
template<typename T, typename Des, typename Fnc>
void deserialize(T &obj, Des &des, Fnc &&fnc) const {
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &des, Reader& , T &obj, Fnc &&fnc) const {
assertType<T>();
bool exists{};
des.boolByte(exists);

View File

@@ -21,7 +21,6 @@
//SOFTWARE.
#ifndef BITSERY_SERIALIZER_H
#define BITSERY_SERIALIZER_H
@@ -29,14 +28,21 @@
#include "details/serialization_common.h"
#include <cassert>
#include <array>
#include <string>
namespace bitsery {
template<typename Writter>
class Serializer {
public:
Serializer(Writter &w) : _writter{w} {};
Serializer(Writter &w, void* context = nullptr) : _writter{w}, _context{context} {};
/*
* get serialization context.
* this is optional, but might be required for some specific serialization flows.
*/
void* getContext() {
return _context;
}
/*
* object function
@@ -46,6 +52,11 @@ namespace bitsery {
details::SerializeFunction<Serializer, T>::invoke(*this, std::forward<T>(obj));
}
template<typename T, typename Fnc>
void object(T &&obj, Fnc &&fnc) {
fnc(*this, std::forward<T>(obj));
};
/*
* value overloads
*/
@@ -67,33 +78,35 @@ namespace bitsery {
}
/*
* custom function
* growable function
*/
template<typename T, typename Fnc>
void custom(T &&obj, Fnc &&fnc) {
fnc(*this, std::forward<T>(obj));
void growable(const T &obj, Fnc &&fnc) {
_writter.beginSession();
fnc(*this, obj);
_writter.endSession();
};
/*
* extension functions
* extend functions
*/
template<typename T, typename Ext, typename Fnc>
void extension(const T &obj, Ext &&ext, Fnc &&fnc) {
ext.serialize(obj, *this, std::forward<Fnc>(fnc));
void extend(const T &obj, Ext &&ext, Fnc &&fnc) {
ext.serialize(*this, _writter, obj, std::forward<Fnc>(fnc));
};
template<size_t VSIZE, typename T, typename Ext>
void extension(const T &obj, Ext &&ext) {
ext.serialize(obj, *this, [](auto &s, auto &v) { s.template value<VSIZE>(v); });
void extend(const T &obj, Ext &&ext) {
ext.serialize(*this, _writter, obj, [](auto &s, auto &v) { s.template value<VSIZE>(v); });
};
template<typename T, typename Ext>
void extension(const T &obj, Ext &&ext) {
ext.serialize(obj, *this, [](auto &s, auto &v) { s.object(v); });
void extend(const T &obj, Ext &&ext) {
ext.serialize(*this, _writter, obj, [](auto &s, auto &v) { s.object(v); });
};
/*
@@ -120,27 +133,27 @@ namespace bitsery {
}
/*
* substitution overloads
* entropy overloads
*/
template<typename T, size_t N, typename Fnc>
void substitution(const T &v, const std::array<T, N> &expectedValues, Fnc &&fnc) {
auto index = details::findSubstitutionIndex(v, expectedValues);
void entropy(const T &v, const T (&expectedValues)[N], Fnc &&fnc) {
auto index = details::findEntropyIndex(v, expectedValues);
range(index, {{}, N + 1});
if (!index)
fnc(*this, v);
};
template<size_t VSIZE, typename T, size_t N>
void substitution(const T &v, const std::array<T, N> &expectedValues) {
auto index = details::findSubstitutionIndex(v, expectedValues);
void entropy(const T &v, const T (&expectedValues)[N]) {
auto index = details::findEntropyIndex(v, expectedValues);
range(index, {{}, N + 1});
if (!index)
value<VSIZE>(v);
};
template<typename T, size_t N>
void substitution(const T &v, const std::array<T, N> &expectedValues) {
auto index = details::findSubstitutionIndex(v, expectedValues);
void entropy(const T &v, const T (&expectedValues)[N]) {
auto index = details::findEntropyIndex(v, expectedValues);
range(index, {{}, N + 1});
if (!index)
object(v);
@@ -151,11 +164,12 @@ namespace bitsery {
*/
template<size_t VSIZE, typename T>
void text(const std::basic_string<T> &str, size_t maxSize) {
assert(str.size() <= maxSize);
void text(const T &str, size_t maxSize) {
auto first = std::begin(str);
auto last = std::end(str);
writeSize(std::distance(first, last));
auto size = static_cast<size_t>(std::distance(first, last));
assert(size <= maxSize);
writeSize(size);
procContainer<VSIZE>(first, last, std::true_type{});
}
@@ -171,8 +185,12 @@ namespace bitsery {
* container overloads
*/
//dynamic size containers
template<typename T, typename Fnc>
void container(const T &obj, size_t maxSize, Fnc &&fnc) {
static_assert(details::IsResizable<T>::value,
"use container(const T&, Fnc) overload without `maxSize` for static containers");
assert(obj.size() <= maxSize);
writeSize(obj.size());
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
@@ -180,6 +198,8 @@ namespace bitsery {
template<size_t VSIZE, typename T>
void container(const T &obj, size_t maxSize) {
static_assert(details::IsResizable<T>::value,
"use container(const T&) overload without `maxSize` for static containers");
static_assert(VSIZE > 0, "");
assert(obj.size() <= maxSize);
writeSize(obj.size());
@@ -189,48 +209,53 @@ namespace bitsery {
template<typename T>
void container(const T &obj, size_t maxSize) {
static_assert(details::IsResizable<T>::value,
"use container(const T&) overload without `maxSize` for static containers");
assert(obj.size() <= maxSize);
writeSize(obj.size());
procContainer(std::begin(obj), std::end(obj));
}
/*
* array overloads (fixed size array (std::array, and c-style array))
*/
//fixed size containers
//std::array overloads
template<typename T, size_t N, typename Fnc>
void array(const std::array<T, N> &arr, Fnc &&fnc) {
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
template<typename T, typename Fnc, typename std::enable_if<!std::is_integral<Fnc>::value>::type * = nullptr>
void container(const T &obj, Fnc &&fnc) {
static_assert(!details::IsResizable<T>::value,
"use container(const T&, size_t, Fnc) overload with `maxSize` for dynamic containers");
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
}
template<size_t VSIZE, typename T, size_t N>
void array(const std::array<T, N> &arr) {
template<size_t VSIZE, typename T>
void container(const T &obj) {
static_assert(!details::IsResizable<T>::value,
"use container(const T&, size_t) overload with `maxSize` for dynamic containers");
static_assert(VSIZE > 0, "");
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
//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{});
}
template<typename T, size_t N>
void array(const std::array<T, N> &arr) {
procContainer(std::begin(arr), std::end(arr));
template<typename T>
void container(const T &obj) {
static_assert(!details::IsResizable<T>::value,
"use container(const T&, size_t) overload with `maxSize` for dynamic containers");
procContainer(std::begin(obj), std::end(obj));
}
//c-style array overloads
template<typename T, size_t N, typename Fnc>
void array(const T (&arr)[N], Fnc &&fnc) {
void container(const T (&arr)[N], Fnc &&fnc) {
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
}
template<size_t VSIZE, typename T, size_t N>
void array(const T (&arr)[N]) {
void container(const T (&arr)[N]) {
static_assert(VSIZE > 0, "");
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
}
template<typename T, size_t N>
void array(const T (&arr)[N]) {
void container(const T (&arr)[N]) {
procContainer(std::begin(arr), std::end(arr));
}
@@ -238,127 +263,112 @@ namespace bitsery {
_writter.align();
}
bool isValid() const {
//serialization cannot fail, it doesn't handle out of memory exception
return true;
}
//overloads for functions with explicit type size
template<typename T>
void value1(T &&v) { value<1>(std::forward<T>(v)); }
void value1b(T &&v) { value<1>(std::forward<T>(v)); }
template<typename T>
void value2(T &&v) { value<2>(std::forward<T>(v)); }
void value2b(T &&v) { value<2>(std::forward<T>(v)); }
template<typename T>
void value4(T &&v) { value<4>(std::forward<T>(v)); }
void value4b(T &&v) { value<4>(std::forward<T>(v)); }
template<typename T>
void value8(T &&v) { value<8>(std::forward<T>(v)); }
void value8b(T &&v) { value<8>(std::forward<T>(v)); }
template<typename T, typename Ext>
void extension1(const T &v, Ext &&ext) { extension<1>(v, std::forward<Ext>(ext)); };
void extend1b(const T &v, Ext &&ext) { extend<1>(v, std::forward<Ext>(ext)); };
template<typename T, typename Ext>
void extension2(const T &v, Ext &&ext) { extension<2>(v, std::forward<Ext>(ext)); };
void extend2b(const T &v, Ext &&ext) { extend<2>(v, std::forward<Ext>(ext)); };
template<typename T, typename Ext>
void extension4(const T &v, Ext &&ext) { extension<4>(v, std::forward<Ext>(ext)); };
void extend4b(const T &v, Ext &&ext) { extend<4>(v, std::forward<Ext>(ext)); };
template<typename T, typename Ext>
void extension8(const T &v, Ext &&ext) { extension<8>(v, std::forward<Ext>(ext)); };
void extend8b(const T &v, Ext &&ext) { extend<8>(v, std::forward<Ext>(ext)); };
template<typename T, size_t N>
void substitution1(const T &v, const std::array<T, N> &expectedValues) {
substitution<1>(v, expectedValues);
void entropy1b(const T &v, const T (&expectedValues)[N]) {
entropy<1>(v, expectedValues);
};
template<typename T, size_t N>
void substitution2(const T &v, const std::array<T, N> &expectedValues) {
substitution<2>(v, expectedValues);
void entropy2b(const T &v, const T (&expectedValues)[N]) {
entropy<2>(v, expectedValues);
};
template<typename T, size_t N>
void substitution4(const T &v, const std::array<T, N> &expectedValues) {
substitution<4>(v, expectedValues);
void entropy4b(const T &v, const T (&expectedValues)[N]) {
entropy<4>(v, expectedValues);
};
template<typename T, size_t N>
void substitution8(const T &v, const std::array<T, N> &expectedValues) {
substitution<8>(v, expectedValues);
void entropy8b(const T &v, const T (&expectedValues)[N]) {
entropy<8>(v, expectedValues);
};
template<typename T>
void text1(const std::basic_string<T> &str, size_t maxSize) { text<1>(str, maxSize); }
void text1b(const T &str, size_t maxSize) { text<1>(str, maxSize); }
template<typename T>
void text2(const std::basic_string<T> &str, size_t maxSize) { text<2>(str, maxSize); }
void text2b(const T &str, size_t maxSize) { text<2>(str, maxSize); }
template<typename T>
void text4(const std::basic_string<T> &str, size_t maxSize) { text<4>(str, maxSize); }
void text4b(const T &str, size_t maxSize) { text<4>(str, maxSize); }
template<typename T, size_t N>
void text1(const T (&str)[N]) { text<1>(str); }
void text1b(const T (&str)[N]) { text<1>(str); }
template<typename T, size_t N>
void text2(const T (&str)[N]) { text<2>(str); }
void text2b(const T (&str)[N]) { text<2>(str); }
template<typename T, size_t N>
void text4(const T (&str)[N]) { text<4>(str); }
void text4b(const T (&str)[N]) { text<4>(str); }
template<typename T>
void container1(T &&obj, size_t maxSize) { container<1>(std::forward<T>(obj), maxSize); }
void container1b(T &&obj, size_t maxSize) { container<1>(std::forward<T>(obj), maxSize); }
template<typename T>
void container2(T &&obj, size_t maxSize) { container<2>(std::forward<T>(obj), maxSize); }
void container2b(T &&obj, size_t maxSize) { container<2>(std::forward<T>(obj), maxSize); }
template<typename T>
void container4(T &&obj, size_t maxSize) { container<4>(std::forward<T>(obj), maxSize); }
void container4b(T &&obj, size_t maxSize) { container<4>(std::forward<T>(obj), maxSize); }
template<typename T>
void container8(T &&obj, size_t maxSize) { container<8>(std::forward<T>(obj), maxSize); }
void container8b(T &&obj, size_t maxSize) { container<8>(std::forward<T>(obj), maxSize); }
template<typename T>
void container1b(T &&obj) { container<1>(std::forward<T>(obj)); }
template<typename T>
void container2b(T &&obj) { container<2>(std::forward<T>(obj)); }
template<typename T>
void container4b(T &&obj) { container<4>(std::forward<T>(obj)); }
template<typename T>
void container8b(T &&obj) { container<8>(std::forward<T>(obj)); }
template<typename T, size_t N>
void array1(const std::array<T, N> &arr) { array<1>(arr); }
void container1b(const T (&arr)[N]) { container<1>(arr); }
template<typename T, size_t N>
void array2(const std::array<T, N> &arr) { array<2>(arr); }
void container2b(const T (&arr)[N]) { container<2>(arr); }
template<typename T, size_t N>
void array4(const std::array<T, N> &arr) { array<4>(arr); }
void container4b(const T (&arr)[N]) { container<4>(arr); }
template<typename T, size_t N>
void array8(const std::array<T, N> &arr) { array<8>(arr); }
template<typename T, size_t N>
void array1(const T (&arr)[N]) { array<1>(arr); }
template<typename T, size_t N>
void array2(const T (&arr)[N]) { array<2>(arr); }
template<typename T, size_t N>
void array4(const T (&arr)[N]) { array<4>(arr); }
template<typename T, size_t N>
void array8(const T (&arr)[N]) { array<8>(arr); }
void container8b(const T (&arr)[N]) { container<8>(arr); }
private:
Writter &_writter;
void* _context;
void writeSize(const size_t size) {
if (size < 0x80u) {
_writter.writeBits(1u, 1);
_writter.writeBits(size, 7);
} else if (size < 0x4000u) {
_writter.writeBits(2u, 2);
_writter.writeBits(size, 14);
} else {
assert(size < 0x40000000u);
_writter.writeBits(0u, 2);
_writter.writeBits(size, 30);
}
details::writeSize(_writter, size);
}
//process value types

View File

@@ -109,19 +109,24 @@ TEST(BufferBitsAndBytesOperations, BufferSizeIsCountedPerByteNotPerBit) {
//read from buffer
BufferReader br{range};
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));
EXPECT_THAT(br.readBits(tmp,2), Eq(false));
br.readBits(tmp,4);
br.readBits(tmp,2);
br.readBits(tmp,2);
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
br.readBits(tmp,2);
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));//false
//part of next byte
BufferReader br1{range};
EXPECT_THAT(br1.readBits(tmp,2), Eq(true));
EXPECT_THAT(br1.readBits(tmp,7), Eq(false));
br1.readBits(tmp,2);
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
br1.readBits(tmp,7);
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));//false
//bigger than byte
BufferReader br2{range};
EXPECT_THAT(br2.readBits(tmp,9), Eq(false));
br2.readBits(tmp,9);
EXPECT_THAT(br2.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));//false
}
TEST(BufferBitsAndBytesOperations, ConsecutiveCallsToAlignHasNoEffect) {
@@ -141,18 +146,20 @@ TEST(BufferBitsAndBytesOperations, ConsecutiveCallsToAlignHasNoEffect) {
unsigned char tmp;
BufferReader br{bw.getWrittenRange()};
EXPECT_THAT(br.readBits(tmp,2), Eq(true));
br.readBits(tmp,2);
EXPECT_THAT(tmp, Eq(3u));
EXPECT_THAT(br.align(), Eq(true));
EXPECT_THAT(br.readBits(tmp,3), Eq(true));
br.align();
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
br.readBits(tmp,3);
br.align();
br.align();
br.align();
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.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
EXPECT_THAT(br.readBits(tmp,4), Eq(true));
br.readBits(tmp,4);
EXPECT_THAT(tmp, Eq(15u));
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
}
TEST(BufferBitsAndBytesOperations, AlignWritesZerosBits) {
@@ -166,18 +173,20 @@ TEST(BufferBitsAndBytesOperations, AlignWritesZerosBits) {
bw.writeBits(3u, 2);
bw.align();
bw.flush();
auto range = bw.getWrittenRange();
EXPECT_THAT(std::distance(range.begin(), range.end()), Eq(1));
unsigned char tmp;
BufferReader br1{bw.getWrittenRange()};
BufferReader br1{range};
br1.readBits(tmp,2);
//read aligned bits
EXPECT_THAT(br1.readBits(tmp,6), Eq(true));
br1.readBits(tmp,6);
EXPECT_THAT(tmp, Eq(0));
BufferReader br2{bw.getWrittenRange()};
BufferReader br2{range};
//read 2 bits
br2.readBits(tmp,2);
EXPECT_THAT(br2.align(), Eq(true));
br2.align();
EXPECT_THAT(br2.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
}
@@ -219,12 +228,13 @@ TEST(BufferBitsAndBytesOperations, WriteAndReadBytes) {
//read from buffer
BufferReader br{range};
IntegralTypes res{};
EXPECT_THAT(br.readBytes<4>(res.b), Eq(true));
EXPECT_THAT(br.readBytes<2>(res.c), Eq(true));
EXPECT_THAT(br.readBytes<1>(res.d), Eq(true));
EXPECT_THAT(br.readBytes<8>(res.a), Eq(true));
EXPECT_THAT(br.readBytes<1>(res.e), Eq(true));
EXPECT_THAT(br.readBuffer<1>(res.f, 2), Eq(true));
br.readBytes<4>(res.b);
br.readBytes<2>(res.c);
br.readBytes<1>(res.d);
br.readBytes<8>(res.a);
br.readBytes<1>(res.e);
br.readBuffer<1>(res.f, 2);
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
//assert results
EXPECT_THAT(data.a, Eq(res.a));
@@ -248,13 +258,9 @@ TEST(BufferBitsAndBytesOperations, ReadWriteBufferFncCanAcceptSignedData) {
//read from buffer
BufferReader br1{bw.getWrittenRange()};
int16_t dst[DATA_SIZE]{};
EXPECT_THAT(br1.readBuffer<2>(dst, DATA_SIZE), Eq(true));
br1.readBuffer<2>(dst, DATA_SIZE);
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
EXPECT_THAT(dst, ContainerEq(src));
//read more than available
BufferReader br2{bw.getWrittenRange()};
int16_t dstMore[DATA_SIZE+1]{};
EXPECT_THAT(br2.readBuffer<2>(dstMore, DATA_SIZE+1), Eq(false));
}
TEST(BufferBitsAndBytesOperations, ReadWriteBufferCanWorkOnUnalignedData) {
@@ -277,16 +283,37 @@ TEST(BufferBitsAndBytesOperations, ReadWriteBufferCanWorkOnUnalignedData) {
uint8_t tmp{};
br1.readBits(tmp, 4);
EXPECT_THAT(tmp, Eq(15));
EXPECT_THAT(br1.readBuffer<2>(dst, DATA_SIZE), Eq(true));
br1.readBuffer<2>(dst, DATA_SIZE);
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
EXPECT_THAT(dst, ContainerEq(src));
br1.readBits(tmp, 4);
EXPECT_THAT(tmp, Eq(12));
//read more than available
BufferReader br2{range};
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));
}
TEST(BufferBitsAndBytesOperations, RegressionTestReadBytesAfterReadBitsWithLotsOfZeroBits) {
//setup data
int16_t data[2]{0x0000, 0x7FFF};
int16_t res[2]{};
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
bw.writeBits(2u, 2);
bw.writeBytes<2>(data[0]);
bw.writeBytes<2>(data[1]);
bw.align();
bw.flush();
auto range = bw.getWrittenRange();
//read from buffer
BufferReader br{range};
uint8_t tmp{};
br.readBits(tmp, 2);
EXPECT_THAT(tmp, Eq(2));
br.readBytes<2>(res[0]);
br.readBytes<2>(res[1]);
br.align();
EXPECT_THAT(res[0], Eq(data[0]));
EXPECT_THAT(res[1], Eq(data[1]));
}

View File

@@ -28,7 +28,6 @@
#include <bitset>
using testing::Eq;
using testing::ContainerEq;
using bitsery::BufferWriter;
using bitsery::BufferReader;
using Buffer = bitsery::DefaultConfig::BufferType;
@@ -42,7 +41,7 @@ struct IntegralTypes {
int8_t f[2];
};
TEST(BufferReading, ReadReturnsFalseIfNotEnoughBufferSize) {
TEST(BufferReading, WhenReadingMoreThanAvailableThenEmptyBufferError) {
//setup data
uint8_t a = 111;
@@ -56,19 +55,34 @@ TEST(BufferReading, ReadReturnsFalseIfNotEnoughBufferSize) {
bw.flush();
//read from buffer
BufferReader br{bw.getWrittenRange()};
int16_t b;
int32_t c;
EXPECT_THAT(br.readBytes<4>(c), Eq(false));
EXPECT_THAT(br.readBytes<2>(b), Eq(true));
EXPECT_THAT(br.readBytes<2>(b), Eq(false));
EXPECT_THAT(br.readBytes<1>(a), Eq(true));
EXPECT_THAT(br.readBytes<1>(a), Eq(false));
EXPECT_THAT(br.readBytes<2>(b), Eq(false));
EXPECT_THAT(br.readBytes<4>(c), Eq(false));
br.readBytes<4>(c);
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
}
TEST(BufferReading, ReadIsCompletedWhenAllBytesAreRead) {
TEST(BufferReading, WhenErrorOccursThenAllOtherOperationsFailsForSameError) {
//setup data
uint8_t a = 111;
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
bw.writeBytes<1>(a);
bw.writeBytes<1>(a);
bw.writeBytes<1>(a);
bw.flush();
//read from buffer
BufferReader br{bw.getWrittenRange()};
int32_t c;
br.readBytes<4>(c);
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
br.readBytes<1>(a);
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
}
TEST(BufferReading, ReadIsCompletedSuccessfullyWhenAllBytesAreReadWithoutErrors) {
//setup data
IntegralTypes data;
data.b = 94545646;
@@ -86,22 +100,59 @@ TEST(BufferReading, ReadIsCompletedWhenAllBytesAreRead) {
//read from buffer
BufferReader br{bw.getWrittenRange()};
IntegralTypes res;
EXPECT_THAT(br.readBytes<4>(res.b), Eq(true));
EXPECT_THAT(br.readBytes<2>(res.c), Eq(true));
EXPECT_THAT(br.isCompleted(), Eq(false));
EXPECT_THAT(br.readBytes<1>(res.d), Eq(true));
EXPECT_THAT(br.isCompleted(), Eq(true));
EXPECT_THAT(br.readBytes<1>(res.d), Eq(false));
EXPECT_THAT(br.isCompleted(), Eq(true));
br.readBytes<4>(res.b);
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
br.readBytes<2>(res.c);
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(false));
br.readBytes<1>(res.d);
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(true));
br.readBytes<1>(res.d);
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(false));
BufferReader br1{bw.getWrittenRange()};
EXPECT_THAT(br1.readBytes<4>(res.b), Eq(true));
EXPECT_THAT(br1.readBytes<2>(res.c), Eq(true));
EXPECT_THAT(br1.isCompleted(), Eq(false));
EXPECT_THAT(br1.readBytes<2>(res.c), Eq(false));
EXPECT_THAT(br1.isCompleted(), Eq(false));
EXPECT_THAT(br1.readBytes<1>(res.d), Eq(true));
EXPECT_THAT(br1.isCompleted(), Eq(true));
br1.readBytes<4>(res.b);
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
br1.readBytes<2>(res.c);
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
EXPECT_THAT(br1.isCompletedSuccessfully(), Eq(false));
br1.readBytes<2>(res.c);
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
EXPECT_THAT(br1.isCompletedSuccessfully(), Eq(false));
br1.readBytes<1>(res.d);
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
EXPECT_THAT(br1.isCompletedSuccessfully(), Eq(false));
}
TEST(BufferReading, WhenReaderHasErrorsAllOperationsReadsReturnZero) {
//setup data
uint8_t a = 111;
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
bw.writeBytes<1>(a);
bw.writeBytes<1>(a);
bw.writeBytes<1>(a);
bw.flush();
//read from buffer
BufferReader br{bw.getWrittenRange()};
int32_t c;
br.readBytes<4>(c);
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
int16_t r1= {-645};
uint32_t r2[2] = {54898,87854};
uint8_t r3 = 0xFF;
br.readBytes<2>(r1);
br.readBuffer<4>(r2, 2);
br.readBits(r3, 7);
EXPECT_THAT(r1, Eq(0));
EXPECT_THAT(r2[0], Eq(0u));
EXPECT_THAT(r2[1], Eq(0u));
EXPECT_THAT(r3, Eq(0u));
}

View File

@@ -0,0 +1,119 @@
//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 "serialization_test_utils.h"
using testing::Eq;
using bitsery::BufferWriter;
using bitsery::BufferReader;
using Buffer = bitsery::DefaultConfig::BufferType;
TEST(BufferReadingErrors, WhenContainerOrTextSizeIsMoreThanMaxThenInvalidBufferDataError) {
SerializationContext ctx;
std::string tmp = "larger text then allowed";
ctx.createSerializer().text1b(tmp,100);
ctx.createDeserializer().text1b(tmp, 10);
EXPECT_THAT(ctx.br->getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
}
TEST(BufferReadingErrors, WhenReadingBoolByteReadsMoreThanOneThenInvalidBufferDataErrorAndResultIsFalse) {
SerializationContext ctx;
auto ser = ctx.createSerializer();
ser.value1b(uint8_t{1});
ser.value1b(uint8_t{2});
bool res{};
auto des = ctx.createDeserializer();
des.boolByte(res);
EXPECT_THAT(res, Eq(true));
des.boolByte(res);
EXPECT_THAT(res, Eq(false));
EXPECT_THAT(ctx.br->getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
}
TEST(BufferReadingErrors, WhenReadingAlignHasNonZerosThenInvalidBufferDataError) {
Buffer buf{};
BufferWriter bw{buf};
uint8_t tmp{0xFF};
bw.writeBytes<1>(tmp);
bw.flush();
BufferReader br{bw.getWrittenRange()};
br.readBits(tmp,3);
br.align();
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
}
TEST(BufferReadingErrors, WhenReadingNewSessionInMiddleOfOldDataThenInvalidBufferError) {
uint8_t tmp{0xFF};
Buffer buf{};
BufferWriter bw{buf};
for (auto i = 0; i < 2; ++i) {
bw.beginSession();
bw.writeBytes<1>(tmp);
bw.writeBytes<1>(tmp);
bw.endSession();
}
bw.flush();
BufferReader br{bw.getWrittenRange()};
for (auto i = 0; i < 2; ++i) {
br.beginSession();
br.readBytes<1>(tmp);
br.beginSession();
br.readBytes<1>(tmp);
br.endSession();
br.endSession();
}
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
}
TEST(BufferReadingErrors, WhenInitializingSessionsWhenNotEnoughDataThenInvalidBufferData) {
uint8_t tmp1{0xFF};
Buffer buf1{};
BufferWriter bw1{buf1};
bw1.writeBytes<1>(tmp1);
bw1.flush();
BufferReader br1{bw1.getWrittenRange()};
br1.beginSession();
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
Buffer buf2{};
BufferWriter bw2{buf2};
uint16_t tmp2{0x8000};
bw2.writeBytes<2>(tmp2);
bw2.flush();
BufferReader br2{bw2.getWrittenRange()};
br2.beginSession();
EXPECT_THAT(br2.getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
}
TEST(BufferReadingErrors, WhenInitializingSessionsWhereSessionsDataOffsetIsCorruptedThenInvalidBufferData) {
Buffer buf{};
BufferWriter bw{buf};
bw.writeBytes<1>(uint8_t{1});
bw.writeBytes<1>(uint8_t{1});
bw.writeBytes<2>(uint16_t{10});
BufferReader br{bw.getWrittenRange()};
br.beginSession();
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
}

View File

@@ -81,9 +81,10 @@ TYPED_TEST(BufferWriting, GetWrittenRangeReturnsBeginEndIterators) {
}
TYPED_TEST(BufferWriting, WhenWritingBitsThenMustFlushWriter) {
Buffer buf;
bitsery::BufferWriter bw{buf};
using Config = typename TestFixture::type;
using Buffer = typename Config::BufferType;
Buffer buf{};
bitsery::BasicBufferWriter<Config> bw{buf};
bw.writeBits(3u, 2);
auto range1 = bw.getWrittenRange();
bw.flush();

View File

@@ -24,6 +24,7 @@
#include <gmock/gmock.h>
#include "serialization_test_utils.h"
#include <algorithm>
#include <numeric>
#include <deque>
#include <list>
@@ -36,29 +37,29 @@ using testing::Eq;
* overload to get container of types
*/
template <typename Container>
template<typename Container>
Container getFilledContainer() {
return {1,2,3,4,5,78,456,8,54};
return {1, 2, 3, 4, 5, 78, 456, 8, 54};
}
template <>
template<>
std::vector<MyStruct1> getFilledContainer<std::vector<MyStruct1>>() {
return {
{0,1},
{2,3},
{4,5},
{6,7},
{8,9},
{11,34},
{5134,1532}
{0, 1},
{2, 3},
{4, 5},
{6, 7},
{8, 9},
{11, 34},
{5134, 1532}
};
}
template <>
template<>
std::list<MyStruct2> getFilledContainer<std::list<MyStruct2>>() {
return {
{MyStruct2::V1, {0,1}} ,
{MyStruct2::V3, {-45,45}}
{MyStruct2::V1, {0, 1}},
{MyStruct2::V3, {-45, 45}}
};
}
@@ -66,16 +67,16 @@ std::list<MyStruct2> getFilledContainer<std::list<MyStruct2>>() {
* start testing session
*/
template <typename T>
class SerializeContainerArthmeticTypes:public testing::Test {
template<typename T>
class SerializeContainerDynamicSizeArthmeticTypes : public testing::Test {
public:
using TContainer = T;
using TValue = typename T::value_type;
const TContainer src= getFilledContainer<TContainer>() ;
const TContainer src = getFilledContainer<TContainer>();
TContainer res{};
size_t getExpectedBufSize(const SerializationContext& ctx) const {
size_t getExpectedBufSize(const SerializationContext &ctx) const {
return ctx.containerSizeSerializedBytesCount(src.size()) + src.size() * sizeof(TValue);
}
};
@@ -85,9 +86,9 @@ using SequenceContainersWithArthmeticTypes = ::testing::Types<
std::list<float>,
std::deque<unsigned short>>;
TYPED_TEST_CASE(SerializeContainerArthmeticTypes, SequenceContainersWithArthmeticTypes);
TYPED_TEST_CASE(SerializeContainerDynamicSizeArthmeticTypes, SequenceContainersWithArthmeticTypes);
TYPED_TEST(SerializeContainerArthmeticTypes, Values) {
TYPED_TEST(SerializeContainerDynamicSizeArthmeticTypes, Values) {
SerializationContext ctx{};
using TValue = typename TestFixture::TValue;
@@ -98,7 +99,7 @@ TYPED_TEST(SerializeContainerArthmeticTypes, Values) {
EXPECT_THAT(this->res, ContainerEq(this->src));
}
TYPED_TEST(SerializeContainerArthmeticTypes, CustomFunctionIncrements) {
TYPED_TEST(SerializeContainerDynamicSizeArthmeticTypes, CustomFunctionIncrements) {
SerializationContext ctx{};
auto ser = ctx.createSerializer();
@@ -108,13 +109,13 @@ TYPED_TEST(SerializeContainerArthmeticTypes, CustomFunctionIncrements) {
s.template value<sizeof(v)>(v);
});
auto des = ctx.createDeserializer();
des.container(this->res, 1000, [](auto &s, auto&v ) {
des.container(this->res, 1000, [](auto &s, auto &v) {
s.template value<sizeof(v)>(v);
//increment by 1 after reading
v++;
});
//decrement result by 2, before comparing for eq
for(auto& v:this->res)
for (auto &v:this->res)
v -= 2;
EXPECT_THAT(ctx.getBufferSize(), Eq(this->getExpectedBufSize(ctx)));
@@ -122,29 +123,28 @@ TYPED_TEST(SerializeContainerArthmeticTypes, CustomFunctionIncrements) {
}
template <typename T>
class SerializeContainerCompositeTypes:public testing::Test {
template<typename T>
class SerializeContainerDynamicSizeCompositeTypes : public testing::Test {
public:
using TContainer = T;
using TValue = typename T::value_type;
const TContainer src= getFilledContainer<TContainer>();
const TContainer src = getFilledContainer<TContainer>();
TContainer res{};
size_t getExpectedBufSize(const SerializationContext& ctx) const {
size_t getExpectedBufSize(const SerializationContext &ctx) const {
return ctx.containerSizeSerializedBytesCount(src.size()) + src.size() * TValue::SIZE;
}
};
using SequenceContainersWithCompositeTypes = ::testing::Types<
using SerializeContainerDynamicSizeWithCompositeTypes = ::testing::Types<
std::vector<MyStruct1>,
std::list<MyStruct2>>;
TYPED_TEST_CASE(SerializeContainerCompositeTypes, SequenceContainersWithCompositeTypes);
TYPED_TEST_CASE(SerializeContainerDynamicSizeCompositeTypes, SerializeContainerDynamicSizeWithCompositeTypes);
TYPED_TEST(SerializeContainerCompositeTypes, DefaultSerializeFunction) {
TYPED_TEST(SerializeContainerDynamicSizeCompositeTypes, DefaultSerializeFunction) {
SerializationContext ctx{};
ctx.createSerializer().container(this->src, 1000);
@@ -155,15 +155,95 @@ TYPED_TEST(SerializeContainerCompositeTypes, DefaultSerializeFunction) {
}
TYPED_TEST(SerializeContainerCompositeTypes, CustomFunctionThatDoNothing) {
TYPED_TEST(SerializeContainerDynamicSizeCompositeTypes, CustomFunctionThatDoNothing) {
SerializationContext ctx{};
auto emptyFnc = [](auto& s, auto& v) {};
auto emptyFnc = [](auto &s, auto &v) {};
ctx.createSerializer().container(this->src, 1000, emptyFnc);
ctx.createDeserializer().container(this->res, 1000, emptyFnc);
EXPECT_THAT(ctx.getBufferSize(), Eq(ctx.containerSizeSerializedBytesCount(this->src.size())));
}
template<typename T>
class SerializeContainerFixedSizeArithmeticTypes : public testing::Test {
public:
using TContainer = T;
size_t getContainerSize() {
T tmp{};
return static_cast<size_t>(std::distance(std::begin(tmp), std::end(tmp)));
}
};
using StaticContainersWithIntegralTypes = ::testing::Types<
std::array<int16_t, 4>,
int16_t[4]>;
TYPED_TEST_CASE(SerializeContainerFixedSizeArithmeticTypes, StaticContainersWithIntegralTypes);
TYPED_TEST(SerializeContainerFixedSizeArithmeticTypes, ArithmeticValues) {
using Container = typename TestFixture::TContainer;
Container src{5, 9, 15, -459};
Container res{};
SerializationContext ctx;
ctx.createSerializer().container<2>(src);
ctx.createDeserializer().container<2>(res);
EXPECT_THAT(ctx.getBufferSize(), Eq(this->getContainerSize() * 2));
EXPECT_THAT(res, ContainerEq(src));
}
template<typename T>
class SerializeContainerFixedSizeCompositeTypes : public SerializeContainerFixedSizeArithmeticTypes<T> {
};
using StaticContainersWithCompositeTypes = ::testing::Types<
std::array<MyStruct1, 4>,
MyStruct1[4]>;
TYPED_TEST_CASE(SerializeContainerFixedSizeCompositeTypes, StaticContainersWithCompositeTypes);
TYPED_TEST(SerializeContainerFixedSizeCompositeTypes, DefaultSerializationFunction) {
using Container = typename TestFixture::TContainer;
Container src{MyStruct1{0, 1}, MyStruct1{8, 9}, MyStruct1{11, 34}, MyStruct1{5134, 1532}};
Container res{};
SerializationContext ctx;
ctx.createSerializer().container(src);
ctx.createDeserializer().container(res);
EXPECT_THAT(ctx.getBufferSize(), Eq(this->getContainerSize() * MyStruct1::SIZE));
EXPECT_THAT(res, ContainerEq(src));
}
TYPED_TEST(SerializeContainerFixedSizeCompositeTypes, CustomFunctionThatSerializesAnEmptyByteEveryElement) {
using Container = typename TestFixture::TContainer;
Container src{MyStruct1{0, 1}, MyStruct1{2, 3}, MyStruct1{4, 5}, MyStruct1{5134, 1532}};
Container res{};
SerializationContext ctx;
auto ser = ctx.createSerializer();
ser.container(src, [](auto &s, auto &v) {
char tmp{};
s.object(v);
s.value1b(tmp);
});
auto des = ctx.createDeserializer();
des.container(res, [](auto &s, auto &v) {
char tmp{};
s.object(v);
s.value1b(tmp);
});
EXPECT_THAT(ctx.getBufferSize(), Eq(this->getContainerSize() * (MyStruct1::SIZE + sizeof(char))));
EXPECT_THAT(res, ContainerEq(src));
}

View File

@@ -25,72 +25,72 @@
#include "serialization_test_utils.h"
using namespace testing;
TEST(SerializeSubstitution, WhenSubstitutedThenOnlyWriteIndexUsingMinRequiredBits) {
TEST(SerializeEntropyEncoding, WhenEntropyEncodedThenOnlyWriteIndexUsingMinRequiredBits) {
int32_t v = 4849;
int32_t res;
constexpr size_t N = 3;
std::array<int32_t,N> subsitution{485,4849,89};
int32_t entropyValues[3]{485,4849,89};
SerializationContext ctx;
ctx.createSerializer().substitution<4>(v, subsitution);
ctx.createDeserializer().substitution<4>(res, subsitution);
ctx.createSerializer().entropy<4>(v, entropyValues);
ctx.createDeserializer().entropy<4>(res, entropyValues);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
SerializationContext ctx1;
ctx1.createSerializer().substitution<4>(v, subsitution);
ctx1.createSerializer().entropy<4>(v, entropyValues);
auto des = ctx1.createDeserializer();
des.range(res, {0, N + 1});
EXPECT_THAT(res, Eq(2));
}
TEST(SerializeSubstitution, WhenNoSubstitutionThenWriteZeroBitsAndValueOrObject) {
TEST(SerializeEntropyEncoding, WhenNoEntropyEncodedThenWriteZeroBitsAndValueOrObject) {
int16_t v = 8945;
int16_t res;
std::array<int16_t,3> subsitution{485,4849,89};
int16_t entropyValues[3]{485,4849,89};
SerializationContext ctx;
ctx.createSerializer().substitution<2>(v, subsitution);
ctx.createDeserializer().substitution<2>(res, subsitution);
ctx.createSerializer().entropy<2>(v, entropyValues);
ctx.createDeserializer().entropy<2>(res, entropyValues);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(sizeof(int16_t)+1));
}
TEST(SerializeSubstitution, CustomTypeSubstituted) {
TEST(SerializeEntropyEncoding, CustomTypeEntropyEncoded) {
MyStruct1 v = {12,10};
MyStruct1 res;
constexpr size_t N = 4;
std::array<MyStruct1, N> subsitution = {
MyStruct1 entropyValues[N]{
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
SerializationContext ctx;
ctx.createSerializer().substitution(v, subsitution);
ctx.createDeserializer().substitution(res, subsitution);
ctx.createSerializer().entropy(v, entropyValues);
ctx.createDeserializer().entropy(res, entropyValues);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
}
TEST(SerializeSubstitution, CustomTypeNotSubstituted) {
TEST(SerializeEntropyEncoding, CustomTypeNotEntropyEncoded) {
MyStruct1 v = {8945,4456};
MyStruct1 res;
constexpr size_t N = 4;
std::array<MyStruct1, N> subsitution = {
MyStruct1 entropyValues[N] {
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
SerializationContext ctx;
ctx.createSerializer().substitution(v, subsitution);
ctx.createDeserializer().substitution(res, subsitution);
ctx.createSerializer().entropy(v, entropyValues);
ctx.createDeserializer().entropy(res, entropyValues);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(MyStruct1::SIZE + 1));
}
TEST(SerializeSubstitution, CustomFunctionNotSubstituted) {
TEST(SerializeEntropyEncoding, CustomFunctionNotEntropyEncoded) {
MyStruct1 v = {8945,4456};
MyStruct1 res;
constexpr size_t N = 4;
std::array<MyStruct1, N> subsitution = {
MyStruct1 entropyValues[N] {
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
@@ -105,30 +105,30 @@ TEST(SerializeSubstitution, CustomFunctionNotSubstituted) {
s.range(v.i1, rangeForValue);
s.range(v.i2, rangeForValue);
};
ser.substitution(v, subsitution, serLambda);
ser.entropy(v, entropyValues, serLambda);
auto des = ctx.createDeserializer();
auto desLambda = [rangeForValue](auto& s, MyStruct1& v) {
s.range(v.i1, rangeForValue);
s.range(v.i2, rangeForValue);
};
des.substitution(res, subsitution, desLambda);
des.entropy(res, entropyValues, desLambda);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq((rangeForIndex.bitsRequired + rangeForValue.bitsRequired * 2 - 1) / 8 + 1 ));
}
TEST(SerializeSubstitution, WhenSubstitutedThenCustomFunctionNotInvoked) {
TEST(SerializeEntropyEncoding, WhenEntropyEncodedThenCustomFunctionNotInvoked) {
MyStruct1 v = {4849,89};
MyStruct1 res;
constexpr size_t N = 4;
std::array<MyStruct1, N> subsitution = {
MyStruct1 entropyValues[N] {
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
SerializationContext ctx;
ctx.createSerializer().substitution(v, subsitution, [](bitsery::Serializer<bitsery::BufferWriter>& ,const MyStruct1& ) {});
ctx.createDeserializer().substitution(res, subsitution, [](bitsery::Deserializer<bitsery::BufferReader>&, MyStruct1& ) {});
ctx.createSerializer().entropy(v, entropyValues, [](bitsery::Serializer<bitsery::BufferWriter>& ,const MyStruct1& ) {});
ctx.createDeserializer().entropy(res, entropyValues, [](bitsery::Deserializer<bitsery::BufferReader>&, MyStruct1& ) {});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));

View File

@@ -43,8 +43,8 @@ using testing::Eq;
template <typename T>
void test(SerializationContext& ctx, const T& v, T& r) {
ctx.createSerializer().extension4(v, extoptional{});
ctx.createDeserializer().extension4(r, extoptional{});
ctx.createSerializer().extend4b(v, extoptional{});
ctx.createDeserializer().extend4b(r, extoptional{});
}
TEST(SerializeExtensionOptional, EmptyOptional) {

View File

@@ -1,135 +0,0 @@
//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 "serialization_test_utils.h"
#include <type_traits>
using testing::ContainerEq;
using testing::Eq;
TEST(SerializeFSArrayStdArray, ArithmeticValues) {
SerializationContext ctx;
std::array<int, 4> src{5,9,15,-459};
std::array<int, 4> res{};
ctx.createSerializer().array<sizeof(int)>(src);
ctx.createDeserializer().array<sizeof(int)>(res);
EXPECT_THAT(ctx.getBufferSize(), Eq(src.size() * sizeof(int)));
EXPECT_THAT(res, ContainerEq(src));
}
TEST(SerializeFSArrayStdArray, CompositeTypes) {
SerializationContext ctx;
std::array<MyStruct1, 7> src{
MyStruct1{0,1}, MyStruct1{2,3}, MyStruct1{4,5}, MyStruct1{6,7},
MyStruct1{8,9}, MyStruct1{11,34}, MyStruct1{5134,1532}};
std::array<MyStruct1, 7> res{};
ctx.createSerializer().array(src);
ctx.createDeserializer().array(res);
EXPECT_THAT(ctx.getBufferSize(), Eq(src.size() * MyStruct1::SIZE));
EXPECT_THAT(res, ContainerEq(src));
}
//
//
TEST(SerializeFSArrayStdArray, CustomFunctionThatSerializesAnEmptyByteEveryElement) {
SerializationContext ctx;
std::array<MyStruct1, 7> src{
MyStruct1{0,1}, MyStruct1{2,3}, MyStruct1{4,5}, MyStruct1{6,7},
MyStruct1{8,9}, MyStruct1{11,34}, MyStruct1{5134,1532}};
std::array<MyStruct1, 7> res{};
auto ser = ctx.createSerializer();
ser.array(src, [](auto &s, auto& v) {
char tmp{};
s.object(v);
s.value1(tmp);
});
auto des = ctx.createDeserializer();
des.array(res, [](auto &s, auto& v) {
char tmp{};
s.object(v);
s.value1(tmp);
});
EXPECT_THAT(ctx.getBufferSize(), Eq(src.size() * (MyStruct1::SIZE + sizeof(char))));
EXPECT_THAT(res, ContainerEq(src));
}
TEST(SerializeFSArrayCArray, ArithmeticValuesSettingValueSizeExplicitly) {
SerializationContext ctx;
int src[4]{5,9,15,-459};
int res[4]{};
ctx.createSerializer().array<sizeof(int)>(src);
ctx.createDeserializer().array<sizeof(int)>(res);
EXPECT_THAT(ctx.getBufferSize(), Eq(std::extent<decltype(src)>::value * sizeof(int)));
EXPECT_THAT(res, ContainerEq(src));
}
TEST(SerializeFSArrayCArray, CompositeTypes) {
SerializationContext ctx;
MyStruct1 src[]{
MyStruct1{0,1}, MyStruct1{2,3}, MyStruct1{4,5}, MyStruct1{6,7},
MyStruct1{8,9}, MyStruct1{11,34}, MyStruct1{5134,1532}};
MyStruct1 res[7]{};
ctx.createSerializer().array(src);
ctx.createDeserializer().array(res);
EXPECT_THAT(ctx.getBufferSize(), Eq(std::extent<decltype(src)>::value * MyStruct1::SIZE));
EXPECT_THAT(res, ContainerEq(src));
}
//
//
TEST(SerializeFSArrayCArray, CustomFunctionThatSerializesAnEmptyByteEveryElement) {
SerializationContext ctx;
MyStruct1 src[]{
MyStruct1{0,1}, MyStruct1{2,3}, MyStruct1{4,5}, MyStruct1{6,7},
MyStruct1{8,9}, MyStruct1{11,34}, MyStruct1{5134,1532}};
MyStruct1 res[7]{};
auto ser = ctx.createSerializer();
ser.array(src, [](auto& s, auto& v) {
char tmp{};
s.object(v);
s.value1(tmp);
});
auto des = ctx.createDeserializer();
des.array(res, [](auto& s, auto& v) {
char tmp{};
s.object(v);
s.value1(tmp);
});
EXPECT_THAT(ctx.getBufferSize(), Eq(std::extent<decltype(src)>::value * (MyStruct1::SIZE + sizeof(char))));
EXPECT_THAT(res, ContainerEq(src));
}

View File

@@ -0,0 +1,412 @@
//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 "serialization_test_utils.h"
using namespace testing;
using Buffer = typename bitsery::DefaultConfig::BufferType;
using DiffType = typename Buffer::difference_type;
struct DataV1 {
int32_t v1;
};
struct DataV2 {
int32_t v1;
int32_t v2;
};
struct DataV3 {
int32_t v1;
int32_t v2;
int32_t v3;
};
TEST(SerializeGrowable, WriteSessionsDataAtBufferEndAfterFlush) {
SerializationContext ctx;
ctx.createSerializer().growable(int8_t{}, [] (auto& s, auto& v) { });
EXPECT_THAT(ctx.getBufferSize(), Eq(0));
ctx.bw->flush();
EXPECT_THAT(ctx.getBufferSize(), Gt(0));
}
TEST(SerializeGrowable, SessionDataConsistOfSessionsEndPosAnd2BytesSessionsDataOffset) {
SerializationContext ctx;
constexpr size_t DATA_SIZE = 4;
int32_t data{};
ctx.createSerializer().growable(data, [](auto&s, auto& v) { s.value4b(v);});
ctx.createDeserializer();//to flush data and create buffer reader
EXPECT_THAT(ctx.getBufferSize(), Eq(3 + DATA_SIZE));
//read value back
auto& br = *(ctx.br);
br.readBytes<DATA_SIZE>(data);
size_t sessionEnd{};
//there should start session data with first size of session
bitsery::details::readSize(br, sessionEnd);
EXPECT_THAT(sessionEnd, Eq(DATA_SIZE));
//this is the the offset from the end of buffer where actual data ends
uint16_t sessionsOffset{};//bufferEnd - sessionsOffset = dataEnd
br.readBytes<2>(sessionsOffset);
EXPECT_THAT(sessionsOffset, Eq(1+2));//1byte for session info, 2 bytes for session offset variable
auto range = ctx.bw->getWrittenRange();
auto dSize = std::distance(range.begin(), std::next(range.end(), -sessionsOffset));
EXPECT_THAT(dSize, Eq(DATA_SIZE));
}
TEST(SerializeGrowable, WhenNestedSessionsThenStoreEachDepthAndSize) {
SerializationContext ctx;
DataV3 data{19457,846, 498418};
ctx.createSerializer();
ctx.bw->beginSession();
ctx.bw->writeBytes<4>(data.v1);
ctx.bw->beginSession();
ctx.bw->writeBytes<4>(data.v2);
ctx.bw->endSession();
ctx.bw->beginSession();
ctx.bw->writeBytes<4>(data.v3);
ctx.bw->endSession();
ctx.bw->endSession();
DataV3 res{};
ctx.createDeserializer();//to flush data and create buffer reader
ctx.br->readBytes<4>(res.v1);
ctx.br->readBytes<4>(res.v2);
ctx.br->readBytes<4>(res.v3);
//read data correctly
EXPECT_THAT(res.v1, Eq(data.v1));
EXPECT_THAT(res.v2, Eq(data.v2));
EXPECT_THAT(res.v3, Eq(data.v3));
size_t sessionEnd[3];
//read sessions sizes
bitsery::details::readSize(*(ctx.br), sessionEnd[0]);
bitsery::details::readSize(*(ctx.br),sessionEnd[1]);
bitsery::details::readSize(*(ctx.br), sessionEnd[2]);
EXPECT_THAT(sessionEnd[0], Eq(12));
EXPECT_THAT(sessionEnd[1], Eq(8));
EXPECT_THAT(sessionEnd[2], Eq(12));
}
TEST(SerializeGrowable, WhenSessionsDataIsMoreThan0x7FFFThenWrite4BytesForSessionsOffset) {
SerializationContext ctx;
ctx.createSerializer();
//create more sessions that can fit in 2 bytes
for (auto i = 0u; i < 0x8000; ++i) {
ctx.bw->beginSession();
ctx.bw->endSession();
}
ctx.createDeserializer();//to flush data and create buffer reader
EXPECT_THAT(ctx.getBufferSize(), Eq(0x8000+4));
uint8_t tmp{};
for (auto i = 0u; i < 0x8000; ++i) {
ctx.br->beginSession();
ctx.br->endSession();
}
EXPECT_THAT(ctx.br->getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
ctx.br->readBytes<1>(tmp);
EXPECT_THAT(ctx.br->getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
}
TEST(SerializeGrowable, MultipleSessionsReadSameVersionData) {
SerializationContext ctx;
DataV2 data{8454,987451};
ctx.createSerializer();
auto& bw = (*ctx.bw);
for (auto i = 0; i < 10; ++i) {
bw.beginSession();
bw.writeBytes<4>(data.v1);
bw.writeBytes<4>(data.v2);
bw.endSession();
}
//create more sessions that can fit in 2 bytes
ctx.createDeserializer();//to flush data and create buffer reader
DataV2 res{};
auto& br = (*ctx.br);
for (auto i = 0; i < 10; ++i) {
br.beginSession();
br.readBytes<4>(res.v1);
br.readBytes<4>(res.v2);
br.endSession();
EXPECT_THAT(res.v1, Eq(data.v1));
EXPECT_THAT(res.v2, Eq(data.v2));
}
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeGrowable, MultipleSessionsReadNewerVersionData) {
SerializationContext ctx;
DataV3 data{8454,987451,54};
ctx.createSerializer();
auto& bw = (*ctx.bw);
for (auto i = 0; i < 10; ++i) {
bw.beginSession();
bw.writeBytes<4>(data.v1);
bw.writeBytes<4>(data.v2);
bw.writeBytes<4>(data.v3);
bw.endSession();
}
//create more sessions that can fit in 2 bytes
ctx.createDeserializer();//to flush data and create buffer reader
DataV2 res{};
auto& br = (*ctx.br);
for (auto i = 0; i < 10; ++i) {
br.beginSession();
br.readBytes<4>(res.v1);
br.readBytes<4>(res.v2);
br.endSession();
EXPECT_THAT(res.v1, Eq(data.v1));
EXPECT_THAT(res.v2, Eq(data.v2));
}
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeGrowable, MultipleSessionsReadOlderVersionData) {
SerializationContext ctx;
DataV2 data{8454,987451};
ctx.createSerializer();
auto& bw = (*ctx.bw);
for (auto i = 0; i < 10; ++i) {
bw.beginSession();
bw.writeBytes<4>(data.v1);
bw.writeBytes<4>(data.v2);
bw.endSession();
}
//create more sessions that can fit in 2 bytes
ctx.createDeserializer();//to flush data and create buffer reader
DataV3 res{4798,657891,985};
auto& br = (*ctx.br);
for (auto i = 0; i < 10; ++i) {
br.beginSession();
br.readBytes<4>(res.v1);
br.readBytes<4>(res.v2);
br.readBytes<4>(res.v3);
br.endSession();
EXPECT_THAT(res.v1, Eq(data.v1));
EXPECT_THAT(res.v2, Eq(data.v2));
EXPECT_THAT(res.v3, Eq(0));
}
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeGrowable, MultipleNestedSessionsReadSameVersionData) {
SerializationContext ctx;
DataV2 data{8454,987451};
ctx.createSerializer();
auto& bw = (*ctx.bw);
for (auto i = 0; i < 2; ++i) {
bw.beginSession();
bw.writeBytes<4>(data.v1);
bw.writeBytes<4>(data.v2);
bw.beginSession();
bw.writeBytes<4>(data.v1);
bw.writeBytes<4>(data.v2);
bw.endSession();
bw.endSession();
}
//create more sessions that can fit in 2 bytes
ctx.createDeserializer();//to flush data and create buffer reader
DataV2 res{};
auto& br = (*ctx.br);
for (auto i = 0; i < 2; ++i) {
br.beginSession();
br.readBytes<4>(res.v1);
br.readBytes<4>(res.v2);
EXPECT_THAT(res.v1, Eq(data.v1));
EXPECT_THAT(res.v2, Eq(data.v2));
br.beginSession();
br.readBytes<4>(res.v1);
br.readBytes<4>(res.v2);
EXPECT_THAT(res.v1, Eq(data.v1));
EXPECT_THAT(res.v2, Eq(data.v2));
br.endSession();
br.endSession();
}
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeGrowable, MultipleNestedSessionsReadOlderVersionData) {
SerializationContext ctx;
DataV2 data{8454,987451};
ctx.createSerializer();
auto& bw = (*ctx.bw);
for (auto i = 0; i < 5; ++i) {
bw.beginSession();
bw.writeBytes<4>(data.v1);
bw.endSession();
bw.writeBytes<4>(data.v2);
}
//create more sessions that can fit in 2 bytes
ctx.createDeserializer();//to flush data and create buffer reader
DataV3 res{};
auto& br = (*ctx.br);
for (auto i = 0; i < 5; ++i) {
br.beginSession();
br.readBytes<4>(res.v1);
EXPECT_THAT(res.v1, Eq(data.v1));
//new flow
br.beginSession();
br.readBytes<4>(res.v3);
EXPECT_THAT(res.v3, Eq(0));
br.beginSession();
br.readBytes<4>(res.v3);
EXPECT_THAT(res.v3, Eq(0));
br.endSession();
br.endSession();
br.beginSession();
br.readBytes<4>(res.v3);
EXPECT_THAT(res.v3, Eq(0));
br.endSession();
br.endSession();
br.readBytes<4>(res.v2);
EXPECT_THAT(res.v2, Eq(data.v2));
}
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeGrowable, MultipleNestedSessionsReadNewerVersionData1) {
SerializationContext ctx;
DataV3 data{8454,987451,54};
ctx.createSerializer();
auto& bw = (*ctx.bw);
for (auto i = 0; i < 2; ++i) {
bw.beginSession();
{
bw.writeBytes<4>(data.v1);
bw.writeBytes<4>(data.v2);
//new flow
bw.beginSession();
{
bw.writeBytes<4>(data.v3);
}
bw.endSession();
bw.beginSession();
{
bw.writeBytes<4>(data.v3);
bw.beginSession();
{
bw.beginSession();
{
bw.writeBytes<4>(data.v3);
bw.writeBytes<4>(data.v3);
}
bw.endSession();
bw.writeBytes<4>(data.v3);
}
bw.endSession();
}
bw.endSession();
bw.writeBytes<4>(data.v3);
}
bw.endSession();
bw.writeBytes<4>(data.v2);
}
//create more sessions that can fit in 2 bytes
ctx.createDeserializer();//to flush data and create buffer reader
DataV2 res{};
auto& br = (*ctx.br);
for (auto i = 0; i < 2; ++i) {
br.beginSession();
{
br.readBytes<4>(res.v1);
br.readBytes<4>(res.v2);
EXPECT_THAT(res.v1, Eq(data.v1));
EXPECT_THAT(res.v2, Eq(data.v2));
}
br.endSession();
br.readBytes<4>(res.v2);
EXPECT_THAT(res.v2, Eq(data.v2));
}
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeGrowable, MultipleNestedSessionsReadNewerVersionData2) {
SerializationContext ctx;
DataV3 data{8454,987451,54};
ctx.createSerializer();
auto& bw = (*ctx.bw);
for (auto i = 0; i < 2; ++i) {
bw.beginSession();
bw.writeBytes<4>(data.v1);
bw.writeBytes<4>(data.v2);
bw.beginSession();
bw.writeBytes<4>(data.v1);
bw.writeBytes<4>(data.v2);
//new flow
bw.beginSession();
bw.writeBytes<4>(data.v3);
bw.endSession();
bw.beginSession();
bw.writeBytes<4>(data.v3);
bw.beginSession();
bw.beginSession();
bw.writeBytes<4>(data.v3);
bw.writeBytes<4>(data.v3);
bw.endSession();
bw.writeBytes<4>(data.v3);
bw.endSession();
bw.endSession();
bw.writeBytes<4>(data.v3);
bw.endSession();
bw.writeBytes<4>(data.v2);
//new flow
bw.writeBytes<4>(data.v3);
bw.beginSession();
bw.writeBytes<4>(data.v3);
bw.endSession();
bw.writeBytes<4>(data.v3);
bw.endSession();
}
//create more sessions that can fit in 2 bytes
ctx.createDeserializer();//to flush data and create buffer reader
DataV2 res{};
auto& br = (*ctx.br);
for (auto i = 0; i < 2; ++i) {
br.beginSession();
br.readBytes<4>(res.v1);
br.readBytes<4>(res.v2);
EXPECT_THAT(res.v1, Eq(data.v1));
EXPECT_THAT(res.v2, Eq(data.v2));
br.beginSession();
br.readBytes<4>(res.v1);
br.readBytes<4>(res.v2);
EXPECT_THAT(res.v1, Eq(data.v1));
EXPECT_THAT(res.v2, Eq(data.v2));
br.endSession();
br.readBytes<4>(res.v2);
EXPECT_THAT(res.v2, Eq(data.v2));
br.endSession();
}
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}

View File

@@ -24,13 +24,6 @@
#include <gmock/gmock.h>
#include "serialization_test_utils.h"
#include <bitsery/delta_serializer.h>
#include <bitsery/delta_deserializer.h>
#include <list>
using testing::Eq;
using testing::StrEq;
using testing::ContainerEq;
@@ -73,8 +66,8 @@ SERIALIZE(Y)
auto writeInt = [](auto& s, auto& v) { s.template value<sizeof(v)>(v); };
s.template text<1>(o.s, 10000);
s.template value<sizeof(o.y)>(o.y);
s.array(o.arr, writeInt);
s.array(o.carr, writeInt);
s.container(o.arr, writeInt);
s.container(o.carr, writeInt);
s.container(o.vx, 10000, [](auto& s, auto& v) { s.object(v); });
}
@@ -126,49 +119,4 @@ TEST(SerializeObject, GeneralConceptTest) {
EXPECT_THAT(zres.x.s, StrEq(z.x.s));
EXPECT_THAT(zres.x.x, Eq(z.x.x));
}
TEST(DeltaSerializer, GeneralConceptTest) {
//std::string buf;
Y y{};
y.y = 3423;
y.arr[0] = 111;
y.arr[1] = 222;
y.arr[2] = 333;
y.carr[0] = 123;
y.carr[1] = 456;
y.carr[2] = 789;
y.vx.push_back(X(234));
y.vx.push_back(X(6245));
y.vx.push_back(X(613461));
y.s = "labal diena";
y.vx[0].s = "very nice";
y.vx[1].s = "very nice string, that is a little bit longer that previous";
Y yRead = y;
Y yNew = y;
yNew.y = 111111;
yNew.arr[2] = 0xFFFFFFFF;
yNew.carr[1] = 0xFFFFFFFF;
yNew.s = "labas dienaABC";
yNew.vx[0].s = "very opapa";
yNew.vx[1].s = "bla";
yNew.vx.push_back(X{ 3 });
bitsery::DefaultConfig::BufferType buf;
bitsery::BufferWriter bw{ buf };
bitsery::DeltaSerializer<bitsery::BufferWriter, Y> ser(bw, y, yNew);
serialize(ser, yNew);
bw.flush();
bitsery::BufferReader br{ bw.getWrittenRange() };
bitsery::DeltaDeserializer<bitsery::BufferReader, Y> des(br, y, yRead);
serialize(des, yRead);
EXPECT_THAT(yRead.y, Eq(yNew.y));
EXPECT_THAT(yRead.vx, ContainerEq(yNew.vx));
EXPECT_THAT(yRead.arr, ContainerEq(yNew.arr));
EXPECT_THAT(yRead.carr, ContainerEq(yNew.carr));
EXPECT_THAT(yRead.s, StrEq(yNew.s));
}
}

View File

@@ -152,3 +152,15 @@ TEST(SerializeRange, DoubleUsingBitsSizeConstraint2) {
EXPECT_THAT(ctx.getBufferSize(), Eq(7));
EXPECT_THAT(res1, ::testing::DoubleNear(t1, (max - min) / (static_cast<bitsery::details::SAME_SIZE_UNSIGNED<double>>(1) << bits)));
}
TEST(SerializeRange, WhenDataIsInvalidThenReturnMinimumRangeValue) {
SerializationContext ctx;
constexpr RangeSpec<int> r1{4, 10};//6 is max, but 3bits required
int res1;
uint8_t tmp{0xFF};//write all 1 so when reading 3 bits we get 7
ctx.createSerializer().value1b(tmp);
ctx.createDeserializer().range(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, Eq(4));
}

View File

@@ -21,8 +21,8 @@
//SOFTWARE.
#ifndef BITSERY_SERIALIZERTESTS_H
#define BITSERY_SERIALIZERTESTS_H
#ifndef BITSERY_SERIALIZER_TEST_UTILS_H
#define BITSERY_SERIALIZER_TEST_UTILS_H
#include <bitsery/bitsery.h>
#include <memory>
@@ -82,10 +82,11 @@ SERIALIZE(MyStruct2) {
class SerializationContext {
public:
bitsery::DefaultConfig::BufferType buf{};
std::unique_ptr<bitsery::BufferWriter> bw;
std::unique_ptr<bitsery::BufferReader> br;
public:
bitsery::Serializer<bitsery::BufferWriter> createSerializer() {
bw = std::make_unique<bitsery::BufferWriter>(buf);
return {*bw};
@@ -113,4 +114,4 @@ public:
};
};
#endif //BITSERY_SERIALIZERTESTS_H
#endif //BITSERY_SERIALIZER_TEST_UTILS_H

View File

@@ -65,8 +65,8 @@ TEST(SerializeValues, ValueSizeOverload1Byte) {
constexpr size_t TSIZE = sizeof(v);
SerializationContext ctx;
ctx.createSerializer().value1(v);
ctx.createDeserializer().value1(res);
ctx.createSerializer().value1b(v);
ctx.createDeserializer().value1b(res);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(TSIZE, Eq(ctx.getBufferSize()));
@@ -78,8 +78,8 @@ TEST(SerializeValues, ValueSizeOverload2Byte) {
constexpr size_t TSIZE = sizeof(v);
SerializationContext ctx;
ctx.createSerializer().value2(v);
ctx.createDeserializer().value2(res);
ctx.createSerializer().value2b(v);
ctx.createDeserializer().value2b(res);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(TSIZE, Eq(ctx.getBufferSize()));
@@ -91,8 +91,8 @@ TEST(SerializeValues, ValueSizeOverload4Byte) {
constexpr size_t TSIZE = sizeof(v);
SerializationContext ctx;
ctx.createSerializer().value4(v);
ctx.createDeserializer().value4(res);
ctx.createSerializer().value4b(v);
ctx.createDeserializer().value4b(res);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(TSIZE, Eq(ctx.getBufferSize()));
@@ -104,8 +104,8 @@ TEST(SerializeValues, ValueSizeOverload8Byte) {
constexpr size_t TSIZE = sizeof(v);
SerializationContext ctx;
ctx.createSerializer().value8(v);
ctx.createDeserializer().value8(res);
ctx.createSerializer().value8b(v);
ctx.createDeserializer().value8b(res);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(TSIZE, Eq(ctx.getBufferSize()));