patterncppMinor
TLV implementation in C++
Viewed 0 times
implementationtlvstackoverflow
Problem
I tried implementing TLV(https://en.wikipedia.org/wiki/Type-length-value).
Actually my implementation is more than TLV because it supports additional "Tag" elements (which you can use for naming certain TLV objects) for TLV objects.
My goal is to serialize the array of TLV objects to byte array and vice versa (this I didn't implement yet).
But I wrote class which handles single TLV and their serialization/deserialization. If this class is OK I believe it is much easier to extend it to arrays.
Here is implementation. Feel free to comment.
Btw:
PS. So basically single TLV object is a data structure which can either hold: a numeric type, string, or byte array, a type variable indicating which type of data it is, and a tag name indicating what value it is e.g. "phone number".
.cpp file:
```
#include "stdafx.h"
#include "TLVObject.h"
TLVObject::TLVObject(void)
{
clearMembers();
}
TLVObject::~TLVObject(void)
{
}
//////////////////////////////////////
// Integers to byte arrays
vector TLVObject::UINT16ToByteArrayLE(uint16_t paramInt)
{
vector arrayOfByte(2);
for (int i = 0; i > (i * 8)) & 0xFF);
return arrayOfByte;
}
vector TLVObject::UINT32ToByteArrayLE(uint32_t paramInt)
{
vector arrayOfByte(4);
for (int i = 0; i > (i * 8)) & 0xFF);
return arrayOfByte;
}
vector TLVObject::UINT64ToByteArrayLE(uint64_t paramInt)
{
vector arrayOfByte(8);
for (int i = 0; i > (i * 8)) & 0xFF);
return arrayOfByte;
}
/////////////////////////////////////////////////
// Byte array to integers
uint16_t TLVObject::LEToUINT16(vector value)
{
if(value.size() != 2)
throw exception("LEToUINT16: Wrong size");
return (uint16_t)(((uint16_t)value[1] value)
{
if(value.size() != 4)
throw exception("LEToUINT32: Wrong size");
return (uint32_t)(((uint32_t)value[3] value)
{
if(value.size() != 8)
throw exception("L
Actually my implementation is more than TLV because it supports additional "Tag" elements (which you can use for naming certain TLV objects) for TLV objects.
My goal is to serialize the array of TLV objects to byte array and vice versa (this I didn't implement yet).
But I wrote class which handles single TLV and their serialization/deserialization. If this class is OK I believe it is much easier to extend it to arrays.
Here is implementation. Feel free to comment.
Btw:
- the class assumes little endian encoding for integers
- and no unicode strings are supported
PS. So basically single TLV object is a data structure which can either hold: a numeric type, string, or byte array, a type variable indicating which type of data it is, and a tag name indicating what value it is e.g. "phone number".
.cpp file:
```
#include "stdafx.h"
#include "TLVObject.h"
TLVObject::TLVObject(void)
{
clearMembers();
}
TLVObject::~TLVObject(void)
{
}
//////////////////////////////////////
// Integers to byte arrays
vector TLVObject::UINT16ToByteArrayLE(uint16_t paramInt)
{
vector arrayOfByte(2);
for (int i = 0; i > (i * 8)) & 0xFF);
return arrayOfByte;
}
vector TLVObject::UINT32ToByteArrayLE(uint32_t paramInt)
{
vector arrayOfByte(4);
for (int i = 0; i > (i * 8)) & 0xFF);
return arrayOfByte;
}
vector TLVObject::UINT64ToByteArrayLE(uint64_t paramInt)
{
vector arrayOfByte(8);
for (int i = 0; i > (i * 8)) & 0xFF);
return arrayOfByte;
}
/////////////////////////////////////////////////
// Byte array to integers
uint16_t TLVObject::LEToUINT16(vector value)
{
if(value.size() != 2)
throw exception("LEToUINT16: Wrong size");
return (uint16_t)(((uint16_t)value[1] value)
{
if(value.size() != 4)
throw exception("LEToUINT32: Wrong size");
return (uint32_t)(((uint32_t)value[3] value)
{
if(value.size() != 8)
throw exception("L
Solution
I see a number of things that may help you improve your code.
Don't abuse
Putting
Use all required
The code uses
And not
While the code may compile as you have it, it's solely due to the implementation details of the particular compiler and library you are using. It can't be relied on and can (and does) change from version to version of even a single compiler on a single platform. You can use a reference such as http://en.cppreference.com to make sure you know which standard header to include for each standard function.
Use polymorphism
The current design has each
Consider ASN.1
There is a standard called ASN.1 that already describes ways of encoding many of these types. Instead of re-inventing your own tags, you may find it useful to use the ones defined by ASN.1.
Use const where practical
Methods that do not modify the underlying object, should be declared
Pass by const reference
Several places in the code, such as
This avoids copying into a new vector and signals the compiler that the passed value will not be altered.
Simplify the code
There are some overly complex pieces of code in this. For example, we have this:
That would be shorter, faster, and more portable if written like this instead:
Creating the temporary vector, passing it by value and then destroying both copies means two constructor and two destructor calls which are wholly unnecessary and only slows down the code.
About type conversions: Note that the arithmetic operators (including
Prefer
Rather than using a
It doesn't make a lot of difference here, but generally the advantage is that the value has an associated type.
Eliminate unused variables
The
Don't abuse
using namespace stdPutting
using namespace std at the top of every program is a bad habit that you'd do well to avoid. It is particularly bad to put it into a header file, so please don't do that. Instead, simply specify the namespace where you use it, so cout becomes std::cout, endl becomes std::endl and so forth.Use all required
#includesThe code uses
std::string and appears to usestd::exception but doesn't include the corresponding headers. The code should have:#include
#include And not
#include While the code may compile as you have it, it's solely due to the implementation details of the particular compiler and library you are using. It can't be relied on and can (and does) change from version to version of even a single compiler on a single platform. You can use a reference such as http://en.cppreference.com to make sure you know which standard header to include for each standard function.
Use polymorphism
The current design has each
TLVObject having one of each possible interpretation embedded within it. A more C++-style design would be to have a single base class and then derive the particular types from that base class. So for example, you might still have TLVObject as the base class, but then derive TLVByte, TLVWord, etc. from that. Not only would that make the interface cleaner, but it would simplify adding new types.Consider ASN.1
There is a standard called ASN.1 that already describes ways of encoding many of these types. Instead of re-inventing your own tags, you may find it useful to use the ones defined by ASN.1.
Use const where practical
Methods that do not modify the underlying object, should be declared
const:Pass by const reference
Several places in the code, such as
TLVObject::LEToUINT64(vector value) take their parameters by value. Instead, they could likely be made more efficient by passing a const reference, as in:TLVObject::LEToUINT64(const vector &value)This avoids copying into a new vector and signals the compiler that the passed value will not be altered.
Simplify the code
There are some overly complex pieces of code in this. For example, we have this:
// Get tag name size.
vector tmp;
tmp.push_back(value.at(1));
tmp.push_back(value.at(2));
uint16_t tagLength = LEToUINT16(tmp);That would be shorter, faster, and more portable if written like this instead:
uint16_t tagLength = value.at(2) << 8 | value.at(1);Creating the temporary vector, passing it by value and then destroying both copies means two constructor and two destructor calls which are wholly unnecessary and only slows down the code.
About type conversions: Note that the arithmetic operators (including
<< and |) don't operate on integral values smaller than int. What happens is automatic type conversion to either int or unsigned so no explicit cast is necessary.Prefer
constexpr to old-style #defineRather than using a
#define for MAXTAGLENGTH the code could use a constexpr:constexpr std::size_t MAXTAGLENGTH = 20000;It doesn't make a lot of difference here, but generally the advantage is that the value has an associated type.
Eliminate unused variables
The
valueSize variable in Deserialize is not used. That's quite strange considering the number of lines of code dedicated to extracting it.Code Snippets
#include <string>
#include <stdexcept>#include <string.h>TLVObject::LEToUINT64(const vector<uint8_t> &value)// Get tag name size.
vector<uint8_t> tmp;
tmp.push_back(value.at(1));
tmp.push_back(value.at(2));
uint16_t tagLength = LEToUINT16(tmp);uint16_t tagLength = value.at(2) << 8 | value.at(1);Context
StackExchange Code Review Q#115395, answer score: 5
Revisions (0)
No revisions yet.