unit test class continued. serialize adapter

functions which are internal, extracted to separate class
This commit is contained in:
Robin Müller 2020-04-13 22:45:23 +02:00
parent fe45c7eb8b
commit b48a0a4a4c
5 changed files with 259 additions and 131 deletions

View File

@ -1,6 +1,13 @@
#ifndef ISDERIVEDFROM_H_ #ifndef ISDERIVEDFROM_H_
#define ISDERIVEDFROM_H_ #define ISDERIVEDFROM_H_
/**
* These template type checks are based on SFINAE
* (https://en.cppreference.com/w/cpp/language/sfinae)
*
* @tparam D Derived Type
* @tparam B Base Type
*/
template<typename D, typename B> template<typename D, typename B>
class IsDerivedFrom { class IsDerivedFrom {
class No { class No {
@ -9,7 +16,9 @@ class IsDerivedFrom {
No no[3]; No no[3];
}; };
// This will be chosen if B is the base type
static Yes Test(B*); // declared, but not defined static Yes Test(B*); // declared, but not defined
// This will be chosen for anything else
static No Test(... ); // declared, but not defined static No Test(... ); // declared, but not defined
public: public:

View File

@ -1,19 +1,18 @@
#ifndef SERIALIZEADAPTER_H_ #ifndef SERIALIZEADAPTER_H_
#define SERIALIZEADAPTER_H_ #define SERIALIZEADAPTER_H_
#include <framework/container/IsDerivedFrom.h>
#include <framework/returnvalues/HasReturnvaluesIF.h> #include <framework/returnvalues/HasReturnvaluesIF.h>
#include <framework/serialize/EndianSwapper.h>
#include <framework/serialize/SerializeIF.h> #include <framework/serialize/SerializeIF.h>
#include <string.h> #include <framework/serialize/SerializeAdapterInternal.h>
#include <type_traits>
/** /**
* @brief This adapter provides an interface to use the SerializeIF functions * @brief These adapters provides an interface to use the SerializeIF functions
* with arbitrary template objects to facilitate and simplify the * with arbitrary template objects to facilitate and simplify the
* serialization of classes with different multiple different data types * serialization of classes with different multiple different data types
* into buffers and vice-versa. * into buffers and vice-versa.
* @details * @details
* Examples: *
* A report class is converted into a TM buffer. The report class implements a * A report class is converted into a TM buffer. The report class implements a
* serialize functions and calls the AutoSerializeAdapter::serialize function * serialize functions and calls the AutoSerializeAdapter::serialize function
* repeatedly on all object data fields. The getSerializedSize function is * repeatedly on all object data fields. The getSerializedSize function is
@ -26,137 +25,42 @@
* bigEndian specifies whether an endian swap is performed on the data before * bigEndian specifies whether an endian swap is performed on the data before
* serialization or deserialization. * serialization or deserialization.
* *
* If the target architecture is little endian (ARM), any data types created might
* have the wrong endiness if they are to be used for the FSFW.
* There are three ways to retrieve data out of a buffer to be used in the FSFW * There are three ways to retrieve data out of a buffer to be used in the FSFW
* to use regular aligned (big endian) data. * to use regular aligned (big endian) data. Examples:
* This can also be applied to uint32_t and uint64_t:
* *
* 1. Use the AutoSerializeAdapter::deSerialize function * 1. Use the AutoSerializeAdapter::deSerialize function
* The pointer *buffer will be incremented automatically by the typeSize of the object, * The pointer *buffer will be incremented automatically by the typeSize
* so this function can be called on &buffer repeatedly without adjusting pointer position. * of the object, so this function can be called on &buffer repeatedly
* Set bigEndian parameter to true to perform endian swapping. * without adjusting pointer position. Set bigEndian parameter to true
* * to perform endian swapping, if necessary
* @code
* uint16_t data; * uint16_t data;
* int32_t dataLen = sizeof(data); * int32_t dataLen = sizeof(data);
* ReturnValue_t result = AutoSerializeAdapter::deSerialize(&data,&buffer,&dataLen,true); * ReturnValue_t result =
* * AutoSerializeAdapter::deSerialize(&data,&buffer,&dataLen,true);
* 2. Perform a bitshift operation. Perform endian swapping if necessary: * @endcode
* *
* 2. Perform a bitshift operation. Watch for for endianness:
* @code
* uint16_t data; * uint16_t data;
* data = buffer[targetByte1] << 8 | buffer[targetByte2]; * data = buffer[targetByte1] << 8 | buffer[targetByte2];
* data = EndianSwapper::swap(data); * data = EndianSwapper::swap(data); //optional, or swap order above
* * @endcode
* 3. Memcpy can be used when data is little-endian. Perform endian-swapping if necessary.
* *
* 3. memcpy or std::copy can also be used, but watch out if system
* endianness is different from required data endianness.
* Perform endian-swapping if necessary.
* @code
* uint16_t data; * uint16_t data;
* memcpy(&data,buffer + positionOfTargetByte1,sizeof(data)); * memcpy(&data,buffer + positionOfTargetByte1,sizeof(data));
* data = EndianSwapper::swap(data); * data = EndianSwapper::swap(data); //optional
* @endcode
* *
* When serializing for downlink, the packets are generally serialized assuming * When serializing for downlink, the packets are generally serialized assuming
* big endian data format like seen in TmPacketStored.cpp for example. * big endian data format like seen in TmPacketStored.cpp for example.
* *
* @ingroup serialize * @ingroup serialize
*/ */
template<typename T, int>
class SerializeAdapter_ {
public:
static ReturnValue_t serialize(const T* object, uint8_t** buffer,
size_t* size, const size_t max_size, bool bigEndian) {
size_t ignoredSize = 0;
if (size == NULL) {
size = &ignoredSize;
}
if (sizeof(T) + *size <= max_size) {
T tmp;
if (bigEndian) {
tmp = EndianSwapper::swap<T>(*object);
} else {
tmp = *object;
}
memcpy(*buffer, &tmp, sizeof(T));
*size += sizeof(T);
(*buffer) += sizeof(T);
return HasReturnvaluesIF::RETURN_OK;
} else {
return SerializeIF::BUFFER_TOO_SHORT;
}
}
/**
* Deserialize buffer into object
* @param object [out] Object to be deserialized with buffer data
* @param buffer buffer containing the data. Non-Const pointer to non-const
* pointer to const buffer.
* @param size int32_t type to allow value to be values smaller than 0,
* needed for range/size checking
* @param bigEndian Specify endianness
* @return
*/
ReturnValue_t deSerialize(T* object, const uint8_t** buffer, ssize_t* size,
bool bigEndian) {
T tmp;
*size -= sizeof(T);
if (*size >= 0) {
memcpy(&tmp, *buffer, sizeof(T));
if (bigEndian) {
*object = EndianSwapper::swap<T>(tmp);
} else {
*object = tmp;
}
*buffer += sizeof(T);
return HasReturnvaluesIF::RETURN_OK;
} else {
return SerializeIF::STREAM_TOO_SHORT;
}
}
size_t getSerializedSize(const T * object) {
return sizeof(T);
}
};
template<typename T>
class SerializeAdapter_<T, 1> {
public:
ReturnValue_t serialize(const T* object, uint8_t** buffer, size_t* size,
const size_t max_size, bool bigEndian) const {
size_t ignoredSize = 0;
if (size == NULL) {
size = &ignoredSize;
}
return object->serialize(buffer, size, max_size, bigEndian);
}
size_t getSerializedSize(const T* object) const {
return object->getSerializedSize();
}
ReturnValue_t deSerialize(T* object, const uint8_t** buffer, ssize_t* size,
bool bigEndian) {
return object->deSerialize(buffer, size, bigEndian);
}
};
template<typename T>
class SerializeAdapter {
public:
static ReturnValue_t serialize(const T* object, uint8_t** buffer,
size_t* size, const size_t max_size, bool bigEndian) {
SerializeAdapter_<T, IsDerivedFrom<T, SerializeIF>::Is> adapter;
return adapter.serialize(object, buffer, size, max_size, bigEndian);
}
static uint32_t getSerializedSize(const T* object) {
SerializeAdapter_<T, IsDerivedFrom<T, SerializeIF>::Is> adapter;
return adapter.getSerializedSize(object);
}
static ReturnValue_t deSerialize(T* object, const uint8_t** buffer,
ssize_t* size, bool bigEndian) {
SerializeAdapter_<T, IsDerivedFrom<T, SerializeIF>::Is> adapter;
return adapter.deSerialize(object, buffer, size, bigEndian);
}
};
// No type specification necessary here. // No type specification necessary here.
class AutoSerializeAdapter { class AutoSerializeAdapter {
@ -180,4 +84,24 @@ public:
} }
}; };
template<typename T>
class SerializeAdapter {
public:
static ReturnValue_t serialize(const T* object, uint8_t** buffer,
size_t* size, const size_t max_size, bool bigEndian) {
SerializeAdapter_<T, IsDerivedFrom<T, SerializeIF>::Is> adapter;
return adapter.serialize(object, buffer, size, max_size, bigEndian);
}
static uint32_t getSerializedSize(const T* object) {
SerializeAdapter_<T, IsDerivedFrom<T, SerializeIF>::Is> adapter;
return adapter.getSerializedSize(object);
}
static ReturnValue_t deSerialize(T* object, const uint8_t** buffer,
ssize_t* size, bool bigEndian) {
SerializeAdapter_<T, IsDerivedFrom<T, SerializeIF>::Is> adapter;
return adapter.deSerialize(object, buffer, size, bigEndian);
}
};
#endif /* SERIALIZEADAPTER_H_ */ #endif /* SERIALIZEADAPTER_H_ */

View File

@ -0,0 +1,114 @@
/**
* @file SerializeAdapterInternal.h
*
* @date 13.04.2020
* @author R. Mueller
*/
#ifndef FRAMEWORK_SERIALIZE_SERIALIZEADAPTERINTERNAL_H_
#define FRAMEWORK_SERIALIZE_SERIALIZEADAPTERINTERNAL_H_
#include <framework/returnvalues/HasReturnvaluesIF.h>
#include <framework/container/IsDerivedFrom.h>
#include <framework/serialize/EndianSwapper.h>
/**
* This template specialization will be chosen for fundamental types.
* @tparam T
* @tparam
*/
template<typename T, int>
class SerializeAdapter_ {
public:
/**
*
* @param object
* @param buffer
* @param size
* @param max_size
* @param bigEndian
* @return
*/
static ReturnValue_t serialize(const T* object, uint8_t** buffer,
size_t* size, const size_t max_size, bool bigEndian) {
size_t ignoredSize = 0;
if (size == nullptr) {
size = &ignoredSize;
}
if (sizeof(T) + *size <= max_size) {
T tmp;
if (bigEndian) {
tmp = EndianSwapper::swap<T>(*object);
} else {
tmp = *object;
}
memcpy(*buffer, &tmp, sizeof(T));
*size += sizeof(T);
(*buffer) += sizeof(T);
return HasReturnvaluesIF::RETURN_OK;
} else {
return SerializeIF::BUFFER_TOO_SHORT;
}
}
/**
* Deserialize buffer into object
* @param object [out] Object to be deserialized with buffer data
* @param buffer buffer containing the data. Non-Const pointer to non-const
* pointer to const buffer.
* @param size int32_t type to allow value to be values smaller than 0,
* needed for range/size checking
* @param bigEndian Specify endianness
* @return
*/
ReturnValue_t deSerialize(T* object, const uint8_t** buffer, ssize_t* size,
bool bigEndian) {
T tmp;
*size -= sizeof(T);
if (*size >= 0) {
memcpy(&tmp, *buffer, sizeof(T));
if (bigEndian) {
*object = EndianSwapper::swap<T>(tmp);
} else {
*object = tmp;
}
*buffer += sizeof(T);
return HasReturnvaluesIF::RETURN_OK;
} else {
return SerializeIF::STREAM_TOO_SHORT;
}
}
size_t getSerializedSize(const T * object) {
return sizeof(T);
}
};
/**
* This template specialization will be chosen for class derived from
* SerializeIF.
* @tparam T
* @tparam
*/
template<typename T>
class SerializeAdapter_<T, true> {
public:
ReturnValue_t serialize(const T* object, uint8_t** buffer, size_t* size,
const size_t max_size, bool bigEndian) const {
size_t ignoredSize = 0;
if (size == NULL) {
size = &ignoredSize;
}
return object->serialize(buffer, size, max_size, bigEndian);
}
size_t getSerializedSize(const T* object) const {
return object->getSerializedSize();
}
ReturnValue_t deSerialize(T* object, const uint8_t** buffer, ssize_t* size,
bool bigEndian) {
return object->deSerialize(buffer, size, bigEndian);
}
};
#endif /* FRAMEWORK_SERIALIZE_SERIALIZEADAPTERINTERNAL_H_ */

View File

@ -17,7 +17,7 @@ UnitTestClass::UnitTestClass() {
UnitTestClass::~UnitTestClass() { UnitTestClass::~UnitTestClass() {
} }
ReturnValue_t UnitTestClass::performTests() { ReturnValue_t UnitTestClass::perform_tests() {
ReturnValue_t result = test_serialization(); ReturnValue_t result = test_serialization();
if(result != RETURN_OK) { if(result != RETURN_OK) {
return result; return result;
@ -28,7 +28,11 @@ ReturnValue_t UnitTestClass::performTests() {
ReturnValue_t UnitTestClass::test_serialization() { ReturnValue_t UnitTestClass::test_serialization() {
// Here, we test all serialization tools. First test basic cases. // Here, we test all serialization tools. First test basic cases.
ReturnValue_t result = test_autoserialization(); ReturnValue_t result = test_endianness_tools();
if(result != RETURN_OK) {
return result;
}
result = test_autoserialization();
if(result != RETURN_OK) { if(result != RETURN_OK) {
return result; return result;
} }
@ -39,6 +43,47 @@ ReturnValue_t UnitTestClass::test_serialization() {
return RETURN_OK; return RETURN_OK;
} }
ReturnValue_t UnitTestClass::test_endianness_tools() {
test_array[0] = 0;
test_array[1] = 0;
uint16_t two_byte_value = 1;
size_t size = 0;
uint8_t* p_array = test_array.data();
AutoSerializeAdapter::serialize(&two_byte_value, &p_array, &size, 2, false);
// Little endian: Value one on first byte
if(test_array[0] != 1 and test_array[1] != 0) {
return put_error(TestIds::ENDIANNESS_TOOLS);
}
p_array = test_array.data();
size = 0;
AutoSerializeAdapter::serialize(&two_byte_value, &p_array, &size, 2, true);
// Big endian: Value one on second byte
if(test_array[0] != 0 and test_array[1] != 1) {
return put_error(TestIds::ENDIANNESS_TOOLS);
}
// Endianness paameter will be changed later.
// p_array = test_array.data();
// ssize_t ssize = size;
// // Resulting parameter should be big endian
// AutoSerializeAdapter::deSerialize(&two_byte_value,
// const_cast<const uint8_t **>(&p_array), &ssize, true);
// if(two_byte_value != 1) {
// return put_error(TestIds::ENDIANNESS_TOOLS);
// }
//
// ssize = size;
// p_array = test_array.data();
// // Resulting parameter should be little endian
// AutoSerializeAdapter::deSerialize(&two_byte_value,
// const_cast<const uint8_t **>(&p_array), &ssize, false);
// if(two_byte_value != 256) {
// return put_error(TestIds::ENDIANNESS_TOOLS);
// }
return RETURN_OK;
}
ReturnValue_t UnitTestClass::test_autoserialization() { ReturnValue_t UnitTestClass::test_autoserialization() {
current_id = TestIds::AUTO_SERIALIZATION_SIZE; current_id = TestIds::AUTO_SERIALIZATION_SIZE;
// Unit Test getSerializedSize // Unit Test getSerializedSize
@ -142,15 +187,21 @@ ReturnValue_t UnitTestClass::test_autoserialization() {
// double tv_sdouble {-2.2421e19}; // double tv_sdouble {-2.2421e19};
if(test_value_bool != true or tv_uint8 != 5 or tv_uint16 != 283 or if(test_value_bool != true or tv_uint8 != 5 or tv_uint16 != 283 or
tv_uint32 != 929221 or tv_uint64 != 2929329429 or tv_int8 != -16 or tv_uint32 != 929221 or tv_uint64 != 2929329429 or tv_int8 != -16 or
tv_int16 != -829 or tv_int32 != -2312 or tv_float != 8.214921 or tv_int16 != -829 or tv_int32 != -2312)
tv_double != 9.2132142141e8 or tv_sfloat != -922.2321321 or
tv_sdouble != -2.2421e19)
{ {
return put_error(current_id); return put_error(current_id);
} }
if(abs(tv_float - 8.214921) > 0.0001 or
abs(tv_double - 9.2132142141e8) > 0.01 or
abs(tv_sfloat - (-922.2321321)) > 0.0001 or
abs(tv_sdouble - (-2.2421e19)) > 0.01) {
return put_error(current_id);
}
return RETURN_OK; return RETURN_OK;
} }
// TODO: Also test for constant buffers.
ReturnValue_t UnitTestClass::test_serial_buffer_adapter() { ReturnValue_t UnitTestClass::test_serial_buffer_adapter() {
current_id = TestIds::SERIALIZATION_BUFFER_ADAPTER; current_id = TestIds::SERIALIZATION_BUFFER_ADAPTER;
@ -203,6 +254,31 @@ ReturnValue_t UnitTestClass::test_serial_buffer_adapter() {
{ {
return put_error(current_id); return put_error(current_id);
} }
memcpy(&tv_uint16, test_array.data() + 7, sizeof(tv_uint16));
if(tv_uint16 != 16) {
return put_error(current_id);
}
// Serialize with size field
SerialBufferAdapter<uint8_t> tv_serial_buffer_adapter3 =
SerialBufferAdapter<uint8_t>(
const_cast<const uint8_t*>(test_serial_buffer.data()),
test_serial_buffer.size(), false);
serialized_size = 0;
p_array = test_array.data();
AutoSerializeAdapter::serialize(&test_value_bool, &p_array,&serialized_size,
test_array.size(), false);
AutoSerializeAdapter::serialize(&tv_serial_buffer_adapter3, &p_array,
&serialized_size, test_array.size(), false);
AutoSerializeAdapter::serialize(&tv_uint16, &p_array, &serialized_size,
test_array.size(), false);
if(serialized_size != 8 or test_array[0] != true or test_array[1] != 5
or test_array[2] != 4 or test_array[3] != 3 or test_array[4] != 2
or test_array[5] != 1)
{
return put_error(current_id);
}
memcpy(&tv_uint16, test_array.data() + 6, sizeof(tv_uint16)); memcpy(&tv_uint16, test_array.data() + 6, sizeof(tv_uint16));
if(tv_uint16 != 16) { if(tv_uint16 != 16) {
return put_error(current_id); return put_error(current_id);
@ -215,3 +291,4 @@ ReturnValue_t UnitTestClass::put_error(TestIds current_id) {
<< static_cast<uint32_t>(current_id) << "\r\n" << std::flush; << static_cast<uint32_t>(current_id) << "\r\n" << std::flush;
return RETURN_FAILED; return RETURN_FAILED;
} }

View File

@ -19,6 +19,8 @@
* 1. TMTC Services * 1. TMTC Services
* 2. Serialization tools * 2. Serialization tools
* 3. Framework internal algorithms * 3. Framework internal algorithms
*
* TODO: Maybe use specialized framework.
*/ */
class UnitTestClass: public HasReturnvaluesIF { class UnitTestClass: public HasReturnvaluesIF {
@ -27,12 +29,13 @@ public:
virtual~ UnitTestClass(); virtual~ UnitTestClass();
enum class TestIds { enum class TestIds {
AUTO_SERIALIZATION_SIZE = 0, ENDIANNESS_TOOLS,
AUTO_SERIALIZATION_SERIALIZE = 1, AUTO_SERIALIZATION_SIZE,
AUTO_SERIALIZATION_DESERIALIZE = 2, AUTO_SERIALIZATION_SERIALIZE,
SERIALIZATION_BUFFER_ADAPTER = 3, AUTO_SERIALIZATION_DESERIALIZE ,
SERIALIZATION_FIXED_ARRAY_LIST_ADAPTER = 4, SERIALIZATION_BUFFER_ADAPTER,
SERIALIZATION_COMBINATION = 5, SERIALIZATION_FIXED_ARRAY_LIST_ADAPTER,
SERIALIZATION_COMBINATION,
TMTC_SERVICES , TMTC_SERVICES ,
MISC MISC
}; };
@ -41,11 +44,12 @@ public:
* Some function which calls all other tests * Some function which calls all other tests
* @return * @return
*/ */
ReturnValue_t performTests(); ReturnValue_t perform_tests();
ReturnValue_t test_serialization(); ReturnValue_t test_serialization();
ReturnValue_t test_autoserialization(); ReturnValue_t test_autoserialization();
ReturnValue_t test_serial_buffer_adapter(); ReturnValue_t test_serial_buffer_adapter();
ReturnValue_t test_endianness_tools();
private: private:
uint32_t errorCounter = 0; uint32_t errorCounter = 0;
TestIds current_id = TestIds::MISC; TestIds current_id = TestIds::MISC;