#include #include #include #include #include #include #include #include #include #include //using namespace pEp; namespace pEp { template std::string type_add_val(const T c, size_t val_len = 30) { static_assert(std::is_pointer::value, "only pointer types are valid"); std::stringstream ss_type; //type ss_type << typeid(T).name(); //addr std::stringstream ss_addr{}; ss_addr << static_cast(c); //value std::stringstream ss_val{}; if (c != nullptr) { ss_val << "\"" << c << "\""; } else { ss_val << ""; } std::stringstream ret{}; ret << "{ " << ss_type.str() << " | " + ss_addr.str() << " | " << pEp::Utils::clip(ss_val.str(), val_len) << " }"; return ret.str(); } char* alloc(const std::string& str) { char* ret = strdup(str.c_str()); pEpLog(type_add_val(ret)); return ret; } template void free(T ptr_type) { pEpLog(type_add_val(ptr_type)); ::pEp_free(ptr_type); } template<> void free(char* ptr_type) { pEpLog(type_add_val(ptr_type)); ::pEp_free(ptr_type); } template<> void free(::pEp_identity* ptr_type) { pEpLog(type_add_val(ptr_type)); ::pEp_free(ptr_type); } //--------------------------------------------------------------------------------------------- #define EXSTR(msg) std::string(__FUNCTION__) + " - " + msg // 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 // set(). Otherwise all(most) functions will throw. // set() can only be called once, will throw otherwise. 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 explicit String(char** c_str_pp) { initialize(c_str_pp); } ~String() { _free(); } void initialize(char** c_str_pp) { if (c_str_pp == nullptr) { throw Exception{ EXSTR("cant initialize on a nullptr") }; } if (_is_initialized) { throw Exception{ EXSTR("double initialization") }; } // init _c_str_pp = c_str_pp; // _c_str_p.reset(*_c_str_pp, [&](char* ptr) { this->_free(ptr); }); _c_str_p = *_c_str_pp; _is_initialized = true; pEpLogClass(to_string() + " - 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(); // // if we point to an initialized string // if (*_c_str_pp != nullptr) { // // if the string there is not the one we created //// if (*_c_str_pp != _c_str_p.get()) { // if (*_c_str_pp != _c_str_p) { // // we need to free it before we lose the pointer to it // pEpLog("freeing foreign allocated string"); // pEp::free(*_c_str_pp); // // } // else it will be free automatically upon .reset() // } // //and we need to anyways free the strings we create ourselfes // if // pEp::free(_c_str_p;) // } // ALLOCATION if (str.empty()) { // if the new value is empty str, lets point to nothing *_c_str_pp = nullptr; // _c_str_p.reset(*_c_str_pp); _c_str_p = *_c_str_pp; } else { *_c_str_pp = pEp::alloc(str); // _c_str_p.reset(*_c_str_pp, [&](char* ptr) { this->_free(ptr); }); _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{ "[" + type_add_val(_c_str_pp) + " / " + type_add_val(*_c_str_pp) + "]" }; return ret; } static bool log_enabled; private: 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 anyways need to free the one we created // NO: 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 }; char** _c_str_pp{ nullptr }; // std::shared_ptr _c_str_p{}; 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.initialize(&_wrappee->address); this->username.initialize(&_wrappee->username); this->user_id.initialize(&_wrappee->user_id); this->fpr.initialize(&_wrappee->fpr); // set the values this->address = address; this->username = username; this->user_id = user_id; this->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 const* const* const c_str_p, pEp::String& pstr) { pEpLog("to_string(): " + pstr.to_string()); assert(pstr.c_data() == *c_str_p); assert(pstr.data() == c_str_p); if (*c_str_p != nullptr) { std::string tmp{ pstr }; pEpLog("operator std::string(): " + tmp); assert(tmp == std::string(*c_str_p)); // will segfault with nullptr, and this is correct std::string tmp2{ *pstr.data() }; pEpLog("data(): " + tmp2); assert(tmp2 == std::string(*c_str_p)); std::string tmp3{ pstr.c_data() }; pEpLog("c_data(): " + tmp2); assert(tmp3 == std::string(*c_str_p)); } else { std::string tmp{ pstr }; pEpLog("operator std::string(): " + tmp); assert(tmp == ""); } } void test_assign_and_getters(char** c_str_p, pEp::String& pstr) { test_getters(c_str_p, pstr); pEpLogH2("assign operator"); pstr = "assign operator"; test_getters(c_str_p, pstr); pEpLogH2("raw c_str assign"); *c_str_p = strdup("raw c_str assign"); test_getters(c_str_p, pstr); } int main() { // pEp::Utils::readKey(); pEp::Adapter::pEpLog::set_enabled(true); // pEp::String::log_enabled = false; if (1) { // 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(&c_str); test_assign_and_getters(&c_str, pstr); } // new pEp::String on already initalized char* { pEpLogH1("new pEp::String on already initalized char*"); char* c_str = strdup("initialized c string"); pEp::String pstr(&c_str); test_assign_and_getters(&c_str, pstr); } // initialize() { pEpLogH1("initialize()"); pEp::String pstr{}; char* c_str = strdup("initialized c string"); //TODO: PITYASSERT_THROWS pstr.initialize(&c_str); test_assign_and_getters(&c_str, pstr); } } 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); }