You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

557 lines
16 KiB

#include <iostream>
#include <cctype>
#include <pEp/pEpEngine.h>
#include <pEp/message_api.h>
#include <pEp/keymanagement.h>
#include <pEp/identity_list.h>
#include <pEp/utils.hh>
#include <pEp/pEpLog.hh>
#include <pEp/inspect.hh>
#include <type_traits>
namespace pEp {
char* alloc_str(const std::string& str)
{
char* ret = strdup(str.c_str());
pEpLog(CXX::Inspect::all(ret));
return ret;
}
template<class T>
void free(T ptr_type)
{
pEpLog(CXX::Inspect::all(ptr_type));
::pEp_free(ptr_type);
}
template<>
void free(char* ptr_type)
{
pEpLog(CXX::Inspect::all(ptr_type));
::pEp_free(ptr_type);
}
template<>
void free(::pEp_identity* ptr_type)
{
pEpLog(CXX::Inspect::all(ptr_type));
::pEp_free(ptr_type);
}
//---------------------------------------------------------------------------------------------
#define EXSTR(msg) std::string(__FUNCTION__) + " - " + msg
template<class T>
class POD {
static_assert(std::is_pod<T>::value && !std::is_pointer<T>::value, "not an integral");
public:
POD() = delete;
explicit POD(T* pod_p)
{
if (pod_p == nullptr) {
throw Exception{ EXSTR("cant init on a nullptr") };
}
_pod_p = pod_p;
pEpLogClass(to_string() + " - taking ownership");
}
// make a copy
POD& operator=(T value)
{
pEpLogClass("Before: " + to_string() + " - new val: '" + CXX::Inspect::val(value) + "'");
*_pod_p = value;
pEpLogClass("After: " + to_string());
return *this;
}
// return a copy
operator T() const
{
return *_pod_p;
}
// return address of the wrappee
T* data()
{
return _pod_p;
}
// return address of the wrappee (const)
const T* c_data() const
{
return _pod_p;
}
std::string to_string() const
{
std::string ret{ "[" + CXX::Inspect::all(_pod_p) + "]" };
return ret;
}
static bool log_enabled;
private:
T* _pod_p{ nullptr };
Adapter::pEpLog::pEpLogger logger{ "pEp::POD", log_enabled };
Adapter::pEpLog::pEpLogger& m4gic_logger_n4me = logger;
class Exception : public std::runtime_error {
public:
explicit Exception(const std::string& msg) : std::runtime_error(msg) {}
};
};
template<class T>
bool pEp::POD<T>::log_enabled{ true };
template<class T>
std::ostream& operator<<(std::ostream& o, pEp::POD<T> pEpEnum)
{
return o << (int)pEpEnum;
}
//---------------------------------------------------------------------------------------------
// Manages a char* to appear like a std::string
// char* c_str
// c_str MUST point to either:
// * NULL - means there is no value/mem alloc yet
// * dyn allocated memory
// in case the mem is already allocated, there must be 2 modes
// * take ownership (and free it)
// * dont take ownership, only provide a C++ interface
// There must be the option to construct the object before the wrapped c_str (char*)
// is known. This leads to a pEp::String object in invalid state, and it must be initialized using
// init(). Otherwise all(most) functions will throw.
// init() can only be called once, will throw otherwise.
// NON-owning mode, just does not free the string when it dies, owning mode does.
// Thats the only difference.
class String {
public:
String() = default;
// Best to use this constructor, as the object is in a valid state when the wrapped
// c_str (char*) is known
String(bool take_ownership, char** c_str_pp)
{
init(take_ownership, c_str_pp);
}
explicit String(bool take_ownership, char** c_str_pp, const std::string& init_val)
{
init(take_ownership, c_str_pp, init_val);
}
~String()
{
if (_is_owning_mode) {
_free();
}
}
void init(bool take_ownership, char** c_str_pp, const std::string& init_val)
{
init(take_ownership, c_str_pp);
this->operator=(init_val);
}
void init(bool take_ownership, char** c_str_pp)
{
if (c_str_pp == nullptr) {
throw Exception{ EXSTR("cant init on a nullptr") };
}
if (_is_initialized) {
throw Exception{ EXSTR("double initialization") };
}
// init
_c_str_pp = c_str_pp;
_c_str_p = *_c_str_pp;
_is_initialized = true;
_is_owning_mode = take_ownership;
pEpLogClass(to_string() + " - init " + (take_ownership ? "/taking ownership" : ""));
}
// make a copy
String& operator=(const std::string& str)
{
pEpLogClass("Before: " + to_string() + " - new val: '" + pEp::Utils::clip(str, 30) + "'");
if (_c_str_pp == nullptr) {
throw Exception{ EXSTR("invalid state") };
}
// DEALLOCATION
_free();
// ALLOCATION
if (str.empty()) {
// if the new value is empty str, lets point to nothing
*_c_str_pp = nullptr;
_c_str_p = *_c_str_pp;
} else {
*_c_str_pp = pEp::alloc_str(str);
_c_str_p = *_c_str_pp;
}
pEpLogClass("After: " + to_string());
return *this;
}
// return a copy of whatever c_str currently is (maybe created by us, maybe changed meanwhile)
operator std::string() const
{
if (_c_str_pp == nullptr) {
throw Exception{ EXSTR("invalid state") };
}
if (*_c_str_pp != nullptr) {
return { *_c_str_pp };
}
return {};
}
char** data()
{
return _c_str_pp;
}
const char* c_data() const
{
if (_c_str_pp == nullptr) {
throw Exception{ EXSTR("invalid state") };
}
return *_c_str_pp;
}
std::string to_string() const
{
if (_c_str_pp == nullptr) {
throw Exception{ EXSTR("invalid state") };
}
std::string ret{ "[" + CXX::Inspect::all(_c_str_pp) + " / " +
CXX::Inspect::all(*_c_str_pp) + "]" };
return ret;
}
static bool log_enabled;
private:
// TODO: this is dodgy, do we really need _c_str_pp AND _c_str_p???
void _free()
{
// if the c_str points to a different address now, than the one we created
if (*_c_str_pp != _c_str_p) {
// a new string has been created, and we need to free it as well
pEpLog("raw access string change detected");
pEp::free(*_c_str_pp);
} else {
// WE ASSUME THAT:
// if the char* has been replaced, it has been freed, as well
if (_c_str_p != nullptr) {
pEp::free(_c_str_p);
}
}
}
bool _is_initialized{ false };
bool _is_owning_mode{ false };
char** _c_str_pp{ nullptr };
char* _c_str_p{ nullptr };
Adapter::pEpLog::pEpLogger logger{ "pEp::String", log_enabled };
Adapter::pEpLog::pEpLogger& m4gic_logger_n4me = logger;
class Exception : public std::runtime_error {
public:
explicit Exception(const std::string& msg) : std::runtime_error(msg) {}
};
};
bool pEp::String::log_enabled{ true };
std::ostream& operator<<(std::ostream& o, const pEp::String& pEpStr)
{
return o << std::string(pEpStr);
}
//---------------------------------------------------------------------------------------------
// TOOD:
// ctor not exception safe
class Identity {
public:
Identity(
const std::string& address = "",
const std::string& username = "",
const std::string& user_id = "",
const std::string& fpr = "")
{
pEpLogClass("called");
_wrappee = ::new_identity(nullptr, nullptr, nullptr, nullptr);
// set the pEp::String wrapper underlying c_str
this->address.init(true, &_wrappee->address, address);
this->username.init(true, &_wrappee->username, username);
this->user_id.init(true, &_wrappee->user_id, user_id);
this->fpr.init(true, &_wrappee->fpr, fpr);
}
~Identity()
{
_free();
}
pEp::String address{};
pEp::String username{};
pEp::String user_id{};
pEp::String fpr{};
operator ::pEp_identity*()
{
return _wrappee;
}
static bool log_enabled;
private:
void _free()
{
// pEp::free(_wrappee);
}
::pEp_identity* _wrappee{ nullptr };
Adapter::pEpLog::pEpLogger logger{ "IdentWrappySP", log_enabled };
Adapter::pEpLog::pEpLogger& m4gic_logger_n4me = logger;
};
bool Identity::log_enabled{ true };
} // namespace pEp
::PEP_SESSION session;
void test_getters(char** c_str_p, pEp::String& pstr, const std::string& expected)
{
pEpLog("to_string(): " + pstr.to_string());
// compare addresses
pEpLog("addresses == c_str_p");
assert(pstr.c_data() == *c_str_p);
assert(pstr.data() == c_str_p);
// compare values
if (*c_str_p != nullptr) {
pEpLog("operator std::string(): " + std::string(pstr));
assert(std::string(pstr) == std::string(*c_str_p));
assert(std::string(pstr) == expected);
// will segfault with nullptr, and this is correct
pEpLog("data(): " + std::string(*pstr.data()));
assert(std::string(*pstr.data()) == std::string(*c_str_p));
assert(std::string(*pstr.data()) == expected);
pEpLog("c_data(): " + std::string(pstr.c_data()));
assert(std::string(pstr.c_data()) == std::string(*c_str_p));
assert(std::string(pstr.c_data()) == expected);
} else {
std::string tmp{ pstr };
pEpLog("operator std::string(): " + tmp);
assert(tmp.empty());
}
}
void test_assign(char** c_str_p, pEp::String& pstr)
{
{
pEpLogH2("assign operator");
std::string new_val{ "assign operator" };
pstr = new_val;
test_getters(c_str_p, pstr, new_val);
}
{
pEpLogH2("raw c_str assign");
std::string new_val{ "raw c_str assign" };
free(*c_str_p);
*c_str_p = strdup(new_val.c_str());
test_getters(c_str_p, pstr, new_val);
}
}
void test_getters(int* c_int_p, pEp::POD<int>& pint, int expected)
{
pEpLog("to_string(): " + pint.to_string());
// compare addresses
pEpLog("addresses == c_int_p");
assert(pint.c_data() == c_int_p);
assert(pint.data() == c_int_p);
// compare values
if (c_int_p != nullptr) {
pEpLog("operator int(): " + std::to_string(pint));
assert(pint == *c_int_p);
assert(pint == expected);
// will segfault with nullptr, and this is correct
pEpLog("data(): " + std::to_string(*pint.data()));
assert(*pint.data() == *c_int_p);
assert(*pint.data() == expected);
pEpLog("c_data(): " + std::to_string(*pint.c_data()));
assert(*pint.c_data() == *c_int_p);
assert(*pint.c_data() == expected);
}
}
void test_assign(int* c_int_p, pEp::POD<int>& pint)
{
{
pEpLogH2("assign operator");
int new_val = 23;
pint = new_val;
test_getters(c_int_p, pint, new_val);
}
{
pEpLogH2("raw c_str assign");
int new_val = 23;
*c_int_p = new_val;
test_getters(c_int_p, pint, new_val);
}
}
// We are testing wrappers for the c-datatypes:
// * integral e.g (int) here
// * enum
// * c-string
// * struct
// Those are all considered POD-types
extern "C" {
typedef enum
{
ONE,
TWO,
THREE
} Test_enum;
typedef struct {
int c_int;
Test_enum c_enum;
char* c_str;
} Test_struct;
}
int main()
{
// c-types are always POD
// we need to handle pointer types and value types differently
// pointer types need to be memory-managed (heap)
// value types are being copied around (stack)
static_assert(std::is_pod<int>::value && !std::is_pointer<int>::value, "not an integral");
static_assert(
std::is_pod<Test_enum>::value && !std::is_pointer<Test_enum>::value,
"not an integral");
static_assert(
std::is_pod<Test_struct>::value && !std::is_pointer<Test_struct>::value,
"not an integral");
// static_assert(std::is_pod<char*>::value && !std::is_pointer<char*>::value, "not an integral");
// pEp::Utils::readKey();
pEp::Adapter::pEpLog::set_enabled(true);
// POD
if (1) {
// VALID USAGE
int init_val = 0;
// new pEp::POD on int
{
pEpLogH1("new pEp::POD on int");
int c_int = init_val;
pEp::POD<int> pint(&c_int);
test_getters(&c_int, pint, init_val);
test_assign(&c_int, pint);
}
}
// String
// pEp::String::log_enabled = false;
if (0) {
//TODO: Test non-owning mode
// INVALID USAGE
// char* c_str_u; // uninitialized == INVALID
// undefined behaviour, most likely "pointer being freed was not allocated"
// {
// char* c_str;
// pEp::String pstr(c_str);
// }
// char* c_str_s = "fdsfs"; // statically initialized == INVALID
// {
// char ca[4] = { 'p', 'E', 'p'};
// char* c_str = ca;
// pEp::String pstr(c_str);
// }
// VALID USAGE
// new pEp::String on char* pointing to NULL
{
pEpLogH1("new pEp::String on char* pointing to NULL");
char* c_str = NULL; // nullptr
pEp::String pstr(true, &c_str);
test_getters(&c_str, pstr, "");
test_assign(&c_str, pstr);
}
std::string init_val{ "initialized c string" };
// new pEp::String on already initalized char*
{
pEpLogH1("new pEp::String on already initalized char*");
char* c_str = strdup(init_val.c_str());
pEp::String pstr(true, &c_str);
test_getters(&c_str, pstr, init_val);
test_assign(&c_str, pstr);
}
// initialize()
{
pEpLogH1("init()");
pEp::String pstr{};
char* c_str = strdup(init_val.c_str());
//TODO: PITYASSERT_THROWS
pstr.init(true, &c_str);
test_getters(&c_str, pstr, init_val);
test_assign(&c_str, pstr);
}
}
if (0) {
setenv("HOME", ".", 1);
::init(&session, nullptr, nullptr, nullptr);
// create identity
pEp::Identity id1{ "wrong@entry.lol", "wrong", "23", "INVA_FPR" };
pEpLog(id1);
id1.username = "alice";
id1.address = "alice@peptest.org";
pEpLog(id1.address);
pEpLog(id1.username);
::myself(session, id1);
pEpLog(id1);
pEp::Identity id2{ "bob" };
::update_identity(session, id2);
pEpLog(id2);
}
}