1 Commits

Author SHA1 Message Date
fraillt
ef278277e3 added endianness support 2017-07-24 16:08:33 +03:00
59 changed files with 2215 additions and 4144 deletions

View File

@@ -1,23 +0,0 @@
language: cpp
compiler:
  - gcc
  # clang
before_install:
# C++14
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- sudo apt-get update -qq
install:
- sudo apt-get install -qq g++-5 lcov
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 90
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 90
before_script:
  - mkdir build
  - cd build
  - cmake ..
script: make && make test

View File

@@ -1,87 +1 @@
# [3.0.0](https://github.com/fraillt/bitsery/compare/v2.0.1...v3.0.0) (2017-09-21)
### Features
* refactored interface, now works with C++11 compiler.
* new new extension **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.
* added new extension for associative *map* containers **ContainerMap**.
* friendly static_assert message when serializing **object**, that doesn't have **serialize** function defined.
* 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.
* added traits for custom types specialization, in *details/traits.h*
* improved serialization performance: added support for fixed size buffer for BufferWriter 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
* 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 **SERIALIZE** macro, and changed interface for all functions that use custom lambdas, to work with C++11. Now lambda function must capture serializer/deserializer.
* to make serializer a little bit *lighter* removed advanced features and made available as extensions via **extend** method
* removed **range** method, instead added **ValueRange** extension.
* removed **substitution** method, instead added **Entropy** extension, to sound more familiar to *entropy encoding* term.
* removed **array**, instead added fixed sizes overloads for **container**.
* removed **isValid** method from Deserializer, only BufferReader/Writer store states.
* 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*.
* changed BufferWriter/Reader behaviour:
* added support for fixed size buffers 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 has constructor with iterators (range), and raw value type pointers (begin, end).
* 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.
* **writeBits** had incorrect assert when writing negative int64_t.
<a name="2.0.1"></a>
# [2.0.1](https://github.com/fraillt/bitsery/compare/v2.0.0...v2.0.1) (2017-08-12)
### Other notes
* added travis build status
<a name="2.0.0"></a>
# [2.0.0](https://github.com/fraillt/bitsery/compare/v1.1.1...v2.0.0) (2017-07-25)
### Features
* Endianness support, default network configuration is *little endian*
* added user extensible function **ext**, to work with objects that require different serialization/deserialization path (e.g. pointers)
* **optional** extension (for *ext* function), to work with *std::optional* types
### Bug Fixes
* *align* method fixed in *BufferReader*
### Breaking changes
* file structure changed, added *details* folder.
* no longer support for implicit size converions for all functions (*value*, *array*, *container*), instead added helper functions with specific size, to avoid typing *s.template value<1>...* within serialization function body
* changed parameters order for all functions that use custom function (lambda)
* *BufferReader* and *BufferWriter* is now alias types for real types *BasicBufferReader/Writer&lt;DefaultConfig&gt;* (*DefaultConfig* is defined in *common.h*)
<a name="1.1.1"></a>
# [1.1.1](https://github.com/fraillt/bitsery/compare/v1.0.0...v1.1.1) (2017-02-23)
### Notes
* changed folder structure
* added more BufferReader constructors
# 1.0.0 (2017-02-22)
### Features
Serialization functions:
* **value** - [fundamental types](doc/design/fundamental_types.md)
* **container** - dynamic size containers
* **array** - fixed size containers
* **text** - for c-array and std::string
* **range** - compresion for fundamental types (e.g. int between [255..512] will take up 8bits
* **substitution** - default value from list (e.g. 4d vector, that is most of the time equals to [0,0,0,1] can store only 1bit)
* **boolBit**/**boolByte** - serialize bool, as 1bit or 1byte.
#

View File

@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.2)
project(bitsery)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(examples)

View File

@@ -1,27 +0,0 @@
# 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 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.

122
README.md
View File

@@ -1,97 +1,77 @@
# Bitsery
[![Build Status](https://travis-ci.org/fraillt/bitsery.svg?branch=master)](https://travis-ci.org/fraillt/bitsery)
[![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 real-time data delivery, especially for games.
It is designed around the networking requirements for multiplayer real-time fast paced games as first person shooters.
All cross-platform requirements are enforced at compile time, so serialized data do not store any run-time type information and is as small as possible.
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.
## Status
> **bitsery** is looking for your feedback on [gitter](https://gitter.im/bitsery/Lobby)
**bitsery** is in pre-release state and is looking for your feedback.
It has basic features, serialize arithmetic types, enums, containers and text, and few advanced features like value ranges and default values (substitution), but is still missing functions for delta changes and geometry compression.
> Current version do not handle Big/Little Endianness.
> Error handling on deserialization is not tested.
## Features
* Cross-platform compatible.
* Optimized for speed and space.
* No code generation required: no IDL or metadata, just use your types directly.
* Runtime error checking on deserialization.
* Supports forward/backward compatibility for your types.
* 2-in-1 declarative control flow, same code for serialization and deserialization.
* Allows fine-grained bit-level serialization control.
* Easily extendable.
* Configurable endianess support.
* No macros.
## Why to use bitsery
Look at the numbers and features list, and decide yourself. *(benchmarked on Ubuntu with GCC 7.1)*
| | serialize | deserialize | data size | executable size |
|-----------------------------------------------------------|-----------|-------------|-------------|-----------------|
| flatbuffers | 1852 ms. | 777 ms. | 27252 bytes | 74544 bytes |
| cereal | 1069 ms. | 1385 ms. | 20208 bytes | 72336 bytes |
| bitsery | 808 ms. | 737 ms. | 14803 bytes | 69784 bytes |
| bitsery fixed-size buffer | 297 ms. | 738 ms. | 14803 bytes | 69928 bytes |
| bitsery optimized serialization flow | 686 ms. | 997 ms. | 6601 bytes | 69320 bytes |
| bitsery optimized serialization flow + fixed-size buffer | 446 ms. | 996 ms. | 6601 bytes | 69464 bytes |
If still not convinced read more in library [motivation](doc/design/README.md) section.
## Usage example
## Example
```cpp
#include <iostream>
#include <vector>
#include <bitsery/bitsery.h>
using namespace bitsery;
enum class MyEnum { V1,V2,V3 };
struct MyStruct {
uint32_t i;
char str[6];
int i;
MyEnum e;
std::vector<float> fs;
};
template <typename S>
void serialize(S& s, MyStruct& o) {
s.value4b(o.i);
s.text1b(o.str);
s.container4b(o.fs, 100);
};
//define how object should be serialized/deserialized
SERIALIZE(MyStruct) {
return s.
value(o.i).
value(o.e).
container(o.fs, 100);
}
using namespace bitsery;
int main() {
std::vector<uint8_t> buffer;
BufferWriter bw{buffer};
Serializer ser{bw};
MyStruct data{8941, "hello", {15.0f, -8.5f, 0.045f}};
ser.object(data); // serializes data
BufferReader br{bw.getWrittenRange()};
Deserializer des{br};
//set some random data
MyStruct data{8941, MyEnum::V2, {15.0f, -8.5f, 0.045f}};
MyStruct res{};
des.object(res); //deserializes data
//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{buffer};
//2) create deserializer
Deserializer<BufferReader> des{br};
//deserialize same object, can also be invoked like this: serialize(des, data)
des.object(res);
//check is equal
std::cout << "is equal: " << (data.fs == res.fs && data.i == res.i && data.e == res.e) << std::endl;
}
```
For more details go directly to [Quick start](doc/tutorial/hello_world.md) tutorial.
## How to use it
This documentation comprises these parts:
* [Tutorial](doc/tutorial/README.md) - getting started.
* [Reference section](doc/README.md) - all the details.
*documentation is in progress, most parts are empty, but [contributions](CONTRIBUTING.md) are welcome.*
## Requirements
Works with C++11 compiler, no additional dependencies, include `<bitsery/bitsery.h>` and you're done.
## Platforms
This library was tested on
* Windows: Visual Studio 2015, MinGW (GCC 5.2)
* Windows: Visual Studio 2015, MinGW (gcc 5.2)
* Linux: GCC 5.4, GCC 6.2, Clang 3.9
* OS X Mavericks: AppleClang 8
## License
**bitsery** is licensed under the [MIT license](LICENSE).

View File

@@ -1,52 +0,0 @@
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:
* `valueNb instead of value`
* `fundamental types`
* `serializer/deserializer functions overloads`
* `extending library functionality`
* `errors handling`
* `forward/backward compatibility via Growable extension`
Core Serializer/Deserializer functions (alphabetical order):
* `align`
* `boolByte`
* `boolBit`
* `container`
* `extend`
* `getContext`
* `object`
* `text`
* `value`
Serializer/Deserializer extensions via `extend` method (alphabetical order):
* `ContainerMap`
* `Entropy`
* `Growable`
* `Optional`
* `ValueRange`
BasicBufferWriter/Reader functions:
* `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.
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** extension, serialized buffer size in bytes, cannot be greater than 2^(n-2) (where n = sizeof(std::size_t) * 8).
Other:
* [Contributing](../CONTRIBUTING.md)
* [Change log](../CHANGELOG.md)

View File

@@ -1,70 +0,0 @@
## Motivation
Inspiration to create **bitsery** came mainly because there aren't any good alternatives for C++.
I wanted serializer that is easy to use like [cereal](http://uscilab.github.io/cereal/), is cross-platform compatible, and has support for forward/backward compatibility like [flatbuffers](https://google.github.io/flatbuffers/), is save to use with untrusted (malicious) data, and most importantly is fast and has small binary footprint.
Furthermore I wanted full serialization control and ability to work on bit level, so I can further reduce data size. For example, serializing container of [quaternions](https://en.wikipedia.org/wiki/Quaternion) I can reduce size by large amount. *Size of orientation quaternion can be reduced from 128bits (4floats) down to 29bits using "smallest three" technique and still retaining decent precision*.
Most well-known serialization libraries sacrifice memory and speed efficiency by supporting multiple data formats (binary, json, xml) and multiple languages (C++, C#, Javascript, etc..), these features also adds additional library complexity.
## A word about JSON
Often times 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.
Adding optional support for JSON doesn't come for free either.
To support JSON, additional metadata is required.
In C++ it can be achieved by two ways:
* with macros, that generate additional types like [cereal](http://uscilab.github.io/cereal/) does.
* with code generation from IDL (interface definition language) like [flatbuffers](https://google.github.io/flatbuffers/) does.
Both solutions adds additional complexity to the library. In the future C++ will get reflection system, currently static reflection was proposed to standard.
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
Bitsery is designed to be lightweight and simple to use, yet powerful and extendable library.
To ensure it works as intended it is unit tested, and has 100% code coverage.
Now let's review features in more detail.
* **Cross-platform compatible.** if same code compiles on Android, PS3 console, and your PC either x64 or x86 architecture, you are 100% sure it works.
To achieve this, bitsery specifically defines size of underlying data, hence syntax is *value\<2\>* (alias function *value2b*) instead or *value*, or *container2b* for element type of 16bits, eg int16_t.
Bitsery also applies endianess transformation if nessesarry.
**If** however, you don't like this verbose syntax, you can just write *serialize* functions for fundamental types, and forget about *value\<N\>*, *container\<N\>*, etc.
But do it on your own risk, or write static asserts.
* **Optimized for speed and space.** library itself doesn't do any allocations (except if you use backward/forward compatibility) so data writing/reading is fast as memcpy to/from your buffer.
It also doesn't serialize any type information, all information needed is writen in your code!
* **No code generation required: no IDL or metadata** since it doesn't support any other formats except binary, it doesn't need any metadata.
* **Runtime error checking on deserialization** library designed to be save with untrusted network data, that's why all overloads that work on containers has *maxSize* value, unless container is static size like *std::array*, this way bitsery ensures that no malicious data will not crash you.
* **Supports forward/backward compatibility for your types** library has optional forward/backward compatibility for types implemented in *BasicBufferReader/BasicBufferWriter* by allowing to have inner data sessions in inside buffer.
This is the only functionality that requires dynamic memory allocation.
*Glowable* extension use these sessions to add compatibility support for your types, in most basic form.
You can implement your own extensions if you want to be able to add default values.
* **2-in-1 declarative control flow, same code for serialization and deserialization.** only one function to define, for serialization and deserialization in same manner as *cereal* does.
It might be handy to have separate *load*, *save* functions, but Bitsery explicitly doesn't support it, to avoid any serialization deserialization path differences, because it is very hard to catch an errors if you make a bug in one of these functions.
The only way around this through extensions, write your custom flow once, and reuse where you need them.
* **Allows fine-grained serialization control** this is a feature that no other libraries provides.
Bitsery allows to use bit-level operations and has two extensions that use them:
* *ValueRange*,- if you have a *int16_t* data type, but you know that your object only stores values in \[0..1000\] range, it will write 10bits, instead of 16bits. ValueRanges also works with floats.
* *Entropy*,- full term is *entropy encoding*, which means that when you have most common value, or multiple values, it will write just few bits instead of full object.
Eg.: imagine that you have a struct Person{ int32_t Id; string Profession; }.
You know that mostly there are young persons, so the most common value will be equal to: "Student", "Child", "NoProfession", in this case you'll pay 2bits for each record, but write no data if string matches.
Using these bit-level operations and extensions you can compose your own extensions for vectors, matrices or any other types.
Further more, all other operations will not align data automatically for you, so data will be compressed as much as possible.
One more advanced and dangerous feature, is ability to have serialization context, so you can control your serialization flow at runtime, but make sure that these contexts are in sync between serializer and deserializer.
One possible use case for serialization context is to pass min/max ranges for *ValueRange* when your information changes at runtime.
* **Easily extendable** library is designed to be easily extendable for any type and flow.
You want to support your custom container, its fine there is *ContainerTraits* for this, only few methods required to implement.
To use same container for buffer writing/reading add specialization to *BufferContainerTraits*.
You want to customize serialization flow - use extensions, only two methods to define, and *ExtensionTraits* to further customize usage.
* **Configurable endianess support.** default is *Little Endian*, but if your primary target is PowerPC architecture, eg. PlayStation3, just change your configuration to be *Big Endian*.
* **No macros.** Not so much to say, if you are like me, then it's a feature :)
*project for performance benchmark will be added to separate github project, i'll give you a link to it when its done.*

View File

@@ -1 +0,0 @@
errors handling design

View File

@@ -1 +0,0 @@
valueN instead of value

View File

@@ -1 +0,0 @@
ints chars floats (except bool)

View File

@@ -1,6 +0,0 @@
*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

@@ -1,7 +0,0 @@
### 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

@@ -1,8 +0,0 @@
The grand plan for this tutorial is to learn how to serialize/deserialize any object efficiently in time and space, so you could focus on other, more interesting things.
This tutorial will cover these main topics:
* `Hello World` write one control flow for both: serialization and deserialization.
* `Composer` efficiently compose complex serialization flows.
* `Squeeze Me!` compress your data when you know what it stores.
* `Anything is Possible` extend library for custom container, compress geometry and more.
* `Little or Big` change endianness if you want best performance on PowerPC.

View File

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

View File

@@ -1,128 +0,0 @@
# Quick Start
This is a quick guide to get **bitsery** up and running in a matter of minutes.
The only prerequisite for running bitsery is a modern C++11 compliant compiler, such as GCC 4.9.4, clang 3.4, MSVC 2015, or newer.
Older versions might work, but it is not tested.
## Get bitsery
bitsery can be directly included in your project or installed anywhere you can access header files.
Grab the latest version, and include directory `bitsery_base_dir/include/` to your project.
There's nothing to build or make - **bitsery** is header only.
## Add serialization methods for your types
**bitsery** needs to know which data members to serialize in your classes.
Let it know by implementing a serialize method for your type:
```cpp
struct MyStruct {
uint32_t i;
char str[6];
std::vector<float> fs;
};
template <typename S>
void serialize(S& s, MyStruct& o) {
s.value4b(o.i);
s.text1b(o.str);
s.container4b(o.fs, 100);
};
```
**bitsery** also can serialize private class members, just move *serialize* function inside structure, and make it *friend* (*fiend void serialize(.....)*).
**bitsery** has verbose syntax, because it is cross-platform compatible by default and has full control over how to serialize data (read more about it in [motivation](../design/README.md))
This example contains core functionality that you'll use all the time, so lets get through it:
* **s.value4b(o.i);** serialize fundamental types (ints, floats, enums) value**4b** means, that data type is 4 bytes. If you use same code on different machines, if it compiles it means it is compatible.
* **s.text1b(o.str);** serialize text (null-terminated) of char type, if you use *wchar* then you would write *text2b*.
* **s.container4b(o.fs, 100);** serializes any container of fundamental types of size 4bytes, **100** is max size of container.
**Bitsery** is designed to be save with untrusted (malicious) data from network, so for dynamic containers you always need to provide max possible size available, to avoid buffer-overflow attacks.
**text** didn't had this max size specified, because it was serializing fixed size container.
External serialization functions should be placed either in the same namespace as the types they serialize or in the **bitsery** namespace so that the compiler can find them properly.
## Serialization and deserialization
### Create serializer
Create a serializer and send the data you want to serialize to it.
```cpp
std::vector<uint8_t> buffer;
BufferWriter bw{buffer};
Serializer ser{bw};
```
Serialization process consists of three independant parts.
* **std::vector<uint8_t> buffer;** core object, that will store the data for serialization and deserialization.
* **BufferWriter bw{buffer};** writer knows how to write bytes to buffer, and how to resize buffer, or how to use fixed-size buffer. It also applies endianess transformations if nesessary.
* **Serializer ser{bw};** serializer is a high level wrapper that knows how to convert object to stream of bytes, and write then to buffer.
Serializer doesn't store any state, it only has reference to buffer, so it is safe to create many of those if nesessary.
BufferWriter also doesn't own buffer, but it stores state about writing position and container size.
One important note that when using bit-level operations, dont forget to flush buffer writer **bw.flush()** otherwise, some data might not be written to buffer.
### Serialize object
```cpp
MyStruct data{8941, "hello", {15.0f, -8.5f, 0.045f}};
ser.object(data); // serializes data
```
**ser.object(data)** is a final core function along with **value, text, container**.
This function is actually equivalent to calling *serialize(ser, data)* directly, but it displays friendly static assert message if it cannot find *serialize* function for your type.
### Deserialize object
```cpp
BufferReader br{bw.getWrittenRange()};
Deserializer des{br};
MyStruct res{};
des.object(res); //deserializes data
```
Deserialization process is equivalent to serialization, except that *BufferReader* reader has getError() method that returns deserialization state.
## Full example code
```cpp
#include <bitsery/bitsery.h>
using namespace bitsery;
struct MyStruct {
uint32_t i;
char str[6];
std::vector<float> fs;
};
template <typename S>
void serialize(S& s, MyStruct& o) {
s.value4b(o.i);
s.text1b(o.str);
s.container4b(o.fs, 100);
};
int main() {
std::vector<uint8_t> buffer;
BufferWriter bw{buffer};
Serializer ser{bw};
MyStruct data{8941, "hello", {15.0f, -8.5f, 0.045f}};
ser.object(data); // serializes data
BufferReader br{bw.getWrittenRange()};
Deserializer des{br};
MyStruct res{};
des.object(res); //deserializes data
}
```
**currently documentation and tutorial is progress, but for more usage examples see examples folder**

View File

@@ -1,19 +1,44 @@
//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 <iostream>
#include <vector>
#include <bitsery/bitsery.h>
enum class MyEnum:uint16_t { V1,V2,V3 };
enum class MyEnum { V1,V2,V3 };
struct MyStruct {
uint32_t i;
int i;
MyEnum e;
std::vector<float> fs;
};
//define how object should be serialized/deserialized
template <typename S>
void serialize(S& s, MyStruct& o) {
s.value4b(o.i);
s.value2b(o.e);
s.container4b(o.fs, 10);
};
SERIALIZE(MyStruct) {
return s.
value4(o.i).
value4(o.e).
container4(o.fs, 100);
}
using namespace bitsery;
@@ -28,7 +53,7 @@ int main() {
//2) create buffer writer that is able to write bytes or bits to buffer
BufferWriter bw{buffer};
//3) create serializer
Serializer ser{bw};
Serializer<BufferWriter> ser{bw};
//serialize object, can also be invoked like this: serialize(ser, data)
ser.object(data);
@@ -38,11 +63,13 @@ int main() {
//create deserializer
//1) create buffer reader
BufferReader br{bw.getWrittenRange()};
BufferReader br{buffer};
//2) create deserializer
Deserializer des{br};
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);
}
//check is equal
std::cout << "is equal: " << (data.fs == res.fs && data.i == res.i && data.e == res.e) << std::endl;
}

View File

@@ -1,101 +0,0 @@
#include <bitsery/bitsery.h>
#include <bitsery/ext/growable.h>
#include <array>
namespace MyTypes {
//define data
enum Color:uint8_t { Red, Green, Blue };
struct Vec3 { float x, y, z; };
struct Weapon {
std::string name;
int16_t damage;
};
struct Monster {
Vec3 pos;
int16_t mana;
int16_t hp;
std::string name;
std::vector<uint8_t> inventory;
Color color;
std::vector<Weapon> weapons;
Weapon equipped;
std::vector<Vec3> path;
};
template <typename S>
void serialize(S& s, Vec3& o) {
s.value4b(o.x);
s.value4b(o.y);
s.value4b(o.z);
}
//define serialization functions
template <typename S>
void serialize (S& s, Weapon& o) {
//forward/backward compatibility for monsters
s.ext(o, bitsery::ext::Growable{}, [&s](Weapon& o1) {
s.text1b(o1.name, 20);
s.value2b(o1.damage);
});
}
template <typename S>
void serialize (S& s, Monster& o) {
//forward/backward compatibility for monsters
s.ext(o, bitsery::ext::Growable{}, [&s](Monster& o1) {
s.value1b(o1.color);
s.value2b(o1.mana);
s.value2b(o1.hp);
s.object(o1.equipped);
s.object(o1.pos);
s.container(o1.path, 1000);
s.container(o1.weapons, 100);
s.container1b(o1.inventory, 50);
s.text1b(o1.name, 20);
});
}
}
using namespace bitsery;
//change configuration
struct NonDefaultConfig: public bitsery::DefaultConfig {
//change underlying buffer
using BufferType = std::array<uint8_t, 1000000>;
};
int main() {
//set some random data
MyTypes::Monster data{};
data.name = "lew";
//create serializer
//1) create buffer to store data
std::array<uint8_t, 1000000> buffer{};
//2) create buffer writer that is able to write bytes or bits to buffer
BasicBufferWriter<NonDefaultConfig> bw{buffer};
//3) create serializer
BasicSerializer<NonDefaultConfig> ser{bw};
//serialize object, can also be invoked like this: serialize(ser, data)
ser.object(data);
//flush to buffer, before creating buffer reader, this will always write sessions data for forward/backward compatibility
bw.flush();
//create deserializer
//1) create buffer reader
BasicBufferReader<NonDefaultConfig> br{bw.getWrittenRange()};
//2) create deserializer
BasicDeserializer<NonDefaultConfig> des{br};
//deserialize same object, can also be invoked like this: serialize(des, data)
MyTypes::Monster res{};
des.object(res);
}

View File

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

View File

@@ -25,164 +25,119 @@
#ifndef BITSERY_BUFFER_READER_H
#define BITSERY_BUFFER_READER_H
#include "details/buffer_common.h"
#include "common.h"
#include <cassert>
#include <algorithm>
#include <cstring>
namespace bitsery {
template<typename Config>
template <typename Config>
struct BasicBufferReader {
using ValueType = typename Config::BufferValueType;
using ScratchType = typename Config::BufferScrathType;
using BufferType = typename Config::BufferType;
using ValueType = typename details::BufferContainerTraits<BufferType>::TValue;
using BufferIteratorType = typename details::BufferContainerTraits<BufferType>::TIterator;
using ScratchType = typename details::SCRATCH_TYPE<ValueType>::type;
BasicBufferReader(ValueType* begin, ValueType* end)
:_pos{begin},
_end{end},
_session{*this, _pos, _end}
BasicBufferReader(const ValueType* data, size_t size) : _pos{data}, _end{data + size}
{
static_assert(std::is_unsigned<ValueType>(), "Config::BufferValueType must be unsigned");
static_assert(std::is_unsigned<ScratchType>(), "Config::BufferScrathType must be unsigned");
static_assert(sizeof(ValueType) * 2 == sizeof(ScratchType),
"ScratchType must be 2x bigger than value type");
static_assert(sizeof(ValueType)*2 == sizeof(ScratchType), "ScratchType must be 2x bigger than value type");
static_assert(sizeof(ValueType) == 1, "currently only supported BufferValueType is 1 byte");
}
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");
explicit BasicBufferReader(const std::vector<ValueType> &buf) : BasicBufferReader(buf.data(), buf.size()) {
}
BasicBufferReader(const BasicBufferReader &) = delete;
BasicBufferReader &operator=(const BasicBufferReader &) = delete;
BasicBufferReader(BasicBufferReader &&) noexcept = default;
BasicBufferReader &operator=(BasicBufferReader &&) noexcept = default;
template <size_t N>
explicit BasicBufferReader(const ValueType (&data)[N]): BasicBufferReader(data, N)
{
}
BasicBufferReader(const BasicBufferReader&) = delete;
BasicBufferReader& operator=(const BasicBufferReader& ) = delete;
BasicBufferReader(BasicBufferReader&&) noexcept = default;
BasicBufferReader& operator=(BasicBufferReader&&) noexcept = default;
~BasicBufferReader() noexcept = default;
template<size_t SIZE, typename T>
void readBytes(T &v) {
bool readBytes(T &v) {
static_assert(std::is_integral<T>(), "");
static_assert(sizeof(T) == SIZE, "");
using UT = typename std::make_unsigned<T>::type;
if (!m_scratchBits)
directRead(&v, 1);
else
readBits(reinterpret_cast<UT &>(v), details::BITS_SIZE<T>::value);
return !m_scratch
? directRead(&v, 1)
: readBits(reinterpret_cast<UT &>(v), details::BITS_SIZE<T>);
}
template<size_t SIZE, typename T>
void readBuffer(T *buf, size_t count) {
bool readBuffer(T *buf, size_t count) {
static_assert(std::is_integral<T>(), "");
static_assert(sizeof(T) == SIZE, "");
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>::value);
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;
}
return true;
}
template<typename T>
void readBits(T &v, size_t bitsCount) {
bool readBits(T &v, size_t bitsCount) {
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
assert(bitsCount <= details::BITS_SIZE<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;
}
void align() {
bool align() {
if (m_scratchBits) {
ScratchType tmp{};
readBitsInternal(tmp, m_scratchBits);
if (tmp)
setError(BufferReaderError::INVALID_BUFFER_DATA);
return tmp == 0;
}
return true;
}
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();
}
bool isCompleted() const {
return _pos == _end;
}
private:
ValueType* _pos;
ValueType* _end;
ScratchType m_scratch{};
size_t m_scratchBits{};
typename std::conditional<Config::BufferSessionsEnabled,
details::BufferSessionsReader<BasicBufferReader<Config>, ValueType*>,
details::DisabledBufferSessionsReader<Config>>::type
_session;
const ValueType* _pos;
const ValueType* _end;
template<typename T>
void directRead(T *v, size_t count) {
bool directRead(T *v, size_t count) {
static_assert(!std::is_const<T>::value, "");
const auto bytesCount = sizeof(T) * count;
if (std::distance(_pos, _end) >= static_cast<typename details::BufferContainerTraits<BufferType>::TDifference>(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);
}
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;
}
template<typename T>
void _swapDataBits(T *v, size_t count, std::true_type) {
std::for_each(v, std::next(v, count), [this](T &x) { x = details::swap(x); });
std::for_each(v, std::next(v, count), [this](T& v) { v = details::swap(v); });
}
template<typename T>
@@ -195,12 +150,12 @@ namespace bitsery {
auto bitsLeft = size;
T res{};
while (bitsLeft > 0) {
auto bits = std::min(bitsLeft, details::BITS_SIZE<ValueType>::value);
auto bits = std::min(bitsLeft, details::BITS_SIZE<ValueType>);
if (m_scratchBits < bits) {
ValueType tmp;
directRead(&tmp, 1);
m_scratch |= static_cast<ScratchType>(tmp) << m_scratchBits;
m_scratchBits += details::BITS_SIZE<ValueType>::value;
m_scratchBits += details::BITS_SIZE<ValueType>;
}
auto shiftedRes =
static_cast<T>(m_scratch & ((static_cast<ScratchType>(1) << bits) - 1)) << (size - bitsLeft);
@@ -212,6 +167,9 @@ 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

@@ -25,8 +25,10 @@
#ifndef BITSERY_BUFFER_WRITER_H
#define BITSERY_BUFFER_WRITER_H
#include "details/buffer_common.h"
#include "common.h"
#include <cassert>
#include <algorithm>
#include <iterator>
#include <utility>
namespace bitsery {
@@ -37,13 +39,13 @@ namespace bitsery {
void writeBytes(const T &) {
static_assert(std::is_integral<T>(), "");
static_assert(sizeof(T) == SIZE, "");
_bitsCount += details::BITS_SIZE<T>::value;
_bitsCount += details::BITS_SIZE<T>;
}
template<typename T>
void writeBits(const T &, size_t bitsCount) {
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
assert(bitsCount <= details::BITS_SIZE<T>::value);
assert(bitsCount <= details::BITS_SIZE<T>);
_bitsCount += bitsCount;
}
@@ -51,71 +53,35 @@ namespace bitsery {
void writeBuffer(const T *, size_t count) {
static_assert(std::is_integral<T>(), "");
static_assert(sizeof(T) == SIZE, "");
_bitsCount += details::BITS_SIZE<T>::value * count;
}
void align() {
auto _scratch = (_bitsCount % 8);
_bitsCount += (8 - _scratch) % 8;
}
void flush() {
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;
_bitsCount += details::BITS_SIZE<T> * count;
}
//get size in bytes
size_t getWrittenBytesCount() const {
size_t getSize() const {
return _bitsCount / 8;
}
private:
size_t _bitsCount{};
size_t _sessionsBytesCount{};
};
template<typename Config>
template <typename Config>
struct BasicBufferWriter {
using BufferType = typename Config::BufferType;
using ValueType = typename details::BufferContainerTraits<BufferType>::TValue;
using ScratchType = typename details::SCRATCH_TYPE<ValueType>::type;
using BufferContext = details::WriteBufferContext<BufferType, details::BufferContainerTraits<BufferType>::isResizable>;
using ValueType = typename Config::BufferValueType;
using ScratchType = typename Config::BufferScrathType;
explicit BasicBufferWriter(BufferType &buffer)
: _bufferContext{buffer}
{
static_assert(std::is_unsigned<ValueType>(), "Config::BufferType value type must be unsigned");
static_assert(sizeof(ValueType) * 2 == sizeof(ScratchType),
"ScratchType must be 2x bigger than value type");
explicit BasicBufferWriter(std::vector<ValueType> &buffer) : _outIt{std::back_inserter(buffer)} {
static_assert(std::is_unsigned<ValueType>(), "Config::BufferValueType must be unsigned");
static_assert(std::is_unsigned<ScratchType>(), "Config::BufferScrathType must be unsigned");
static_assert(sizeof(ValueType)*2 == sizeof(ScratchType), "ScratchType must be 2x bigger than value type");
static_assert(sizeof(ValueType) == 1, "currently only supported BufferValueType is 1 byte");
}
BasicBufferWriter(const BasicBufferWriter &) = delete;
BasicBufferWriter &operator=(const BasicBufferWriter &) = delete;
BasicBufferWriter(BasicBufferWriter &&) noexcept = default;
BasicBufferWriter &operator=(BasicBufferWriter &&) noexcept = default;
BasicBufferWriter(const BasicBufferWriter&) = delete;
BasicBufferWriter& operator=(const BasicBufferWriter& ) = delete;
BasicBufferWriter(BasicBufferWriter&&) noexcept = default;
BasicBufferWriter& operator=(BasicBufferWriter&&) noexcept = default;
~BasicBufferWriter() noexcept = default;
template<size_t SIZE, typename T>
@@ -127,7 +93,7 @@ namespace bitsery {
directWrite(&v, 1);
} else {
using UT = typename std::make_unsigned<T>::type;
writeBits(reinterpret_cast<const UT &>(v), details::BITS_SIZE<T>::value);
writeBits(reinterpret_cast<const UT &>(v), details::BITS_SIZE<T>);
}
}
@@ -142,67 +108,57 @@ namespace bitsery {
//todo improve implementation
const auto end = buf + count;
for (auto it = buf; it != end; ++it)
writeBits(reinterpret_cast<const UT &>(*it), details::BITS_SIZE<T>::value);
writeBits(reinterpret_cast<const UT &>(*it), details::BITS_SIZE<T>);
}
}
template<typename T>
void writeBits(const T &v, size_t bitsCount) {
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
assert(0 < bitsCount && bitsCount <= details::BITS_SIZE<T>::value);
assert(v <= (bitsCount < 64
? (1ULL << bitsCount) - 1
: (1ULL << (bitsCount-1)) + ((1ULL << (bitsCount-1)) -1)));
assert(0 < bitsCount && bitsCount <= details::BITS_SIZE<T>);
assert(v <= ((1ULL << bitsCount) - 1));
writeBitsInternal(v, bitsCount);
}
void align() {
writeBitsInternal(ValueType{}, (details::BITS_SIZE<ValueType>::value - _scratchBits) % 8);
if (_scratchBits)
writeBitsInternal(ValueType{}, details::BITS_SIZE<ValueType> - _scratchBits);
}
void flush() {
align();
_session.flushSessions(*this);
if (_scratchBits) {
auto tmp = static_cast<ValueType>( _scratch & _MASK );
directWrite(&tmp, 1);
_scratch >>= _scratchBits;
_scratchBits -= _scratchBits;
}
}
BufferRange<typename details::BufferContainerTraits<BufferType>::TIterator> getWrittenRange() const {
return _bufferContext.getWrittenRange();
}
void beginSession() {
align();
_session.begin(*this);
}
void endSession() {
align();
_session.end(*this);
}
private:
template<typename T>
void directWrite(T &&v, size_t count) {
void directWrite(T&& v, size_t count) {
_directWriteSwapTag(std::forward<T>(v), count, std::integral_constant<bool,
Config::NetworkEndianness != details::getSystemEndianness()>{});
}
template<typename T>
void _directWriteSwapTag(const T *v, size_t count, std::true_type) {
std::for_each(v, std::next(v, count), [this](const T &v) {
std::for_each(v, std::next(v, count), [this](const T& v) {
const auto res = details::swap(v);
_bufferContext.write(reinterpret_cast<const ValueType *>(&res), sizeof(T));
std::copy_n(reinterpret_cast<const ValueType*>(&res), sizeof(T), _outIt);
});
}
template<typename T>
void _directWriteSwapTag(const T *v, size_t count, std::false_type) {
_bufferContext.write(reinterpret_cast<const ValueType *>(v), count * sizeof(T));
std::copy_n(reinterpret_cast<const ValueType*>(v), count * sizeof(T), _outIt);
}
template<typename T>
void writeBitsInternal(const T &v, size_t size) {
constexpr size_t valueSize = details::BITS_SIZE<ValueType>::value;
constexpr size_t valueSize = details::BITS_SIZE<ValueType>;
auto value = v;
auto bitsLeft = size;
while (bitsLeft > 0) {
@@ -226,24 +182,20 @@ namespace bitsery {
if (size > 0) {
_scratch |= static_cast<ScratchType>( v ) << _scratchBits;
_scratchBits += size;
if (_scratchBits >= details::BITS_SIZE<ValueType>::value) {
if (_scratchBits >= details::BITS_SIZE<ValueType>) {
auto tmp = static_cast<ValueType>(_scratch & _MASK);
directWrite(&tmp, 1);
_scratch >>= details::BITS_SIZE<ValueType>::value;
_scratchBits -= details::BITS_SIZE<ValueType>::value;
_scratch >>= details::BITS_SIZE<ValueType>;
_scratchBits -= details::BITS_SIZE<ValueType>;
}
}
}
const ValueType _MASK = std::numeric_limits<ValueType>::max();
BufferContext _bufferContext;
std::back_insert_iterator<std::vector<ValueType>> _outIt;
ScratchType _scratch{};
size_t _scratchBits{};
typename std::conditional<Config::BufferSessionsEnabled,
details::BufferSessionsWriter<BasicBufferWriter<Config>>,
details::DisabledBufferSessionsWriter<Config>>::type
_session{};
};
//helper type

View File

@@ -24,30 +24,24 @@
#ifndef BITSERY_COMMON_H
#define BITSERY_COMMON_H
#include <vector>
#include "bitsery/details/buffer_common.h"
namespace bitsery {
/*
* endianess
*/
enum class EndiannessType {
LittleEndian,
BigEndian
};
//default configuration for buffer writing/reading operations
struct DefaultConfig {
static constexpr EndiannessType NetworkEndianness = EndiannessType::LittleEndian;
//this functionality allows to support backward/forward compatibility for any type
//disabling it, saves 100+bytes per BufferReader/Writer and also reduces executable size
static constexpr bool BufferSessionsEnabled = true;
//buffer value type must be unsigned, currently only uint8_t supported
//fixed size buffer type also supported, for faster serialization performance
using BufferType = std::vector<uint8_t>;
using BufferValueType = uint8_t;
using BufferScrathType = uint16_t;
};
}
/*
* serializer macro, serialize function specialization that accepts T& and const T&
*/
#define SERIALIZE(ObjectType) \
template <typename S, typename T, typename std::enable_if<std::is_same<T, ObjectType>::value || std::is_same<T, const ObjectType>::value>::type* = nullptr> \
S& serialize(S& s, T& o)
}
#endif //BITSERY_COMMON_H

