diff --git a/retroshare-friendserver/src/friendserver.cc b/retroshare-friendserver/src/friendserver.cc index e63156ae6..19c53e767 100644 --- a/retroshare-friendserver/src/friendserver.cc +++ b/retroshare-friendserver/src/friendserver.cc @@ -77,27 +77,40 @@ void FriendServer::handleClientPublish(const RsFriendServerClientPublishItem *it // First of all, read PGP key and short invites, parse them, and check that they contain the same information - std::map::iterator pi = handleIncomingClientData(item->pgp_public_key_b64,item->short_invite); + RsPeerId pid; - // No need to test for it==mCurrentClients.end() because it will be directly caught by the exception handling below even before. - // Respond with a list of potential friends + if(!handleIncomingClientData(item->pgp_public_key_b64,item->short_invite,pid)) + { + RsErr() << "Client data is dropped because of error." ; + return ; + } + + // All good. + + auto pi(mCurrentClientPeers.find(pid)); + + // Update the list of closest peers for other peers, based on which known friends it reports, and of current peer depending + // on friendship levels of other peers. + + updateClosestPeers(pi->first,pi->second.pgp_fingerprint,item->already_received_peers); RsDbg() << "Sending response item to " << item->PeerId() ; RsFriendServerServerResponseItem sr_item; - std::map friends; - sr_item.nonce = pi->second.last_nonce; - sr_item.friend_invites = computeListOfFriendInvites(item->n_requested_friends,item->already_received_peers,pi->first,friends); + std::set friends; + sr_item.unique_identifier = pi->second.last_identifier; + sr_item.friend_invites = computeListOfFriendInvites(pi->first,item->n_requested_friends,item->already_received_peers,friends); sr_item.PeerId(item->PeerId()); - // Update the have_added_as_friend for the list of each peer. We do that before sending because sending destroys - // the item. + // Update friendship levels of the peer that will receive the new list - for(const auto& pid:friends) + for(auto fr:friends) { - auto& p(mCurrentClientPeers[pid.first]); - p.have_added_this_peer[computePeerDistance(p.pgp_fingerprint, pi->second.pgp_fingerprint)] = pi->first; + auto& p(pi->second.friendship_levels[fr]); + + if(static_cast(p) < static_cast(RsFriendServer::PeerFriendshipLevel::HAS_KEY)) + p = RsFriendServer::PeerFriendshipLevel::HAS_KEY; } // Now encrypt the item with the public PGP key of the destination. This prevents the wrong person to request for @@ -126,9 +139,6 @@ void FriendServer::handleClientPublish(const RsFriendServerClientPublishItem *it // Send the item. mni->SendItem(encrypted_response_item); - // Update the list of closest peers for all peers currently in the database. - - updateClosestPeers(pi->first,pi->second.pgp_fingerprint); } catch(std::exception& e) { @@ -142,14 +152,14 @@ void FriendServer::handleClientPublish(const RsFriendServerClientPublishItem *it } } -std::map FriendServer::computeListOfFriendInvites(uint32_t nb_reqs_invites, - const std::set& already_known_peers, - const RsPeerId &pid, - std::map& friends) +std::map FriendServer::computeListOfFriendInvites(const RsPeerId &pid, uint32_t nb_reqs_invites, + const std::map& already_known_peers, + std::set& chosen_peers) const { // Strategy: we want to return the same set of friends for a given PGP profile key. // Still, using some closest distance strategy, the n-closest peers for profile A is not the - // same set than the n-closest peers for profile B. We have multiple options: + // same set than the n-closest peers for profile B, so some peers will not be in both sets. + // We have multiple options: // // Option 1: // @@ -169,41 +179,45 @@ std::map FriendServer::computeListOfFriendInvites(uint32_t nb // // So we choose Option 2. - std::map res; + std::map res; + chosen_peers.clear(); + auto pinfo_it(mCurrentClientPeers.find(pid)); - auto add_from = [&res,&friends,nb_reqs_invites,already_known_peers,this](bool added, - const std::map& lst) -> bool + if(pinfo_it == mCurrentClientPeers.end()) { - for(const auto& pid:lst) - if(already_known_peers.find(pid.second) == already_known_peers.end()) - { - const auto p = mCurrentClientPeers.find(pid.second); - res.insert(std::make_pair(p->second.short_certificate,added)); - friends.insert(std::make_pair(p->first,p->second.pgp_fingerprint)); - - if(res.size() + already_known_peers.size() >= nb_reqs_invites) - return true; - } - - return false; - }; - - const auto& pinfo(mCurrentClientPeers[pid]); - - // First add from peers who already added the current peer as friend, and leave if we already have enough - - if(add_from(true,pinfo.have_added_this_peer)) + RsErr() << "inconsistency in computeListOfFriendInvites. Something's wrong in the code." ; return res; + } + auto pinfo(pinfo_it->second); - add_from(false,pinfo.closest_peers); + for(const auto& pit:pinfo.closest_peers) + if(already_known_peers.find(pit.second) == already_known_peers.end()) + { + const auto p = mCurrentClientPeers.find(pit.second); + + if(p == mCurrentClientPeers.end()) // should not happen, but just an extra security. + continue; + + auto pp = p->second.friendship_levels.find(pid); + + auto peer_friendship_level = (pp==p->second.friendship_levels.end())?(RsFriendServer::PeerFriendshipLevel::UNKNOWN):(pp->second); + + res[p->second.short_certificate] = peer_friendship_level; + chosen_peers.insert(p->first); + + if(res.size() + already_known_peers.size() >= nb_reqs_invites) + break; + } return res; } -std::map::iterator FriendServer::handleIncomingClientData(const std::string& pgp_public_key_b64,const std::string& short_invite_b64) +bool FriendServer::handleIncomingClientData(const std::string& pgp_public_key_b64,const std::string& short_invite_b64,RsPeerId& pid) { // 1 - Check that the incoming data is sound. + try + { RsDbg() << " Checking item data..."; std::string error_string; @@ -239,24 +253,21 @@ std::map::iterator FriendServer::handleIncomingClientData(con // 3 - if the key is not already here, add it to keyring. { - RsPgpFingerprint fpr_test; - if(mPgpHandler->isPgpPubKeyAvailable(RsPgpId::fromBufferUnsafe(received_key_info.fingerprint+12))) - RsDbg() << " PGP Key is already into keyring."; - else - { - RsPgpId pgp_id; - if(!mPgpHandler->LoadCertificateFromBinaryData(key_binary_data.data(),key_binary_data.size(), pgp_id, error_string)) - throw std::runtime_error("Cannot load client's pgp public key into keyring: " + error_string) ; + RsPgpFingerprint fpr_test; + if(mPgpHandler->isPgpPubKeyAvailable(RsPgpId::fromBufferUnsafe(received_key_info.fingerprint+12))) + RsDbg() << " PGP Key is already into keyring."; + else + { + RsPgpId pgp_id; + if(!mPgpHandler->LoadCertificateFromBinaryData(key_binary_data.data(),key_binary_data.size(), pgp_id, error_string)) + throw std::runtime_error("Cannot load client's pgp public key into keyring: " + error_string) ; - RsDbg() << " Public key added to keyring."; - RsDbg() << " Sync-ing the PGP keyring on disk"; + RsDbg() << " Public key added to keyring."; + RsDbg() << " Sync-ing the PGP keyring on disk"; - mPgpHandler->syncDatabase(); + mPgpHandler->syncDatabase(); + } } - } - - // All good. - // Store/update the peer info auto& pi(mCurrentClientPeers[shortInviteDetails.id]); @@ -265,13 +276,19 @@ std::map::iterator FriendServer::handleIncomingClientData(con pi.last_connection_TS = time(nullptr); pi.pgp_fingerprint = shortInviteDetails.fpr; - while(pi.last_nonce == 0) // reuse the same identifier (so it's not really a nonce, but it's kept secret whatsoever). - pi.last_nonce = RsRandom::random_u64(); + while(pi.last_identifier == 0) // reuse the same identifier (so it's not really a nonce, but it's kept secret whatsoever). + pi.last_identifier = RsRandom::random_u64(); - return mCurrentClientPeers.find(shortInviteDetails.id); + pid = shortInviteDetails.id; + return true; + } + catch (std::exception& e) + { + RsErr() << "Exception while adding client data: " << e.what() ; + return false; + } } - void FriendServer::handleClientRemove(const RsFriendServerClientRemoveItem *item) { RsDbg() << "Received a client remove item:" << *item ; @@ -284,14 +301,14 @@ void FriendServer::handleClientRemove(const RsFriendServerClientRemoveItem *item return; } - if(it->second.last_nonce != item->nonce) + if(it->second.last_identifier != item->unique_identifier) { - RsErr() << " ERROR: Client supplied a nonce " << std::hex << item->nonce << std::dec << " that is not correct (expected " - << std::hex << it->second.last_nonce << std::dec << ")"; + RsErr() << " ERROR: Client supplied a nonce " << std::hex << item->unique_identifier << std::dec << " that is not correct (expected " + << std::hex << it->second.last_identifier << std::dec << ")"; return; } - RsDbg() << " Nonce is correct: " << std::hex << item->nonce << std::dec << ". Removing peer " << item->peer_id ; + RsDbg() << " Nonce is correct: " << std::hex << item->unique_identifier << std::dec << ". Removing peer " << item->peer_id ; removePeer(item->peer_id); } @@ -310,7 +327,7 @@ void FriendServer::removePeer(const RsPeerId& peer_id) for(auto pit(it.second.closest_peers.begin());pit!=it.second.closest_peers.end();) if(pit->second == peer_id) { - RsDbg() << " Removing from n-closest peers of peer " << pit->first ; + RsDbg() << " Removing from n-closest peers of peer " << it.first ; auto tmp(pit); ++tmp; @@ -320,27 +337,26 @@ void FriendServer::removePeer(const RsPeerId& peer_id) else ++pit; - // Also remove that peer from peers that have accepted each peer + // Also remove that peer from friendship levels of that particular peer. - for(auto fit(it.second.have_added_this_peer.begin());fit!=it.second.have_added_this_peer.end();) - if(fit->second == peer_id) - { - RsDbg() << " Removing from have_added_as_friend peers of peer " << fit->first ; + auto fit = it.second.friendship_levels.find(peer_id); - auto tmp(fit); - ++tmp; - it.second.have_added_this_peer.erase(fit); - fit=tmp; - } - else - ++fit; + if(fit != it.second.friendship_levels.end()) + { + RsDbg() << " Removing from have_added_as_friend peers of peer " << it.first ; + it.second.friendship_levels.erase(fit); + } } } PeerInfo::PeerDistance FriendServer::computePeerDistance(const RsPgpFingerprint& p1,const RsPgpFingerprint& p2) { - std::cerr << "Computing peer distance: p1=" << p1 << " p2=" << p2 << " p1^p2=" << (p1^p2) << " distance=" << ((p1^p2)^mRandomPeerBias) << std::endl; - return (p1 ^ p2)^mRandomPeerBias; + auto res = (p1 ^ p2)^mRandomPeerBias; + auto res2 = RsDirUtil::sha1sum(res.toByteArray(),res.SIZE_IN_BYTES); // sha1sum prevents reverse finding the random bias + + std::cerr << "Computing peer distance: p1=" << p1 << " p2=" << p2 << " p1^p2=" << (p1^p2) << " distance=" << res2 << std::endl; + + return res2; } FriendServer::FriendServer(const std::string& base_dir,const std::string& listening_address,uint16_t listening_port) : mListeningAddress(listening_address),mListeningPort(listening_port) @@ -389,18 +405,73 @@ void FriendServer::autoWash() removePeer(peer_id); } -void FriendServer::updateClosestPeers(const RsPeerId& pid,const RsPgpFingerprint& fpr) +void FriendServer::updateClosestPeers(const RsPeerId& pid,const RsPgpFingerprint& fpr,const std::map& friended_peers) { + auto find_multi = [](PeerInfo::PeerDistance dist,std::map< std::pair,RsPeerId >& mp) + -> std::map< std::pair,RsPeerId >::iterator + { + auto it = mp.find(std::make_pair(RsFriendServer::PeerFriendshipLevel::UNKNOWN,dist)) ; + + if(it == mp.end()) it = mp.find(std::make_pair(RsFriendServer::PeerFriendshipLevel::NO_KEY ,dist)); + if(it == mp.end()) it = mp.find(std::make_pair(RsFriendServer::PeerFriendshipLevel::HAS_KEY ,dist)); + if(it == mp.end()) it = mp.find(std::make_pair(RsFriendServer::PeerFriendshipLevel::HAS_ACCEPTED_KEY,dist)); + + return it; + }; + auto remove_from_map = [find_multi](PeerInfo::PeerDistance dist, + std::map< std::pair,RsPeerId>& mp) -> bool + { + auto mpit = find_multi(dist,mp); + + if(mpit != mp.end()) + { + mp.erase(mpit); + return true; + } + else + return false; + }; + + auto& pit(mCurrentClientPeers[pid]); + for(auto& it:mCurrentClientPeers) if(it.first != pid) { + // 1 - for all existing peers, update the level at which the given peer has added the peer as friend. + + auto peer_iterator = friended_peers.find(it.first); + auto peer_friendship_level = (peer_iterator==friended_peers.end())? (RsFriendServer::PeerFriendshipLevel::UNKNOWN):(peer_iterator->second); + PeerInfo::PeerDistance d = computePeerDistance(fpr,it.second.pgp_fingerprint); - it.second.closest_peers.insert(std::make_pair(d,pid)); + // Remove the peer from the map. This is costly. I need to find something better. If the peer is already + // in the list, it has a map key with the same distance. - if(it.second.closest_peers.size() > MAXIMUM_PEERS_TO_REQUEST) + remove_from_map(d,it.second.closest_peers); + + it.second.closest_peers[std::make_pair(peer_friendship_level,d)] = pid; + + while(it.second.closest_peers.size() > MAXIMUM_PEERS_TO_REQUEST) it.second.closest_peers.erase(std::prev(it.second.closest_peers.end())); + + // 2 - for the current peer, update the list of closest peers + + auto pit2 = it.second.friendship_levels.find(pid); + peer_friendship_level = (pit2==it.second.friendship_levels.end())? (RsFriendServer::PeerFriendshipLevel::UNKNOWN):(pit2->second); + + remove_from_map(d,pit.closest_peers); + + pit.closest_peers[std::make_pair(peer_friendship_level,d)] = it.first; + + while(pit.closest_peers.size() > MAXIMUM_PEERS_TO_REQUEST) + pit.closest_peers.erase(std::prev(pit.closest_peers.end())); } + + // Also update the friendship levels for the current peer, of all friends from the list. + + for(auto it:friended_peers) + pit.friendship_levels[it.first] = it.second; } Sha1CheckSum FriendServer::computeDataHash() @@ -416,17 +487,18 @@ Sha1CheckSum FriendServer::computeDataHash() s << inf.pgp_fingerprint; s << inf.short_certificate; s << (uint64_t)inf.last_connection_TS; - s << inf.last_nonce; + s << inf.last_identifier; for(auto d(inf.closest_peers.begin());d!=inf.closest_peers.end();++d) { - s << d->first ; + s << static_cast(d->first.first) ; + s << d->first.second ; s << d->second; } - for(auto d(inf.have_added_this_peer.begin());d!=inf.have_added_this_peer.end();++d) + for(auto d:inf.friendship_levels) { - s << d->first ; - s << d->second; + s << d.first ; + s << static_cast(d.second); } } return s.hash(); @@ -449,16 +521,11 @@ void FriendServer::debugPrint(bool force) for(const auto& it:mCurrentClientPeers) { - RsDbg() << " " << it.first << ": nonce=" << std::hex << it.second.last_nonce << std::dec << " fpr: " << it.second.pgp_fingerprint << ", last contact: " << now - it.second.last_connection_TS << " secs ago."; + RsDbg() << " " << it.first << ": identifier=" << std::hex << it.second.last_identifier << std::dec << " fpr: " << it.second.pgp_fingerprint << ", last contact: " << now - it.second.last_connection_TS << " secs ago."; RsDbg() << " Closest peers:" ; - for(const auto& pit:it.second.closest_peers) - RsDbg() << " " << pit.second << " distance=" << pit.first ; - - RsDbg() << " Have added this peer:" ; - - for(const auto& pit:it.second.have_added_this_peer) - RsDbg() << " " << pit.second << " distance=" << pit.first ; + for(auto pit:it.second.closest_peers) + RsDbg() << " " << pit.second << " distance=" << pit.first.second << " Peer reciprocal status:" << static_cast(pit.first.first); } RsDbg() << "==============================================="; diff --git a/retroshare-friendserver/src/friendserver.h b/retroshare-friendserver/src/friendserver.h index 7b688147a..476ce3cf3 100644 --- a/retroshare-friendserver/src/friendserver.h +++ b/retroshare-friendserver/src/friendserver.h @@ -24,23 +24,39 @@ #include "util/rsthreads.h" #include "pqi/pqistreamer.h" #include "pgp/pgphandler.h" +#include "retroshare/rsfriendserver.h" #include "network.h" class RsFriendServerClientRemoveItem; class RsFriendServerClientPublishItem; +// Storage for peer-related information as known by the friend server. +// Peers send to the friend server the list of peers they already have, with their own friendship level with that peer. +// The FS needs to send back a list of peers, with the friendship level to the current peer. +// In the list of closest peers, the reverse friendship levels are stored: for a peer A the reverse friendship level to peer B is whether B has +// added A as friend or not. +// In the list of friends for a peer, the forward FL is stored. The forward FL of a peer A to a peer B is whether A has added B as friend or not. + struct PeerInfo { - typedef RsPgpFingerprint PeerDistance; + typedef Sha1CheckSum PeerDistance; RsPgpFingerprint pgp_fingerprint; std::string short_certificate; rstime_t last_connection_TS; - uint64_t last_nonce; + uint64_t last_identifier; - std::map closest_peers; - std::map have_added_this_peer; + // The following map contains the list of closest peers. The sorting is based + // on a combination of the peer XOR distance and the friendship level, so that + // peers which already have added a peer are considered first as potential receivers of his key. + // The friendship level here is a reverse FL, e.g. whether each closest peer has added the current peer as friend. + + std::map,RsPeerId > closest_peers; // limited in size. + + // Which peers have received the key for that particular peer, along with the direct friendship level: whether current peer has added each peer. + + std::map friendship_levels; // unlimited in size, but no distance sorting. }; class FriendServer : public RsTickingThread @@ -60,16 +76,18 @@ private: void handleClientPublish(const RsFriendServerClientPublishItem *item); // Updates for each peer in the database, the list of closest peers w.r.t. some arbitrary distance. - void updateClosestPeers(const RsPeerId& pid,const RsPgpFingerprint& fpr); + void updateClosestPeers(const RsPeerId& pid, const RsPgpFingerprint& fpr, const std::map &friended_peers); // removes a single peer from all lists. void removePeer(const RsPeerId& peer_id); // Adds the incoming peer data to the list of current clients and returns the - std::map::iterator handleIncomingClientData(const std::string& pgp_public_key_b64,const std::string& short_invite_b64); + bool handleIncomingClientData(const std::string& pgp_public_key_b64, const std::string& short_invite_b64, RsPeerId &pid); // Computes the appropriate list of short invites to send to a given peer. - std::map computeListOfFriendInvites(uint32_t nb_reqs_invites, const std::set &already_received_peers, const RsPeerId &pid, std::map& friends); + std::map computeListOfFriendInvites(const RsPeerId &pid, uint32_t nb_reqs_invites, + const std::map& already_known_peers, + std::set& chosen_peers) const; // Compute the distance between peers using the random bias (It's not really a distance though. I'm not sure about the triangular inequality). PeerInfo::PeerDistance computePeerDistance(const RsPgpFingerprint &p1, const RsPgpFingerprint &p2);