#include #include #include #include #include #include #include #include #include #include #include #include "../examples/libc99/libc99.h" 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, "only POD value types supported"); public: POD() { pEpLogClass("called"); }; explicit POD(T* pod_p, const T& init_val) { bind(pod_p, init_val); } // Best to use this constructor, as the object is in a valid state when the wrapped // c_str (char*) is known explicit POD(T* pod_p) { bind(pod_p); } void bind(T* pod_p, const T& init_val) { bind(pod_p); this->operator=(init_val); } void bind(T* pod_p) { if (pod_p == nullptr) { throw Exception{ EXSTR("cant bind on a nullptr") }; } if (_is_initialized) { throw Exception{ EXSTR("can only bind once") }; } // init _pod_p = pod_p; _is_initialized = true; pEpLogClass(to_string()); } // 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: bool _is_initialized{ false }; 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 pEpPOD) { return o << (T)pEpPOD; } //--------------------------------------------------------------------------------------------- // 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 // bind(). Otherwise all(most) functions will throw. // bind() 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() { pEpLogClass("called"); } // Best to use this constructor, as the object is in a valid state when the wrapped // c_str (char*) is known String(bool is_owner, char** c_str_pp) { bind(is_owner, c_str_pp); } String(bool is_owner, char** c_str_pp, const std::string& init_val) { bind(is_owner, c_str_pp, init_val); } void bind(bool is_owner, char** c_str_pp, const std::string& init_val) { bind(is_owner, c_str_pp); this->operator=(init_val); } // bind and set ownership void bind(bool is_owner, char** c_str_pp) { if (c_str_pp == nullptr) { throw Exception{ EXSTR("cant bind on a nullptr") }; } if (_is_bound) { throw Exception{ EXSTR("can only be bound once") }; } // init _c_str_pp = c_str_pp; _c_str_p = *_c_str_pp; _is_bound = true; pEpLogClass(to_string()); set_owner(is_owner); //if the c_str_p is nullptr then init with "" if (*_c_str_pp == nullptr) { this->operator=(""); } } ~String() { if (_is_owner) { _free(); } } // 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 *_c_str_pp = _copy(str); 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; } bool operator==(const pEp::String& pstr) const { return *(pstr.c_data()) == (*c_data()); } bool operator!=(const pEp::String& pstr) const { return !(*this == pstr); } 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; } void set_owner(bool is_owner) { pEpLogClass(std::to_string(is_owner)); _is_owner = is_owner; } bool is_owner() const { return _is_owner; } static bool log_enabled; private: // TODO: this is dodgy, do we really need _c_str_pp AND _c_str_p??? void _free() { pEpLogClass("called"); // 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 pEpLogClass("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_bound{ false }; bool _is_owner{ 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); } } // namespace pEp 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); } } 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); } } namespace pEp { // TOOD: // ctor not exception safe // You can create an instance of a c-struct or you can wrap an already existing c-struct // In both cases, you can define if the wrapper owns the instance, or not. // If if owns it, it will free() it when the wrapper dies, otherwise it doesnt. template class PODStruct { public: PODStruct() = delete; // Creates a new instance or binds an existing one PODStruct( bool is_owner, std::function alloc, std::function free, T** c_struct_pp = nullptr) : _alloc(std::move(alloc)), _free(std::move(free)) { pEpLogClass("called"); // if no pp is given, alloc new, // otherwise bind to it if (!c_struct_pp) { _c_struct_p = _alloc(); bind(is_owner, &_c_struct_p); } else { bind(is_owner, c_struct_pp); } } void bind(bool is_owner, T** c_struct_pp) { if (c_struct_pp == nullptr) { throw Exception{ EXSTR("cant bind on a nullptr") }; } if (_is_bound) { throw Exception{ EXSTR("can only bind once") }; } // init _c_struct_pp = c_struct_pp; _c_struct_p = *_c_struct_pp; _is_bound = true; pEpLogClass(to_string()); set_owner(is_owner); } ~PODStruct() { if (_is_owner) { _free(_c_struct_p); } } operator T*() { return _c_struct_p; } std::string to_string() const { std::string ret{ "[" + CXX::Inspect::all(_c_struct_p) + "]" }; return ret; } void set_owner(bool is_owner) { pEpLogClass(std::to_string(is_owner)); _is_owner = is_owner; } bool is_owner() const { return _is_owner; } static bool log_enabled; protected: const std::function _alloc; const std::function _free; bool _is_bound{ false }; bool _is_owner{ false }; T** _c_struct_pp{ nullptr }; T* _c_struct_p{ nullptr }; Adapter::pEpLog::pEpLogger logger{ "typeid(T).name()", 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) {} }; }; // TOOD: // ctor not exception safe // You can create an instance of a c-struct or you can wrap an already existing c-struct // In both cases, you can define if the wrapper owns the instance, or not. // If if owns it, it will free() it when the wrapper dies, otherwise it doesnt. // // alloc/free: // the alloc() and free() functions HAVE to use calloc/malloc NOT 'new', because we are interfacing // c99 code that could possibly get ownership of the struct and can only free memory that has // been allocated using malloc/calloc. (malloc/free - new/delete are NOT compatible) class TestStruct1 : public PODStruct<::Test_struct1> { public: TestStruct1(bool is_owner, ::Test_struct1** test_struct1_p = nullptr) : PODStruct<::Test_struct1>(is_owner, _alloc, _free, test_struct1_p) { } // fields of the struct as 'properties' ;) pEp::POD c_int{ &_c_struct_p->c_int }; pEp::POD<::Test_enum> c_enum{ &_c_struct_p->c_enum }; pEp::String c_str{ false, &_c_struct_p->c_str,"fd" }; private: static Test_struct1* _alloc() { return (Test_struct1*)calloc(1, sizeof(Test_struct1)); } static void _free(Test_struct1* ptr) { pEpLog("called"); ::free_test_struct(ptr); } }; template<> bool PODStruct<::Test_struct1>::log_enabled{ true }; } // namespace pEp 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<::Test_enum>::value && !std::is_pointer<::Test_enum>::value, "not an integral"); static_assert( std::is_pod<::Test_struct1>::value && !std::is_pointer<::Test_struct1>::value, "not an integral"); // pEp::Utils::readKey(); pEp::Adapter::pEpLog::set_enabled(true); // POD if (0) { // 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); } // equality operator { pEpLogH1("equality operator"); int c_int1 = init_val; int c_int2 = init_val; pEp::POD pint1(&c_int1); pEp::POD pint2(&c_int2); assert(pint1 == pint2); c_int2 = 23; assert(pint1 != pint2); } } // 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); } // bind() { pEpLogH1("bind()"); pEp::String pstr{}; char* c_str = strdup(init_val.c_str()); //TODO: PITYASSERT_THROWS pstr.bind(true, &c_str); test_getters(&c_str, pstr, init_val); test_assign(&c_str, pstr); } // equality operator { pEpLogH1("equality operator"); pEp::String pstr1{}; char* c_str1 = strdup(init_val.c_str()); char* c_str2 = strdup(init_val.c_str()); pstr1.bind(true, &c_str1); pEp::String pstr2{ true, &c_str2 }; assert(pstr1 == pstr2); pstr2 = "huhu"; assert(pstr1 != pstr2); } } // Struct if (1) { // create an instance if (1) { pEp::TestStruct1 pstruct1{ true }; // pEp::TestStruct1 pstruct1(true, 23, TWO, "pEp"); pEpLog(pstruct1.c_int); pEpLog(pstruct1.c_enum); pEpLog(pstruct1.c_str); ::Test_struct1* c_struct1 = pstruct1; pEpLog(c_struct1->c_int); pEpLog(c_struct1->c_enum); pEpLog(c_struct1->c_str); pEpLog("DONE"); } // wrap already existing instance { ::Test_struct1* c_struct1 = ::new_test_struct(0, ONE, "pEp"); pEpLog(c_struct1->c_int); pEpLog(c_struct1->c_enum); pEpLog(c_struct1->c_str); pEpLog("DONE"); pEp::TestStruct1 pstruct1(true, &c_struct1); pEpLog(pstruct1.c_int); pEpLog(pstruct1.c_enum); pEpLog(pstruct1.c_str); pEpLog("DONE"); } } // if (0) { // ::PEP_SESSION session; // 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); // } } //--------------------------------------------------------------------------------------------- // //// 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.bind(true, &_wrappee->address, address); // this->username.bind(true, &_wrappee->username, username); // this->user_id.bind(true, &_wrappee->user_id, user_id); // this->fpr.bind(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 }; //