View File

@@ -0,0 +1,220 @@
//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))
return 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

@@ -0,0 +1,220 @@
//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

@@ -31,319 +31,321 @@
namespace bitsery {
template<typename Config>
class BasicDeserializer {
template<typename Reader>
class Deserializer {
public:
explicit BasicDeserializer(BasicBufferReader<Config> &r, void* context = nullptr) : _reader{r}, _context{context} {};
Deserializer(Reader& r):_reader{r}, _isValid{true} {};
/*
* get serialization context.
* this is optional, but might be required for some specific deserialization flows.
*/
void* getContext() {
return _context;
template <typename T>
Deserializer& object(T&& obj) {
return serialize(*this, std::forward<T>(obj));
}
/*
* object function
*/
template<typename T>
void object(T &&obj) {
details::SerializeFunction<BasicDeserializer, T>::invoke(*this, std::forward<T>(obj));
}
template<typename T, typename Fnc>
void object(T &&obj, Fnc &&fnc) {
fnc(std::forward<T>(obj));
//in c++17 change "class" to typename
template <template <typename> class Extension, typename TValue, typename Fnc>
Deserializer& ext(TValue& v, Fnc&& fnc) {
static_assert(!std::is_const<TValue>(), "");
Extension<TValue> ext{v};
ext.deserialize(*this, std::forward<Fnc>(fnc));
return *this;
};
/*
* value overloads
*/
template<size_t VSIZE, typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
void value(T &v) {
template<size_t VSIZE, typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
Deserializer& value(T& v) {
static_assert(std::numeric_limits<T>::is_iec559, "");
_reader.template readBytes<VSIZE>(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T> &>(v));
if (_isValid) {
_isValid = _reader.template readBytes<VSIZE>(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T>&>(v));
}
return *this;
}
template<size_t VSIZE, typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
void value(T &v) {
using UT = typename std::underlying_type<T>::type;
_reader.template readBytes<VSIZE>(reinterpret_cast<UT &>(v));
template<size_t VSIZE, typename T, typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
Deserializer& value(T& v) {
using UT = std::underlying_type_t<T>;
if (_isValid) {
_isValid = _reader.template readBytes<VSIZE>(reinterpret_cast<UT&>(v));
}
return *this;
}
template<size_t VSIZE, typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
void value(T &v) {
_reader.template readBytes<VSIZE>(v);
template<size_t VSIZE, typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
Deserializer& value(T& v) {
if (_isValid) {
_isValid = _reader.template readBytes<VSIZE>(v);
}
return *this;
}
/*
* extension functions
*/
template<typename T, typename Ext, typename Fnc>
void ext(T &obj, Ext &&extension, Fnc &&fnc) {
using ExtType = typename std::decay<Ext>::type;
static_assert(details::ExtensionTraits<ExtType,T>::SupportLambdaOverload,
"extension doesn't support overload with lambda");
extension.deserialize(*this, _reader, obj, std::forward<Fnc>(fnc));
};
template<size_t VSIZE, typename T, typename Ext>
void ext(T &obj, Ext &&extension) {
using ExtType = typename std::decay<Ext>::type;
static_assert(details::ExtensionTraits<ExtType,T>::SupportValueOverload,
"extension doesn't support overload with `value<N>`");
using ExtVType = typename details::ExtensionTraits<ExtType, T>::TValue;
using VType = typename std::conditional<std::is_void<ExtVType>::value, details::DummyType, ExtVType>::type;
extension.deserialize(*this, _reader, obj, [this](VType &v) { value<VSIZE>(v); });
};
template<typename T, typename Ext>
void ext(T &obj, Ext &&extension) {
using ExtType = typename std::decay<Ext>::type;
static_assert(details::ExtensionTraits<ExtType,T>::SupportObjectOverload,
"extension doesn't support overload with `object`");
using ExtVType = typename details::ExtensionTraits<ExtType, T>::TValue;
using VType = typename std::conditional<std::is_void<ExtVType>::value, details::DummyType, ExtVType>::type;
extension.deserialize(*this, _reader, obj, [this](VType &v) { object(v); });
};
/*
* bool
*/
void boolBit(bool &v) {
uint8_t tmp{};
_reader.readBits(tmp, 1);
v = tmp == 1;
Deserializer& boolBit(bool& v) {
if (_isValid) {
unsigned char tmp;
_isValid = _reader.readBits(tmp, 1);
v = tmp == 1;
}
return *this;
}
void boolByte(bool &v) {
unsigned char tmp;
_reader.template readBytes<1>(tmp);
if (tmp > 1)
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
v = tmp == 1;
Deserializer& boolByte(bool& v) {
if (_isValid) {
unsigned char tmp;
_isValid = _reader.template readBytes<1>(tmp);
if (_isValid)
_isValid = tmp < 2;
v = tmp == 1;
}
return *this;
}
/*
* range
*/
template <typename T>
Deserializer& 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);
}
return *this;
}
/*
* substitution overloads
*/
template<typename T, size_t N, typename Fnc>
Deserializer& substitution(T& v, const std::array<T,N>& expectedValues, Fnc&& fnc) {
size_t index;
range(index, {{}, N + 1});
if (_isValid) {
if (index)
v = expectedValues[index-1];
else
fnc(*this, v);
}
return *this;
};
template<size_t VSIZE, typename T, size_t N>
Deserializer& substitution(T& v, const std::array<T,N>& expectedValues) {
size_t index;
range(index, {{}, N + 1});
if (_isValid) {
if (index)
v = expectedValues[index-1];
else
value<VSIZE>(v);
}
return *this;
};
template<typename T, size_t N>
Deserializer& substitution(T& v, const std::array<T,N>& expectedValues) {
size_t index;
range(index, {{}, N + 1});
if (_isValid) {
if (index)
v = expectedValues[index-1];
else
object(v);
}
return *this;
};
/*
* text overloads
*/
template<size_t VSIZE, typename T>
void text(T &str, size_t maxSize) {
static_assert(details::TextTraits<T>::isResizable,
"use text(T&) overload without `maxSize` for static containers");
template <size_t VSIZE, typename T>
Deserializer& text(std::basic_string<T>& str, size_t maxSize) {
size_t size;
details::readSize(_reader, size, maxSize);
details::TextTraits<T>::resize(str, size);
auto begin = std::begin(str);
auto end = std::next(begin, size);
procContainer<VSIZE>(begin, end, std::true_type{});
//null terminated character at the end
*end = {};
}
template<size_t VSIZE, typename T>
void text(T &str) {
static_assert(!details::TextTraits<T>::isResizable,
"use text(T&, size_t) overload with `maxSize` for dynamic containers");
size_t size;
auto begin = std::begin(str);
auto containerEnd = std::end(str);
assert(begin != containerEnd);
details::readSize(_reader, size, static_cast<size_t>(std::distance(begin, containerEnd) - 1));
//end of string, not en
auto end = std::next(begin, size);
procContainer<VSIZE>(std::begin(str), std::end(str), std::true_type{});
//null terminated character at the end
*end = {};
readSize(size, maxSize);
if (_isValid) {
str.resize(size);
procContainer<VSIZE>(std::begin(str), std::end(str), std::true_type{});
}
return *this;
}
template<size_t VSIZE, typename T, size_t N>
void text(T (&str)[N]) {
Deserializer& text(T (&str)[N]) {
size_t size;
details::readSize(_reader, size, N - 1);
auto first = std::begin(str);
procContainer<VSIZE>(first, std::next(first, size), std::true_type{});
//null-terminated string
str[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] = {};
}
return *this;
}
/*
* container overloads
*/
//dynamic size containers
template<typename T, typename Fnc>
void container(T &obj, size_t maxSize, Fnc &&fnc) {
static_assert(details::ContainerTraits<T>::isResizable,
"use container(T&) overload without `maxSize` for static containers");
size_t size{};
details::readSize(_reader, size, maxSize);
details::ContainerTraits<T>::resize(obj, size);
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
template <typename T, typename Fnc>
Deserializer& container(T&& obj, size_t maxSize, Fnc&& fnc) {
decltype(obj.size()) size{};
readSize(size, maxSize);
if (_isValid) {
obj.resize(size);
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
}
return *this;
}
template<size_t VSIZE, typename T>
void container(T &obj, size_t maxSize) {
static_assert(details::ContainerTraits<T>::isResizable,
"use container(T&) overload without `maxSize` for static containers");
size_t size{};
details::readSize(_reader, size, maxSize);
details::ContainerTraits<T>::resize(obj, size);
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
template <size_t VSIZE, typename T>
Deserializer& container(T& obj, size_t maxSize) {
decltype(obj.size()) size{};
readSize(size, maxSize);
if (_isValid) {
obj.resize(size);
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
}
return *this;
}
template<typename T>
void container(T &obj, size_t maxSize) {
static_assert(details::ContainerTraits<T>::isResizable,
"use container(T&) overload without `maxSize` for static containers");
size_t size{};
details::readSize(_reader, size, maxSize);
details::ContainerTraits<T>::resize(obj, 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::ContainerTraits<T>::isResizable,
"use container(T&, size_t, Fnc) overload with `maxSize` for dynamic containers");
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
template <typename T>
Deserializer& container(T& obj, size_t maxSize) {
decltype(obj.size()) size{};
readSize(size, maxSize);
if (_isValid) {
obj.resize(size);
procContainer(std::begin(obj), std::end(obj));
}
return *this;
}
template<size_t VSIZE, typename T>
void container(T &obj) {
static_assert(!details::ContainerTraits<T>::isResizable,
"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{});
/*
* array overloads (fixed size array (std::array, and c-style array))
*/
//std::array overloads
template<typename T, size_t N, typename Fnc>
Deserializer& array(std::array<T,N> &arr, Fnc && fnc) {
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
return *this;
}
template<typename T>
void container(T &obj) {
static_assert(!details::ContainerTraits<T>::isResizable,
"use container(T&, size_t) overload with `maxSize` for dynamic containers");
procContainer(std::begin(obj), std::end(obj));
template<size_t VSIZE, typename T, size_t N>
Deserializer& array(std::array<T,N> &arr) {
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
return *this;
}
template<typename T, size_t N>
Deserializer& array(std::array<T,N> &arr) {
procContainer(std::begin(arr), std::end(arr));
return *this;
}
//c-style array overloads
template<typename T, size_t N, typename Fnc>
void container(T (&arr)[N], Fnc &&fnc) {
Deserializer& array(T (&arr)[N], Fnc&& fnc) {
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
return *this;
}
template<size_t VSIZE, typename T, size_t N>
void container(T (&arr)[N]) {
Deserializer& array(T (&arr)[N]) {
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
return *this;
}
template<typename T, size_t N>
void container(T (&arr)[N]) {
Deserializer& array(T (&arr)[N]) {
procContainer(std::begin(arr), std::end(arr));
return *this;
}
bool isValid() const {
return _isValid;
}
void align() {
Deserializer& align() {
_reader.align();
return *this;
}
//overloads for functions with explicit type size
template<typename T> Deserializer& value1(T &&v) { return value<1>(std::forward<T>(v)); }
template<typename T> Deserializer& value2(T &&v) { return value<2>(std::forward<T>(v)); }
template<typename T> Deserializer& value4(T &&v) { return value<4>(std::forward<T>(v)); }
template<typename T> Deserializer& value8(T &&v) { return value<8>(std::forward<T>(v)); }
template<typename T>
void value1b(T &&v) { value<1>(std::forward<T>(v)); }
template<typename T, size_t N> Deserializer& substitution1
(T &v, const std::array<T, N> &expectedValues) { return substitution<1>(v, expectedValues); };
template<typename T, size_t N> Deserializer& substitution2
(T &v, const std::array<T, N> &expectedValues) { return substitution<2>(v, expectedValues); };
template<typename T, size_t N> Deserializer& substitution4
(T &v, const std::array<T, N> &expectedValues) { return substitution<4>(v, expectedValues); };
template<typename T, size_t N> Deserializer& substitution8
(T &v, const std::array<T, N> &expectedValues) { return substitution<8>(v, expectedValues); };
template<typename T>
void value2b(T &&v) { value<2>(std::forward<T>(v)); }
template<typename T> Deserializer& text1(std::basic_string<T> &str, size_t maxSize) {
return text<1>(str, maxSize); }
template<typename T> Deserializer& text2(std::basic_string<T> &str, size_t maxSize) {
return text<2>(str, maxSize); }
template<typename T> Deserializer& text4(std::basic_string<T> &str, size_t maxSize) {
return text<4>(str, maxSize); }
template<typename T>
void value4b(T &&v) { value<4>(std::forward<T>(v)); }
template<typename T, size_t N> Deserializer& text1(T (&str)[N]) { return text<1>(str); }
template<typename T, size_t N> Deserializer& text2(T (&str)[N]) { return text<2>(str); }
template<typename T, size_t N> Deserializer& text4(T (&str)[N]) { return text<4>(str); }
template<typename T>
void value8b(T &&v) { value<8>(std::forward<T>(v)); }
template<typename T, typename Ext>
void ext1b(T &v, Ext &&extension) { ext<1, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T, typename Ext>
void ext2b(T &v, Ext &&extension) { ext<2, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T, typename Ext>
void ext4b(T &v, Ext &&extension) { ext<4, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T, typename Ext>
void ext8b(T &v, Ext &&extension) { ext<8, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T>
void text1b(T &str, size_t maxSize) { text<1>(str, maxSize); }
template<typename T>
void text2b(T &str, size_t maxSize) { text<2>(str, maxSize); }
template<typename T>
void text4b(T &str, size_t maxSize) { text<4>(str, maxSize); }
template<typename T, size_t N>
void text1b(T (&str)[N]) { text<1>(str); }
template<typename T, size_t N>
void text2b(T (&str)[N]) { text<2>(str); }
template<typename T, size_t N>
void text4b(T (&str)[N]) { text<4>(str); }
template<typename T>
void container1b(T &&obj, size_t maxSize) { container<1>(std::forward<T>(obj), maxSize); }
template<typename T>
void container2b(T &&obj, size_t maxSize) { container<2>(std::forward<T>(obj), maxSize); }
template<typename T>
void container4b(T &&obj, size_t maxSize) { container<4>(std::forward<T>(obj), maxSize); }
template<typename T>
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 container1b(T (&arr)[N]) { container<1>(arr); }
template<typename T, size_t N>
void container2b(T (&arr)[N]) { container<2>(arr); }
template<typename T, size_t N>
void container4b(T (&arr)[N]) { container<4>(arr); }
template<typename T, size_t N>
void container8b(T (&arr)[N]) { container<8>(arr); }
template<typename T> Deserializer& container1(T &&obj, size_t maxSize) {
return container<1>(std::forward<T>(obj), maxSize); }
template<typename T> Deserializer& container2(T &&obj, size_t maxSize) {
return container<2>(std::forward<T>(obj), maxSize); }
template<typename T> Deserializer& container4(T &&obj, size_t maxSize) {
return container<4>(std::forward<T>(obj), maxSize); }
template<typename T> Deserializer& container8(T &&obj, size_t maxSize) {
return container<8>(std::forward<T>(obj), maxSize); }
private:
BasicBufferReader<Config> &_reader;
void* _context;
Reader& _reader;
bool _isValid;
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;
}
}
//process value types
//false_type means that we must process all elements individually
template<size_t VSIZE, typename It>
void procContainer(It first, It last, std::false_type) {
for (; first != last; ++first)
for (;_isValid && first != last; ++first)
value<VSIZE>(*first);
};
@@ -351,38 +353,26 @@ 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 (first != last)
_reader.template readBuffer<VSIZE>(&(*first), std::distance(first, last));
if (_isValid && first != last)
_isValid = _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 (; first != last; ++first)
fnc(*first);
for (;_isValid && first != last; ++first)
fnc(*this, *first);
};
//process object types
template<typename It>
void procContainer(It first, It last) {
for (; first != last; ++first)
for (;_isValid && first != last; ++first)
object(*first);
};
//these are dummy functions for extensions that have TValue = void
void object(details::DummyType&) {
}
template <size_t VSIZE>
void value(details::DummyType&) {
}
};
//helper type
using Deserializer = BasicDeserializer<DefaultConfig>;
}

View File

@@ -1,84 +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_DETAILS_BOTH_COMMON_H
#define BITSERY_DETAILS_BOTH_COMMON_H
#include <cstdint>
#include <cstddef>
namespace bitsery {
enum class BufferReaderError {
NO_ERROR,
BUFFER_OVERFLOW,
INVALID_BUFFER_DATA
};
namespace details {
/*
* size read/write functions
*/
template <typename Reader>
void readSize(Reader& r, size_t& size, size_t maxSize) {
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;
}
}
if (size > maxSize) {
r.setError(BufferReaderError::INVALID_BUFFER_DATA);
size = {};
}
}
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,41 +20,28 @@
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_DETAILS_BUFFER_COMMON_H
#define BITSERY_DETAILS_BUFFER_COMMON_H
#include <algorithm>
#include <utility>
#include <cassert>
#include <vector>
#include <stack>
#include <cstring>
#include "both_common.h"
#include "traits.h"
#include "../common.h"
#ifndef BITSERY_BUFFER_COMMON_H
#define BITSERY_BUFFER_COMMON_H
namespace bitsery {
template<typename I>
struct BufferRange : std::pair<I, I> {
using std::pair<I, I>::pair;
I begin() const { return this->first; }
I end() const { return this->second; }
/*
* endianess
*/
enum class EndiannessType {
LittleEndian,
BigEndian
};
namespace details {
template<typename T>
struct BITS_SIZE:public std::integral_constant<size_t, sizeof(T) << 3> {
};
constexpr size_t BITS_SIZE = sizeof(T) << 3;
//add swap functions to class, to avoid compilation warning about unused functions
struct SwapImpl {
struct swapImpl {
static uint64_t exec(uint64_t value) {
#ifdef __GNUC__
return __builtin_bswap64(value);
return __builtin_bswap64( value );
#else
value = ( value & 0x00000000FFFFFFFF ) << 32 | ( value & 0xFFFFFFFF00000000 ) >> 32;
value = ( value & 0x0000FFFF0000FFFF ) << 16 | ( value & 0xFFFF0000FFFF0000 ) >> 16;
@@ -63,16 +50,18 @@ namespace bitsery {
#endif
}
static uint32_t exec(uint32_t value) {
static uint32_t exec(uint32_t value)
{
#ifdef __GNUC__
return __builtin_bswap32(value);
return __builtin_bswap32( value );
#else
return ( value & 0x000000ff ) << 24 | ( value & 0x0000ff00 ) << 8 | ( value & 0x00ff0000 ) >> 8 | ( value & 0xff000000 ) >> 24;
#endif
}
static uint16_t exec(uint16_t value) {
return (value & 0x00ff) << 8 | (value & 0xff00) >> 8;
static uint16_t exec(uint16_t value)
{
return ( value & 0x00ff ) << 8 | ( value & 0xff00 ) >> 8;
}
static uint8_t exec(uint8_t value) {
@@ -80,345 +69,28 @@ namespace bitsery {
}
};
template<typename TValue>
template <typename TValue>
TValue swap(TValue value) {
constexpr size_t TSize = sizeof(TValue);
using UT = typename std::conditional<TSize == 1, uint8_t,
typename std::conditional<TSize == 2, uint16_t,
typename std::conditional<TSize == 4, uint32_t, uint64_t>::type>::type>::type;
return SwapImpl::exec(static_cast<UT>(value));
typename std::conditional<TSize == 4, uint32_t , uint64_t>::type>::type>::type;
return swapImpl::exec(static_cast<UT>(value));
}
//add test data in separate struct, because some compilers only support constexpr functions with return-only body
struct EndiannessTestData {
static constexpr uint32_t _sample4Bytes = 0x01020304;
static constexpr uint8_t _sample1stByte = (const uint8_t &) _sample4Bytes;
static constexpr uint8_t _sample1stByte = (const uint8_t&)_sample4Bytes;
};
constexpr EndiannessType getSystemEndianness() {
static_assert(EndiannessTestData::_sample1stByte == 0x04 || EndiannessTestData::_sample1stByte == 0x01,
"system must be either little or big endian");
return EndiannessTestData::_sample1stByte == 0x04 ? EndiannessType::LittleEndian
: EndiannessType::BigEndian;
static_assert(EndiannessTestData::_sample1stByte == 0x04 || EndiannessTestData::_sample1stByte == 0x01, "system must be either little or big endian");
return EndiannessTestData::_sample1stByte == 0x04 ? EndiannessType::LittleEndian : EndiannessType::BigEndian;
}
template<typename T>
struct SCRATCH_TYPE {
};
template<>
struct SCRATCH_TYPE<uint8_t> {
using type = uint16_t;
};
template<>
struct SCRATCH_TYPE<uint16_t> {
using type = uint32_t;
};
template<>
struct SCRATCH_TYPE<uint32_t> {
using type = uint64_t;
};
template <typename Config>
struct DisabledBufferSessionsWriter {
template <typename TWriter>
void begin(TWriter& ) {
static_assert(Config::BufferSessionsEnabled, "Buffer sessions is disabled, enable it via configuration");
}
template <typename TWriter>
void end(TWriter& ) {
static_assert(Config::BufferSessionsEnabled, "Buffer sessions is disabled, enable it via configuration");
}
template <typename TWriter>
void flushSessions(TWriter& ) {
}
};
template <typename Config>
struct DisabledBufferSessionsReader {
template <typename TReader, typename TIterator>
DisabledBufferSessionsReader(TReader& , TIterator& , TIterator& ) {
}
void begin() {
static_assert(Config::BufferSessionsEnabled, "Buffer sessions is disabled, enable it via configuration");
}
void end() {
static_assert(Config::BufferSessionsEnabled, "Buffer sessions is disabled, enable it via configuration");
}
bool hasActiveSessions() const {
return false;
}
};
template <typename TWriter>
class BufferSessionsWriter {
public:
void begin(TWriter& ) {
//write position
_sessionIndex.push(_sessions.size());
_sessions.emplace_back(0);
}
void end(TWriter& writer) {
assert(!_sessionIndex.empty());
//change position to session end
auto sessionIt = std::next(std::begin(_sessions), _sessionIndex.top());
_sessionIndex.pop();
auto range = writer.getWrittenRange();
*sessionIt = static_cast<size_t>(std::distance(range.begin(), range.end()));
}
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 {
BufferSessionsReader(TReader& r, TIterator& begin, TIterator& end)
:_reader{r},
_begin{begin},
_pos{begin},
_end{end}
{
}
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(_begin, *_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(_begin, _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 _begin;
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;
//read size
if (std::distance(_pos, _end) < 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) {
endSessionsSizesIt = std::next(endSessionsSizesIt, -2);
_pos = endSessionsSizesIt;
if (std::distance(_begin, _pos) < 0) {
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
return;
}
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;
auto bufferSize = std::distance(_begin, _end);
if (static_cast<size_t>(bufferSize) < 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) {
size_t size;
details::readSize(_reader, size, bufferSize);
*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;
}
};
//this class writes bytes and bits to underlying buffer, it has specializations for resizable and non-resizable buffers
template<typename Buffer, bool isResizable>
class WriteBufferContext {
};
template<typename Buffer>
class WriteBufferContext<Buffer, false> {
public:
using TValue = typename BufferContainerTraits<Buffer>::TValue;
using TIterator = typename BufferContainerTraits<Buffer>::TIterator;
using TDifference = typename BufferContainerTraits<Buffer>::TDifference;
explicit WriteBufferContext(Buffer &buffer)
: _buffer{buffer},
_outIt{std::addressof(*std::begin(buffer))},
_end{std::addressof(*std::end(buffer))}
{
}
void write(const TValue *data, size_t size) {
assert(std::distance(_outIt, _end) >= static_cast<TDifference>(size));
memcpy(_outIt, data, size);
_outIt += size;
}
BufferRange<TIterator> getWrittenRange() const {
auto begin = std::begin(_buffer);
return BufferRange<TIterator>{begin, std::next(begin, _outIt - std::addressof(*begin))};
}
private:
Buffer &_buffer;
TValue* _outIt;
TValue* _end;
};
template<typename Buffer>
class WriteBufferContext<Buffer, true> {
public:
using TValue = typename BufferContainerTraits<Buffer>::TValue;
using TIterator = typename BufferContainerTraits<Buffer>::TIterator;
using TDifference = typename BufferContainerTraits<Buffer>::TDifference;
explicit WriteBufferContext(Buffer &buffer)
: _buffer{buffer}
{
getIterators(0);
}
void write(const TValue *data, size_t size) {
if ((_end - _outIt) >= static_cast< TDifference >(size)) {
std::memcpy(_outIt, data, size);
_outIt += size;
} else {
//get current position before invalidating iterators
auto pos = std::distance(std::addressof(*std::begin(_buffer)), _outIt);
//increase container size
BufferContainerTraits<Buffer>::increaseBufferSize(_buffer);
//restore iterators
getIterators(pos);
write(data, size);
}
}
BufferRange<TIterator> getWrittenRange() const {
auto begin = std::begin(_buffer);
return BufferRange<TIterator>{begin, std::next(begin, _outIt - std::addressof(*begin))};
}
private:
void getIterators(TDifference writePos) {
_end = std::addressof(*std::end(_buffer));
_outIt = std::addressof(*std::next(std::begin(_buffer), writePos));
}
Buffer &_buffer;
TValue* _outIt;
TValue* _end;
};
}
}
#endif //BITSERY_DETAILS_BUFFER_COMMON_H
#endif //BITSERY_BUFFER_COMMON_H

View File

@@ -20,11 +20,12 @@
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_DETAILS_SERIALIZATION_COMMON_H
#define BITSERY_DETAILS_SERIALIZATION_COMMON_H
#ifndef BITSERY_SERIALIZATION_COMMON_H
#define BITSERY_SERIALIZATION_COMMON_H
#include <cstdint>
#include <type_traits>
#include "both_common.h"
#include <array>
namespace bitsery {
@@ -32,52 +33,169 @@ namespace bitsery {
template<typename T, typename Enable = void>
struct SAME_SIZE_UNSIGNED_TYPE {
using type = typename std::make_unsigned<T>::type;
typedef std::make_unsigned_t<T> type;
};
template<typename T>
struct SAME_SIZE_UNSIGNED_TYPE<T, typename std::enable_if<std::is_enum<T>::value>::type> {
using type = typename std::make_unsigned<typename std::underlying_type<T>::type>::type;
typedef std::make_unsigned_t<std::underlying_type_t<T>> type;
};
template<typename T>
struct SAME_SIZE_UNSIGNED_TYPE<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
using type = typename std::conditional<std::is_same<T, float>::value, uint32_t, uint64_t>::type;
typedef std::conditional_t<std::is_same<T, float>::value, uint32_t, uint64_t> type;
};
template<typename T>
using SAME_SIZE_UNSIGNED = typename SAME_SIZE_UNSIGNED_TYPE<T>::type;
template <typename T>
constexpr size_t getSize(T v, size_t s) {
return v > 0 ? getSize(v / 2, s + 1) : s;
}
template<typename T>
constexpr size_t calcRequiredBits(T min, T max) {
//call recursive function, because some compilers only support constexpr functions with return-only body
return getSize(max - min, 0);
}
}
/*
* functions for object serialization
* range functions in bitsery namespace because these are used by user
*/
template<typename S, typename T, typename Enabled = void>
struct SerializeFunction {
static void invoke(S &s, T &v) {
static_assert(!std::is_void<Enabled>::value,
"\nPlease define 'serialize' function for your type:\n"
" template<typename S>\n"
" void serialize(S& s, <YourType>& o)\n"
" {\n"
" ...\n"
" }\n");
}
template<typename T, typename Enable = void>
struct RangeSpec {
constexpr RangeSpec(T minValue, T maxValue)
: min{minValue},
max{maxValue},
bitsRequired{details::calcRequiredBits(min, max)} {
}
const T min;
const T max;
const size_t bitsRequired;
};
template<typename T>
struct RangeSpec<T, typename std::enable_if<std::is_enum<T>::value>::type> {
constexpr RangeSpec(T minValue, T maxValue) :
min{minValue},
max{maxValue},
bitsRequired{details::calcRequiredBits(
static_cast<std::underlying_type_t<T>>(min),
static_cast<std::underlying_type_t<T>>(max))} {
}
const T min;
const T max;
const size_t bitsRequired;
};
//this class is used to make default RangeSpec float specialization always prefer constructor with precision
struct BitsConstraint {
explicit constexpr BitsConstraint(size_t bits) : value{bits} {}
const size_t value;
};
template<typename T>
struct RangeSpec<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
constexpr RangeSpec(T minValue, T maxValue, BitsConstraint bits) :
min{minValue},
max{maxValue},
bitsRequired{bits.value} {
}
constexpr RangeSpec(T minValue, T maxValue, T precision) :
min{minValue},
max{maxValue},
bitsRequired{details::calcRequiredBits<details::SAME_SIZE_UNSIGNED<T>>({}, ((max - min) / precision))} {
}
const T min;
const T max;
const size_t bitsRequired;
};
namespace details {
/*
* functions for range
*/
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
auto getRangeValue(const T &v, const RangeSpec<T> &r) {
return static_cast<SAME_SIZE_UNSIGNED<T>>(v - r.min);
};
template<typename S, typename T>
struct SerializeFunction<S, T, typename std::enable_if<
std::is_same<void, decltype(serialize(std::declval<S &>(), std::declval<T &>()))>::value
>::type> {
static void invoke(S &s, T &v) {
serialize(s, v);
}
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);
};
//used for extensions, when extension TValue = void
struct DummyType {
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
auto getRangeValue(const T &v, const RangeSpec<T> &r) {
using VT = SAME_SIZE_UNSIGNED<T>;
const VT maxUint = (static_cast<VT>(1) << r.bitsRequired) - 1;
const auto ratio = (v - r.min) / (r.max - r.min);
return static_cast<VT>(ratio * maxUint);
};
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void setRangeValue(T& v, const RangeSpec<T>& r) {
v += r.min;
};
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
void setRangeValue(T& v, const RangeSpec<T>& r) {
using VT = std::underlying_type_t<T>;
reinterpret_cast<VT&>(v) += static_cast<VT>(r.min);
};
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void setRangeValue(T& v, const RangeSpec<T>& r) {
using UIT = SAME_SIZE_UNSIGNED<T>;
const auto intRep = reinterpret_cast<UIT&>(v);
const UIT maxUint = (static_cast<UIT>(1) << r.bitsRequired) - 1;
v = r.min + (static_cast<T>(intRep) / maxUint) * (r.max - r.min);
};
template<typename T, typename std::enable_if<std::is_arithmetic<T>::value>::type * = nullptr>
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
return !(r.min > v || v > r.max);
}
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
using VT = std::underlying_type_t<T>;
return !(static_cast<VT>(r.min) > static_cast<VT>(v)
|| static_cast<VT>(v) > static_cast<VT>(r.max));
}
/*
* functions for substitution
*/
template<typename T, size_t N>
size_t findSubstitutionIndex(const T &v, const std::array<T, N> &defValues) {
auto index{1u};
for (auto &d:defValues) {
if (d == v)
return index;
++index;
}
return 0u;
};
/*
* delta functions
*/
@@ -87,8 +205,7 @@ namespace bitsery {
template<typename T>
ObjectMemoryPosition(const T &oldObj, const T &newObj)
:ObjectMemoryPosition{reinterpret_cast<const char *>(&oldObj),
reinterpret_cast<const char *>(&newObj),
:ObjectMemoryPosition{reinterpret_cast<const char *>(&oldObj), reinterpret_cast<const char *>(&newObj),
sizeof(T)} {
}
@@ -119,4 +236,4 @@ namespace bitsery {
}
#endif //BITSERY_DETAILS_SERIALIZATION_COMMON_H
#endif //BITSERY_SERIALIZATION_COMMON_H

View File

@@ -1,165 +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_DETAILS_TRAITS_H
#define BITSERY_DETAILS_TRAITS_H
#include <type_traits>
#include <string>
namespace bitsery {
namespace details {
/*
* helper traits that is used internaly, or by other traits
*/
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 {};
/*
* core library traits, used to extend library for custom types
*/
//traits for extension
template<typename Extension, typename T>
struct ExtensionTraits {
//this type is used, when using extesion without custom lambda
// eg.: extension4b>(obj, myextension{}) will call s.value4b(obj) for TValue
// or extesion(obj, myextension{}) will call s.object(obj) for TValue
//when this is void, it will compile, but value and object overloads will do nothing.
using TValue = void;
//does extension support ext<N>(...) syntax, by calling value<N> with TValue
static constexpr bool SupportValueOverload = true;
//does extension support ext(...) syntax, by calling object with TValue
static constexpr bool SupportObjectOverload = true;
//does extension support ext(..., lambda)
static constexpr bool SupportLambdaOverload = true;
};
//primary traits for containers
template<typename T>
struct ContainerTraits {
using TValue = typename T::value_type;
//default behaviour is resizable if container has method T::resize(size_t)
static constexpr bool isResizable = IsResizable<T>::value;
//resize function, called only if container is resizable
static void resize(T& container, size_t size) {
container.resize(size);
}
//get container size
static size_t size(const T& container) {
return container.size();
}
};
//specialization for C style array
template<typename T, size_t N>
struct ContainerTraits<T[N]> {
using TValue = T;
static constexpr bool isResizable = IsResizable<T>::value;
static void resize(T (&container)[N], size_t size) {
}
static size_t size(const T (&container)[N]) {
return N;
}
};
//traits for text
template<typename T>
struct TextTraits {
static constexpr bool isResizable = true;
//resize is without null-terminated character as with std::string,
//but null terminated character will always be written
//if you container doesn't add null-terminated character automaticaly, resize it to size+1;
static void resize(T& container, size_t size) {
container.resize(size);
}
//used for serialization to get text length
//length is until null-terminated character, size and length might not be equal
static size_t length(const T& container) {
auto begin = std::begin(container);
using TValue = typename std::decay<decltype(*begin)>::type;
return std::char_traits<TValue>::length(std::addressof(*begin));
}
};
//text traits specialization for std::string
//for std::string return length as size(), for faster performance, so we don't need to traverse string to find null-terminated characeter
//although it is not correct behaviour, meaning that string might have null-terminated characters in the middle,
//but in this case it your decision if you store buffer in string and serialize it as a text.
template<typename ... Args>
struct TextTraits<std::basic_string<Args...>> {
static constexpr bool isResizable = true;
//resize is without null-terminated character as with std::string,
//but null terminated character will always be written
//if you container doesn't add null-terminated character automaticaly, resize it to size+1;
static void resize(std::basic_string<Args...>& container, size_t size) {
container.resize(size);
}
//used for serialization to get text length
//length is until null-terminated character, size and length might not be equal
static size_t length(const std::basic_string<Args...>& container) {
return container.size();
}
};
//traits only for buffer reader/writer
template <typename T>
struct BufferContainerTraits: public ContainerTraits<T> {
//this function is only used by BufferWriter, when writing data to buffer,
//it is called only current buffer size is not enough to write.
//it is used to dramaticaly improve performance by updating buffer directly
//instead of using back_insert_iterator to append each byte to buffer.
//thats why BufferWriter return range iterators
static void increaseBufferSize(T& container) {
//use default implementation behaviour;
//call push_back to use default resize strategy
container.push_back({});
//after allocation resize to take all capacity
container.resize(container.capacity());
}
using TDifference = typename T::difference_type;
using TIterator = typename T::iterator;
};
}
}
#endif //BITSERY_DETAILS_TRAITS_H

View File

@@ -1,81 +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_EXT_CONTAINER_MAP_H
#define BITSERY_EXT_CONTAINER_MAP_H
namespace bitsery {
namespace ext {
class ContainerMap {
public:
constexpr explicit ContainerMap(size_t maxSize):_maxSize{maxSize} {}
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &, Writer &writer, const T &obj, Fnc &&fnc) const {
using TKey = typename T::key_type;
using TValue = typename T::mapped_type;
auto size = obj.size();
assert(size <= _maxSize);
details::writeSize(writer, size);
for (auto &v:obj)
fnc(const_cast<TKey &>(v.first), const_cast<TValue &>(v.second));
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &, Reader &reader, T &obj, Fnc &&fnc) const {
using TKey = typename T::key_type;
using TValue = typename T::mapped_type;
size_t size{};
details::readSize(reader, size, _maxSize);
auto hint = obj.begin();
obj.clear();
for (auto i = 0u; i < size; ++i) {
TKey key;
TValue value;
fnc(key, value);
hint = obj.emplace_hint(hint, std::move(key), std::move(value));
}
}
private:
size_t _maxSize;
};
}
namespace details {
template<typename T>
struct ExtensionTraits<ext::ContainerMap, T> {
using TValue = void;
static constexpr bool SupportValueOverload = false;
static constexpr bool SupportObjectOverload = false;
static constexpr bool SupportLambdaOverload = true;
};
}
}
#endif //BITSERY_EXT_CONTAINER_MAP_H

View File

@@ -1,91 +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_EXT_ENTROPY_H
#define BITSERY_EXT_ENTROPY_H
#include "value_range.h"
namespace bitsery {
namespace details {
template<typename TValue, typename TContainer>
size_t findEntropyIndex(const TValue &v, const TContainer &defValues) {
size_t index{1u};
for (auto &d:defValues) {
if (d == v)
return index;
++index;
}
return 0u;
};
}
namespace ext {
template<typename TContainer>
class Entropy {
public:
constexpr explicit Entropy(TContainer& values) : _values{values} {
};
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &s, Writer &writer, const T &obj, Fnc &&fnc) const {
assert(details::ContainerTraits<TContainer>::size(_values) > 0);
auto index = details::findEntropyIndex(obj, _values);
s.ext(index, ext::ValueRange<size_t>{0u, details::ContainerTraits<TContainer>::size(_values)});
if (!index)
fnc(const_cast<T &>(obj));
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &d, Reader &reader, T &obj, Fnc &&fnc) const {
assert(details::ContainerTraits<TContainer>::size(_values) > 0);
size_t index{};
d.ext(index, ext::ValueRange<size_t>{0u, details::ContainerTraits<TContainer>::size(_values)});
if (index)
obj = *std::next(std::begin(_values), index-1);
else
fnc(obj);
}
private:
TContainer& _values;
};
}
namespace details {
template<typename TContainer, typename T>
struct ExtensionTraits<ext::Entropy<TContainer>, T> {
using TValue = T;
static constexpr bool SupportValueOverload = true;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = true;
};
}
}
#endif //BITSERY_EXT_ENTROPY_H

View File

@@ -1,65 +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_EXT_GROWABLE_H
#define BITSERY_EXT_GROWABLE_H
namespace bitsery {
namespace ext {
/*
* enables to add additional serialization methods at the end of method, without breaking existing older code
*/
class Growable {
public:
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &s, Writer &writer, const T &obj, Fnc &&fnc) const {
writer.beginSession();
fnc(const_cast<T&>(obj));
writer.endSession();
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &d, Reader &reader, T &obj, Fnc &&fnc) const {
reader.beginSession();
fnc(obj);
reader.endSession();
}
};
}
namespace details {
template<typename T>
struct ExtensionTraits<ext::Growable, T> {
using TValue = T;
static constexpr bool SupportValueOverload = false;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = true;
};
}
}
#endif //BITSERY_EXT_GROWABLE_H

View File

@@ -24,68 +24,43 @@
#ifndef BITSERY_EXT_OPTIONAL_H
#define BITSERY_EXT_OPTIONAL_H
//this module do not include optional, but expects it to be declared in std::optional
//if you're using experimental optional from <experimental/optional>
//add it in std namespace like this:
//namespace std {
// template <typename T>
// using optional = experimental::optional<T>;
//}
#include <type_traits>
namespace bitsery {
namespace ext {
template<typename T>
template <typename T>
using std_optional = ::std::optional<T>;
class Optional {
template <typename T>
class optional {
public:
template<typename T>
constexpr void assertType() const {
explicit optional(T& v):_value{v} {
using TOpt = typename std::remove_cv<T>::type;
using TVal = typename TOpt::value_type;
static_assert(std::is_same<TOpt, std_optional<TVal>>(), "");
static_assert(std::is_default_constructible<TVal>::value, "");
};
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(const_cast<typename T::value_type & >(*obj));
template <typename TSerializer, typename Fnc>
void serialize(TSerializer& ser, const Fnc& fnc) {
ser.boolByte(static_cast<bool>(_value));
if (_value)
fnc(ser, *_value);
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &des, Reader &, T &obj, Fnc &&fnc) const {
assertType<T>();
template <typename TSerializer, typename Fnc>
void deserialize(TSerializer& ser, const Fnc& fnc) {
bool exists{};
des.boolByte(exists);
ser.boolByte(exists);
if (exists) {
typename T::value_type tmp{};
fnc(tmp);
obj = tmp;
fnc(ser, tmp);
_value = tmp;
} else {
//experimental optional doesnt have .reset method
obj = T{};
_value = T{};
}
}
private:
T& _value;
};
}
namespace details {
template<typename T>
struct ExtensionTraits<ext::Optional, T> {
using TValue = typename T::value_type;
static constexpr bool SupportValueOverload = true;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = true;
};
}
}

View File

@@ -1,206 +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_EXT_VALUE_RANGE_H
#define BITSERY_EXT_VALUE_RANGE_H
#include "../details/serialization_common.h"
#include "../details/buffer_common.h"
#include <cassert>
namespace bitsery {
namespace ext {
//this class is used to make default RangeSpec float specialization always prefer constructor with precision
struct BitsConstraint {
explicit constexpr BitsConstraint(size_t bits) : value{bits} {}
const size_t value;
};
}
//implementation details for range functionality
namespace details {
template<typename T>
constexpr size_t getSize(T v, size_t s) {
return v > 0 ? getSize(v / 2, s + 1) : s;
}
template<typename T>
constexpr size_t calcRequiredBits(T min, T max) {
//call recursive function, because some compilers only support constexpr functions with return-only body
return getSize(max - min, 0);
}
template<typename T, typename Enable = void>
struct RangeSpec {
constexpr RangeSpec(T minValue, T maxValue)
: min{minValue},
max{maxValue},
bitsRequired{calcRequiredBits(min, max)} {
}
const T min;
const T max;
const size_t bitsRequired;
};
template<typename T>
struct RangeSpec<T, typename std::enable_if<std::is_enum<T>::value>::type> {
constexpr RangeSpec(T minValue, T maxValue) :
min{minValue},
max{maxValue},
bitsRequired{calcRequiredBits(
static_cast<typename std::underlying_type<T>::type>(min),
static_cast<typename std::underlying_type<T>::type>(max))} {
}
const T min;
const T max;
const size_t bitsRequired;
};
template<typename T>
struct RangeSpec<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
constexpr RangeSpec(T minValue, T maxValue, ext::BitsConstraint bits) :
min{minValue},
max{maxValue},
bitsRequired{bits.value} {
}
constexpr RangeSpec(T minValue, T maxValue, T precision) :
min{minValue},
max{maxValue},
bitsRequired{calcRequiredBits<details::SAME_SIZE_UNSIGNED<T>>({}, ((max - min) / precision))} {
}
const T min;
const T max;
const size_t bitsRequired;
};
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
details::SAME_SIZE_UNSIGNED<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
return static_cast<details::SAME_SIZE_UNSIGNED<T>>(v - r.min);
};
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
details::SAME_SIZE_UNSIGNED<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
using VT = details::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>
details::SAME_SIZE_UNSIGNED<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
using VT = details::SAME_SIZE_UNSIGNED<T>;
const VT maxUint = (static_cast<VT>(1) << r.bitsRequired) - 1;
const auto ratio = (v - r.min) / (r.max - r.min);
return static_cast<VT>(ratio * maxUint);
};
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
void setRangeValue(T &v, const RangeSpec<T> &r) {
v += r.min;
};
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
void setRangeValue(T &v, const RangeSpec<T> &r) {
using VT = typename std::underlying_type<T>::type;
reinterpret_cast<VT &>(v) += static_cast<VT>(r.min);
};
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
void setRangeValue(T &v, const RangeSpec<T> &r) {
using UIT = details::SAME_SIZE_UNSIGNED<T>;
const auto intRep = reinterpret_cast<UIT &>(v);
const UIT maxUint = (static_cast<UIT>(1) << r.bitsRequired) - 1;
v = r.min + (static_cast<T>(intRep) / maxUint) * (r.max - r.min);
};
template<typename T, typename std::enable_if<std::is_arithmetic<T>::value>::type * = nullptr>
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
return !(r.min > v || v > r.max);
}
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
using VT = typename std::underlying_type<T>::type;
return !(static_cast<VT>(r.min) > static_cast<VT>(v)
|| static_cast<VT>(v) > static_cast<VT>(r.max));
}
}
namespace ext {
template<typename TValue>
class ValueRange {
public:
template<typename ... Args>
explicit constexpr ValueRange(Args &&... args):_range{std::forward<Args>(args)...} {};
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &, Writer &writer, const T &v, Fnc &&) const {
assert(details::isRangeValid(v, _range));
using BT = decltype(details::getRangeValue(v, _range));
writer.template writeBits<BT>(details::getRangeValue(v, _range), _range.bitsRequired);
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &, Reader &reader, T &v, Fnc &&) const {
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;
}
}
constexpr size_t getRequiredBits() const {
return _range.bitsRequired;
};
private:
details::RangeSpec<TValue> _range;
};
}
namespace details {
template<typename T>
struct ExtensionTraits<ext::ValueRange<T>, T> {
using TValue = void;
static constexpr bool SupportValueOverload = false;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = false;
};
}
}
#endif //BITSERY_EXT_VALUE_RANGE_H

View File

@@ -21,322 +21,284 @@
//SOFTWARE.
#ifndef BITSERY_SERIALIZER_H
#define BITSERY_SERIALIZER_H
#include "common.h"
#include <array>
#include "details/serialization_common.h"
#include <cassert>
namespace bitsery {
template<typename Config>
class BasicSerializer {
template<typename Writter>
class Serializer {
public:
explicit BasicSerializer(BasicBufferWriter<Config> &w, void* context = nullptr) : _writter{w}, _context{context} {};
Serializer(Writter &w) : _writter{w} {};
/*
* get serialization context.
* this is optional, but might be required for some specific serialization flows.
*/
void* getContext() {
return _context;
}
/*
* object function
*/
template<typename T>
void object(const T &obj) {
details::SerializeFunction<BasicSerializer, T>::invoke(*this, const_cast<T& >(obj));
Serializer& object(const T &obj) {
return serialize(*this, obj);
}
template<typename T, typename Fnc>
void object(const T &obj, Fnc &&fnc) {
fnc(const_cast<T& >(obj));
//in c++17 change "class" to typename
template <template <typename> class Extension, typename TValue, typename Fnc>
Serializer& ext(const TValue& v, Fnc&& fnc ) {
Extension<const TValue> ext{v};
ext.serialize(*this, std::forward<Fnc>(fnc));
return *this;
};
/*
* value overloads
*/
template<size_t VSIZE, typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
void value(const T &v) {
Serializer& value(const T &v) {
static_assert(std::numeric_limits<T>::is_iec559, "");
_writter.template writeBytes<VSIZE>(reinterpret_cast<const details::SAME_SIZE_UNSIGNED<T> &>(v));
return *this;
}
template<size_t VSIZE, typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
void value(const T &v) {
_writter.template writeBytes<VSIZE>(reinterpret_cast<const typename std::underlying_type<T>::type &>(v));
Serializer& value(const T &v) {
_writter.template writeBytes<VSIZE>(reinterpret_cast<const std::underlying_type_t<T> &>(v));
return *this;
}
template<size_t VSIZE, typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
void value(const T &v) {
Serializer& value(const T &v) {
_writter.template writeBytes<VSIZE>(v);
return *this;
}
/*
* extension functions
*/
template<typename T, typename Ext, typename Fnc>
void ext(const T &obj, Ext &&extension, Fnc &&fnc) {
using ExtType = typename std::decay<Ext>::type;
static_assert(details::ExtensionTraits<ExtType,T>::SupportLambdaOverload,
"extension doesn't support overload with lambda");
extension.serialize(*this, _writter, obj, std::forward<Fnc>(fnc));
};
template<size_t VSIZE, typename T, typename Ext>
void ext(const T &obj, Ext &&extension) {
using ExtType = typename std::decay<Ext>::type;
static_assert(details::ExtensionTraits<ExtType,T>::SupportValueOverload,
"extension doesn't support overload with `value<N>`");
using ExtVType = typename details::ExtensionTraits<ExtType, T>::TValue;
using VType = typename std::conditional<std::is_void<ExtVType>::value, details::DummyType, ExtVType>::type;
extension.serialize(*this, _writter, obj, [this](VType &v) { value<VSIZE>(v); });
};
template<typename T, typename Ext>
void ext(const T &obj, Ext &&extension) {
using ExtType = typename std::decay<Ext>::type;
static_assert(details::ExtensionTraits<ExtType,T>::SupportObjectOverload,
"extension doesn't support overload with `object`");
using ExtVType = typename details::ExtensionTraits<ExtType, T>::TValue;
using VType = typename std::conditional<std::is_void<ExtVType>::value, details::DummyType, ExtVType>::type;
extension.serialize(*this, _writter, obj, [this](VType &v) { object(v); });
};
/*
* bool
*/
void boolBit(bool v) {
Serializer& boolBit(bool v) {
_writter.writeBits(static_cast<unsigned char>(v ? 1 : 0), 1);
return *this;
}
void boolByte(bool v) {
Serializer& boolByte(bool v) {
_writter.template writeBytes<1>(static_cast<unsigned char>(v ? 1 : 0));
return *this;
}
/*
* range
*/
template<typename T>
Serializer& range(const T &v, const RangeSpec<T> &range) {
assert(details::isRangeValid(v, range));
_writter.template writeBits<decltype(details::getRangeValue(v, range))>(details::getRangeValue(v, range), range.bitsRequired);
return *this;
}
/*
* substitution overloads
*/
template<typename T, size_t N, typename Fnc>
Serializer& substitution(const T &v, const std::array<T, N> &expectedValues, Fnc &&fnc) {
auto index = details::findSubstitutionIndex(v, expectedValues);
range(index, {{}, N +1});
if (!index)
fnc(*this, v);
return *this;
};
template<size_t VSIZE, typename T, size_t N>
Serializer& substitution(const T &v, const std::array<T, N> &expectedValues) {
auto index = details::findSubstitutionIndex(v, expectedValues);
range(index, {{}, N +1});
if (!index)
value<VSIZE>(v);
return *this;
};
template<typename T, size_t N>
Serializer& substitution(const T &v, const std::array<T, N> &expectedValues) {
auto index = details::findSubstitutionIndex(v, expectedValues);
range(index, {{}, N +1});
if (!index)
object(v);
return *this;
};
/*
* text overloads
*/
template<size_t VSIZE, typename T>
void text(const T &str, size_t maxSize) {
static_assert(details::TextTraits<T>::isResizable,
"use text(const T&) overload without `maxSize` for static container");
auto size = details::TextTraits<T>::length(str);
//size can be equal to maxSize
assert(size <= maxSize);
details::writeSize(_writter, size);
auto begin = std::begin(str);
procContainer<VSIZE>(begin, std::next(begin, size), std::true_type{});
}
template<size_t VSIZE, typename T>
void text(const T &str) {
static_assert(!details::TextTraits<T>::isResizable,
"use text(const T&, size_t) overload with `maxSize` for dynamic containers");
auto size = details::TextTraits<T>::length(str);
auto begin = std::begin(str);
auto end = std::end(str);
//size must be less than container capacity, because we need to store null-terminated character
assert(size < std::distance(begin, end));
details::writeSize(_writter, size);
procContainer<VSIZE>(begin, std::next(begin, size), std::true_type{});
Serializer& text(const std::basic_string<T> &str, size_t maxSize) {
assert(str.size() <= maxSize);
auto first = std::begin(str);
auto last = std::end(str);
writeSize(std::distance(first, last));
procContainer<VSIZE>(first, last, std::true_type{});
return *this;
}
template<size_t VSIZE, typename T, size_t N>
void text(const T (&str)[N]) {
auto size = details::TextTraits<T[N]>::length(str);
assert(size < N);
details::writeSize(_writter, size);
auto begin = std::begin(str);
procContainer<VSIZE>(begin, std::next(begin, size), std::true_type{});
Serializer& text(const T (&str)[N]) {
auto first = std::begin(str);
auto last = std::next(first, std::min(std::char_traits<T>::length(str), N - 1));
writeSize(std::distance(first, last));
procContainer<VSIZE>(first, last, std::true_type{});
return *this;
}
/*
* container overloads
*/
//dynamic size containers
template<typename T, typename Fnc>
void container(const T &obj, size_t maxSize, Fnc &&fnc) {
static_assert(details::ContainerTraits<T>::isResizable,
"use container(const T&, Fnc) overload without `maxSize` for static containers");
auto size = details::ContainerTraits<T>::size(obj);
assert(size <= maxSize);
details::writeSize(_writter, size);
Serializer& container(const T &obj, size_t maxSize, Fnc &&fnc) {
assert(obj.size() <= maxSize);
writeSize(obj.size());
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
return *this;
}
template<size_t VSIZE, typename T>
void container(const T &obj, size_t maxSize) {
static_assert(details::ContainerTraits<T>::isResizable,
"use container(const T&) overload without `maxSize` for static containers");
Serializer& container(const T &obj, size_t maxSize) {
static_assert(VSIZE > 0, "");
auto size = details::ContainerTraits<T>::size(obj);
assert(size <= maxSize);
details::writeSize(_writter, size);
assert(obj.size() <= maxSize);
writeSize(obj.size());
//todo optimisation is possible for contigous containers, but currently there is no compile-time check for this
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
return *this;
}
template<typename T>
void container(const T &obj, size_t maxSize) {
static_assert(details::ContainerTraits<T>::isResizable,
"use container(const T&) overload without `maxSize` for static containers");
auto size = details::ContainerTraits<T>::size(obj);
assert(size <= maxSize);
details::writeSize(_writter, size);
Serializer& container(const T &obj, size_t maxSize) {
assert(obj.size() <= maxSize);
writeSize(obj.size());
procContainer(std::begin(obj), std::end(obj));
return *this;
}
//fixed size containers
/*
* array overloads (fixed size array (std::array, and c-style array))
*/
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::ContainerTraits<T>::isResizable,
"use container(const T&, size_t, Fnc) overload with `maxSize` for dynamic containers");
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
//std::array overloads
template<typename T, size_t N, typename Fnc>
Serializer& array(const std::array<T, N> &arr, Fnc &&fnc) {
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
return *this;
}
template<size_t VSIZE, typename T>
void container(const T &obj) {
static_assert(!details::ContainerTraits<T>::isResizable,
"use container(const T&, size_t) overload with `maxSize` for dynamic containers");
template<size_t VSIZE, typename T, size_t N>
Serializer& array(const std::array<T, N> &arr) {
static_assert(VSIZE > 0, "");
//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{});
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
return *this;
}
template<typename T>
void container(const T &obj) {
static_assert(!details::ContainerTraits<T>::isResizable,
"use container(const T&, size_t) overload with `maxSize` for dynamic containers");
procContainer(std::begin(obj), std::end(obj));
template<typename T, size_t N>
Serializer& array(const std::array<T, N> &arr) {
procContainer(std::begin(arr), std::end(arr));
return *this;
}
//c-style array overloads
template<typename T, size_t N, typename Fnc>
void container(const T (&arr)[N], Fnc &&fnc) {
Serializer& array(const T (&arr)[N], Fnc &&fnc) {
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
return *this;
}
template<size_t VSIZE, typename T, size_t N>
void container(const T (&arr)[N]) {
Serializer& array(const T (&arr)[N]) {
static_assert(VSIZE > 0, "");
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
return *this;
}
template<typename T, size_t N>
void container(const T (&arr)[N]) {
Serializer& array(const T (&arr)[N]) {
procContainer(std::begin(arr), std::end(arr));
return *this;
}
void align() {
Serializer& align() {
_writter.align();
return *this;
}
//overloads for functions with explicit type size
template<typename T> Serializer& value1(T &&v) { return value<1>(std::forward<T>(v)); }
template<typename T> Serializer& value2(T &&v) { return value<2>(std::forward<T>(v)); }
template<typename T> Serializer& value4(T &&v) { return value<4>(std::forward<T>(v)); }
template<typename T> Serializer& value8(T &&v) { return value<8>(std::forward<T>(v)); }
template<typename T>
void value1b(T &&v) { value<1>(std::forward<T>(v)); }
template<typename T, size_t N> Serializer& substitution1
(const T &v, const std::array<T, N> &expectedValues) { return substitution<1>(v, expectedValues); };
template<typename T, size_t N> Serializer& substitution2
(const T &v, const std::array<T, N> &expectedValues) { return substitution<2>(v, expectedValues); };
template<typename T, size_t N> Serializer& substitution4
(const T &v, const std::array<T, N> &expectedValues) { return substitution<4>(v, expectedValues); };
template<typename T, size_t N> Serializer& substitution8
(const T &v, const std::array<T, N> &expectedValues) { return substitution<8>(v, expectedValues); };
template<typename T>
void value2b(T &&v) { value<2>(std::forward<T>(v)); }
template<typename T> Serializer& text1(const std::basic_string<T> &str, size_t maxSize) {
return text<1>(str, maxSize); }
template<typename T> Serializer& text2(const std::basic_string<T> &str, size_t maxSize) {
return text<2>(str, maxSize); }
template<typename T> Serializer& text4(const std::basic_string<T> &str, size_t maxSize) {
return text<4>(str, maxSize); }
template<typename T>
void value4b(T &&v) { value<4>(std::forward<T>(v)); }
template<typename T, size_t N> Serializer& text1(const T (&str)[N]) { return text<1>(str); }
template<typename T, size_t N> Serializer& text2(const T (&str)[N]) { return text<2>(str); }
template<typename T, size_t N> Serializer& text4(const T (&str)[N]) { return text<4>(str); }
template<typename T>
void value8b(T &&v) { value<8>(std::forward<T>(v)); }
template<typename T> Serializer& container1(T &&obj, size_t maxSize) {
return container<1>(std::forward<T>(obj), maxSize); }
template<typename T> Serializer& container2(T &&obj, size_t maxSize) {
return container<2>(std::forward<T>(obj), maxSize); }
template<typename T> Serializer& container4(T &&obj, size_t maxSize) {
return container<4>(std::forward<T>(obj), maxSize); }
template<typename T> Serializer& container8(T &&obj, size_t maxSize) {
return container<8>(std::forward<T>(obj), maxSize); }
template<typename T, typename Ext>
void ext1b(const T &v, Ext &&extension) { ext<1, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T, size_t N> Serializer& array1(const std::array<T, N> &arr) { return array<1>(arr); }
template<typename T, size_t N> Serializer& array2(const std::array<T, N> &arr) { return array<2>(arr); }
template<typename T, size_t N> Serializer& array4(const std::array<T, N> &arr) { return array<4>(arr); }
template<typename T, size_t N> Serializer& array8(const std::array<T, N> &arr) { return array<8>(arr); }
template<typename T, typename Ext>
void ext2b(const T &v, Ext &&extension) { ext<2, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T, typename Ext>
void ext4b(const T &v, Ext &&extension) { ext<4, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T, typename Ext>
void ext8b(const T &v, Ext &&extension) { ext<8, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T>
void text1b(const T &str, size_t maxSize) { text<1>(str, maxSize); }
template<typename T>
void text2b(const T &str, size_t maxSize) { text<2>(str, maxSize); }
template<typename T>
void text4b(const T &str, size_t maxSize) { text<4>(str, maxSize); }
template<typename T, size_t N>
void text1b(const T (&str)[N]) { text<1, T, N>(str); }
template<typename T, size_t N>
void text2b(const T (&str)[N]) { text<2, T, N>(str); }
template<typename T, size_t N>
void text4b(const T (&str)[N]) { text<4, T, N>(str); }
template<typename T>
void container1b(T &&obj, size_t maxSize) { container<1>(std::forward<T>(obj), maxSize); }
template<typename T>
void container2b(T &&obj, size_t maxSize) { container<2>(std::forward<T>(obj), maxSize); }
template<typename T>
void container4b(T &&obj, size_t maxSize) { container<4>(std::forward<T>(obj), maxSize); }
template<typename T>
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 container1b(const T (&arr)[N]) { container<1>(arr); }
template<typename T, size_t N>
void container2b(const T (&arr)[N]) { container<2>(arr); }
template<typename T, size_t N>
void container4b(const T (&arr)[N]) { container<4>(arr); }
template<typename T, size_t N>
void container8b(const T (&arr)[N]) { container<8>(arr); }
template<typename T, size_t N> Serializer& array1(const T (&arr)[N]) { return array<1>(arr); }
template<typename T, size_t N> Serializer& array2(const T (&arr)[N]) { return array<2>(arr); }
template<typename T, size_t N> Serializer& array4(const T (&arr)[N]) { return array<4>(arr); }
template<typename T, size_t N> Serializer& array8(const T (&arr)[N]) { return array<8>(arr); }
private:
BasicBufferWriter<Config> &_writter;
void* _context;
Writter &_writter;
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);
}
}
//process value types
//false_type means that we must process all elements individually
template<size_t VSIZE, typename It>
void procContainer(It first, It last, std::false_type) {
for (; first != last; ++first)
for (;first != last; ++first)
value<VSIZE>(*first);
};
@@ -344,39 +306,25 @@ 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) {
_writter.template writeBuffer<VSIZE>(&(*first), std::distance(first, last));
if (first != last)
_writter.template writeBuffer<VSIZE>(&(*first), std::distance(first, last));
};
//process by calling functions
template<typename It, typename Fnc>
void procContainer(It first, It last, Fnc fnc) {
using TValue = typename std::decay<decltype(*first)>::type;
for (; first != last; ++first) {
fnc(const_cast<TValue&>(*first));
}
for (;first != last; ++first)
fnc(*this, *first);
};
//process object types
template<typename It>
void procContainer(It first, It last) {
for (; first != last; ++first)
for (;first != last; ++first)
object(*first);
};
//these are dummy functions for extensions that have TValue = void
void object(const details::DummyType&) {
}
template <size_t VSIZE>
void value(const details::DummyType&) {
}
};
//helper type
using Serializer = BasicSerializer<DefaultConfig>;
}
#endif //BITSERY_SERIALIZER_H

205
tests/buffer_bits_op.cpp Normal file
View File

@@ -0,0 +1,205 @@
//MIT License
//
//Copyright (c) 2017 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#include <gmock/gmock.h>
#include <bitsery/buffer_writer.h>
#include <bitsery/buffer_reader.h>
#include <bitsery/details/serialization_common.h>
using testing::Eq;
using testing::ContainerEq;
using bitsery::BufferWriter;
using bitsery::BufferReader;
using Buffer = std::vector<bitsery::DefaultConfig::BufferValueType>;
struct IntegralUnsignedTypes {
uint32_t a;
uint16_t b;
uint8_t c;
uint8_t d;
uint64_t e;
};
template <typename T>
constexpr size_t getBits(T v) {
return bitsery::details::calcRequiredBits<T>({}, v);
};
TEST(BufferBitsOperations, WriteAndReadBits) {
//setup data
constexpr IntegralUnsignedTypes data{
485454,//bits 19
45978,//bits 16
0,//bits 1
36,//bits 6
479845648946//bits 39
};
constexpr size_t aBITS = getBits(data.a) + 2;
constexpr size_t bBITS = getBits(data.b) + 0;
constexpr size_t cBITS = getBits(data.c) + 2;
constexpr size_t dBITS = getBits(data.d) + 1;
constexpr size_t eBITS = getBits(data.e) + 8;
//create and write to buffer
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(data.a, aBITS);
bw.writeBits(data.b, bBITS);
bw.writeBits(data.c, cBITS);
bw.writeBits(data.d, dBITS);
bw.writeBits(data.e, eBITS);
bw.flush();
auto bytesCount = ((aBITS + bBITS + cBITS + dBITS + eBITS) / 8) +1 ;
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(bytesCount));
//read from buffer
BufferReader br{buf};
IntegralUnsignedTypes res;
br.readBits(res.a, aBITS);
br.readBits(res.b, bBITS);
br.readBits(res.c, cBITS);
br.readBits(res.d, dBITS);
br.readBits(res.e, eBITS);
EXPECT_THAT(res.a, Eq(data.a));
EXPECT_THAT(res.b, Eq(data.b));
EXPECT_THAT(res.c, Eq(data.c));
EXPECT_THAT(res.d, Eq(data.d));
EXPECT_THAT(res.e, Eq(data.e));
}
TEST(BufferBitsOperations, WhenFinishedFlushWriter) {
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(3u, 2);
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(0));
bw.flush();
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
}
TEST(BufferBitsOperations, BufferSizeIsCountedPerByteNotPerBit) {
//setup data
//create and write to buffer
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(7u,3);
bw.flush();
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
//read from buffer
BufferReader br{buf};
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));
//part of next byte
BufferReader br1{buf};
EXPECT_THAT(br1.readBits(tmp,2), Eq(true));
EXPECT_THAT(br1.readBits(tmp,7), Eq(false));
//bigger than byte
BufferReader br2{buf};
EXPECT_THAT(br2.readBits(tmp,9), Eq(false));
}
TEST(BufferBitsOperations, ConsecutiveCallsToAlignHasNoEffect) {
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(3u, 2);
//3 calls to align after 1st data
bw.align();
bw.align();
bw.align();
bw.writeBits(7u, 3);
//1 call to align after 2nd data
bw.align();
bw.writeBits(15u, 4);
bw.flush();
unsigned char tmp;
BufferReader br{buf};
EXPECT_THAT(br.readBits(tmp,2), Eq(true));
EXPECT_THAT(tmp, Eq(3u));
EXPECT_THAT(br.align(), Eq(true));
EXPECT_THAT(br.readBits(tmp,3), Eq(true));
EXPECT_THAT(tmp, Eq(7u));
EXPECT_THAT(br.align(), Eq(true));
EXPECT_THAT(br.align(), Eq(true));
EXPECT_THAT(br.align(), Eq(true));
EXPECT_THAT(br.readBits(tmp,4), Eq(true));
EXPECT_THAT(tmp, Eq(15u));
}
TEST(BufferBitsOperations, WhenAlignedFlushHasNoEffect) {
//setup data
//create and write to buffer
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(3u, 2);
bw.align();
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
bw.flush();
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
}
TEST(BufferBitsOperations, AlignMustWriteZerosBits) {
//setup data
//create and write to buffer
Buffer buf;
BufferWriter bw{buf};
//write 2 bits and align
bw.writeBits(3u, 2);
bw.align();
unsigned char tmp;
BufferReader br1{buf};
br1.readBits(tmp,2);
//read aligned bits
EXPECT_THAT(br1.readBits(tmp,6), Eq(true));
EXPECT_THAT(tmp, Eq(0));
BufferReader br2{buf};
//read 2 bits
br2.readBits(tmp,2);
EXPECT_THAT(br2.align(), Eq(true));
}

274
tests/buffer_bytes_op.cpp Normal file
View File

@@ -0,0 +1,274 @@
//MIT License
//
//Copyright (c) 2017 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#include <gmock/gmock.h>
#include <bitsery/buffer_writer.h>
#include <bitsery/buffer_reader.h>
#include <list>
#include <bitset>
using testing::Eq;
using testing::ContainerEq;
using bitsery::BufferWriter;
using bitsery::BufferReader;
using Buffer = std::vector<bitsery::DefaultConfig::BufferValueType>;
struct IntegralTypes {
int64_t a;
uint32_t b;
int16_t c;
uint8_t d;
int8_t e;
int8_t f[2];
};
IntegralTypes getInitializedIntegralTypes() {
IntegralTypes data;
data.a = -4894541654564;
data.b = 94545646;
data.c = -8778;
data.d = 200;
data.e = -98;
data.f[0] = 43;
data.f[1] = -45;
return data;
}
void writeIntegralTypesToBuffer(BufferWriter& bw, const IntegralTypes& data) {
bw.writeBytes<4>(data.b);
bw.writeBytes<1>(data.f[0]);
bw.writeBytes<2>(data.c);
bw.writeBytes<1>(data.d);
bw.writeBytes<8>(data.a);
bw.writeBytes<1>(data.e);
bw.writeBytes<1>(data.f[1]);
}
TEST(BufferBytesOperations, WriteAndReadBytes) {
//setup data
auto data =getInitializedIntegralTypes();
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
writeIntegralTypesToBuffer(bw, data);
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(18));
//read from buffer
BufferReader br{buf};
IntegralTypes res{};
EXPECT_THAT(br.readBytes<4>(res.b), Eq(true));
EXPECT_THAT(br.readBytes<1>(res.f[0]), 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.readBytes<1>(res.f[1]), Eq(true));
//assert results
EXPECT_THAT(data.a, Eq(res.a));
EXPECT_THAT(data.b, Eq(res.b));
EXPECT_THAT(data.c, Eq(res.c));
EXPECT_THAT(data.d, Eq(res.d));
EXPECT_THAT(data.e, Eq(res.e));
EXPECT_THAT(data.f, ContainerEq(res.f));
}
TEST(BufferBytesOperations, BufferReaderUsingDataPlusSizeCtor) {
//setup data
auto data =getInitializedIntegralTypes();
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
writeIntegralTypesToBuffer(bw, data);
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(18));
//read from buffer
BufferReader br{buf.data(), buf.size()};
IntegralTypes res{};
EXPECT_THAT(br.readBytes<4>(res.b), Eq(true));
EXPECT_THAT(br.readBytes<1>(res.f[0]), 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.readBytes<1>(res.f[1]), Eq(true));
//assert results
EXPECT_THAT(data.a, Eq(res.a));
EXPECT_THAT(data.b, Eq(res.b));
EXPECT_THAT(data.c, Eq(res.c));
EXPECT_THAT(data.d, Eq(res.d));
EXPECT_THAT(data.e, Eq(res.e));
EXPECT_THAT(data.f, ContainerEq(res.f));
}
TEST(BufferBytesOperations, BufferReaderUsingCArrayCtor) {
//setup data
auto data =getInitializedIntegralTypes();
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
writeIntegralTypesToBuffer(bw, data);
ASSERT_THAT(std::distance(buf.begin(), buf.end()), Eq(18));
uint8_t cArrBuf[18];
std::copy(buf.begin(), buf.end(), cArrBuf);
//read from buffer
BufferReader br{cArrBuf};
IntegralTypes res{};
EXPECT_THAT(br.readBytes<4>(res.b), Eq(true));
EXPECT_THAT(br.readBytes<1>(res.f[0]), 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.readBytes<1>(res.f[1]), Eq(true));
//assert results
EXPECT_THAT(data.a, Eq(res.a));
EXPECT_THAT(data.b, Eq(res.b));
EXPECT_THAT(data.c, Eq(res.c));
EXPECT_THAT(data.d, Eq(res.d));
EXPECT_THAT(data.e, Eq(res.e));
EXPECT_THAT(data.f, ContainerEq(res.f));
}
TEST(BufferBytesOperations, ReadReturnsFalseIfNotEnoughBufferSize) {
//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);
//read from buffer
BufferReader br{buf};
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));
}
TEST(BufferBytesOperations, ReadIsCompletedWhenAllBytesAreRead) {
//setup data
IntegralTypes data;
data.b = 94545646;
data.c = -8778;
data.d = 200;
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
bw.writeBytes<4>(data.b);
bw.writeBytes<2>(data.c);
bw.writeBytes<1>(data.d);
//read from buffer
BufferReader br{buf};
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));
BufferReader br1{buf};
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));
}
TEST(BufferBytesOperations, ReadWriteBufferFncCanAcceptSignedData) {
//setup data
constexpr size_t DATA_SIZE = 3;
int16_t src[DATA_SIZE] {54,-4877,30067};
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
bw.writeBuffer<2>(src, DATA_SIZE);
bw.flush();
//read from buffer
BufferReader br1{buf};
int16_t dst[DATA_SIZE]{};
EXPECT_THAT(br1.readBuffer<2>(dst, DATA_SIZE), Eq(true));
EXPECT_THAT(dst, ContainerEq(src));
//read more than available
BufferReader br2{buf};
int16_t dstMore[DATA_SIZE+1]{};
EXPECT_THAT(br2.readBuffer<2>(dstMore, DATA_SIZE+1), Eq(false));
}
TEST(BufferBytesOperations, ReadWriteBufferCanWorkOnUnalignedData) {
//setup data
constexpr size_t DATA_SIZE = 3;
int16_t src[DATA_SIZE] {54,-4877,30067};
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
bw.writeBits(15u, 4);
bw.writeBuffer<2>(src, DATA_SIZE);
bw.writeBits(12u, 4);
bw.flush();
EXPECT_THAT(buf.size(), Eq(sizeof(src) + 1));
//read from buffer
BufferReader br1{buf};
int16_t dst[DATA_SIZE]{};
uint8_t tmp{};
br1.readBits(tmp, 4);
EXPECT_THAT(tmp, Eq(15));
EXPECT_THAT(br1.readBuffer<2>(dst, DATA_SIZE), Eq(true));
EXPECT_THAT(dst, ContainerEq(src));
br1.readBits(tmp, 4);
EXPECT_THAT(tmp, Eq(12));
//read more than available
BufferReader br2{buf};
br2.readBits(tmp, 4);
int16_t dstMore[DATA_SIZE+1]{};
EXPECT_THAT(tmp, Eq(15));
EXPECT_THAT(br2.readBuffer<2>(dstMore, DATA_SIZE+1), Eq(false));
}

View File

@@ -24,13 +24,13 @@
#include <gmock/gmock.h>
#include <bitsery/buffer_writer.h>
#include <bitsery/buffer_reader.h>
#include <bitsery/ext/value_range.h>
#include <bitsery/details/serialization_common.h>
using testing::Eq;
using testing::ContainerEq;
using bitsery::EndiannessType;
using bitsery::DefaultConfig;
using Buffer = bitsery::DefaultConfig::BufferType;
using Buffer = std::vector<bitsery::DefaultConfig::BufferValueType>;
constexpr EndiannessType getInverseEndianness(EndiannessType e) {
return e == EndiannessType::LittleEndian
@@ -38,8 +38,10 @@ constexpr EndiannessType getInverseEndianness(EndiannessType e) {
: EndiannessType::LittleEndian;
}
struct InverseEndiannessConfig:public DefaultConfig {
struct InverseEndiannessConfig {
static constexpr bitsery::EndiannessType NetworkEndianness = getInverseEndianness(DefaultConfig::NetworkEndianness);
using BufferValueType = DefaultConfig::BufferValueType;
using BufferScrathType = DefaultConfig::BufferScrathType;
};
struct IntegralTypes {
@@ -78,7 +80,7 @@ TEST(BufferEndianness, WhenWriteBytesThenBytesAreSwapped) {
bw.writeBytes<1>(src.e);
bw.flush();
//read from buffer using inverse endianness config
bitsery::BasicBufferReader<InverseEndiannessConfig> br{bw.getWrittenRange()};
bitsery::BasicBufferReader<InverseEndiannessConfig> br{buf};
IntegralTypes res{};
br.readBytes<8>(res.a);
br.readBytes<4>(res.b);
@@ -104,7 +106,7 @@ TEST(BufferEndianness, WhenWriteBuffer1ByteValuesThenEndiannessIsIgnored) {
bw.writeBuffer<1>(src, SIZE);
bw.flush();
//read from buffer using inverse endianness config
bitsery::BasicBufferReader<InverseEndiannessConfig> br{bw.getWrittenRange()};
bitsery::BasicBufferReader<InverseEndiannessConfig> br{buf};
br.readBuffer<1>(res, SIZE);
//result is identical, because we write separate values, of size 1byte, that requires no swapping
//check results
@@ -123,7 +125,7 @@ TEST(BufferEndianness, WhenWriteBufferMoreThan1ByteValuesThenValuesAreSwapped) {
bw.writeBuffer<2>(src, SIZE);
bw.flush();
//read from buffer using inverse endianness config
bitsery::BasicBufferReader<InverseEndiannessConfig> br{bw.getWrittenRange()};
bitsery::BasicBufferReader<InverseEndiannessConfig> br{buf};
br.readBuffer<2>(res, SIZE);
//result is identical, because we write separate values, of size 1byte, that requires no swapping
//check results
@@ -145,7 +147,7 @@ struct IntegralUnsignedTypes {
TEST(BufferEndianness, WhenBufferValueTypeIs1ByteThenBitOperationsIsNotAffectedByEndianness) {
//fill initial values
static_assert(sizeof(bitsery::details::BufferContainerTraits<DefaultConfig::BufferType>::TValue) == 1, "currently only 1 byte size, value size is supported");
static_assert(sizeof(DefaultConfig::BufferValueType) == 1, "currently only 1 byte size, value size is supported");
//fill initial values
constexpr IntegralUnsignedTypes src {
0x0000334455667788,//bits 19
@@ -167,7 +169,7 @@ TEST(BufferEndianness, WhenBufferValueTypeIs1ByteThenBitOperationsIsNotAffectedB
bw.writeBits(src.d, dBITS);
bw.flush();
//read from buffer using inverse endianness config
bitsery::BasicBufferReader<InverseEndiannessConfig> br{bw.getWrittenRange()};
bitsery::BasicBufferReader<InverseEndiannessConfig> br{buf};
IntegralUnsignedTypes res{};
br.readBits(res.a, aBITS);
br.readBits(res.b, bBITS);

View File

@@ -1,344 +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 <bitsery/buffer_writer.h>
#include <bitsery/buffer_reader.h>
#include <bitsery/ext/value_range.h>
using testing::Eq;
using testing::ContainerEq;
using bitsery::BufferWriter;
using bitsery::BufferReader;
using Buffer = bitsery::DefaultConfig::BufferType;
struct IntegralUnsignedTypes {
uint32_t a;
uint16_t b;
uint8_t c;
uint8_t d;
uint64_t e;
};
template <typename T>
constexpr size_t getBits(T v) {
return bitsery::details::calcRequiredBits<T>({}, v);
};
// *** bits operations
TEST(BufferBitsAndBytesOperations, WriteAndReadBitsMaxTypeValues) {
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(std::numeric_limits<uint64_t>::max(), 64);
bw.writeBits(std::numeric_limits<uint32_t>::max(), 32);
bw.writeBits(std::numeric_limits<uint16_t>::max(), 16);
bw.writeBits(std::numeric_limits<uint8_t>::max(), 8);
bw.flush();
BufferReader br{bw.getWrittenRange()};
uint64_t v64{};
uint32_t v32{};
uint16_t v16{};
uint8_t v8{};
br.readBits(v64, 64);
br.readBits(v32, 32);
br.readBits(v16, 16);
br.readBits(v8, 8);
EXPECT_THAT(v64, Eq(std::numeric_limits<uint64_t>::max()));
EXPECT_THAT(v32, Eq(std::numeric_limits<uint32_t>::max()));
EXPECT_THAT(v16, Eq(std::numeric_limits<uint16_t>::max()));
EXPECT_THAT(v8, Eq(std::numeric_limits<uint8_t>::max()));
}
TEST(BufferBitsAndBytesOperations, WriteAndReadBits) {
//setup data
constexpr IntegralUnsignedTypes data{
485454,//bits 19
45978,//bits 16
0,//bits 1
36,//bits 6
479845648946//bits 39
};
constexpr size_t aBITS = getBits(data.a) + 2;
constexpr size_t bBITS = getBits(data.b) + 0;
constexpr size_t cBITS = getBits(data.c) + 2;
constexpr size_t dBITS = getBits(data.d) + 1;
constexpr size_t eBITS = getBits(data.e) + 8;
//create and write to buffer
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(data.a, aBITS);
bw.writeBits(data.b, bBITS);
bw.writeBits(data.c, cBITS);
bw.writeBits(data.d, dBITS);
bw.writeBits(data.e, eBITS);
bw.flush();
auto range = bw.getWrittenRange();
auto bytesCount = ((aBITS + bBITS + cBITS + dBITS + eBITS) / 8) +1 ;
EXPECT_THAT(std::distance(range.begin(), range.end()), Eq(bytesCount));
//read from buffer
BufferReader br{range};
IntegralUnsignedTypes res;
br.readBits(res.a, aBITS);
br.readBits(res.b, bBITS);
br.readBits(res.c, cBITS);
br.readBits(res.d, dBITS);
br.readBits(res.e, eBITS);
EXPECT_THAT(res.a, Eq(data.a));
EXPECT_THAT(res.b, Eq(data.b));
EXPECT_THAT(res.c, Eq(data.c));
EXPECT_THAT(res.d, Eq(data.d));
EXPECT_THAT(res.e, Eq(data.e));
}
TEST(BufferBitsAndBytesOperations, BufferSizeIsCountedPerByteNotPerBit) {
//setup data
//create and write to buffer
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(7u,3);
bw.flush();
auto range = bw.getWrittenRange();
EXPECT_THAT(std::distance(range.begin(), range.end()), Eq(1));
//read from buffer
BufferReader br{range};
uint16_t tmp;
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};
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};
br2.readBits(tmp,9);
EXPECT_THAT(br2.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));//false
}
TEST(BufferBitsAndBytesOperations, ConsecutiveCallsToAlignHasNoEffect) {
Buffer buf;
BufferWriter bw{buf};
bw.writeBits(3u, 2);
//3 calls to align after 1st data
bw.align();
bw.align();
bw.align();
bw.writeBits(7u, 3);
//1 call to align after 2nd data
bw.align();
bw.writeBits(15u, 4);
bw.flush();
unsigned char tmp;
BufferReader br{bw.getWrittenRange()};
br.readBits(tmp,2);
EXPECT_THAT(tmp, Eq(3u));
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.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
br.readBits(tmp,4);
EXPECT_THAT(tmp, Eq(15u));
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
}
TEST(BufferBitsAndBytesOperations, AlignWritesZerosBits) {
//setup data
//create and write to buffer
Buffer buf;
BufferWriter bw{buf};
//write 2 bits and align
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{range};
br1.readBits(tmp,2);
//read aligned bits
br1.readBits(tmp,6);
EXPECT_THAT(tmp, Eq(0));
BufferReader br2{range};
//read 2 bits
br2.readBits(tmp,2);
br2.align();
EXPECT_THAT(br2.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
}
// *** bytes operations
struct IntegralTypes {
int64_t a;
uint32_t b;
int16_t c;
uint8_t d;
int8_t e;
int8_t f[2];
};
TEST(BufferBitsAndBytesOperations, WriteAndReadBytes) {
//setup data
IntegralTypes data;
data.a = -4894541654564;
data.b = 94545646;
data.c = -8778;
data.d = 200;
data.e = -98;
data.f[0] = 43;
data.f[1] = -45;
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
bw.writeBytes<4>(data.b);
bw.writeBytes<2>(data.c);
bw.writeBytes<1>(data.d);
bw.writeBytes<8>(data.a);
bw.writeBytes<1>(data.e);
bw.writeBuffer<1>(data.f, 2);
bw.flush();
auto range = bw.getWrittenRange();
EXPECT_THAT(std::distance(range.begin(), range.end()), Eq(18));
//read from buffer
BufferReader br{range};
IntegralTypes res{};
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));
EXPECT_THAT(data.b, Eq(res.b));
EXPECT_THAT(data.c, Eq(res.c));
EXPECT_THAT(data.d, Eq(res.d));
EXPECT_THAT(data.e, Eq(res.e));
EXPECT_THAT(data.f, ContainerEq(res.f));
}
TEST(BufferBitsAndBytesOperations, ReadWriteBufferFncCanAcceptSignedData) {
//setup data
constexpr size_t DATA_SIZE = 3;
int16_t src[DATA_SIZE] {54,-4877,30067};
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
bw.writeBuffer<2>(src, DATA_SIZE);
bw.flush();
//read from buffer
BufferReader br1{bw.getWrittenRange()};
int16_t dst[DATA_SIZE]{};
br1.readBuffer<2>(dst, DATA_SIZE);
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
EXPECT_THAT(dst, ContainerEq(src));
}
TEST(BufferBitsAndBytesOperations, ReadWriteBufferCanWorkOnUnalignedData) {
//setup data
constexpr size_t DATA_SIZE = 3;
int16_t src[DATA_SIZE] {54,-4877,30067};
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
bw.writeBits(15u, 4);
bw.writeBuffer<2>(src, DATA_SIZE);
bw.writeBits(12u, 4);
bw.flush();
auto range = bw.getWrittenRange();
EXPECT_THAT(std::distance(range.begin(), range.end()), Eq(sizeof(src) + 1));
//read from buffer
BufferReader br1{range};
int16_t dst[DATA_SIZE]{};
uint8_t tmp{};
br1.readBits(tmp, 4);
EXPECT_THAT(tmp, Eq(15));
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));
}
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

@@ -1,158 +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 <bitsery/buffer_writer.h>
#include <bitsery/buffer_reader.h>
#include <list>
#include <bitset>
using testing::Eq;
using bitsery::BufferWriter;
using bitsery::BufferReader;
using Buffer = bitsery::DefaultConfig::BufferType;
struct IntegralTypes {
int64_t a;
uint32_t b;
int16_t c;
uint8_t d;
int8_t e;
int8_t f[2];
};
TEST(BufferReading, WhenReadingMoreThanAvailableThenEmptyBufferError) {
//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));
}
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;
data.c = -8778;
data.d = 200;
//create and write to buffer
Buffer buf{};
BufferWriter bw{buf};
bw.writeBytes<4>(data.b);
bw.writeBytes<2>(data.c);
bw.writeBytes<1>(data.d);
bw.flush();
//read from buffer
BufferReader br{bw.getWrittenRange()};
IntegralTypes res;
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()};
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

@@ -1,119 +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"
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

@@ -1,117 +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 <bitsery/buffer_writer.h>
#include <bitsery/buffer_reader.h>
#include <bitsery/details/serialization_common.h>
using testing::Eq;
using testing::ContainerEq;
using bitsery::EndiannessType;
using bitsery::DefaultConfig;
using Buffer = bitsery::DefaultConfig::BufferType;
struct FixedBufferConfig: public DefaultConfig {
using BufferType = std::array<uint8_t, 100>;
};
struct NonFixedBufferConfig: public DefaultConfig {
using BufferType = std::vector<uint8_t>;
};
template <typename Config>
class BufferWriting:public testing::Test {
public:
using type = Config;
};
using BufferWritingConfigs = ::testing::Types<
NonFixedBufferConfig,
FixedBufferConfig>;
TYPED_TEST_CASE(BufferWriting, BufferWritingConfigs);
static constexpr size_t DATA_SIZE = 14u;
template <typename BW>
void writeData(BW& bw) {
uint16_t tmp1{45}, tmp2{6543}, tmp3{46533};
uint32_t tmp4{8979445}, tmp5{7987564};
bw.template writeBytes<2>(tmp1);
bw.template writeBytes<2>(tmp2);
bw.template writeBytes<2>(tmp3);
bw.template writeBytes<4>(tmp4);
bw.template writeBytes<4>(tmp5);
}
TYPED_TEST(BufferWriting, GetWrittenRangeReturnsBeginEndIterators) {
using Config = typename TestFixture::type;
using Buffer = typename Config::BufferType;
Buffer buf{};
bitsery::BasicBufferWriter<Config> bw{buf};
writeData(bw);
bw.flush();
auto range = bw.getWrittenRange();
EXPECT_THAT(std::distance(range.begin(), range.end()), DATA_SIZE);
}
TYPED_TEST(BufferWriting, WhenWritingBitsThenMustFlushWriter) {
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();
auto range2 = bw.getWrittenRange();
EXPECT_THAT(std::distance(range1.begin(), range1.end()), Eq(0));
EXPECT_THAT(std::distance(range2.begin(), range2.end()), Eq(1));
}
TYPED_TEST(BufferWriting, WhenDataAlignedThenFlushHasNoEffect) {
using Config = typename TestFixture::type;
using Buffer = typename Config::BufferType;
Buffer buf{};
bitsery::BasicBufferWriter<Config> bw{buf};
bw.writeBits(3u, 2);
bw.align();
auto range1 = bw.getWrittenRange();
bw.flush();
auto range2 = bw.getWrittenRange();
EXPECT_THAT(std::distance(range1.begin(), range1.end()), Eq(1));
EXPECT_THAT(std::distance(range2.begin(), range2.end()), Eq(1));
}
TEST(BufferWrittingNonFixedBuffer, BufferIsAlwaysResizedToCapacity) {
using Buffer = typename NonFixedBufferConfig::BufferType;
Buffer buf{};
bitsery::BasicBufferWriter<NonFixedBufferConfig> bw{buf};
for (auto i = 0; i < 5; ++i) {
uint32_t tmp{};
bw.writeBytes<4>(tmp);
bw.writeBytes<4>(tmp);
EXPECT_TRUE(buf.size() == buf.capacity());
}
}

View File

@@ -33,12 +33,8 @@ TEST(SerializeBooleans, BoolAsBit) {
bool t2{false};
bool res1;
bool res2;
auto ser = ctx.createSerializer();
ser.boolBit(t1);
ser.boolBit(t2);
auto des = ctx.createDeserializer();
des.boolBit(res1);
des.boolBit(res2);
ctx.createSerializer().boolBit(t1).boolBit(t2);
ctx.createDeserializer().boolBit(res1).boolBit(res2);
EXPECT_THAT(res1, Eq(t1));
EXPECT_THAT(res2, Eq(t2));
@@ -51,12 +47,8 @@ TEST(SerializeBooleans, BoolAsByte) {
bool t2{false};
bool res1;
bool res2;
auto ser = ctx.createSerializer();
ser.boolByte(t1);
ser.boolByte(t2);
auto des = ctx.createDeserializer();
des.boolByte(res1);
des.boolByte(res2);
ctx.createSerializer().boolByte(t1).boolByte(t2);
ctx.createDeserializer().boolByte(res1).boolByte(res2);
EXPECT_THAT(res1, Eq(t1));
EXPECT_THAT(res2, Eq(t2));

View File

@@ -23,12 +23,10 @@
#include <gmock/gmock.h>
#include <algorithm>
#include "serialization_test_utils.h"
#include <numeric>
#include <deque>
#include <list>
#include "serialization_test_utils.h"
using testing::ContainerEq;
using testing::Eq;
@@ -38,29 +36,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}}
};
}
@@ -68,16 +66,16 @@ std::list<MyStruct2> getFilledContainer<std::list<MyStruct2>>() {
* start testing session
*/
template<typename T>
class SerializeContainerDynamicSizeArthmeticTypes : public testing::Test {
template <typename T>
class SerializeContainerArthmeticTypes: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);
}
};
@@ -87,9 +85,9 @@ using SequenceContainersWithArthmeticTypes = ::testing::Types<
std::list<float>,
std::deque<unsigned short>>;
TYPED_TEST_CASE(SerializeContainerDynamicSizeArthmeticTypes, SequenceContainersWithArthmeticTypes);
TYPED_TEST_CASE(SerializeContainerArthmeticTypes, SequenceContainersWithArthmeticTypes);
TYPED_TEST(SerializeContainerDynamicSizeArthmeticTypes, Values) {
TYPED_TEST(SerializeContainerArthmeticTypes, Values) {
SerializationContext ctx{};
using TValue = typename TestFixture::TValue;
@@ -100,51 +98,53 @@ TYPED_TEST(SerializeContainerDynamicSizeArthmeticTypes, Values) {
EXPECT_THAT(this->res, ContainerEq(this->src));
}
TYPED_TEST(SerializeContainerDynamicSizeArthmeticTypes, CustomFunctionIncrements) {
TYPED_TEST(SerializeContainerArthmeticTypes, CustomFunctionIncrements) {
SerializationContext ctx{};
using TValue = typename TestFixture::TValue;
auto ser = ctx.createSerializer();
ser.container(this->src, 1000, [&ser](TValue& v) {
ser.template value<sizeof(v)>(v);
ser.container(this->src, 1000, [](auto &s, auto v) {
//increment by 1 before writing
v++;
s.template value<sizeof(v)>(v);
});
auto des = ctx.createDeserializer();
des.container(this->res, 1000, [&des](TValue &v) {
des.template value<sizeof(v)>(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 1, before comparing for eq
for (auto &v:this->res)
v -= 1;
//decrement result by 2, before comparing for eq
for(auto& v:this->res)
v -= 2;
EXPECT_THAT(ctx.getBufferSize(), Eq(this->getExpectedBufSize(ctx)));
EXPECT_THAT(this->res, ContainerEq(this->src));
}
template<typename T>
class SerializeContainerDynamicSizeCompositeTypes : public testing::Test {
template <typename T>
class SerializeContainerCompositeTypes: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 SerializeContainerDynamicSizeWithCompositeTypes = ::testing::Types<
using SequenceContainersWithCompositeTypes = ::testing::Types<
std::vector<MyStruct1>,
std::list<MyStruct2>>;
TYPED_TEST_CASE(SerializeContainerDynamicSizeCompositeTypes, SerializeContainerDynamicSizeWithCompositeTypes);
TYPED_TEST_CASE(SerializeContainerCompositeTypes, SequenceContainersWithCompositeTypes);
TYPED_TEST(SerializeContainerDynamicSizeCompositeTypes, DefaultSerializeFunction) {
TYPED_TEST(SerializeContainerCompositeTypes, DefaultSerializeFunction) {
SerializationContext ctx{};
ctx.createSerializer().container(this->src, 1000);
@@ -155,95 +155,15 @@ TYPED_TEST(SerializeContainerDynamicSizeCompositeTypes, DefaultSerializeFunction
}
TYPED_TEST(SerializeContainerDynamicSizeCompositeTypes, CustomFunctionThatDoNothing) {
TYPED_TEST(SerializeContainerCompositeTypes, CustomFunctionThatDoNothing) {
SerializationContext ctx{};
using TValue = typename TestFixture::TValue;
auto emptyFnc = [](TValue &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{};
using TValue = decltype(*std::begin(res));
SerializationContext ctx;
auto ser = ctx.createSerializer();
ser.container(src, [&ser](TValue &v) {
char tmp{};
ser.object(v);
ser.value1b(tmp);
});
auto des = ctx.createDeserializer();
des.container(res, [&des](TValue &v) {
char tmp{};
des.object(v);
des.value1b(tmp);
});
EXPECT_THAT(ctx.getBufferSize(), Eq(this->getContainerSize() * (MyStruct1::SIZE + sizeof(char))));
EXPECT_THAT(res, ContainerEq(src));
}

View File

@@ -1,140 +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 <bitsery/ext/container_map.h>
#include <bitsery/ext/entropy.h>
#include <unordered_map>
#include <map>
using ContainerMap = bitsery::ext::ContainerMap;
using testing::Eq;
template<typename Container>
Container createData() {
return {};
}
template<>
std::unordered_map<std::string, MyStruct1> createData<std::unordered_map<std::string, MyStruct1>>() {
return {
std::make_pair("some key", MyStruct1{874,456}),
std::make_pair("other key", MyStruct1{-34,8645}),
std::make_pair("secret key", MyStruct1{-4878,3468975})
};
}
template<>
std::unordered_map<int32_t, float> createData<std::unordered_map<int32_t, float>>() {
return {
std::pair<int32_t , float>(545, 45.485f),
std::pair<int32_t , float>(6748, -7891.5f),
std::pair<int32_t , float>(845, -457.0f)
};
}
template<>
std::map<MyEnumClass, MyStruct1> createData<std::map<MyEnumClass, MyStruct1>>() {
return {
std::make_pair(MyEnumClass::E3, MyStruct1{874,456}),
std::make_pair(MyEnumClass::E6, MyStruct1{-34,8645}),
std::make_pair(MyEnumClass::E2, MyStruct1{-4878,3468975})
};
}
template<>
std::map<int32_t ,int64_t> createData<std::map<int32_t ,int64_t>>() {
return {//these are optimized with range and entropy
std::pair<int32_t, int64_t>(-45, -984196845ll),
std::pair<int32_t, int64_t>(54, 1ll),
std::pair<int32_t, int64_t>(98, 3ll)
};
}
template<typename T>
class SerializeExtensionContainerMap : public testing::Test {
public:
using TContainer = T;
const TContainer src = createData<TContainer>();
TContainer res{};
};
using SerializeExtensionContainerMapTypes = ::testing::Types<
std::unordered_map<std::string, MyStruct1>,
std::unordered_map<int32_t, float>,
std::map<MyEnumClass , MyStruct1>,
std::map<int32_t ,int64_t>
>;
TYPED_TEST_CASE(SerializeExtensionContainerMap, SerializeExtensionContainerMapTypes);
namespace bitsery {
template <typename S>
void serialize(S& s, std::unordered_map<std::string, MyStruct1>& o) {
s.ext(o, ContainerMap{10}, [&s](std::string& key, MyStruct1& value) {
s.text1b(key, 100);
s.object(value);
});
}
template <typename S>
void serialize(S& s, std::unordered_map<int32_t, float>& o) {
s.ext(o, ContainerMap{10}, [&s](int32_t& key, float& value) {
s.value4b(key);
s.value4b(value);
});
}
template <typename S>
void serialize(S& s, std::map<MyEnumClass , MyStruct1>& o) {
s.ext(o, ContainerMap{10}, [&s](MyEnumClass& key, MyStruct1& value) {
s.value4b(key);
s.object(value);
});
}
template <typename S>
void serialize(S& s, std::map<int32_t ,int64_t>& o) {
s.ext(o, ContainerMap{10}, [&s](int32_t& key, int64_t& value) {
int64_t values[3]{1ll, 2ll, 3ll};
s.ext(key, bitsery::ext::ValueRange<int32_t>{-100,100});
s.ext8b(value, bitsery::ext::Entropy<int64_t[3]>{values});
});
}
}
TYPED_TEST(SerializeExtensionContainerMap, SerializeAndDeserializeEquals) {
SerializationContext ctx1;
ctx1.createSerializer().object(this->src);
ctx1.createDeserializer().object(this->res);
EXPECT_THAT(this->res, Eq(this->src));
}

View File

@@ -1,143 +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 <bitsery/ext/entropy.h>
#include <vector>
#include <list>
using namespace testing;
using bitsery::ext::Entropy;
TEST(SerializeExtensionEntropy, WhenEntropyEncodedThenOnlyWriteIndexUsingMinRequiredBits) {
int32_t v = 4849;
int32_t res;
constexpr size_t N = 3;
int32_t values[3] = {485,4849,89};
SerializationContext ctx;
ctx.createSerializer().ext4b(v, Entropy<int32_t[3]>{values});
ctx.createDeserializer().ext4b(res, Entropy<int32_t[3]>{values});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
SerializationContext ctx1;
ctx1.createSerializer().ext4b(v, Entropy<int32_t[3]>{values});
auto des = ctx1.createDeserializer();
des.ext(res, bitsery::ext::ValueRange<int32_t>{0, static_cast<int32_t>(N + 1)});
EXPECT_THAT(res, Eq(2));
}
TEST(SerializeExtensionEntropy, WhenNoEntropyEncodedThenWriteZeroBitsAndValueOrObject) {
int16_t v = 8945;
int16_t res;
std::initializer_list<int> values{485,4849,89};
SerializationContext ctx;
ctx.createSerializer().ext2b(v, Entropy<std::initializer_list<int>>{values});
ctx.createDeserializer().ext2b(res, Entropy<std::initializer_list<int>>{values});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(sizeof(int16_t)+1));
}
TEST(SerializeExtensionEntropy, CustomTypeEntropyEncoded) {
MyStruct1 v = {12,10};
MyStruct1 res;
constexpr size_t N = 4;
MyStruct1 values[N]{
MyStruct1{12, 10}, MyStruct1{485, 454},
MyStruct1{4849, 89}, MyStruct1{0, 1}};
SerializationContext ctx;
ctx.createSerializer().ext(v, Entropy<MyStruct1[N]>{values});
ctx.createDeserializer().ext(res, Entropy<MyStruct1[N]>{values});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
}
TEST(SerializeExtensionEntropy, CustomTypeNotEntropyEncoded) {
MyStruct1 v = {8945,4456};
MyStruct1 res;
std::initializer_list<MyStruct1> values {
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
SerializationContext ctx;
ctx.createSerializer().ext(v, Entropy<std::initializer_list<MyStruct1>>{values});
ctx.createDeserializer().ext(res, Entropy<std::initializer_list<MyStruct1>>{values});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(MyStruct1::SIZE + 1));
}
TEST(SerializeExtensionEntropy, CustomFunctionNotEntropyEncoded) {
MyStruct1 v = {8945,4456};
MyStruct1 res;
constexpr size_t N = 4;
std::vector<MyStruct1> values{
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
auto rangeForValue = bitsery::ext::ValueRange<int>{0, 10000};
auto rangeForIndex = bitsery::ext::ValueRange<size_t>{0u, N+1};
SerializationContext ctx;
auto ser = ctx.createSerializer();
//lambdas differ only in capture clauses, it would make sense to use std::bind, but debugger crashes when it sees std::bind...
auto serLambda = [&ser, &rangeForValue](MyStruct1& v) {
ser.ext(v.i1, rangeForValue);
ser.ext(v.i2, rangeForValue);
};
ser.ext(v, Entropy<std::vector<MyStruct1>>(values), serLambda);
auto des = ctx.createDeserializer();
auto desLambda = [&des, &rangeForValue](MyStruct1& v) {
des.ext(v.i1, rangeForValue);
des.ext(v.i2, rangeForValue);
};
des.ext(res, Entropy<std::vector<MyStruct1>>(values), desLambda);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq((rangeForIndex.getRequiredBits() + rangeForValue.getRequiredBits() * 2 - 1) / 8 + 1 ));
}
TEST(SerializeExtensionEntropy, WhenEntropyEncodedThenCustomFunctionNotInvoked) {
MyStruct1 v = {4849,89};
MyStruct1 res;
std::list<MyStruct1> values {MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
SerializationContext ctx;
ctx.createSerializer().ext(v, Entropy<std::list<MyStruct1>>{values}, [](MyStruct1& ) {});
ctx.createDeserializer().ext(res, Entropy<std::list<MyStruct1>>{values}, []( MyStruct1& ) {});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
}

View File

@@ -1,445 +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 <bitsery/ext/growable.h>
using namespace testing;
using bitsery::ext::Growable;
using Buffer = typename bitsery::DefaultConfig::BufferType;
using DiffType = typename bitsery::details::BufferContainerTraits<Buffer>::TDifference;
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(SerializeExtensionGrowable, WriteSessionsDataAtBufferEndAfterFlush) {
SerializationContext ctx;
ctx.createSerializer().ext(int8_t{}, Growable{}, [] (int8_t& v) { });
EXPECT_THAT(ctx.getBufferSize(), Eq(0));
ctx.bw->flush();
EXPECT_THAT(ctx.getBufferSize(), Gt(0));
}
TEST(SerializeExtensionGrowable, SessionDataConsistOfSessionsEndPosAnd2BytesSessionsDataOffset) {
SerializationContext ctx;
constexpr size_t DATA_SIZE = 4;
int32_t data{};
auto ser = ctx.createSerializer();
ser.ext(data, Growable{}, [&ser](int32_t & v) { ser.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, 1000000u);
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(SerializeExtensionGrowable, 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],10000000u);
bitsery::details::readSize(*(ctx.br), sessionEnd[1],10000000u);
bitsery::details::readSize(*(ctx.br), sessionEnd[2],10000000u);
EXPECT_THAT(sessionEnd[0], Eq(12));
EXPECT_THAT(sessionEnd[1], Eq(8));
EXPECT_THAT(sessionEnd[2], Eq(12));
}
TEST(SerializeExtensionGrowable, 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(SerializeExtensionGrowable, 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(SerializeExtensionGrowable, 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(SerializeExtensionGrowable, 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(SerializeExtensionGrowable, 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(SerializeExtensionGrowable, 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(SerializeExtensionGrowable, 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(SerializeExtensionGrowable, 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));
}
TEST(SerializeExtensionGrowable, SessionsStartsAtEndOfSerialization) {
SerializationContext ctx;
DataV2 data{8454,987451};
ctx.createSerializer();
auto& bw = (*ctx.bw);
for (auto i = 0; i < 100; ++i)
bw.writeBytes<4>(data.v1);
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 < 100; ++i)
br.readBytes<4>(res.v1);
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));
}

View File

@@ -24,30 +24,36 @@
#include "serialization_test_utils.h"
#if __cplusplus > 201402L
#include<optional>
# include<optional>
#else
# include <experimental/optional>
namespace std {
template <typename T>
using optional = experimental::optional<T>;
}
#endif
#include <bitsery/ext/optional.h>
using Optional = bitsery::ext::Optional;
template <typename T>
using extoptional = bitsery::ext::optional<T>;
using testing::Eq;
template <typename T>
void test(SerializationContext& ctx, const T& v, T& r) {
ctx.createSerializer().ext4b(v, Optional{});
ctx.createDeserializer().ext4b(r, Optional{});
auto fnc = [](auto ser, auto& v) {
ser.template value<sizeof(v)>(v);
};
ctx.createSerializer().ext<extoptional>(v, fnc);
ctx.createDeserializer().ext<extoptional>(r, fnc);
}
TEST(SerializeExtensionOptional, EmptyOptional) {
std::optional<int32_t> t1{};
std::optional<int32_t> r1{};
std::optional<int> t1{};
std::optional<int> r1{};
SerializationContext ctx1;
test(ctx1,t1, r1);
@@ -63,8 +69,8 @@ TEST(SerializeExtensionOptional, EmptyOptional) {
}
TEST(SerializeExtensionOptional, OptionalHasValue) {
std::optional<int32_t> t1{43};
std::optional<int32_t> r1{52};
std::optional<int> t1{43};
std::optional<int> r1{52};
SerializationContext ctx1;
test(ctx1,t1, r1);
@@ -78,5 +84,3 @@ TEST(SerializeExtensionOptional, OptionalHasValue) {
EXPECT_THAT(t1.value(), Eq(r1.value()));
}
#endif

View File

@@ -0,0 +1,131 @@
//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).value1(tmp);
});
auto des = ctx.createDeserializer();
des.array(res, [](auto &s, auto& v) {
char tmp{};
s.object(v).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).value1(tmp);
});
auto des = ctx.createDeserializer();
des.array(res, [](auto& s, auto& v) {
char tmp{};
s.object(v).value1(tmp);
});
EXPECT_THAT(ctx.getBufferSize(), Eq(std::extent<decltype(src)>::value * (MyStruct1::SIZE + sizeof(char))));
EXPECT_THAT(res, ContainerEq(src));
}

View File

@@ -24,6 +24,13 @@
#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;
@@ -49,29 +56,28 @@ struct Y {
struct Z { X x{}; Y y{}; };
template <typename S>
void serialize(S& s, Z& o)
SERIALIZE(Z)
{
s.object(o.x);
s.object(o.y);
return s;
}
template <typename S>
void serialize(S& s, X& o)
SERIALIZE(X)
{
s.template value<sizeof(o.x)>(o.x);
s.template text<1>(o.s, 1000);
return s.template value<sizeof(o.x)>(o.x)
.template text<1>(o.s, 1000);
}
template <typename S>
void serialize(S& s, Y& o)
SERIALIZE(Y)
{
auto writeInt = [&s]( int& v) { s.template value<sizeof(v)>(v); };
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.container(o.arr, writeInt);
s.container(o.carr, writeInt);
s.container(o.vx, 10000, [&s](X& v) { s.object(v); });
s.array(o.arr, writeInt);
s.array(o.carr, writeInt);
s.container(o.vx, 10000, [](auto& s, auto& v) { s.object(v); });
return s;
}
@@ -122,4 +128,49 @@ 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 });
std::vector<bitsery::DefaultConfig::BufferValueType> buf;
bitsery::BufferWriter bw{ buf };
bitsery::DeltaSerializer<bitsery::BufferWriter, Y> ser(bw, y, yNew);
serialize(ser, yNew);
bw.flush();
bitsery::BufferReader br{ buf };
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

@@ -23,15 +23,11 @@
#include <gmock/gmock.h>
#include "serialization_test_utils.h"
#include <bitsery/ext/value_range.h>
using namespace testing;
using bitsery::details::RangeSpec;
using bitsery::ext::BitsConstraint;
using bitsery::ext::ValueRange;
using bitsery::RangeSpec;
using bitsery::BitsConstraint;
#if __cplusplus > 201402L
TEST(SerializeExtensionValueRange, RequiredBitsIsConstexpr) {
TEST(SerializeRange, RequiredBitsIsConstexpr) {
constexpr RangeSpec<int> r1{0, 31};
static_assert(r1.bitsRequired == 5, "r1.bitsRequired == 5");
@@ -47,126 +43,112 @@ TEST(SerializeExtensionValueRange, RequiredBitsIsConstexpr) {
}
#endif
TEST(SerializeExtensionValueRange, IntegerNegative) {
TEST(SerializeRange, IntegerNegative) {
SerializationContext ctx;
ValueRange<int> r1{-50, 50};
constexpr RangeSpec<int> r1{-50, 50};
int t1{-8};
int res1;
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, Eq(t1));
}
TEST(SerializeExtensionValueRange, IntegerPositive) {
TEST(SerializeRange, IntegerPositive) {
SerializationContext ctx;
ValueRange<unsigned> r1{4u, 10u};
constexpr RangeSpec<unsigned> r1{4, 10};
unsigned t1{8};
unsigned res1;
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, Eq(t1));
}
TEST(SerializeExtensionValueRange, EnumTypes) {
TEST(SerializeRange, EnumTypes) {
SerializationContext ctx;
ValueRange<MyEnumClass> r1{MyEnumClass::E2, MyEnumClass::E4};
constexpr RangeSpec<MyEnumClass> r1{MyEnumClass::E2, MyEnumClass::E4};
MyEnumClass t1{MyEnumClass::E2};
MyEnumClass res1;
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, Eq(t1));
}
TEST(SerializeExtensionValueRange, FloatUsingPrecisionConstraint1) {
TEST(SerializeRange, FloatUsingPrecisionConstraint1) {
SerializationContext ctx;
constexpr float precision{0.01f};
constexpr float min{-1.0f};
constexpr float max{1.0f};
float t1{0.5f};
ValueRange<float> r1{min, max, precision};
constexpr RangeSpec<float> r1{min, max, precision};
float res1;
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, ::testing::FloatNear(t1, (max - min) * precision));
}
TEST(SerializeExtensionValueRange, DoubleUsingPrecisionConstraint2) {
TEST(SerializeRange, DoubleUsingPrecisionConstraint2) {
SerializationContext ctx;
constexpr double precision{0.000002};
constexpr double min{50.0};
constexpr double max{100000.0};
double t1{38741.0};
ValueRange<double> r1{min, max, precision};
constexpr RangeSpec<double> r1{min, max, precision};
double res1;
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(5));
EXPECT_THAT(res1, ::testing::DoubleNear(t1, (max - min) * precision));
}
TEST(SerializeExtensionValueRange, FloatUsingBitsSizeConstraint1) {
TEST(SerializeRange, FloatUsingBitsSizeConstraint1) {
SerializationContext ctx;
constexpr size_t bits = 8;
constexpr float min{-1.0f};
constexpr float max{1.0f};
float t1{0.5f};
ValueRange<float> r1{min, max, BitsConstraint(bits)};
constexpr RangeSpec<float> r1{min, max, BitsConstraint(bits)};
float res1;
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, ::testing::FloatNear(t1, (max - min) / (static_cast<bitsery::details::SAME_SIZE_UNSIGNED<float>>(1) << bits)));
}
TEST(SerializeExtensionValueRange, DoubleUsingBitsSizeConstraint2) {
TEST(SerializeRange, DoubleUsingBitsSizeConstraint2) {
SerializationContext ctx;
constexpr size_t bits = 50;
constexpr double min{50.0};
constexpr double max{100000.0};
double t1{38741};
ValueRange<double> r1{min, max, BitsConstraint(bits)};
constexpr RangeSpec<double> r1{min, max, BitsConstraint(bits)};
double res1;
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
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(SerializeExtensionValueRange, WhenDataIsInvalidThenReturnMinimumRangeValue) {
SerializationContext ctx;
ValueRange<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().ext(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, Eq(4));
}

View File

@@ -28,9 +28,9 @@ using testing::Eq;
bool SerializeDeserializeContainerSize(SerializationContext& ctx, const size_t size) {
std::vector<char> t1(size);
ctx.createSerializer().container(t1, size+1, []( char& ){});
ctx.createSerializer().container(t1, size+1, [](auto , auto ){});
t1.clear();
ctx.createDeserializer().container(t1, size+1, []( char& ){});
ctx.createDeserializer().container(t1, size+1, [](auto , auto ){});
return t1.size() == size;
}

View File

@@ -0,0 +1,135 @@
//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;
TEST(SerializeSubstitution, WhenSubstitutedThenOnlyWriteIndexUsingMinRequiredBits) {
int32_t v = 4849;
int32_t res;
constexpr size_t N = 3;
std::array<int32_t,N> subsitution{485,4849,89};
SerializationContext ctx;
ctx.createSerializer().substitution<4>(v, subsitution);
ctx.createDeserializer().substitution<4>(res, subsitution);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
SerializationContext ctx1;
ctx1.createSerializer().substitution<4>(v, subsitution);
auto des = ctx1.createDeserializer();
des.range(res, {0, N + 1});
EXPECT_THAT(res, Eq(2));
}
TEST(SerializeSubstitution, WhenNoSubstitutionThenWriteZeroBitsAndValueOrObject) {
int16_t v = 8945;
int16_t res;
std::array<int16_t,3> subsitution{485,4849,89};
SerializationContext ctx;
ctx.createSerializer().substitution<2>(v, subsitution);
ctx.createDeserializer().substitution<2>(res, subsitution);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(sizeof(int16_t)+1));
}
TEST(SerializeSubstitution, CustomTypeSubstituted) {
MyStruct1 v = {12,10};
MyStruct1 res;
constexpr size_t N = 4;
std::array<MyStruct1, N> subsitution = {
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
SerializationContext ctx;
ctx.createSerializer().substitution(v, subsitution);
ctx.createDeserializer().substitution(res, subsitution);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
}
TEST(SerializeSubstitution, CustomTypeNotSubstituted) {
MyStruct1 v = {8945,4456};
MyStruct1 res;
constexpr size_t N = 4;
std::array<MyStruct1, N> subsitution = {
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
SerializationContext ctx;
ctx.createSerializer().substitution(v, subsitution);
ctx.createDeserializer().substitution(res, subsitution);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(MyStruct1::SIZE + 1));
}
TEST(SerializeSubstitution, CustomFunctionNotSubstituted) {
MyStruct1 v = {8945,4456};
MyStruct1 res;
constexpr size_t N = 4;
std::array<MyStruct1, N> subsitution = {
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
auto rangeForValue = bitsery::RangeSpec<int>(0, 10000);
auto rangeForIndex = bitsery::RangeSpec<size_t>{0, N+1};
SerializationContext ctx;
auto ser = ctx.createSerializer();
//lambdas differ only in capture clauses, it would make sense to use std::bind, but debugger crashes when it sees std::bind...
auto serLambda = [rangeForValue](auto& s, const MyStruct1& v) {
s.range(v.i1, rangeForValue);
s.range(v.i2, rangeForValue);
};
ser.substitution(v, subsitution, 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);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq((rangeForIndex.bitsRequired + rangeForValue.bitsRequired * 2 - 1) / 8 + 1 ));
}
TEST(SerializeSubstitution, WhenSubstitutedThenCustomFunctionNotInvoked) {
MyStruct1 v = {4849,89};
MyStruct1 res;
constexpr size_t N = 4;
std::array<MyStruct1, N> subsitution = {
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& ) {});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
}

View File

@@ -21,8 +21,8 @@
//SOFTWARE.
#ifndef BITSERY_SERIALIZER_TEST_UTILS_H
#define BITSERY_SERIALIZER_TEST_UTILS_H
#ifndef BITSERY_SERIALIZERTESTS_H
#define BITSERY_SERIALIZERTESTS_H
#include <bitsery/bitsery.h>
#include <memory>
@@ -33,27 +33,23 @@
*/
struct MyStruct1 {
MyStruct1(int32_t v1, int32_t v2) : i1{v1}, i2{v2} {}
MyStruct1() : MyStruct1{0, 0} {}
MyStruct1(int v1, int v2):i1{v1}, i2{v2} {}
MyStruct1():MyStruct1{0,0} {}
int i1;
int i2;
bool operator==(const MyStruct1 &rhs) const {
bool operator == (const MyStruct1& rhs) const {
return i1 == rhs.i1 && i2 == rhs.i2;
}
static constexpr size_t SIZE = sizeof(MyStruct1::i1) + sizeof(MyStruct1::i2);
};
template <typename S>
void serialize(S& s, MyStruct1& o) {
s.template value<sizeof(o.i1)>(o.i1);
s.template value<sizeof(o.i2)>(o.i2);
SERIALIZE(MyStruct1) {
return s.
template value<sizeof(o.i1)>(o.i1).
template value<sizeof(o.i2)>(o.i2);
}
enum class MyEnumClass:int32_t {
enum class MyEnumClass {
E1, E2, E3, E4, E5, E6
};
@@ -62,44 +58,37 @@ struct MyStruct2 {
V1, V2, V3, V4, V5, V6
};
MyStruct2(MyEnum e, MyStruct1 s) : e1{e}, s1{s} {}
MyStruct2() : MyStruct2{V1, {0, 0}} {}
MyStruct2(MyEnum e, MyStruct1 s):e1{e}, s1{s} {}
MyStruct2():MyStruct2{V1,{0,0}} {}
MyEnum e1;
MyStruct1 s1;
bool operator==(const MyStruct2 &rhs) const {
bool operator == (const MyStruct2& rhs) const {
return e1 == rhs.e1 && s1 == rhs.s1;
}
static constexpr size_t SIZE = MyStruct1::SIZE + sizeof(MyStruct2::e1);
};
template <typename S>
void serialize(S&s, MyStruct2& o) {
s.template value<sizeof(o.e1)>(o.e1);
s.object(o.s1);
SERIALIZE(MyStruct2) {
return s.
template value<sizeof(o.e1)>(o.e1).
object(o.s1);
}
class SerializationContext {
public:
bitsery::DefaultConfig::BufferType buf{};
std::vector<bitsery::DefaultConfig::BufferValueType> buf{};
std::unique_ptr<bitsery::BufferWriter> bw;
std::unique_ptr<bitsery::BufferReader> br;
bitsery::Serializer createSerializer() {
//make_unique is not in c++11
bw = std::unique_ptr<bitsery::BufferWriter>(new bitsery::BufferWriter(buf));
return bitsery::Serializer{*bw};
public:
bitsery::Serializer<bitsery::BufferWriter> createSerializer() {
bw = std::make_unique<bitsery::BufferWriter>(buf);
return {*bw};
};
size_t getBufferSize() const {
auto range = bw->getWrittenRange();
return std::distance(range.begin(), range.end());
return buf.size();
}
//since all containers .size() method returns size_t, it cannot be directly serialized, because size_t is platform dependant
//this function returns number of bytes writen to buffer, when reading/writing size of container
static size_t containerSizeSerializedBytesCount(size_t elemsCount) {
@@ -110,12 +99,11 @@ public:
return 4;
}
bitsery::Deserializer createDeserializer() {
bitsery::Deserializer<bitsery::BufferReader> createDeserializer() {
bw->flush();
//make_unique is not in c++11
br = std::unique_ptr<bitsery::BufferReader>(new bitsery::BufferReader(bw->getWrittenRange()));
return bitsery::Deserializer{*br};
br = std::make_unique<bitsery::BufferReader>(buf);
return {*br};
};
};
#endif //BITSERY_SERIALIZER_TEST_UTILS_H
#endif //BITSERY_SERIALIZERTESTS_H

View File

@@ -108,11 +108,17 @@ TEST(SerializeText, CArraySerializesTextLength) {
EXPECT_THAT(r1, ContainerEq(t1));
}
TEST(SerializeText, WhenCArrayNotNullterminatedThenAssert) {
TEST(SerializeText, WhenCArrayNotNullterminatedThenMakeItNullterminated) {
SerializationContext ctx;
char16_t t1[CARR_LENGTH]{u"some text"};
//make last character not nullterminated
t1[CARR_LENGTH-1] = 'x';
char16_t r1[CARR_LENGTH]{};
EXPECT_DEATH(ctx.createSerializer().text<2>(t1), "");
ctx.createSerializer().text<2>(t1);
ctx.createDeserializer().text<2>(r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(ctx.containerSizeSerializedBytesCount(CARR_LENGTH) +
(CARR_LENGTH - 1) * 2));
EXPECT_THAT(r1[CARR_LENGTH-1], Eq(0));
}

View File

@@ -65,8 +65,8 @@ TEST(SerializeValues, ValueSizeOverload1Byte) {
constexpr size_t TSIZE = sizeof(v);
SerializationContext ctx;
ctx.createSerializer().value1b(v);
ctx.createDeserializer().value1b(res);
ctx.createSerializer().value1(v);
ctx.createDeserializer().value1(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().value2b(v);
ctx.createDeserializer().value2b(res);
ctx.createSerializer().value2(v);
ctx.createDeserializer().value2(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().value4b(v);
ctx.createDeserializer().value4b(res);
ctx.createSerializer().value4(v);
ctx.createDeserializer().value4(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().value8b(v);
ctx.createDeserializer().value8b(res);
ctx.createSerializer().value8(v);
ctx.createDeserializer().value8(res);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(TSIZE, Eq(ctx.getBufferSize()));