From 4789b4ac4f7811f8bb338455cd66f11e4d30d88f Mon Sep 17 00:00:00 2001 From: 64JohnLee <64lamei@gmail.com> Date: Mon, 4 May 2026 17:18:20 +0800 Subject: [PATCH 1/5] add friend requests backend files --- src/retroshare/rsfriendrequest.h | 118 ++++++++ src/rsserver/p3friendrequest.cc | 450 +++++++++++++++++++++++++++++++ src/rsserver/p3friendrequest.h | 141 ++++++++++ 3 files changed, 709 insertions(+) create mode 100755 src/retroshare/rsfriendrequest.h create mode 100755 src/rsserver/p3friendrequest.cc create mode 100755 src/rsserver/p3friendrequest.h diff --git a/src/retroshare/rsfriendrequest.h b/src/retroshare/rsfriendrequest.h new file mode 100755 index 0000000000..db79e51d59 --- /dev/null +++ b/src/retroshare/rsfriendrequest.h @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2024 RetroShare Team +// SPDX-License-Identifier: AGPL-3.0-only +// +// rsfriendrequest.h +// Public interface for incoming friend-request management. +// libretroshare — consumed by Qt GUI, JSON API, and AuthSSL. + +#pragma once + +#include +#include +#include + +#include "retroshare/rsids.h" +#include "retroshare/rspeers.h" // RsPeerId, RsPgpId + +// ── Forward declaration & singleton ─────────────────────────────────────── + +class RsFriendRequest; + +/// Global singleton, set during RS initialisation in p3Server::init(). +/// Always null-check before use: AuthSSL may call before init completes. +extern RsFriendRequest* rsFriendRequest; + +// ── Data structure ───────────────────────────────────────────────────────── + +/// One entry in the pending friend-request list, persisted across restarts. +struct RsFriendRequestEntry +{ + RsPeerId sslId; ///< SSL/node ID of the requesting peer + RsPgpId pgpId; ///< PGP key ID of the requesting peer + std::string pgpName; ///< Human-readable name from their certificate CN + std::string pgpFingerprint; ///< Hex fingerprint string (for display) + rstime_t firstSeen; ///< Unix timestamp of the first connection attempt + rstime_t lastSeen; ///< Unix timestamp of the most recent attempt + uint32_t attemptCount; ///< Total number of connection attempts recorded + bool rejected; ///< true = user dismissed; hidden from pending list +}; + +// ── Interface ────────────────────────────────────────────────────────────── + +/** + * @brief RsFriendRequest + * + * Tracks peers that attempted to connect but are not yet friends. + * Entries survive restarts (persisted via p3Config). + * + * Typical GUI usage: + * 1. Poll getPendingRequests() on a timer, or react to RS events. + * 2. Display badge = pendingCount(). + * 3. acceptRequest(sslId) → adds the peer as a friend. + * 4. rejectRequest(sslId) → hides the entry; no network message sent. + * 5. deleteRequest(sslId) → permanently removes the entry. + * + * AuthSSL usage: + * Call onUnknownPeerConnectionAttempt() from VerifyX509Callback when a + * peer whose PGP key is not in the friend list attempts to connect. + */ +class RsFriendRequest +{ +public: + virtual ~RsFriendRequest() = default; + + // ── Query ────────────────────────────────────────────────────────────── + + /// Returns all non-rejected pending requests, newest-first. + virtual bool getPendingRequests( + std::list& requests) = 0; + + /// Returns all requests including rejected ones (for an "All" view). + virtual bool getAllRequests( + std::list& requests) = 0; + + /// Returns the count of non-rejected, non-accepted entries. + /// Cheaper than loading the full list; use for badge display. + virtual uint32_t pendingCount() = 0; + + // ── Actions ──────────────────────────────────────────────────────────── + + /// Accept: calls rsPeers->addSslOnlyFriend() and removes the entry. + virtual bool acceptRequest(const RsPeerId& sslId) = 0; + + /// Reject: marks the entry hidden. No network message is sent. + /// Future connection attempts from the same peer update lastSeen/count + /// but do not un-hide the entry (the user's choice is preserved). + virtual bool rejectRequest(const RsPeerId& sslId) = 0; + + /// Permanently delete a single entry. A new entry will be created if + /// the peer connects again. + virtual bool deleteRequest(const RsPeerId& sslId) = 0; + + /// Remove all rejected entries from storage. + virtual bool clearRejected() = 0; + + // ── AuthSSL hook ─────────────────────────────────────────────────────── + + /** + * @brief onUnknownPeerConnectionAttempt + * + * Must be called from AuthSSLimpl::VerifyX509Callback (authssl.cc) + * when a peer whose PGP key is NOT in the friend list attempts to connect. + * + * Callers can use rsFriendRequest directly — no cast to the concrete + * type is needed. Always null-check rsFriendRequest first. + * + * Thread-safe: may be invoked from the OpenSSL I/O thread. + * + * Example call site in authssl.cc: + * if (rsFriendRequest) + * rsFriendRequest->onUnknownPeerConnectionAttempt( + * sslId, pgpId, pgpName, pgpFingerprint); + */ + virtual void onUnknownPeerConnectionAttempt( + const RsPeerId& sslId, + const RsPgpId& pgpId, + const std::string& pgpName, + const std::string& pgpFingerprint) = 0; +}; diff --git a/src/rsserver/p3friendrequest.cc b/src/rsserver/p3friendrequest.cc new file mode 100755 index 0000000000..bfb80cccd1 --- /dev/null +++ b/src/rsserver/p3friendrequest.cc @@ -0,0 +1,450 @@ +// SPDX-FileCopyrightText: 2024 RetroShare Team +// SPDX-License-Identifier: AGPL-3.0-only +// +// p3friendrequest.cc + +#include "p3friendrequest.h" + +#include // memcpy +#include // time() + +#include "retroshare/rspeers.h" // rsPeers->addSslOnlyFriend +#include "rsserver/p3peermgr.h" +#include "serialiser/rsbaseserial.h" +#include "util/rsdebug.h" + +RS_SET_CONTEXT_DEBUG_LEVEL(1) + +// ══════════════════════════════════════════════════════════════════════════ +// RsFriendRequestItem — serialisation helpers +// ══════════════════════════════════════════════════════════════════════════ +// +// All multi-byte integers are stored big-endian (network byte order), +// consistent with the rest of RS serialisation. + +static inline void putU8 (uint8_t*& p, uint8_t v) +{ + *p++ = v; +} + +static inline void putU32(uint8_t*& p, uint32_t v) +{ + p[0] = (v >> 24) & 0xFF; + p[1] = (v >> 16) & 0xFF; + p[2] = (v >> 8) & 0xFF; + p[3] = v & 0xFF; + p += 4; +} + +static inline void putU64(uint8_t*& p, uint64_t v) +{ + putU32(p, (uint32_t)(v >> 32)); + putU32(p, (uint32_t)(v & 0xFFFFFFFF)); +} + +static inline void putBytes(uint8_t*& p, const void* src, uint32_t len) +{ + std::memcpy(p, src, len); + p += len; +} + +static inline void putStr(uint8_t*& p, const std::string& s) +{ + uint32_t len = static_cast(s.size()); + putU32(p, len); + putBytes(p, s.data(), len); +} + +static inline bool getU8(const uint8_t*& p, const uint8_t* end, uint8_t& v) +{ + if (p + 1 > end) return false; + v = *p++; + return true; +} + +static inline bool getU32(const uint8_t*& p, const uint8_t* end, uint32_t& v) +{ + if (p + 4 > end) return false; + v = ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) + | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; + p += 4; + return true; +} + +static inline bool getU64(const uint8_t*& p, const uint8_t* end, uint64_t& v) +{ + uint32_t hi, lo; + if (!getU32(p, end, hi)) return false; + if (!getU32(p, end, lo)) return false; + v = ((uint64_t)hi << 32) | lo; + return true; +} + +static inline bool getBytes(const uint8_t*& p, const uint8_t* end, + void* dst, uint32_t len) +{ + if (p + len > end) return false; + std::memcpy(dst, p, len); + p += len; + return true; +} + +static inline bool getStr(const uint8_t*& p, const uint8_t* end, std::string& s) +{ + uint32_t len = 0; + if (!getU32(p, end, len)) return false; + if (p + len > end) return false; + s.assign(reinterpret_cast(p), len); + p += len; + return true; +} + +// ── RsFriendRequestItem ──────────────────────────────────────────────────── + +RsFriendRequestItem::RsFriendRequestItem() + : RsItem(RS_PKT_VERSION2, PKT_CLASS, PKT_TYPE, PKT_SUBTYPE) +{} + +void RsFriendRequestItem::clear() +{ + entries.clear(); +} + +std::ostream& RsFriendRequestItem::print(std::ostream& out, uint16_t indent) +{ + printRsItemBase(out, "RsFriendRequestItem", indent); + out << std::string(indent + 4, ' ') + << "entries: " << entries.size() << std::endl; + return out; +} + +uint32_t RsFriendRequestItem::serial_size() const +{ + // header (8) + version (1) + count (4) + uint32_t sz = 8 + 1 + 4; + for (const auto& [id, e] : entries) + { + sz += 32; // sslId + sz += 20; // pgpId + sz += 4 + static_cast(e.pgpName.size()); + sz += 4 + static_cast(e.pgpFingerprint.size()); + sz += 8; // firstSeen + sz += 8; // lastSeen + sz += 4; // attemptCount + sz += 1; // rejected + } + return sz; +} + +bool RsFriendRequestItem::serialise(void* data, uint32_t& pktsize) const +{ + uint32_t needed = serial_size(); + if (pktsize < needed) { pktsize = needed; return false; } + pktsize = needed; + + uint8_t* p = static_cast(data); + + // RS item header (8 bytes) + putU8 (p, RS_PKT_VERSION2); + putU8 (p, PKT_CLASS); + putU8 (p, PKT_TYPE); + putU8 (p, (uint8_t)(PKT_SUBTYPE >> 8)); + putU8 (p, (uint8_t)(PKT_SUBTYPE & 0xFF)); + putU32(p, needed); // total size field (overwrites last 4 of the 8-byte hdr) + // Note: RS item header layout: [ver][class][type][sub_hi][sub_lo][size32] + // The size field starts at offset 4 (4 bytes). Rewind and write correctly: + // + // Actually RS uses getRsItemId() layout. Use the base-class helper instead. + // Reset and use getRsItemHeader() pattern: + p = static_cast(data); + uint32_t hdr = 0; + hdr = (uint32_t)RS_PKT_VERSION2 << 24 + | (uint32_t)PKT_CLASS << 16 + | (uint32_t)PKT_TYPE << 8 + | (uint32_t)(PKT_SUBTYPE & 0xFF); + putU32(p, hdr); + putU32(p, needed); // packet size + + putU8 (p, SERIAL_VERSION); + putU32(p, static_cast(entries.size())); + + for (const auto& [id, e] : entries) + { + // sslId: RsPeerId is a fixed 32-byte identifier + const std::string sslStr = e.sslId.toStdString(); + // toStdString() on RsPeerId returns the hex string; use the raw bytes + // via getId() which returns a const uint8_t* of SSL_ID_SIZE bytes. + putBytes(p, e.sslId.getId(), RsPeerId::SIZE_IN_BYTES); + + // pgpId: 20 bytes + putBytes(p, e.pgpId.getId(), RsPgpId::SIZE_IN_BYTES); + + putStr(p, e.pgpName); + putStr(p, e.pgpFingerprint); + putU64(p, static_cast(e.firstSeen)); + putU64(p, static_cast(e.lastSeen)); + putU32(p, e.attemptCount); + putU8 (p, e.rejected ? 1 : 0); + } + + return true; +} + +bool RsFriendRequestItem::deserialise(void* data, uint32_t pktsize) +{ + const uint8_t* p = static_cast(data); + const uint8_t* end = p + pktsize; + + // Skip the 8-byte RS item header (already parsed by the framework). + p += 8; + + uint8_t ver = 0; + uint32_t count = 0; + + if (!getU8 (p, end, ver)) return false; + if (ver != SERIAL_VERSION) + { + RS_ERR("RsFriendRequestItem: unknown serial version ", (int)ver); + return false; + } + if (!getU32(p, end, count)) return false; + + entries.clear(); + for (uint32_t i = 0; i < count; ++i) + { + RsFriendRequestEntry e; + + uint8_t sslBuf[RsPeerId::SIZE_IN_BYTES] = {}; + uint8_t pgpBuf[RsPgpId::SIZE_IN_BYTES] = {}; + + if (!getBytes(p, end, sslBuf, RsPeerId::SIZE_IN_BYTES)) return false; + if (!getBytes(p, end, pgpBuf, RsPgpId::SIZE_IN_BYTES)) return false; + + e.sslId.init(sslBuf); + e.pgpId.init(pgpBuf); + + if (!getStr(p, end, e.pgpName)) return false; + if (!getStr(p, end, e.pgpFingerprint)) return false; + + uint64_t fs = 0, ls = 0; + if (!getU64(p, end, fs)) return false; + if (!getU64(p, end, ls)) return false; + e.firstSeen = static_cast(fs); + e.lastSeen = static_cast(ls); + + if (!getU32(p, end, e.attemptCount)) return false; + + uint8_t rej = 0; + if (!getU8(p, end, rej)) return false; + e.rejected = (rej != 0); + + entries[e.sslId] = e; + } + + return true; +} + +// ══════════════════════════════════════════════════════════════════════════ +// p3FriendRequest +// ══════════════════════════════════════════════════════════════════════════ + +p3FriendRequest::p3FriendRequest(p3PeerMgr* peerMgr) + : p3Config(CONFIG_TYPE_FRIEND_REQUESTS) + , mPeerMgr(peerMgr) +{} + +// ── AuthSSL hook ─────────────────────────────────────────────────────────── + +void p3FriendRequest::onUnknownPeerConnectionAttempt( + const RsPeerId& sslId, + const RsPgpId& pgpId, + const std::string& pgpName, + const std::string& pgpFingerprint) +{ + { + std::lock_guard lk(mMtx); + + rstime_t now = static_cast(time(nullptr)); + auto it = mEntries.find(sslId); + + if (it == mEntries.end()) + { + RsFriendRequestEntry entry; + entry.sslId = sslId; + entry.pgpId = pgpId; + entry.pgpName = pgpName; + entry.pgpFingerprint = pgpFingerprint; + entry.firstSeen = now; + entry.lastSeen = now; + entry.attemptCount = 1; + entry.rejected = false; + mEntries[sslId] = entry; + RS_DBG1("New friend request from '", pgpName, "' (", sslId, ")"); + } + else + { + // Preserve the rejected flag — the user's decision stands. + it->second.lastSeen = now; + it->second.attemptCount++; + RS_DBG2("Repeated attempt from '", it->second.pgpName, + "' (#", it->second.attemptCount, ")"); + } + } // lock released before markDirty + + markDirty(); +} + +// ── Query ────────────────────────────────────────────────────────────────── + +bool p3FriendRequest::getPendingRequests( + std::list& requests) +{ + std::lock_guard lk(mMtx); + requests.clear(); + for (const auto& [id, e] : mEntries) + if (!e.rejected) + requests.push_back(e); + requests.sort([](const RsFriendRequestEntry& a, + const RsFriendRequestEntry& b) { + return a.lastSeen > b.lastSeen; + }); + return true; +} + +bool p3FriendRequest::getAllRequests( + std::list& requests) +{ + std::lock_guard lk(mMtx); + requests.clear(); + for (const auto& [id, e] : mEntries) + requests.push_back(e); + requests.sort([](const RsFriendRequestEntry& a, + const RsFriendRequestEntry& b) { + return a.lastSeen > b.lastSeen; + }); + return true; +} + +uint32_t p3FriendRequest::pendingCount() +{ + std::lock_guard lk(mMtx); + uint32_t n = 0; + for (const auto& [id, e] : mEntries) + if (!e.rejected) ++n; + return n; +} + +// ── Actions ──────────────────────────────────────────────────────────────── + +bool p3FriendRequest::acceptRequest(const RsPeerId& sslId) +{ + RsPgpId pgpId; + { + std::lock_guard lk(mMtx); + auto it = mEntries.find(sslId); + if (it == mEntries.end()) + { + RS_ERR("acceptRequest: sslId not found: ", sslId); + return false; + } + pgpId = it->second.pgpId; + mEntries.erase(it); + } // lock released — rsPeers call must not hold mMtx (avoids re-entry risk) + + bool ok = rsPeers->addSslOnlyFriend(sslId, pgpId); + if (!ok) + RS_ERR("addSslOnlyFriend failed for ", sslId); + else + RS_DBG1("Accepted friend request from ", sslId); + + markDirty(); + return ok; +} + +bool p3FriendRequest::rejectRequest(const RsPeerId& sslId) +{ + { + std::lock_guard lk(mMtx); + auto it = mEntries.find(sslId); + if (it == mEntries.end()) return false; + it->second.rejected = true; + RS_DBG1("Rejected friend request from ", sslId); + } + markDirty(); + return true; +} + +bool p3FriendRequest::deleteRequest(const RsPeerId& sslId) +{ + bool erased; + { + std::lock_guard lk(mMtx); + erased = mEntries.erase(sslId) > 0; + } + if (erased) markDirty(); + return erased; +} + +bool p3FriendRequest::clearRejected() +{ + bool changed = false; + { + std::lock_guard lk(mMtx); + for (auto it = mEntries.begin(); it != mEntries.end(); ) + { + if (it->second.rejected) { it = mEntries.erase(it); changed = true; } + else ++it; + } + } + if (changed) markDirty(); + return true; +} + +// ── p3Config persistence ─────────────────────────────────────────────────── + +RsSerialiser* p3FriendRequest::setupSerialiser() +{ + // We use the direct serialise()/deserialise() methods on + // RsFriendRequestItem rather than a separate RsSerialiser class. + // Return a bare RsSerialiser with no added types; the item handles + // its own binary I/O. + return new RsSerialiser; +} + +bool p3FriendRequest::saveList(bool& cleanup, std::list& lst) +{ + cleanup = true; // caller owns and deletes items after saving + + auto* item = new RsFriendRequestItem; + { + std::lock_guard lk(mMtx); + item->entries = mEntries; // snapshot under lock + } + lst.push_back(item); + return true; +} + +bool p3FriendRequest::loadList(std::list& load) +{ + { + std::lock_guard lk(mMtx); + for (RsItem* raw : load) + { + auto* item = dynamic_cast(raw); + if (item) + for (auto& [id, e] : item->entries) + mEntries.emplace(id, e); // emplace: don't overwrite newer in-memory data + delete raw; + } + } + load.clear(); + RS_DBG1("Loaded ", mEntries.size(), " friend request entries"); + return true; +} + +void p3FriendRequest::markDirty() +{ + // Use the no-argument overload available in all RS versions. + // This avoids any dependency on the non-existent CheckPriority enum. + p3Config::IndicateConfigChanged(); +} diff --git a/src/rsserver/p3friendrequest.h b/src/rsserver/p3friendrequest.h new file mode 100755 index 0000000000..5c3f8a319d --- /dev/null +++ b/src/rsserver/p3friendrequest.h @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: 2024 RetroShare Team +// SPDX-License-Identifier: AGPL-3.0-only +// +// p3friendrequest.h +// Concrete implementation of RsFriendRequest. +// Lives in libretroshare/src/rsserver/ + +#pragma once + +#include +#include +#include + +#include "retroshare/rsfriendrequest.h" +#include "rsitems/rsitem.h" +#include "util/rstime.h" + +// Forward declarations +class p3PeerMgr; + +// ── Config type constant ─────────────────────────────────────────────────── +// ACTION FOR INTEGRATOR: add CONFIG_TYPE_FRIEND_REQUESTS to the config-type +// enum in libretroshare/src/rsserver/p3cfgmgr.h, choosing the next unused +// value (check the file for the current maximum and increment by 1). +// This guard lets the code compile in the meantime. +#ifndef CONFIG_TYPE_FRIEND_REQUESTS +# define CONFIG_TYPE_FRIEND_REQUESTS 0x0019 +#endif + +// ── Serialisable config item ─────────────────────────────────────────────── + +/** + * RsFriendRequestItem + * + * Stores the entire pending-request map in a single RsItem and implements + * its own binary serialisation, following the pattern used by other + * p3Config subclasses in RS (e.g. p3BanList) that avoid a separate + * RsSerialiser class. + * + * Wire format: + * uint8 SERIAL_VERSION (= 1) + * uint32 entry_count + * for each entry: + * uint8[32] sslId (raw bytes) + * uint8[20] pgpId (raw bytes) + * uint32 pgpName length, then raw UTF-8 bytes + * uint32 pgpFingerprint length, then raw UTF-8 bytes + * uint64 firstSeen (seconds since epoch) + * uint64 lastSeen + * uint32 attemptCount + * uint8 rejected (0 or 1) + */ +class RsFriendRequestItem : public RsItem +{ +public: + static constexpr uint8_t PKT_VERSION = 0x02; + static constexpr uint8_t PKT_CLASS = 0x05; + static constexpr uint8_t PKT_TYPE = 0x20; + static constexpr uint16_t PKT_SUBTYPE = 0x0001; + + RsFriendRequestItem(); + + void clear() override; + std::ostream& print(std::ostream& out, uint16_t indent = 0) override; + + // Called by p3Config's internal I/O. RS expects these three methods on + // items that manage their own serialisation. + uint32_t serial_size() const; + bool serialise(void* data, uint32_t& pktsize) const; + bool deserialise(void* data, uint32_t pktsize); + + std::map entries; + +private: + static constexpr uint8_t SERIAL_VERSION = 1; + + // Helpers — pointer is advanced past the written/read bytes on success. + static bool writeU8 (uint8_t*& p, uint8_t* end, uint8_t v); + static bool writeU32 (uint8_t*& p, uint8_t* end, uint32_t v); + static bool writeU64 (uint8_t*& p, uint8_t* end, uint64_t v); + static bool writeBytes(uint8_t*& p, uint8_t* end, const void* src, uint32_t len); + static bool writeStr (uint8_t*& p, uint8_t* end, const std::string& s); + + static bool readU8 (const uint8_t*& p, const uint8_t* end, uint8_t& v); + static bool readU32 (const uint8_t*& p, const uint8_t* end, uint32_t& v); + static bool readU64 (const uint8_t*& p, const uint8_t* end, uint64_t& v); + static bool readBytes(const uint8_t*& p, const uint8_t* end, void* dst, uint32_t len); + static bool readStr (const uint8_t*& p, const uint8_t* end, std::string& s); +}; + +// ── p3FriendRequest ──────────────────────────────────────────────────────── + +class p3FriendRequest + : public RsFriendRequest + , public p3Config +{ +public: + explicit p3FriendRequest(p3PeerMgr* peerMgr); + ~p3FriendRequest() override = default; + + // ── RsFriendRequest interface ────────────────────────────────────────── + + bool getPendingRequests( + std::list& requests) override; + + bool getAllRequests( + std::list& requests) override; + + uint32_t pendingCount() override; + + bool acceptRequest (const RsPeerId& sslId) override; + bool rejectRequest (const RsPeerId& sslId) override; + bool deleteRequest (const RsPeerId& sslId) override; + bool clearRejected () override; + + // Promoted to the RsFriendRequest public interface so AuthSSL can call it + // via rsFriendRequest without a static_cast to the concrete type. + // Thread-safe: may be called from the OpenSSL verify callback thread. + void onUnknownPeerConnectionAttempt( + const RsPeerId& sslId, + const RsPgpId& pgpId, + const std::string& pgpName, + const std::string& pgpFingerprint) override; + + // ── p3Config persistence ─────────────────────────────────────────────── + +protected: + RsSerialiser* setupSerialiser() override; + bool saveList(bool& cleanup, std::list& lst) override; + bool loadList(std::list& load) override; + +private: + p3PeerMgr* mPeerMgr; + + mutable std::mutex mMtx; + std::map mEntries; // guarded by mMtx + + // Calls the no-argument p3Config::IndicateConfigChanged(). + // Avoids any dependency on the non-existent CheckPriority enum. + void markDirty(); +}; From b76086bce081fc2902aa4b69d913976b0ee6a56b Mon Sep 17 00:00:00 2001 From: 64JohnLee <64lamei@gmail.com> Date: Thu, 4 Jun 2026 09:11:32 +0800 Subject: [PATCH 2/5] build: register friend-request sources in CMake and qmake Add rsfriendrequest.h, p3friendrequest.{h,cc} to RS_SOURCES/HEADERS (CMakeLists.txt) and the qmake HEADERS/SOURCES/PUBLIC_HEADERS lists so the friend-request backend is actually compiled and linked. Co-Authored-By: Claude Opus 4.8 --- src/CMakeLists.txt | 3 +++ src/libretroshare.pro | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ef55447a0c..8d4868b030 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -46,6 +46,7 @@ list( retroshare/rsevents.h retroshare/rsfiles.h retroshare/rsinit.h + retroshare/rsfriendrequest.h retroshare/rspeers.h ) list( @@ -417,6 +418,7 @@ list( rsserver/p3serverconfig.cc rsserver/rsloginhandler.cc rsserver/p3face-server.cc + rsserver/p3friendrequest.cc rsserver/p3peers.cc rsserver/rsaccounts.cc rsserver/rsinit.cc ) @@ -425,6 +427,7 @@ list( APPEND RS_IMPLEMENTATION_HEADERS rsserver/p3face.h rsserver/p3history.h + rsserver/p3friendrequest.h rsserver/p3peers.h rsserver/p3serverconfig.h rsserver/p3status.h diff --git a/src/libretroshare.pro b/src/libretroshare.pro index 812c593856..9269faacae 100644 --- a/src/libretroshare.pro +++ b/src/libretroshare.pro @@ -136,6 +136,7 @@ PUBLIC_HEADERS = retroshare/rsdisc.h \ retroshare/rsplugin.h \ retroshare/rschats.h \ retroshare/rsmail.h \ + retroshare/rsfriendrequest.h \ retroshare/rspeers.h \ retroshare/rsstatus.h \ retroshare/rsturtle.h \ @@ -442,6 +443,7 @@ HEADERS += pqi/authssl.h \ HEADERS += rsserver/p3face.h \ rsserver/p3history.h \ # rsserver/p3msgs.h \ + rsserver/p3friendrequest.h \ rsserver/p3peers.h \ rsserver/p3status.h \ rsserver/rsaccounts.h \ @@ -615,6 +617,7 @@ SOURCES += rsserver/p3face-config.cc \ rsserver/p3face-info.cc \ rsserver/p3history.cc \ # rsserver/p3msgs.cc \ + rsserver/p3friendrequest.cc \ rsserver/p3peers.cc \ rsserver/p3status.cc \ rsserver/rsinit.cc \ From ea62629d1af118f6d4a0e78c5f71ae8ee17de4f4 Mon Sep 17 00:00:00 2001 From: 64JohnLee <64lamei@gmail.com> Date: Thu, 4 Jun 2026 09:22:21 +0800 Subject: [PATCH 3/5] fix: define rsFriendRequest global, wire service into startup + AuthSSL Previously the friend-request backend compiled but was never instantiated and the global rsFriendRequest pointer (declared extern in retroshare/rsfriendrequest.h) was defined nowhere, causing an undefined- reference link error in any consumer (e.g. the GUI Friend Requests page). - Define RsFriendRequest* rsFriendRequest (mirrors rsPeers in p3peers.cc) - Instantiate p3FriendRequest in rsinit.cc, assign the global pointer, and register it with p3ConfigMgr (friend_requests.cfg) for persistence - Call onUnknownPeerConnectionAttempt() from AuthSSL::VerifyX509Callback on the ISSUER_UNKNOWN (not-a-friend) path so attempts populate the UI Co-Authored-By: Claude Opus 4.8 --- src/pqi/authssl.cc | 8 ++++++++ src/rsserver/p3friendrequest.cc | 4 ++++ src/rsserver/rsinit.cc | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/src/pqi/authssl.cc b/src/pqi/authssl.cc index 565315ea37..37f42ab526 100644 --- a/src/pqi/authssl.cc +++ b/src/pqi/authssl.cc @@ -44,6 +44,7 @@ #include "retroshare/rspeers.h" // for RsPeerDetails structure #include "retroshare/rsids.h" // for RsPeerDetails structure +#include "retroshare/rsfriendrequest.h" // record unknown-peer connection attempts #include "rsserver/p3face.h" /******************** notify of new Cert **************************/ @@ -1453,6 +1454,13 @@ int AuthSSLimpl::VerifyX509Callback(int /*preverify_ok*/, X509_STORE_CTX* ctx) //if (auth_diagnostic == RS_SSL_HANDSHAKE_DIAGNOSTIC_ISSUER_UNKNOWN) // RsServer::notify()->AddPopupMessage(RS_POPUP_CONNECT_ATTEMPT, pgpId.toStdString(), sslCn, sslId.toStdString()); /* notify Connect Attempt */ + // Record unknown-peer connection attempts for the Friend Requests UI. + // ISSUER_UNKNOWN means "not a friend yet" (other diagnostics are bad + // signatures etc.). rsFriendRequest is thread-safe and may be null + // early during startup. + if(rsFriendRequest && auth_diagnostic == RS_SSL_HANDSHAKE_DIAGNOSTIC_ISSUER_UNKNOWN) + rsFriendRequest->onUnknownPeerConnectionAttempt(sslId, pgpId, std::string(), sslCn); + return verificationFailed; } #ifdef AUTHSSL_DEBUG diff --git a/src/rsserver/p3friendrequest.cc b/src/rsserver/p3friendrequest.cc index bfb80cccd1..d93dc29540 100755 --- a/src/rsserver/p3friendrequest.cc +++ b/src/rsserver/p3friendrequest.cc @@ -15,6 +15,10 @@ RS_SET_CONTEXT_DEBUG_LEVEL(1) +// Global interface pointer (mirrors rsPeers in p3peers.cc). Declared extern in +// retroshare/rsfriendrequest.h; assigned during startup in rsserver/rsinit.cc. +RsFriendRequest* rsFriendRequest = nullptr; + // ══════════════════════════════════════════════════════════════════════════ // RsFriendRequestItem — serialisation helpers // ══════════════════════════════════════════════════════════════════════════ diff --git a/src/rsserver/rsinit.cc b/src/rsserver/rsinit.cc index 1629eb3c8c..0d8c0356fd 100644 --- a/src/rsserver/rsinit.cc +++ b/src/rsserver/rsinit.cc @@ -875,6 +875,7 @@ RsGRouter *rsGRouter = NULL ; #include "rsserver/p3peers.h" #include "rsserver/p3status.h" #include "rsserver/p3history.h" +#include "rsserver/p3friendrequest.h" #include "rsserver/p3serverconfig.h" #include "pqi/p3peermgr.h" @@ -1756,6 +1757,9 @@ int RsServer::StartupRetroShare() mConfigMgr->addConfiguration("gxsnettunnel.cfg", mGxsNetTunnel); mConfigMgr->addConfiguration("peers.cfg" , mPeerMgr); + p3FriendRequest *friendReqSrv = new p3FriendRequest(mPeerMgr); + rsFriendRequest = friendReqSrv; + mConfigMgr->addConfiguration("friend_requests.cfg", friendReqSrv); mConfigMgr->addConfiguration("general.cfg" , mGeneralConfig); mConfigMgr->addConfiguration("msgs.cfg" , msgSrv); mConfigMgr->addConfiguration("chat.cfg" , chatSrv); From 3898daa88ce75bef8d6f2499ea52846d891a96f6 Mon Sep 17 00:00:00 2001 From: 64JohnLee <64lamei@gmail.com> Date: Thu, 4 Jun 2026 09:25:08 +0800 Subject: [PATCH 4/5] fix: p3FriendRequest must call p3Config() default ctor p3Config only declares a no-arg constructor (p3cfgmgr.h), so ': p3Config(CONFIG_TYPE_FRIEND_REQUESTS)' failed to compile (no matching constructor). RS identifies a config by its filename + RsItem subtype, not a constructor arg, so drop the bogus argument and the now-dead CONFIG_TYPE_FRIEND_REQUESTS macro. Co-Authored-By: Claude Opus 4.8 --- src/rsserver/p3friendrequest.cc | 2 +- src/rsserver/p3friendrequest.h | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/rsserver/p3friendrequest.cc b/src/rsserver/p3friendrequest.cc index d93dc29540..e4a2c45c23 100755 --- a/src/rsserver/p3friendrequest.cc +++ b/src/rsserver/p3friendrequest.cc @@ -253,7 +253,7 @@ bool RsFriendRequestItem::deserialise(void* data, uint32_t pktsize) // ══════════════════════════════════════════════════════════════════════════ p3FriendRequest::p3FriendRequest(p3PeerMgr* peerMgr) - : p3Config(CONFIG_TYPE_FRIEND_REQUESTS) + : p3Config() , mPeerMgr(peerMgr) {} diff --git a/src/rsserver/p3friendrequest.h b/src/rsserver/p3friendrequest.h index 5c3f8a319d..12bf50d38b 100755 --- a/src/rsserver/p3friendrequest.h +++ b/src/rsserver/p3friendrequest.h @@ -18,15 +18,6 @@ // Forward declarations class p3PeerMgr; -// ── Config type constant ─────────────────────────────────────────────────── -// ACTION FOR INTEGRATOR: add CONFIG_TYPE_FRIEND_REQUESTS to the config-type -// enum in libretroshare/src/rsserver/p3cfgmgr.h, choosing the next unused -// value (check the file for the current maximum and increment by 1). -// This guard lets the code compile in the meantime. -#ifndef CONFIG_TYPE_FRIEND_REQUESTS -# define CONFIG_TYPE_FRIEND_REQUESTS 0x0019 -#endif - // ── Serialisable config item ─────────────────────────────────────────────── /** From 6a33f1a3d531a5a89805adc8b73e57f55e3506f3 Mon Sep 17 00:00:00 2001 From: 64JohnLee <64lamei@gmail.com> Date: Fri, 5 Jun 2026 23:08:46 +0800 Subject: [PATCH 5/5] fix: make p3FriendRequest compile against the real libretroshare API The service did not build. Corrected against the actual API: - include pqi/p3cfgmgr.h so the p3Config base resolves (this cascade also caused the spurious override / "not a direct base" errors) - fix include path rsserver/p3peermgr.h -> pqi/p3peermgr.h - RS_PKT_VERSION2 -> RS_PKT_VERSION_SERVICE (the former does not exist) - RsPeerId/RsPgpId: getId() -> toByteArray(), .init(buf) -> ctor-from-bytes - call inherited IndicateConfigChanged() without the p3Config:: qualifier - implement the RsSerializable serial_process() pure virtual (stub) so the item is no longer abstract Persistence is not yet wired (setupSerialiser() returns a bare RsSerialiser), so entries are in-memory only; serial_process carries a TODO describing how to finish it via an RsServiceSerializer (cf. p3BanList). Co-Authored-By: Claude Opus 4.8 --- src/rsserver/p3friendrequest.cc | 41 ++++++++++++++++++++++----------- src/rsserver/p3friendrequest.h | 3 +++ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/rsserver/p3friendrequest.cc b/src/rsserver/p3friendrequest.cc index e4a2c45c23..0fdae1eb11 100755 --- a/src/rsserver/p3friendrequest.cc +++ b/src/rsserver/p3friendrequest.cc @@ -9,8 +9,9 @@ #include // time() #include "retroshare/rspeers.h" // rsPeers->addSslOnlyFriend -#include "rsserver/p3peermgr.h" +#include "pqi/p3peermgr.h" #include "serialiser/rsbaseserial.h" +#include "serialiser/rsserializer.h" #include "util/rsdebug.h" RS_SET_CONTEXT_DEBUG_LEVEL(1) @@ -106,7 +107,7 @@ static inline bool getStr(const uint8_t*& p, const uint8_t* end, std::string& s) // ── RsFriendRequestItem ──────────────────────────────────────────────────── RsFriendRequestItem::RsFriendRequestItem() - : RsItem(RS_PKT_VERSION2, PKT_CLASS, PKT_TYPE, PKT_SUBTYPE) + : RsItem(RS_PKT_VERSION_SERVICE, PKT_CLASS, PKT_TYPE, PKT_SUBTYPE) {} void RsFriendRequestItem::clear() @@ -149,7 +150,7 @@ bool RsFriendRequestItem::serialise(void* data, uint32_t& pktsize) const uint8_t* p = static_cast(data); // RS item header (8 bytes) - putU8 (p, RS_PKT_VERSION2); + putU8 (p, RS_PKT_VERSION_SERVICE); putU8 (p, PKT_CLASS); putU8 (p, PKT_TYPE); putU8 (p, (uint8_t)(PKT_SUBTYPE >> 8)); @@ -162,7 +163,7 @@ bool RsFriendRequestItem::serialise(void* data, uint32_t& pktsize) const // Reset and use getRsItemHeader() pattern: p = static_cast(data); uint32_t hdr = 0; - hdr = (uint32_t)RS_PKT_VERSION2 << 24 + hdr = (uint32_t)RS_PKT_VERSION_SERVICE << 24 | (uint32_t)PKT_CLASS << 16 | (uint32_t)PKT_TYPE << 8 | (uint32_t)(PKT_SUBTYPE & 0xFF); @@ -175,13 +176,11 @@ bool RsFriendRequestItem::serialise(void* data, uint32_t& pktsize) const for (const auto& [id, e] : entries) { // sslId: RsPeerId is a fixed 32-byte identifier - const std::string sslStr = e.sslId.toStdString(); - // toStdString() on RsPeerId returns the hex string; use the raw bytes - // via getId() which returns a const uint8_t* of SSL_ID_SIZE bytes. - putBytes(p, e.sslId.getId(), RsPeerId::SIZE_IN_BYTES); + // raw fixed-size bytes of each identifier + putBytes(p, e.sslId.toByteArray(), RsPeerId::SIZE_IN_BYTES); // pgpId: 20 bytes - putBytes(p, e.pgpId.getId(), RsPgpId::SIZE_IN_BYTES); + putBytes(p, e.pgpId.toByteArray(), RsPgpId::SIZE_IN_BYTES); putStr(p, e.pgpName); putStr(p, e.pgpFingerprint); @@ -224,8 +223,8 @@ bool RsFriendRequestItem::deserialise(void* data, uint32_t pktsize) if (!getBytes(p, end, sslBuf, RsPeerId::SIZE_IN_BYTES)) return false; if (!getBytes(p, end, pgpBuf, RsPgpId::SIZE_IN_BYTES)) return false; - e.sslId.init(sslBuf); - e.pgpId.init(pgpBuf); + e.sslId = RsPeerId(sslBuf); + e.pgpId = RsPgpId(pgpBuf); if (!getStr(p, end, e.pgpName)) return false; if (!getStr(p, end, e.pgpFingerprint)) return false; @@ -248,6 +247,20 @@ bool RsFriendRequestItem::deserialise(void* data, uint32_t pktsize) return true; } +void RsFriendRequestItem::serial_process( + RsGenericSerializer::SerializeJob /*j*/, + RsGenericSerializer::SerializeContext& /*ctx*/) +{ + // TODO(persistence): RsFriendRequestItem is not yet wired into RS's + // serialisation framework. setupSerialiser() returns a bare RsSerialiser, + // so neither this method nor the manual serialise()/deserialise() above are + // invoked — pending requests are NOT persisted across restarts. To enable + // persistence, register an RsServiceSerializer (cf. p3BanList / + // RsBanListSerialiser) and implement this with RsTypeSerializer over + // `entries`. This stub only satisfies the RsSerializable pure-virtual so + // the service compiles and runs in-memory. +} + // ══════════════════════════════════════════════════════════════════════════ // p3FriendRequest // ══════════════════════════════════════════════════════════════════════════ @@ -448,7 +461,7 @@ bool p3FriendRequest::loadList(std::list& load) void p3FriendRequest::markDirty() { - // Use the no-argument overload available in all RS versions. - // This avoids any dependency on the non-existent CheckPriority enum. - p3Config::IndicateConfigChanged(); + // Inherited from p3Config; the no-argument form uses the default + // CheckPriority and is available in all RS versions. + IndicateConfigChanged(); } diff --git a/src/rsserver/p3friendrequest.h b/src/rsserver/p3friendrequest.h index 12bf50d38b..752a4c24eb 100755 --- a/src/rsserver/p3friendrequest.h +++ b/src/rsserver/p3friendrequest.h @@ -13,6 +13,7 @@ #include "retroshare/rsfriendrequest.h" #include "rsitems/rsitem.h" +#include "pqi/p3cfgmgr.h" #include "util/rstime.h" // Forward declarations @@ -53,6 +54,8 @@ class RsFriendRequestItem : public RsItem void clear() override; std::ostream& print(std::ostream& out, uint16_t indent = 0) override; + void serial_process(RsGenericSerializer::SerializeJob j, + RsGenericSerializer::SerializeContext& ctx) override; // Called by p3Config's internal I/O. RS expects these three methods on // items that manage their own serialisation.