#pragma once #include #include #include #include #include #include class UtunNode; class DbManager; struct MessageRow; class ChatPropagator : public QObject { Q_OBJECT public: ChatPropagator(UtunNode* node, DbManager* db, QObject* parent = nullptr); void setKeys(const QByteArray& edPriv, const QByteArray& edPub, quint64 nodeId); void start(); void propagateNewMessage(const QString& channelId, const QByteArray& content, qint64 timestamp, const QByteArray& signature); signals: void messageReceived(const QString& channelId, quint64 authorNodeId, const QByteArray& content, qint64 timestamp); private: UtunNode* m_node; DbManager* m_db; QTimer* m_ackTimer; QByteArray m_edPriv; QByteArray m_edPub; quint64 m_myNodeId = 0; struct GossipEntry { QString channelId; QByteArray packet; /* full NEW_MSG packet */ QByteArray sig; /* message signature (key) */ QList peers; /* target peers */ QList acked; /* peers that acknowledged */ qint64 sentAt; /* when sent (ms) */ int retries = 0; }; QHash m_pending; /* sig → entry */ QByteArray m_mySig; /* sig we're waiting for ACK on */ void onReceived(quint64 peerId, const QByteArray& data); void handleNewMsg(quint64 fromPeer, const QByteArray& payload); void handleNewAck(const QByteArray& payload); void onAckTimeout(); void startCatchUp(); void handleBucketsRsp(const QByteArray& payload); void handleHashesRsp(const QByteArray& payload); void handleMsgsRsp(const QByteArray& payload); void fanoutGossip(const QString& channelId, const QByteArray& packet, const QByteArray& sig, int fanout, const QList& exclude); void sendGossip(quint64 peer, const QByteArray& packet); QList pickRandomPeers(int count, const QList& exclude); QByteArray buildNewMsgPacket(const QString& channelId, const QByteArray& content, qint64 timestamp, const QByteArray& signature); QByteArray buildAckPacket(const QByteArray& sig); bool parseNewMsg(const QByteArray& payload, QString& channelId, QByteArray& content, qint64& timestamp, QByteArray& sig); };