commit 7e90e4da33c537329f53a1e60e26b9093949fca2 Author: Green Sky Date: Thu Aug 3 22:27:19 2023 +0200 hello mr fox diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e1bdb7e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(solanaceae) + +add_library(solanaceae_zox + ./solanaceae/zox/ngc.hpp + ./solanaceae/zox/ngc.cpp + + ./solanaceae/zox/ngc_hs.hpp + ./solanaceae/zox/ngc_hs.cpp +) + +target_include_directories(solanaceae_zox PUBLIC .) +target_compile_features(solanaceae_zox PUBLIC cxx_std_17) +target_link_libraries(solanaceae_zox PUBLIC + solanaceae_util + solanaceae_message3 + solanaceae_toxcore + solanaceae_tox_contacts + solanaceae_tox_messages +) + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2780797 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +The Code is under the following License, if not stated otherwise: + +MIT License + +Copyright (c) 2023 Erik Scholz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..d427f0b --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +`plant !` + +provides zox stuff + diff --git a/solanaceae/zox/ngc.cpp b/solanaceae/zox/ngc.cpp new file mode 100644 index 0000000..df04bac --- /dev/null +++ b/solanaceae/zox/ngc.cpp @@ -0,0 +1,220 @@ +#include "./ngc.hpp" + +#include +#include +#include + +constexpr size_t zox_magic_size = 6; +static bool is_zox_magic(const uint8_t* data, size_t size) { + //0x667788113435 + return size >= zox_magic_size && + data != nullptr && + data[0] == 0x66 && + data[1] == 0x77 && + data[2] == 0x88 && + data[3] == 0x11 && + data[4] == 0x34 && + data[5] == 0x35; +} + +constexpr size_t zox_header_size = zox_magic_size + 2; +std::optional> parse_zox_pkg_header(const uint8_t* data, size_t size) { + if (!is_zox_magic(data, size)) { + return std::nullopt; + } + + data += zox_magic_size; + size -= zox_magic_size; + + if (size < 2) { + return std::nullopt; + } + + const uint8_t version = data[0]; + const uint8_t pkt_id = data[1]; + + return std::make_pair(version, pkt_id); +} + +void ZoxNGCEventProvider::subscribeToEvents(void) { + _tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_CUSTOM_PACKET); + _tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_CUSTOM_PRIVATE_PACKET); +} + +ZoxNGCEventProvider::ZoxNGCEventProvider(ToxEventProviderI& tep) : _tep(tep) { + subscribeToEvents(); +} + +bool ZoxNGCEventProvider::onZoxGroupEvent( + uint32_t group_number, uint32_t peer_number, + uint8_t version, uint8_t pkt_id, + const uint8_t* data, size_t data_size, + bool _private +) { + if (version == 0x01 && pkt_id == 0x01) { + // ngch_request + return parse_ngch_request(group_number, peer_number, data, data_size, _private); + } else if (version == 0x01 && pkt_id == 0x02) { + // ngch_syncmsg + return parse_ngch_syncmsg(group_number, peer_number, data, data_size, _private); + } else if (version == 0x01 && pkt_id == 0x03) { + std::cout << "ZOX waring: ngch_syncmsg_file not implemented\n"; + } else if (version == 0x01 && pkt_id == 0x11) { + std::cout << "ZOX waring: ngc_ft not implemented\n"; + } else { + std::cout << "ZOX waring: unknown packet v" << (int)version << " id" << (int)pkt_id <<"\n"; + } + + return false; +} + +bool ZoxNGCEventProvider::parse_ngch_request( + uint32_t group_number, uint32_t peer_number, + const uint8_t* data, size_t data_size, + bool _private +) { + if (data_size > 1) { + std::cerr << "ZOX ngch_request has wrong size, should: <=1 , is: " << data_size << "\n"; + return false; + } + + uint8_t sync_delta = 130u; + if (data_size == 1) { + sync_delta = data[0]; + + // clamp + if (sync_delta < 5u) { + sync_delta = 5u; + } else if (sync_delta > 130u) { + sync_delta = 130u; + } + } + + return dispatch( + ZoxNGC_Event::ngch_request, + Events::ZoxNGC_ngch_request{ + group_number, + peer_number, + _private, + sync_delta + } + ); +} + +bool ZoxNGCEventProvider::parse_ngch_syncmsg( + uint32_t group_number, uint32_t peer_number, + const uint8_t* data, size_t data_size, + bool _private +) { + + constexpr size_t min_pkg_size = 4 + 32 + 4 + 25; + if (data_size <= 4 + 32 + 4 + 25) { + std::cerr << "ZOX ngch_syncmsg has wrong size, should: >=" << min_pkg_size << " , is: " << data_size << "\n"; + return false; + } + + // 4 bytes, message id + uint32_t message_id = 0; + message_id |= uint32_t(data[0]) << 8*3; + message_id |= uint32_t(data[1]) << 8*2; + message_id |= uint32_t(data[2]) << 8*1; + message_id |= uint32_t(data[3]) << 8*0; + + data += 4; + data_size -= 4; + + // 32 bytes, sender pub key + std::array sender_pub_key { + data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15], + data[16], data[17], data[18], data[19], data[20], data[21], data[22], data[23], + data[24], data[25], data[26], data[27], data[28], data[29], data[30], data[31], + }; + + data += 32; + data_size -= 32; + + // 4 bytes, timestamp + uint32_t timestamp = 0; + timestamp |= uint32_t(data[0]) << 8*3; + timestamp |= uint32_t(data[1]) << 8*2; + timestamp |= uint32_t(data[2]) << 8*1; + timestamp |= uint32_t(data[3]) << 8*0; + + data += 4; + data_size -= 4; + + // 25 bytes, sender name, truncated/filled with 0 + std::string_view sender_name{reinterpret_cast(data), 25}; + sender_name = sender_name.substr(0, sender_name.find_first_of('\0')); // trim \0 + + data += 25; + data_size -= 25; + + // up to 39927 bytes, message + std::string_view message_text{reinterpret_cast(data), data_size}; + message_text = message_text.substr(0, message_text.find_first_of('\0')); // trim \0 + + return dispatch( + ZoxNGC_Event::ngch_syncmsg, + Events::ZoxNGC_ngch_syncmsg{ + group_number, + peer_number, + _private, + message_id, + sender_pub_key, + timestamp, + sender_name, + message_text + } + ); +} + +bool ZoxNGCEventProvider::onToxEvent(const Tox_Event_Group_Custom_Packet* e) { + const uint32_t group_number = tox_event_group_custom_packet_get_group_number(e); + const uint32_t peer_number = tox_event_group_custom_packet_get_peer_id(e); + const uint8_t* data = tox_event_group_custom_packet_get_data(e); + size_t size = tox_event_group_custom_packet_get_data_length(e); + + auto res_opt = parse_zox_pkg_header(data, size); + if (!res_opt) { + return false; + } + + auto [version, pkt_id] = *res_opt; + + data += zox_header_size; + size -= zox_header_size; + + return onZoxGroupEvent( + group_number, peer_number, + version, pkt_id, + data, size, + false + ); +} + +bool ZoxNGCEventProvider::onToxEvent(const Tox_Event_Group_Custom_Private_Packet* e) { + const uint32_t group_number = tox_event_group_custom_private_packet_get_group_number(e); + const uint32_t peer_number = tox_event_group_custom_private_packet_get_peer_id(e); + const uint8_t* data = tox_event_group_custom_private_packet_get_data(e); + size_t size = tox_event_group_custom_private_packet_get_data_length(e); + + auto res_opt = parse_zox_pkg_header(data, size); + if (!res_opt) { + return false; + } + + auto [version, pkt_id] = *res_opt; + + data += zox_header_size; + size -= zox_header_size; + + return onZoxGroupEvent( + group_number, peer_number, + version, pkt_id, + data, size, + true + ); +} + diff --git a/solanaceae/zox/ngc.hpp b/solanaceae/zox/ngc.hpp new file mode 100644 index 0000000..db0b215 --- /dev/null +++ b/solanaceae/zox/ngc.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include + +#include + +#include +#include + +// fwd +//struct ToxI; + +// zoff ngc history sync (draft1?) +// https://gist.github.com/zoff99/81917ddb2e55b2ce602cac4772a7b68c + +namespace Events { + + struct ZoxNGC_ngch_request { + uint32_t group_number {0u}; + uint32_t peer_number {0u}; + + bool _private {true}; + + uint8_t sync_delta {130u}; + }; + + struct ZoxNGC_ngch_syncmsg { + uint32_t group_number {0u}; + uint32_t peer_number {0u}; + + bool _private {true}; + + uint32_t message_id {0u}; + // TODO: span + std::array sender_pub_key; + uint32_t timestamp {0u}; + std::string_view sender_name; + std::string_view message_text; + }; + +} // Events + +enum class ZoxNGC_Event : uint32_t { + // hs + v0x01_id0x01 = 0, + ngch_request = v0x01_id0x01, + + v0x01_id0x02, + ngch_syncmsg = v0x01_id0x02, + + v0x01_id0x03, + ngch_syncmsg_file = v0x01_id0x03, + + //v0x01_id0x04, + //v0x01_id0x05, + //v0x01_id0x06, + //v0x01_id0x07, + //v0x01_id0x08, + //v0x01_id0x09, + //v0x01_id0x0a, + //v0x01_id0x0b, + //v0x01_id0x0c, + //v0x01_id0x0d, + //v0x01_id0x0e, + //v0x01_id0x0f, + //v0x01_id0x10, + + v0x01_id0x11, + ngch_ft = v0x01_id0x11, + + // ... + + // v0x02_id0x01 + + MAX +}; + +static_assert(size_t(ZoxNGC_Event::v0x01_id0x02) == size_t(ZoxNGC_Event::v0x01_id0x01) + 1u); + +struct ZoxNGCEventI { + using enumType = ZoxNGC_Event; + virtual bool onEvent(const Events::ZoxNGC_ngch_request&) { return false; } + virtual bool onEvent(const Events::ZoxNGC_ngch_syncmsg&) { return false; } +}; + +using ZoxNGCEventProviderI = EventProviderI; + +class ZoxNGCEventProvider : public ToxEventI, public ZoxNGCEventProviderI { + ToxEventProviderI& _tep; + //ToxI& _t; + + void subscribeToEvents(void); // private + + public: + ZoxNGCEventProvider(ToxEventProviderI& tep/*, ToxI& t*/); + + protected: + bool onZoxGroupEvent( + uint32_t group_number, uint32_t peer_number, + uint8_t version, uint8_t pkt_id, + const uint8_t* data, size_t data_size, + bool _private + ); + + bool parse_ngch_request( + uint32_t group_number, uint32_t peer_number, + const uint8_t* data, size_t data_size, + bool _private + ); + + bool parse_ngch_syncmsg( + uint32_t group_number, uint32_t peer_number, + const uint8_t* data, size_t data_size, + bool _private + ); + + protected: + bool onToxEvent(const Tox_Event_Group_Custom_Packet* e) override; + bool onToxEvent(const Tox_Event_Group_Custom_Private_Packet* e) override; +}; + diff --git a/solanaceae/zox/ngc_hs.cpp b/solanaceae/zox/ngc_hs.cpp new file mode 100644 index 0000000..5441d8e --- /dev/null +++ b/solanaceae/zox/ngc_hs.cpp @@ -0,0 +1,412 @@ +#include "./ngc_hs.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +void ZoxNGCHistorySync::subscribeToEvents(void) { + _zngcepi.subscribe(this, ZoxNGC_Event::ngch_request); + _zngcepi.subscribe(this, ZoxNGC_Event::ngch_syncmsg); + + _tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_PEER_JOIN); +} + +ZoxNGCHistorySync::ZoxNGCHistorySync(ToxEventProviderI& tep, ZoxNGCEventProviderI& zngcepi, ToxI& t, Contact3Registry& cr, ToxContactModel2& tcm, RegistryMessageModel& rmm) + : _tep(tep), _zngcepi(zngcepi), _t(t), _cr(cr), _tcm(tcm), _rmm(rmm), _rng(std::random_device{}()) +{ + subscribeToEvents(); +} + +void ZoxNGCHistorySync::tick(float delta) { + // send queued requests + for (auto it = _request_queue.begin(); it != _request_queue.end();) { + it->second.timer += delta; + + if (it->second.timer >= it->second.delay) { + if (!_cr.all_of(it->first)) { + // peer nolonger online + it = _request_queue.erase(it); + continue; + } + const auto [group_number, peer_number] = _cr.get(it->first); + + if (sendRequest(group_number, peer_number, it->second.sync_delta)) { + // on success, requeue with longer delay (minutes) + + it->second.timer = 0.f; + it->second.delay = _delay_next_request_min + _rng_dist(_rng)*_delay_next_request_add; + + // double the delay for overlap (9m-15m) + // TODO: finetune + it->second.sync_delta = uint8_t((it->second.delay/60.f)*2.f) + 1; + + std::cout << "ZOX #### requeued request in " << it->second.delay << "s\n"; + + it++; + } else { + // on failure, assume disconnected + it = _request_queue.erase(it); + } + } else { + it++; + } + } + + for (auto it = _sync_queue.begin(); it != _sync_queue.end();) { + it->second.timer += delta; + if (it->second.timer >= it->second.delay) { + Message3 msg_e = it->second.ents.front(); + it->second.ents.pop(); + + if (!_cr.all_of(it->first)) { + // peer nolonger online + it = _sync_queue.erase(it); + continue; + } + const auto [group_number, peer_number] = _cr.get(it->first); + + auto* reg_ptr = _rmm.get(it->first); + if (reg_ptr == nullptr) { + //std::cout << "°°°°°°°° no reg for contact\n"; + it = _sync_queue.erase(it); + continue; + } + + Message3Registry& reg = *reg_ptr; + + if (!reg.valid(msg_e)) { + std::cerr << "ZOX NGCHS error: invalid message in sync send queue\n"; + it = _sync_queue.erase(it); + continue; + } + + const auto& msg_sender = reg.get(msg_e).c; + + if (!_cr.all_of(msg_sender)) { + std::cerr << "ZOX NGCHS error: msg sender without persistant\n"; + continue; + } + //if (auto peer_persist_opt = _cm.toPersistent(msg_sender); peer_persist_opt.has_value() && std::holds_alternative(peer_persist_opt.value())) { + // get name for peer + // TODO: make sure there is no alias leaked + //const auto msg_sender_name = _cm.getContactName(msg_sender); + std::string_view msg_sender_name; + if (_cr.all_of(msg_sender)) { + msg_sender_name = _cr.get(msg_sender).name; + } + + + if (!sendSyncMessage( + group_number, + peer_number, + reg.get(msg_e).id, + _cr.get(msg_sender).peer_key.data, + std::chrono::duration_cast(std::chrono::milliseconds{reg.get(msg_e).ts}).count(), + msg_sender_name, + reg.get(msg_e).text + ) || it->second.ents.empty()) { + it = _sync_queue.erase(it); + continue; + } + } + + it++; + } +} + +bool ZoxNGCHistorySync::sendRequest( + uint32_t group_number, uint32_t peer_number, + uint8_t sync_delta +) { + std::vector packet; + + { // magic + //0x667788113435 + packet.push_back(0x66); + packet.push_back(0x77); + packet.push_back(0x88); + packet.push_back(0x11); + packet.push_back(0x34); + packet.push_back(0x35); + + } + + packet.push_back(0x01); // version + packet.push_back(0x01); // pkt_id + + packet.push_back(sync_delta); + + auto ret = _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, true, packet); + // TODO: log error + + return ret == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK; +} + +bool ZoxNGCHistorySync::sendSyncMessage( + uint32_t group_number, uint32_t peer_number, + uint32_t message_id, + const std::array& sender_pub_key, + uint32_t timestamp, + std::string_view sender_name, + std::string_view message_text +) { + std::vector packet; + + { // magic + //0x667788113435 + packet.push_back(0x66); + packet.push_back(0x77); + packet.push_back(0x88); + packet.push_back(0x11); + packet.push_back(0x34); + packet.push_back(0x35); + } + + packet.push_back(0x01); // version + packet.push_back(0x02); // pkt_id + + // 4 bytes, message id + packet.push_back(0xff & (message_id >> 8*3)); + packet.push_back(0xff & (message_id >> 8*2)); + packet.push_back(0xff & (message_id >> 8*1)); + packet.push_back(0xff & (message_id >> 8*0)); + + // 32 bytes, sender pub key + packet.insert(packet.end(), sender_pub_key.cbegin(), sender_pub_key.cend()); + + + // 4 bytes, timestamp + packet.push_back(0xff & (timestamp >> 8*3)); + packet.push_back(0xff & (timestamp >> 8*2)); + packet.push_back(0xff & (timestamp >> 8*1)); + packet.push_back(0xff & (timestamp >> 8*0)); + + + // 25 bytes, sender name, truncated/filled with 0 + // TODO: handle unicode properly + for (size_t i = 0; i < 25; i++) { + if (i < sender_name.size()) { + packet.push_back(sender_name.at(i)); + } else { + packet.push_back('\0'); + } + } + + // up to 39927 bytes, message +#if 0 + packet.insert(packet.end(), message_text.cbegin(), message_text.cend()); +#else + //const int64_t msg_max_possible_size = _t.toxGroup + // TODO: make pr and add functions + const uint64_t msg_max_possible_size = std::clamp( + TOX_GROUP_MAX_CUSTOM_LOSSLESS_PACKET_LENGTH - packet.size(), + 0, // low + 39927 // high + ); + + for (size_t i = 0; i < msg_max_possible_size && i < message_text.size(); i++) { + packet.push_back(message_text.at(i)); + } +#endif + + auto ret = _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, true, packet); + // TODO: log error + + return ret == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK; +} + +bool ZoxNGCHistorySync::onEvent(const Events::ZoxNGC_ngch_request& e) { + std::cout << "ZOX ngch_request" + << " grp:" << e.group_number + << " per:" << e.peer_number + << " prv:" << e._private + << " sdl:" << (int)e.sync_delta + << "\n"; + + // if blacklisted / on cool down + + const auto request_sender = _tcm.getContactGroupPeer(e.group_number, e.peer_number); + if (_sync_queue.count(request_sender)) { + std::cerr << "ZNGCHS waring: ngch_request but still in sync send queue\n"; + return true; + } + + // const -> dont create (this is a request for existing messages) + auto* reg_ptr = static_cast(_rmm).get(request_sender); + if (reg_ptr == nullptr) { + std::cerr << "ZNGCHS error: group without reg\n"; + return true; + } + + const Message3Registry& reg = *reg_ptr; + + std::queue msg_send_queue; + + // convert sync delta to ms + const int64_t sync_delta_offset_ms = int64_t(e.sync_delta) * 1000 * 60; + const uint64_t ts_start = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() - sync_delta_offset_ms; + + auto view = reg.view().use(); + view.each([&](const Message3 e, const auto&, const auto& c_t, const auto& ts, const auto&, const auto&) { + // private + if (!_cr.all_of(c_t.c)) { + return; + } + + if (ts.ts < ts_start) { + std::cout << "---- " << ts.ts << " < " << ts_start << " -> too old\n"; + return; + } + std::cout << "---- " << ts.ts << " >= " << ts_start << " -> selected\n"; + + msg_send_queue.push(e); + }); + + std::cout << "ZOX ngch_request selected " << msg_send_queue.size() << " messages\n"; + + if (!msg_send_queue.empty()) { + _sync_queue[request_sender] = SyncQueueInfo{ + _delay_between_syncs_min + _rng_dist(_rng)*_delay_between_syncs_add, + 0.f, + std::move(msg_send_queue) + }; + } + + return true; +} + +bool ZoxNGCHistorySync::onEvent(const Events::ZoxNGC_ngch_syncmsg& e) { + std::cout << "ZOX ngch_syncmsg" + // who sent the syncmsg + << " grp:" << e.group_number + << " per:" << e.peer_number + << " prv:" << e._private + + // its contents + << " mid:" << e.message_id + << " spk:" << std::hex << (uint16_t)e.sender_pub_key[0] << (uint16_t)e.sender_pub_key[1] << std::dec + << " ts:" << e.timestamp + << " snm:" << e.sender_name + << " txt:" << e.message_text + << "\n"; + + auto sync_by_c = _tcm.getContactGroupPeer(e.group_number, e.peer_number); + + assert(static_cast(sync_by_c)); + + auto* reg_ptr = _rmm.get(sync_by_c); + if (reg_ptr == nullptr) { + std::cerr << "ZNGCHS error: group without reg\n"; + return false; + } + + Message3Registry& reg = *reg_ptr; + + const auto sync_c = _tcm.getContactGroupPeer(e.group_number, ToxKey{e.sender_pub_key.data(), e.sender_pub_key.size()}); + assert(static_cast(sync_c)); // TODO: make conditional + + // convert to ms + uint64_t sync_ts = std::chrono::milliseconds(std::chrono::seconds{e.timestamp}).count(); // o.o + uint64_t now_ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + // find matches + Message3 matching_e = entt::null; + { + const auto view = reg.view(); + for (const auto ent : view.use()) { + if (view.get(ent).id != e.message_id) { + continue; + } + + // how far apart the 2 timestamps can be, before they are considered different messages + if (std::abs(int64_t(view.get(ent).ts) - int64_t(sync_ts)) > _max_age_difference_ms) { + std::cout << "ZOX NGCHS info: same message id, but different timestamp\n"; + continue; + } + + const auto& ent_c = view.get(ent).c; + if (!(ent_c == sync_c)) { + std::cout << "ZOX NGCHS info: same message id, but different sender\n"; + continue; + } + + matching_e = ent; + break; // TODO: matching list + } + } + + if (reg.valid(matching_e)) { + // TODO: do something else, like average?, trust mods more? + + const bool has_tw = reg.all_of(matching_e); + auto& msg_ts_w = reg.get_or_emplace(matching_e, sync_ts); + if (has_tw) { + if (msg_ts_w.ts > sync_ts) { + msg_ts_w.ts = sync_ts; + reg.emplace_or_replace(matching_e, sync_ts); + + // TODO: resort + //_rmm.resort({ContactGroupEphemeral{e.group_number}}); + _rmm.throwEventUpdate(reg, matching_e); + } + } else { + // TODO: actually, dont do anything? + // TODO: resort + //_rmm.resort({ContactGroupEphemeral{e.group_number}}); + _rmm.throwEventUpdate(reg, matching_e); + } + } else { + // tmp, assume message new + matching_e = reg.create(); + + reg.emplace(matching_e, sync_c); + reg.emplace(matching_e, sync_by_c.get().parent); + + reg.emplace(matching_e, e.message_id); + + reg.emplace(matching_e, e.message_text); + + reg.emplace(matching_e, now_ts); + reg.emplace(matching_e, sync_ts); + reg.emplace(matching_e, sync_ts); // reactive? + + // TODO: resort + //_rmm.resort({ContactGroupEphemeral{e.group_number}}); + _rmm.throwEventConstruct(reg, matching_e); + } + + { // by whom + auto& synced_by = reg.get_or_emplace(matching_e).list; + synced_by.emplace(sync_by_c); + // TODO: throw update? + } + + return true; +} + +bool ZoxNGCHistorySync::onToxEvent(const Tox_Event_Group_Peer_Join* e) { + const auto group_number = tox_event_group_peer_join_get_group_number(e); + const auto peer_number = tox_event_group_peer_join_get_peer_id(e); + + const auto c = _tcm.getContactGroupPeer(group_number, peer_number); + + if (!_request_queue.count(c)) { + _request_queue[c] = { + _delay_before_first_request_min + _rng_dist(_rng)*_delay_before_first_request_add, + 0.f, + 130u // TODO: magic number + }; + } + + return false; +} + diff --git a/solanaceae/zox/ngc_hs.hpp b/solanaceae/zox/ngc_hs.hpp new file mode 100644 index 0000000..c9fe048 --- /dev/null +++ b/solanaceae/zox/ngc_hs.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include "./ngc.hpp" + +#include +#include + +#include +#include +#include +#include + +// fwd +struct ToxI; +struct ContactModelI; +class RegistryMessageModel; +class ToxContactModel2; + +// zoff ngc history sync (draft1?) +// https://gist.github.com/zoff99/81917ddb2e55b2ce602cac4772a7b68c + +class ZoxNGCHistorySync : public ToxEventI, public ZoxNGCEventI { + ToxEventProviderI& _tep; + ZoxNGCEventProviderI& _zngcepi; + ToxI& _t; + Contact3Registry& _cr; + ToxContactModel2& _tcm; + RegistryMessageModel& _rmm; + + // how far apart the 2 timestamps can be, before they are considered different messages + const int64_t _max_age_difference_ms {130*60*1000}; // TODO: make this larger? + + // 5s-11s + const float _delay_before_first_request_min {5.f}; + const float _delay_before_first_request_add {6.f}; + + // 30m-64m + const float _delay_next_request_min {30.f*60.f}; + const float _delay_next_request_add {64.f*60.f}; + + // 0.3s-0.6s + const float _delay_between_syncs_min {0.3f}; + const float _delay_between_syncs_add {0.3f}; + + std::uniform_real_distribution _rng_dist {0.0f, 1.0f}; + std::minstd_rand _rng; + + struct RequestQueueInfo { + float delay; // const + float timer; + uint8_t sync_delta; + }; + // request queue + // c -> delay, timer + std::map _request_queue; + + struct SyncQueueInfo { + float delay; // const + float timer; + std::queue ents; + //std::reference_wrapper reg; + }; + std::map _sync_queue; + + // sync queue + + void subscribeToEvents(void); // private + + public: + ZoxNGCHistorySync(ToxEventProviderI& tep, ZoNGCEventProviderI& zngcepi, ToxI& t, Contact3Registry& cr, ToxContactModel2& tcm, RegistryMessageModel& rmm); + + void tick(float delta); + + public: + // always private + bool sendRequest( + uint32_t group_number, uint32_t peer_number, + uint8_t sync_delta = 130u + ); + + // always private + bool sendSyncMessage( + uint32_t group_number, uint32_t peer_number, + uint32_t message_id, + const std::array& sender_pub_key, + uint32_t timestamp, + std::string_view sender_name, + std::string_view message_text + ); + + protected: + bool onEvent(const Events::ZoxNGC_ngch_request& e) override; + bool onEvent(const Events::ZoxNGC_ngch_syncmsg& e) override; + + protected: + bool onToxEvent(const Tox_Event_Group_Peer_Join* e) override; + //bool onToxEvent(const Tox_Event_Group_Peer_Exit* e) override; + //bool onToxEvent(const Tox_Event_Group_Custom_Packet* e) override; + //bool onToxEvent(const Tox_Event_Group_Custom_Private_Packet* e) override; +}; +