#include #include #include #include #include #include #include #include #include #include namespace pEp { char* alloc_str(const std::string& str) { char* ret = strdup(str.c_str()); pEpLog(CXX::Inspect::all(ret)); return ret; } template 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 POD { static_assert(std::is_pod::value && !std::is_pointer::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 bool pEp::POD::log_enabled{ true }; template std::ostream& operator<<(std::ostream& o, pEp::POD 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& 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& 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::value && !std::is_pointer::value, "not an integral"); static_assert( std::is_pod::value && !std::is_pointer::value, "not an integral"); static_assert( std::is_pod::value && !std::is_pointer::value, "not an integral"); // static_assert(std::is_pod::value && !std::is_pointer::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 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); } }