diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 85dfd0a..0ae09e3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,6 +34,9 @@ add_library(sha1_ngcft1 STATIC ./hash_utils.hpp ./hash_utils.cpp + ./ft1_sha1_info.hpp + ./ft1_sha1_info.cpp + ./sha1_ngcft1.hpp ./sha1_ngcft1.cpp ) diff --git a/src/ft1_sha1_info.cpp b/src/ft1_sha1_info.cpp new file mode 100644 index 0000000..f809b93 --- /dev/null +++ b/src/ft1_sha1_info.cpp @@ -0,0 +1,130 @@ +#include "./ft1_sha1_info.hpp" + +#include + +SHA1Digest::SHA1Digest(const std::vector& v) { + assert(v.size() == data.size()); + for (size_t i = 0; i < data.size(); i++) { + data[i] = v[i]; + } +} + +SHA1Digest::SHA1Digest(const uint8_t* d, size_t s) { + assert(s == data.size()); + for (size_t i = 0; i < data.size(); i++) { + data[i] = d[i]; + } +} + +std::ostream& operator<<(std::ostream& out, const SHA1Digest& v) { + std::string str{}; + str.resize(v.size()*2, '?'); + + // HECK, std is 1 larger than size returns ('\0') + sodium_bin2hex(str.data(), str.size()+1, v.data.data(), v.data.size()); + + out << str; + + return out; +} + +std::vector FT1InfoSHA1::toBuffer(void) const { + std::vector buffer; + + assert(!file_name.empty()); + // TODO: optimize + for (size_t i = 0; i < 256; i++) { + if (i < file_name.size()) { + buffer.push_back(file_name.at(i)); + } else { + buffer.push_back(0); + } + } + assert(buffer.size() == 256); + + { // HACK: endianess + buffer.push_back((file_size>>(0*8)) & 0xff); + buffer.push_back((file_size>>(1*8)) & 0xff); + buffer.push_back((file_size>>(2*8)) & 0xff); + buffer.push_back((file_size>>(3*8)) & 0xff); + buffer.push_back((file_size>>(4*8)) & 0xff); + buffer.push_back((file_size>>(5*8)) & 0xff); + buffer.push_back((file_size>>(6*8)) & 0xff); + buffer.push_back((file_size>>(7*8)) & 0xff); + } + assert(buffer.size() == 256+8); + + // chunk size + { // HACK: endianess + buffer.push_back((chunk_size>>(0*8)) & 0xff); + buffer.push_back((chunk_size>>(1*8)) & 0xff); + buffer.push_back((chunk_size>>(2*8)) & 0xff); + buffer.push_back((chunk_size>>(3*8)) & 0xff); + } + + assert(buffer.size() == 256+8+4); + + for (const auto& chunk : chunks) { + for (size_t i = 0; i < chunk.data.size(); i++) { + buffer.push_back(chunk.data[i]); + } + } + assert(buffer.size() == 256+8+4+20*chunks.size()); + + return buffer; +} + +void FT1InfoSHA1::fromBuffer(const std::vector& buffer) { + assert(buffer.size() >= 256+8+4); + + // TODO: optimize + file_name.clear(); + for (size_t i = 0; i < 256; i++) { + char next_char = static_cast(buffer[i]); + if (next_char == 0) { + break; + } + file_name.push_back(next_char); + } + + { // HACK: endianess + file_size = 0; + file_size |= uint64_t(buffer[256+0]) << (0*8); + file_size |= uint64_t(buffer[256+1]) << (1*8); + file_size |= uint64_t(buffer[256+2]) << (2*8); + file_size |= uint64_t(buffer[256+3]) << (3*8); + file_size |= uint64_t(buffer[256+4]) << (4*8); + file_size |= uint64_t(buffer[256+5]) << (5*8); + file_size |= uint64_t(buffer[256+6]) << (6*8); + file_size |= uint64_t(buffer[256+7]) << (7*8); + } + + { // HACK: endianess + chunk_size = 0; + chunk_size |= uint32_t(buffer[256+8+0]) << (0*8); + chunk_size |= uint32_t(buffer[256+8+1]) << (1*8); + chunk_size |= uint32_t(buffer[256+8+2]) << (2*8); + chunk_size |= uint32_t(buffer[256+8+3]) << (3*8); + } + + assert((buffer.size()-(256+8+4)) % 20 == 0); + + for (size_t offset = 256+8+4; offset < buffer.size();) { + assert(buffer.size() >= offset + 20); + + auto& chunk = chunks.emplace_back(); + for (size_t i = 0; i < chunk.size(); i++, offset++) { + chunk.data[i] = buffer.at(offset); + } + // TODO: error/leftover checking + } +} + +std::ostream& operator<<(std::ostream& out, const FT1InfoSHA1& v) { + out << " file_name: " << v.file_name << "\n"; + out << " file_size: " << v.file_size << "\n"; + out << " chunk_size: " << v.chunk_size << "\n"; + out << " chunks.size(): " << v.chunks.size() << "\n"; + return out; +} + diff --git a/src/ft1_sha1_info.hpp b/src/ft1_sha1_info.hpp new file mode 100644 index 0000000..c7c9bbe --- /dev/null +++ b/src/ft1_sha1_info.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +struct SHA1Digest { + std::array data; + + SHA1Digest(void) = default; + SHA1Digest(const std::vector& v); + SHA1Digest(const uint8_t* d, size_t s); + + bool operator==(const SHA1Digest& other) const { return data == other.data; } + bool operator!=(const SHA1Digest& other) const { return data != other.data; } + + size_t size(void) const { return data.size(); } +}; + +std::ostream& operator<<(std::ostream& out, const SHA1Digest& v); + +namespace std { // inject + template<> struct hash { + std::size_t operator()(const SHA1Digest& h) const noexcept { + return + size_t(h.data[0]) << (0*8) | + size_t(h.data[1]) << (1*8) | + size_t(h.data[2]) << (2*8) | + size_t(h.data[3]) << (3*8) | + size_t(h.data[4]) << (4*8) | + size_t(h.data[5]) << (5*8) | + size_t(h.data[6]) << (6*8) | + size_t(h.data[7]) << (7*8) + ; + } + }; +} // std + +struct FT1InfoSHA1 { + std::string file_name; + uint64_t file_size {0}; + uint32_t chunk_size {128*1024}; // 128KiB for now + std::vector chunks; + + std::vector toBuffer(void) const; + void fromBuffer(const std::vector& buffer); +}; +std::ostream& operator<<(std::ostream& out, const FT1InfoSHA1& v); + diff --git a/src/ngcft1.cpp b/src/ngcft1.cpp index 5ef1fbd..69fdd22 100644 --- a/src/ngcft1.cpp +++ b/src/ngcft1.cpp @@ -550,7 +550,7 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_data_ack& e) { // delete if all packets acked if (transfer.file_size == transfer.file_size_current && transfer.ssb.size() == 0) { - std::cout << "NGCFT1: " << e.transfer_id << " done\n"; + std::cout << "NGCFT1: " << int(e.transfer_id) << " done\n"; peer.send_transfers[e.transfer_id].reset(); } diff --git a/src/plugin_ngcft1.cpp b/src/plugin_ngcft1.cpp index f50faad..7d6766a 100644 --- a/src/plugin_ngcft1.cpp +++ b/src/plugin_ngcft1.cpp @@ -7,9 +7,6 @@ #include #include -// fwd -//class RegMessageModel; - #define RESOLVE_INSTANCE(x) static_cast(solana_api->resolveInstance(#x)) #define PROVIDE_INSTANCE(x, p, v) solana_api->provideInstance(#x, p, static_cast(v)) @@ -37,12 +34,14 @@ SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api) ToxI* tox_i = nullptr; ToxEventProviderI* tox_event_provider_i = nullptr; + Contact3Registry* cr = nullptr; RegistryMessageModel* rmm = nullptr; ToxContactModel2* tcm = nullptr; { // make sure required types are loaded tox_i = RESOLVE_INSTANCE(ToxI); tox_event_provider_i = RESOLVE_INSTANCE(ToxEventProviderI); + cr = RESOLVE_INSTANCE(Contact3Registry); rmm = RESOLVE_INSTANCE(RegistryMessageModel); tcm = RESOLVE_INSTANCE(ToxContactModel2); @@ -56,6 +55,11 @@ SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api) return 2; } + if (cr == nullptr) { + std::cerr << "PLUGIN NGCEXT missing Contact3Registry\n"; + return 2; + } + if (rmm == nullptr) { std::cerr << "PLUGIN NGCEXT missing RegistryMessageModel\n"; return 2; @@ -71,7 +75,7 @@ SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api) // construct with fetched dependencies g_ngcextep = std::make_unique(*tox_event_provider_i); g_ngcft1 = std::make_unique(*tox_i, *tox_event_provider_i, *g_ngcextep.get()); - g_sha1_ngcft1 = std::make_unique(*rmm, *g_ngcft1.get(), *tcm); + g_sha1_ngcft1 = std::make_unique(*cr, *rmm, *g_ngcft1.get(), *tcm); // register types PROVIDE_INSTANCE(NGCEXTEventProviderI, "NGCEXT", g_ngcextep.get()); diff --git a/src/sha1_ngcft1.cpp b/src/sha1_ngcft1.cpp index d0c6751..c1fc814 100644 --- a/src/sha1_ngcft1.cpp +++ b/src/sha1_ngcft1.cpp @@ -2,21 +2,52 @@ #include +#include +#include +#include + +#include + +#include "./ft1_sha1_info.hpp" +#include "./hash_utils.hpp" + #include +namespace Components { + + using FT1InfoSHA1 = FT1InfoSHA1; + + struct FT1InfoSHA1Data { + std::vector data; + }; + + struct FT1InfoSHA1Hash { + std::vector hash; + }; + +} // Components + SHA1_NGCFT1::SHA1_NGCFT1( + Contact3Registry& cr, RegistryMessageModel& rmm, - NGCFT1EventProviderI& nftep, + NGCFT1& nft, ToxContactModel2& tcm ) : + _cr(cr), _rmm(rmm), - _nftep(nftep), + _nft(nft), _tcm(tcm) { - _nftep.subscribe(this, NGCFT1_Event::recv_request); - _nftep.subscribe(this, NGCFT1_Event::recv_init); - _nftep.subscribe(this, NGCFT1_Event::recv_data); - _nftep.subscribe(this, NGCFT1_Event::send_data); + _nft.subscribe(this, NGCFT1_Event::recv_request); + _nft.subscribe(this, NGCFT1_Event::recv_init); + _nft.subscribe(this, NGCFT1_Event::recv_data); + _nft.subscribe(this, NGCFT1_Event::send_data); + + //_rmm.subscribe(this, RegistryMessageModel_Event::message_construct); + //_rmm.subscribe(this, RegistryMessageModel_Event::message_updated); + //_rmm.subscribe(this, RegistryMessageModel_Event::message_destroy); + + _rmm.subscribe(this, RegistryMessageModel_Event::send_file_path); } bool SHA1_NGCFT1::onEvent(const Events::NGCFT1_recv_request& e) { @@ -27,6 +58,32 @@ bool SHA1_NGCFT1::onEvent(const Events::NGCFT1_recv_request& e) { //std::cout << "SHA1_NGCFT1: FT1_REQUEST fk:" << int(e.file_kind) << " [" << bin2hex({e.file_id, e.file_id+e.file_id_size}) << "]\n"; + if (e.file_kind == NGCFT1_file_kind::HASH_SHA1_INFO) { + if (e.file_id_size != 20) { + // error + return false; + } + + SHA1Digest info_hash{e.file_id, e.file_id_size}; + if (!_info_to_message.count(info_hash)) { + // we dont know about this + return false; + } + + auto msg = _info_to_message.at(info_hash); + + assert(msg.all_of()); + + // assume we have the info, send init + _nft.NGC_FT1_send_init_private( + e.group_number, e.peer_number, + static_cast(e.file_kind), + e.file_id, e.file_id_size, + msg.get().data.size(), + nullptr + ); + } + return false; } @@ -47,3 +104,113 @@ bool SHA1_NGCFT1::onEvent(const Events::NGCFT1_send_data& e) { return false; } +bool SHA1_NGCFT1::sendFilePath(const Contact3 c, std::string_view file_name, std::string_view file_path) { + if ( + // TODO: add support of offline queuing + !_cr.all_of(c) + ) { + return false; + } + + std::cout << "SHA1_NGCFT1: got sendFilePath()\n"; + + auto* reg_ptr = _rmm.get(c); + if (reg_ptr == nullptr) { + return false; + } + + // TODO: rw + auto file_impl = std::make_unique(file_path); + if (!file_impl->isGood()) { + std::cerr << "SHA1_NGCFT1 error: failed opening file '" << file_path << "'!\n"; + return true; + } + + // get current time unix epoch utc + uint64_t ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + // 1. build info by hashing all chunks + + FT1InfoSHA1 sha1_info; + // build info + sha1_info.file_name = file_name; + sha1_info.file_size = file_impl->_file_size; + + { // build chunks + // HACK: load file fully + // TODO: the speed is truly horrid + const auto file_data = file_impl->read(0, file_impl->_file_size); + size_t i = 0; + for (; i + sha1_info.chunk_size < file_data.size(); i += sha1_info.chunk_size) { + sha1_info.chunks.push_back(hash_sha1(file_data.data()+i, sha1_info.chunk_size)); + } + + if (i < file_data.size()) { + sha1_info.chunks.push_back(hash_sha1(file_data.data()+i, file_data.size()-i)); + } + } + + // 2. hash info + std::vector sha1_info_data; + std::vector sha1_info_hash; + + std::cout << "SHA1_NGCFT1 info is: \n" << sha1_info; + sha1_info_data = sha1_info.toBuffer(); + std::cout << "SHA1_NGCFT1 sha1_info size: " << sha1_info_data.size() << "\n"; + sha1_info_hash = hash_sha1(sha1_info_data.data(), sha1_info_data.size()); + std::cout << "SHA1_NGCFT1 sha1_info_hash: " << bin2hex(sha1_info_hash) << "\n"; + + const auto c_self = _cr.get(c).self; + if (!_cr.valid(c_self)) { + std::cerr << "SHA1_NGCFT1 error: failed to get self!\n"; + return true; + } + + const auto e = reg_ptr->create(); + reg_ptr->emplace(e, c); + reg_ptr->emplace(e, c_self); + reg_ptr->emplace(e, ts); // reactive? + + reg_ptr->emplace(e); + reg_ptr->emplace(e); + + reg_ptr->emplace(e, sha1_info); + reg_ptr->emplace(e, sha1_info_data); // keep around? or file? + reg_ptr->emplace(e, sha1_info_hash); + _info_to_message[sha1_info_hash] = {*reg_ptr, e}; + + //reg_ptr->emplace(e, file_kind); + // file id would be sha1_info hash or something + //reg_ptr->emplace(e, file_id); + + { // file info + auto& file_info = reg_ptr->emplace(e); + file_info.file_list.emplace_back() = {std::string{file_name}, file_impl->_file_size}; + file_info.total_size = file_impl->_file_size; + + reg_ptr->emplace(e, std::vector{std::string{file_path}}); + } + + reg_ptr->emplace(e); + + // TODO: determine if this is true + //reg_ptr->emplace(e); + + // TODO: ft1 specific comp + reg_ptr->emplace(e, std::move(file_impl)); +#if 0 + const auto friend_number = _cr.get(c).friend_number; + const auto&& [transfer_id, err] = _t.toxFileSend(friend_number, file_kind, file_impl->_file_size, file_id, file_name); + if (err == TOX_ERR_FILE_SEND_OK) { + reg_ptr->emplace(e, friend_number, transfer_id.value()); + // TODO: add tag signifying init sent status? + + toxFriendLookupAdd({*reg_ptr, e}); + } // else queue? +#endif + + _rmm.throwEventConstruct(*reg_ptr, e); + + return true; +} + diff --git a/src/sha1_ngcft1.hpp b/src/sha1_ngcft1.hpp index 0fc0499..0bb3774 100644 --- a/src/sha1_ngcft1.hpp +++ b/src/sha1_ngcft1.hpp @@ -2,29 +2,39 @@ // solanaceae port of sha1 fts for NGCFT1 +#include #include #include #include "./ngcft1.hpp" +#include "./ft1_sha1_info.hpp" + class SHA1_NGCFT1 : public RegistryMessageModelEventI, public NGCFT1EventI { + Contact3Registry& _cr; RegistryMessageModel& _rmm; - NGCFT1EventProviderI& _nftep; + NGCFT1& _nft; ToxContactModel2& _tcm; + // limit this to each group? + entt::dense_map _info_to_message; + public: SHA1_NGCFT1( + Contact3Registry& cr, RegistryMessageModel& rmm, - NGCFT1EventProviderI& nftep, + NGCFT1& nft, ToxContactModel2& tcm ); //void iterate(float delta); - protected: + protected: // events bool onEvent(const Events::NGCFT1_recv_request&) override; bool onEvent(const Events::NGCFT1_recv_init&) override; bool onEvent(const Events::NGCFT1_recv_data&) override; bool onEvent(const Events::NGCFT1_send_data&) override; // const? + + bool sendFilePath(const Contact3 c, std::string_view file_name, std::string_view file_path) override; };