patterncppMinor
Converting to and from Base64 using OpenSSL API
Viewed 0 times
opensslusingbase64andconvertingfromapi
Problem
I am trying to get a working and good version of base64 conversions using OpenSSL. My alternatives were an implementation from an answer on SO and Boost (which I didn't chose because I read that is pretty broken).
I will need OpenSSL in my project and I thought it would be a good idea to use it for base64 too so I can get use with the API.
For start, let's just assume:
This is the header (
and here is the source file:
```
#include
#include
#include
#include
#include
#include
#include "base64.hpp"
namespace redi
{
namespace util
{
namespace base64
{
namespace
{
void rawBase64Encode(const std::uint8_t input, std::size_t inputLen, char& output, std::size_t& outputLen)
{
BIO* bio;
BIO* b64;
BUF_MEM* bufferPtr;
b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
BIO_write(bio, input, static_cast(inputLen));
BIO_flush(bio);
BIO_get_mem_ptr(bio, std::addressof(bufferPtr));
BIO_set_close(bio, BIO_NOCLOSE);
output = bufferPtr->data;
outputLen = bufferPtr->length;
free(bufferPtr);
BIO_free_all(bio);
}
std::size_t calcDecodeLength(const std::uint8_t* input, std::size_t len)
{
std::size_t padding = 0;
if (input[len - 1] == '=')
{
++padding;
if (input[len - 2] == '=') ++padding;
}
return len * 3 / 4 -
I will need OpenSSL in my project and I thought it would be a good idea to use it for base64 too so I can get use with the API.
For start, let's just assume:
using ByteBuffer = std::vector;This is the header (
redi is the main namespace of my project):#ifndef REDI_BASE64_HPP
#define REDI_BASE64_HPP
#include "../bytebuffer.hpp"
namespace redi
{
namespace util
{
namespace base64
{
ByteBuffer encode(const ByteBuffer& data);
ByteBuffer encode(const std::string& str);
std::string encodeToString(const std::string& str);
ByteBuffer decode(const ByteBuffer& data);
ByteBuffer decode(const std::string& str);
std::string decodeToString(const ByteBuffer& data);
std::string decodeToString(const std::string& str);
} // namespace base64
} // namespace util
} // namespace redi
#endif // REDI_BASE64_HPPand here is the source file:
```
#include
#include
#include
#include
#include
#include
#include "base64.hpp"
namespace redi
{
namespace util
{
namespace base64
{
namespace
{
void rawBase64Encode(const std::uint8_t input, std::size_t inputLen, char& output, std::size_t& outputLen)
{
BIO* bio;
BIO* b64;
BUF_MEM* bufferPtr;
b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
BIO_write(bio, input, static_cast(inputLen));
BIO_flush(bio);
BIO_get_mem_ptr(bio, std::addressof(bufferPtr));
BIO_set_close(bio, BIO_NOCLOSE);
output = bufferPtr->data;
outputLen = bufferPtr->length;
free(bufferPtr);
BIO_free_all(bio);
}
std::size_t calcDecodeLength(const std::uint8_t* input, std::size_t len)
{
std::size_t padding = 0;
if (input[len - 1] == '=')
{
++padding;
if (input[len - 2] == '=') ++padding;
}
return len * 3 / 4 -
Solution
I would like to know if is a good design
A few points about your design from a c++ perspective:
From the point of OOP view, you should rather use classes than free functions to wrap / encapsulate the C API:
Using classes as mentioned above you should prefer specializing classes (may be interface implementations) rather introducing a new
Same for the function naming:
and
will become
and
as (probably
The
obfuscates more, and leaves research efforts to API user, than it helps to save typing more code for the API provider.
Also I wouldn't provide that separately from the encoding / decoding features, but may be as a
Your code all depends on a
As mentioned above rather encapsulate that into a class. May be even into a common base class used for both, encoding and decoding:
As suggested in this answer are you sure you really need the OpenSS
As mentioned in my comment you probably don't need to have copies to interact with the OppenSSL library.
Using the
A few points about your design from a c++ perspective:
- Usage of free functions instead of classes
From the point of OOP view, you should rather use classes than free functions to wrap / encapsulate the C API:
namespace redi
{
namespace util
{
namespace base64
{
class Encoder {
public:
Encoder(const ByteBuffer& data);
Encoder(const std::string& str);
ByteBuffer operator(); // Do the encoding
std::string to_string();
};
class Decoder {
public:
Decoder(const ByteBuffer& data);
Decoder(const std::string& str);
ByteBuffer operator(); // Do the decoding
std::string to_string();
};
} // namespace base64
} // namespace util
} // namespace redi- Prefer class specializations over introducing a
namespace
Using classes as mentioned above you should prefer specializing classes (may be interface implementations) rather introducing a new
namespace for a specific data category:namespace redi
{
namespace util
{
class Base64Encoder;
// ^^^^^^
class Base64Decoder;
// ^^^^^^
} // namespace util
} // namespace rediSame for the function naming:
void rawBase64Encode(const std::uint8_t* input, std::size_t inputLen, char*& output, std::size_t& outputLen);and
void rawBase64Decode(const std::uint8_t* input, std::size_t inputLen, T& container);will become
void Base64Encoder::rawEncode();and
void Base64Decoder::rawDecode();as (probably
private) class member functions.- Don't use
usingortypedefunnecessarily in published API's
The
using ByteBuffer = std::vector;obfuscates more, and leaves research efforts to API user, than it helps to save typing more code for the API provider.
Also I wouldn't provide that separately from the encoding / decoding features, but may be as a
public typedef provided with classes.- In c++ encapsulate c-API handles
Your code all depends on a
BIO* bio; API handle, at least internallyAs mentioned above rather encapsulate that into a class. May be even into a common base class used for both, encoding and decoding:
class Base64EncoderDecoderBase {
protected:
Bio* bio;
};
class Base64Encode : public Base64EncoderDecoderBase {
// ...
};
class Base64Decode : public Base64EncoderDecoderBase {
// ...
};- Are you sure you need to use OpenSSL?
As suggested in this answer are you sure you really need the OpenSS
Bio structure, or that could be left to a simpler solution?- You probably don't need to copy
As mentioned in my comment you probably don't need to have copies to interact with the OppenSSL library.
Using the
data()members of these container classes directly, may be helpful to avoid that (not sure for the Bio* context though).Code Snippets
namespace redi
{
namespace util
{
namespace base64
{
class Encoder {
public:
Encoder(const ByteBuffer& data);
Encoder(const std::string& str);
ByteBuffer operator(); // Do the encoding
std::string to_string();
};
class Decoder {
public:
Decoder(const ByteBuffer& data);
Decoder(const std::string& str);
ByteBuffer operator(); // Do the decoding
std::string to_string();
};
} // namespace base64
} // namespace util
} // namespace redinamespace redi
{
namespace util
{
class Base64Encoder;
// ^^^^^^
class Base64Decoder;
// ^^^^^^
} // namespace util
} // namespace redivoid rawBase64Encode(const std::uint8_t* input, std::size_t inputLen, char*& output, std::size_t& outputLen);void rawBase64Decode(const std::uint8_t* input, std::size_t inputLen, T& container);void Base64Encoder::rawEncode();Context
StackExchange Code Review Q#151805, answer score: 3
Revisions (0)
No revisions yet.