LCOV - code coverage report
Current view: top level - src/jamidht - jami_contact.h (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 92.2 % 129 119
Test Date: 2026-07-06 08:25:38 Functions: 100.0 % 21 21

            Line data    Source code
       1              : /*
       2              :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3              :  *
       4              :  *  This program is free software: you can redistribute it and/or modify
       5              :  *  it under the terms of the GNU General Public License as published by
       6              :  *  the Free Software Foundation, either version 3 of the License, or
       7              :  *  (at your option) any later version.
       8              :  *
       9              :  *  This program is distributed in the hope that it will be useful,
      10              :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11              :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12              :  *  GNU General Public License for more details.
      13              :  *
      14              :  *  You should have received a copy of the GNU General Public License
      15              :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16              :  */
      17              : #pragma once
      18              : 
      19              : #include "string_utils.h"
      20              : #include "timestamp.h"
      21              : 
      22              : #include <opendht/infohash.h>
      23              : #include <opendht/value.h>
      24              : #include <opendht/default_types.h>
      25              : 
      26              : #include <msgpack.hpp>
      27              : #include <json/json.h>
      28              : 
      29              : #include <map>
      30              : #include <optional>
      31              : #include <string>
      32              : #include <string_view>
      33              : 
      34              : namespace jami {
      35              : 
      36              : namespace ContactMapKeys {
      37              : static constexpr const char* ADDED {"added"};
      38              : static constexpr const char* REMOVED {"removed"};
      39              : static constexpr const char* CONFIRMED {"confirmed"};
      40              : static constexpr const char* BANNED {"banned"};
      41              : static constexpr const char* CONVERSATIONID {"conversationId"};
      42              : static constexpr const char* DEVICE {"device"};
      43              : static constexpr const char* RECEIVED {"received"};
      44              : static constexpr const char* PAYLOAD {"payload"};
      45              : // Millisecond-resolution variants. Legacy keys above keep carrying seconds so
      46              : // that older devices (which ignore unknown keys) remain compatible.
      47              : static constexpr const char* ADDED_MS {"addedMs"};
      48              : static constexpr const char* REMOVED_MS {"removedMs"};
      49              : static constexpr const char* RECEIVED_MS {"receivedMs"};
      50              : } // namespace ContactMapKeys
      51              : 
      52              : struct Contact
      53              : {
      54              :     /** Time of contact addition */
      55              :     TimePoint added {};
      56              : 
      57              :     /** Time of contact removal */
      58              :     TimePoint removed {};
      59              : 
      60              :     /** True if we got confirmation that this contact also added us */
      61              :     bool confirmed {false};
      62              : 
      63              :     /** True if the contact is banned (if not active) */
      64              :     bool banned {false};
      65              : 
      66              :     /** Non empty if a swarm is linked */
      67              :     std::string conversationId {};
      68              : 
      69              :     /** True if the contact is an active contact (not banned nor removed) */
      70          690 :     bool isActive() const { return added > removed; }
      71          234 :     bool isBanned() const { return not isActive() and banned; }
      72              : 
      73          180 :     Contact() = default;
      74            3 :     Contact(const Json::Value& json)
      75            3 :     {
      76              :         // Prefer the millisecond keys, fall back to the legacy seconds keys
      77              :         // (written by older devices).
      78            3 :         if (json.isMember(ContactMapKeys::ADDED_MS))
      79            2 :             added = timePointFromMilliseconds(json[ContactMapKeys::ADDED_MS].asLargestInt());
      80              :         else
      81            1 :             added = timePointFromSeconds(json[ContactMapKeys::ADDED].asLargestInt());
      82            3 :         if (json.isMember(ContactMapKeys::REMOVED_MS))
      83            0 :             removed = timePointFromMilliseconds(json[ContactMapKeys::REMOVED_MS].asLargestInt());
      84              :         else
      85            3 :             removed = timePointFromSeconds(json[ContactMapKeys::REMOVED].asLargestInt());
      86            3 :         confirmed = json[ContactMapKeys::CONFIRMED].asBool();
      87            3 :         banned = json[ContactMapKeys::BANNED].asBool();
      88            3 :         conversationId = json[ContactMapKeys::CONVERSATIONID].asString();
      89            3 :     }
      90              : 
      91              :     /**
      92              :      * Update this contact using other known contact information,
      93              :      * return true if contact state was changed.
      94              :      */
      95           61 :     bool update(const Contact& c)
      96              :     {
      97           61 :         const auto copy = *this;
      98           61 :         auto isMoreRecent = std::max(c.added, c.removed) > std::max(added, removed);
      99           61 :         if (isMoreRecent) {
     100            5 :             added = c.added;
     101            5 :             removed = c.removed;
     102            5 :             banned = c.banned;
     103            5 :             conversationId = c.conversationId;
     104            5 :             confirmed = c.confirmed;
     105           56 :         } else if (isActive() && added == c.added) {
     106           48 :             confirmed = confirmed or c.confirmed;
     107              :         }
     108          122 :         return hasDifferentState(copy);
     109           61 :     }
     110              : 
     111           61 :     bool hasDifferentState(const Contact& other) const
     112              :     {
     113           61 :         return other.isActive() != isActive() or other.isBanned() != isBanned() or other.confirmed != confirmed;
     114              :     }
     115              : 
     116            5 :     Json::Value toJson() const
     117              :     {
     118            5 :         Json::Value json;
     119            5 :         json[ContactMapKeys::ADDED] = Json::Int64(toSecondsSinceEpoch(added));
     120            5 :         json[ContactMapKeys::ADDED_MS] = Json::Int64(toMillisecondsSinceEpoch(added));
     121            5 :         if (removed != TimePoint {}) {
     122            0 :             json[ContactMapKeys::REMOVED] = Json::Int64(toSecondsSinceEpoch(removed));
     123            0 :             json[ContactMapKeys::REMOVED_MS] = Json::Int64(toMillisecondsSinceEpoch(removed));
     124              :         }
     125            5 :         if (confirmed)
     126            3 :             json[ContactMapKeys::CONFIRMED] = confirmed;
     127            5 :         if (banned)
     128            0 :             json[ContactMapKeys::BANNED] = banned;
     129            5 :         json[ContactMapKeys::CONVERSATIONID] = conversationId;
     130            5 :         return json;
     131            0 :     }
     132              : 
     133            7 :     std::map<std::string, std::string> toMap() const
     134              :     {
     135            7 :         std::map<std::string, std::string> result {{"added", std::to_string(toSecondsSinceEpoch(added))},
     136           14 :                                                    {"removed", std::to_string(toSecondsSinceEpoch(removed))},
     137           35 :                                                    {"conversationId", conversationId}};
     138              : 
     139            7 :         if (isActive())
     140            6 :             result.emplace("confirmed", confirmed ? TRUE_STR : FALSE_STR);
     141            7 :         if (isBanned())
     142            0 :             result.emplace("banned", TRUE_STR);
     143              : 
     144            7 :         return result;
     145           21 :     }
     146              : 
     147              :     // Hand-written msgpack serialization (replaces MSGPACK_DEFINE_MAP) to emit
     148              :     // dual keys: legacy seconds (added/removed) + milliseconds
     149              :     // (addedMs/removedMs). Readers prefer the ms keys and fall back to
     150              :     // seconds * 1000.
     151              :     template<typename Packer>
     152          334 :     void msgpack_pack(Packer& pk) const
     153              :     {
     154          334 :         int64_t addedSec = toSecondsSinceEpoch(added);
     155          334 :         int64_t removedSec = toSecondsSinceEpoch(removed);
     156          334 :         int64_t addedMs = toMillisecondsSinceEpoch(added);
     157          334 :         int64_t removedMs = toMillisecondsSinceEpoch(removed);
     158              :         msgpack::type::make_define_map(ContactMapKeys::ADDED,
     159              :                                        addedSec,
     160              :                                        ContactMapKeys::REMOVED,
     161              :                                        removedSec,
     162              :                                        ContactMapKeys::CONFIRMED,
     163          334 :                                        confirmed,
     164              :                                        ContactMapKeys::BANNED,
     165          334 :                                        banned,
     166              :                                        ContactMapKeys::CONVERSATIONID,
     167          334 :                                        conversationId,
     168              :                                        ContactMapKeys::ADDED_MS,
     169              :                                        addedMs,
     170              :                                        ContactMapKeys::REMOVED_MS,
     171              :                                        removedMs)
     172          334 :             .msgpack_pack(pk);
     173          334 :     }
     174              : 
     175           76 :     void msgpack_unpack(const msgpack::object& o)
     176              :     {
     177           76 :         if (o.type != msgpack::type::MAP)
     178            0 :             throw msgpack::type_error();
     179           76 :         int64_t addedSec = 0, removedSec = 0;
     180           76 :         std::optional<int64_t> addedMs, removedMs;
     181          608 :         for (uint32_t i = 0; i < o.via.map.size; ++i) {
     182          532 :             const auto& kv = o.via.map.ptr[i];
     183          532 :             if (kv.key.type != msgpack::type::STR)
     184            0 :                 continue;
     185          532 :             std::string_view key(kv.key.via.str.ptr, kv.key.via.str.size);
     186          532 :             if (key == ContactMapKeys::ADDED)
     187           76 :                 kv.val.convert(addedSec);
     188          456 :             else if (key == ContactMapKeys::REMOVED)
     189           76 :                 kv.val.convert(removedSec);
     190          380 :             else if (key == ContactMapKeys::ADDED_MS)
     191           76 :                 addedMs = kv.val.as<int64_t>();
     192          304 :             else if (key == ContactMapKeys::REMOVED_MS)
     193           76 :                 removedMs = kv.val.as<int64_t>();
     194          228 :             else if (key == ContactMapKeys::CONFIRMED)
     195           76 :                 kv.val.convert(confirmed);
     196          152 :             else if (key == ContactMapKeys::BANNED)
     197           76 :                 kv.val.convert(banned);
     198           76 :             else if (key == ContactMapKeys::CONVERSATIONID)
     199           76 :                 kv.val.convert(conversationId);
     200              :         }
     201           76 :         added = addedMs ? timePointFromMilliseconds(*addedMs) : timePointFromSeconds(addedSec);
     202           76 :         removed = removedMs ? timePointFromMilliseconds(*removedMs) : timePointFromSeconds(removedSec);
     203           76 :     }
     204              : 
     205              :     template<typename MSGPACK_OBJECT>
     206              :     void msgpack_object(MSGPACK_OBJECT* o, msgpack::zone& z) const
     207              :     {
     208              :         int64_t addedSec = toSecondsSinceEpoch(added);
     209              :         int64_t removedSec = toSecondsSinceEpoch(removed);
     210              :         int64_t addedMs = toMillisecondsSinceEpoch(added);
     211              :         int64_t removedMs = toMillisecondsSinceEpoch(removed);
     212              :         msgpack::type::make_define_map(ContactMapKeys::ADDED,
     213              :                                        addedSec,
     214              :                                        ContactMapKeys::REMOVED,
     215              :                                        removedSec,
     216              :                                        ContactMapKeys::CONFIRMED,
     217              :                                        confirmed,
     218              :                                        ContactMapKeys::BANNED,
     219              :                                        banned,
     220              :                                        ContactMapKeys::CONVERSATIONID,
     221              :                                        conversationId,
     222              :                                        ContactMapKeys::ADDED_MS,
     223              :                                        addedMs,
     224              :                                        ContactMapKeys::REMOVED_MS,
     225              :                                        removedMs)
     226              :             .msgpack_object(o, z);
     227              :     }
     228              : };
     229              : 
     230              : struct TrustRequest
     231              : {
     232              :     std::shared_ptr<dht::crypto::PublicKey> device;
     233              :     std::string conversationId;
     234              :     TimePoint received {};
     235              :     std::vector<uint8_t> payload;
     236              : 
     237              :     // Hand-written msgpack serialization (replaces MSGPACK_DEFINE_MAP) to emit
     238              :     // dual keys: legacy seconds (received) + milliseconds (receivedMs). Readers
     239              :     // prefer the ms key and fall back to seconds * 1000.
     240              :     template<typename Packer>
     241           94 :     void msgpack_pack(Packer& pk) const
     242              :     {
     243           94 :         int64_t receivedSec = toSecondsSinceEpoch(received);
     244           94 :         int64_t receivedMs = toMillisecondsSinceEpoch(received);
     245              :         msgpack::type::make_define_map(ContactMapKeys::DEVICE,
     246           94 :                                        device,
     247              :                                        ContactMapKeys::CONVERSATIONID,
     248           94 :                                        conversationId,
     249              :                                        ContactMapKeys::RECEIVED,
     250              :                                        receivedSec,
     251              :                                        ContactMapKeys::PAYLOAD,
     252           94 :                                        payload,
     253              :                                        ContactMapKeys::RECEIVED_MS,
     254              :                                        receivedMs)
     255           94 :             .msgpack_pack(pk);
     256           94 :     }
     257              : 
     258            5 :     void msgpack_unpack(const msgpack::object& o)
     259              :     {
     260            5 :         if (o.type != msgpack::type::MAP)
     261            0 :             throw msgpack::type_error();
     262            5 :         int64_t receivedSec = 0;
     263            5 :         std::optional<int64_t> receivedMs;
     264           30 :         for (uint32_t i = 0; i < o.via.map.size; ++i) {
     265           25 :             const auto& kv = o.via.map.ptr[i];
     266           25 :             if (kv.key.type != msgpack::type::STR)
     267            0 :                 continue;
     268           25 :             std::string_view key(kv.key.via.str.ptr, kv.key.via.str.size);
     269           25 :             if (key == ContactMapKeys::DEVICE)
     270            5 :                 kv.val.convert(device);
     271           20 :             else if (key == ContactMapKeys::CONVERSATIONID)
     272            5 :                 kv.val.convert(conversationId);
     273           15 :             else if (key == ContactMapKeys::RECEIVED)
     274            5 :                 kv.val.convert(receivedSec);
     275           10 :             else if (key == ContactMapKeys::RECEIVED_MS)
     276            5 :                 receivedMs = kv.val.as<int64_t>();
     277            5 :             else if (key == ContactMapKeys::PAYLOAD)
     278            5 :                 kv.val.convert(payload);
     279              :         }
     280            5 :         received = receivedMs ? timePointFromMilliseconds(*receivedMs) : timePointFromSeconds(receivedSec);
     281            5 :     }
     282              : 
     283              :     template<typename MSGPACK_OBJECT>
     284              :     void msgpack_object(MSGPACK_OBJECT* o, msgpack::zone& z) const
     285              :     {
     286              :         int64_t receivedSec = toSecondsSinceEpoch(received);
     287              :         int64_t receivedMs = toMillisecondsSinceEpoch(received);
     288              :         msgpack::type::make_define_map(ContactMapKeys::DEVICE,
     289              :                                        device,
     290              :                                        ContactMapKeys::CONVERSATIONID,
     291              :                                        conversationId,
     292              :                                        ContactMapKeys::RECEIVED,
     293              :                                        receivedSec,
     294              :                                        ContactMapKeys::PAYLOAD,
     295              :                                        payload,
     296              :                                        ContactMapKeys::RECEIVED_MS,
     297              :                                        receivedMs)
     298              :             .msgpack_object(o, z);
     299              :     }
     300              : };
     301              : 
     302              : struct DeviceAnnouncement : public dht::SignedValue<DeviceAnnouncement>
     303              : {
     304              : private:
     305              :     using BaseClass = dht::SignedValue<DeviceAnnouncement>;
     306              : 
     307              : public:
     308              :     static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA;
     309              :     dht::InfoHash dev;
     310              :     std::shared_ptr<dht::crypto::PublicKey> pk;
     311         5891 :     MSGPACK_DEFINE_MAP(dev, pk)
     312              : };
     313              : 
     314              : struct KnownDeviceSync
     315              : {
     316              :     std::string name;
     317          781 :     MSGPACK_DEFINE_MAP(name)
     318              : };
     319              : 
     320              : struct DeviceSync : public dht::EncryptedValue<DeviceSync>
     321              : {
     322              :     static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA;
     323              :     uint64_t date;
     324              :     std::string device_name;
     325              :     std::map<dht::PkId, KnownDeviceSync> devices;
     326              :     std::map<dht::InfoHash, Contact> peers;
     327              :     std::map<dht::InfoHash, TrustRequest> trust_requests;
     328          707 :     MSGPACK_DEFINE_MAP(date, device_name, devices, peers, trust_requests)
     329              : };
     330              : 
     331              : struct KnownDevice
     332              : {
     333              :     using clock = std::chrono::system_clock;
     334              :     using time_point = clock::time_point;
     335              : 
     336              :     /** Device certificate */
     337              :     std::shared_ptr<dht::crypto::Certificate> certificate;
     338              : 
     339              :     /** Device name */
     340              :     std::string name {};
     341              : 
     342              :     /** Time of last received device sync */
     343              :     time_point last_sync {time_point::min()};
     344              : 
     345         3971 :     KnownDevice(const std::shared_ptr<dht::crypto::Certificate>& cert,
     346              :                 const std::string& n = {},
     347              :                 time_point sync = time_point::min())
     348         3971 :         : certificate(cert)
     349         3971 :         , name(n)
     350         3971 :         , last_sync(sync)
     351         3971 :     {}
     352              : };
     353              : 
     354              : } // namespace jami
        

Generated by: LCOV version 2.0-1