LCOV - code coverage report
Current view: top level - src/jamidht - commit_message.h (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 86.4 % 88 76
Test Date: 2026-06-13 09:18:46 Functions: 100.0 % 12 12

            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              : 
      18              : #pragma once
      19              : 
      20              : #include "json_utils.h"
      21              : 
      22              : #include <optional>
      23              : #include <string>
      24              : 
      25              : namespace jami {
      26              : 
      27              : namespace CommitKey {
      28              : constexpr const char* const TYPE {"type"};
      29              : constexpr const char* const BODY {"body"};
      30              : constexpr const char* const REPLY_TO {"reply-to"};
      31              : constexpr const char* const REACT_TO {"react-to"};
      32              : constexpr const char* const EDIT {"edit"};
      33              : constexpr const char* const ACTION {"action"};
      34              : constexpr const char* const URI {"uri"};
      35              : constexpr const char* const DEVICE {"device"};
      36              : constexpr const char* const CONF_ID {"confId"};
      37              : constexpr const char* const TO {"to"};
      38              : constexpr const char* const REASON {"reason"};
      39              : constexpr const char* const DURATION {"duration"};
      40              : constexpr const char* const TID {"tid"};
      41              : constexpr const char* const DISPLAY_NAME {"displayName"};
      42              : constexpr const char* const TOTAL_SIZE {"totalSize"};
      43              : constexpr const char* const SHA3SUM {"sha3sum"};
      44              : constexpr const char* const MODE {"mode"};
      45              : constexpr const char* const INVITED {"invited"};
      46              : } // namespace CommitKey
      47              : 
      48              : namespace CommitType {
      49              : constexpr const char* const TEXT {"text/plain"};
      50              : constexpr const char* const MEMBER {"member"};
      51              : constexpr const char* const CALL_HISTORY {"application/call-history+json"};
      52              : constexpr const char* const DATA_TRANSFER {"application/data-transfer+json"};
      53              : constexpr const char* const INITIAL {"initial"};
      54              : constexpr const char* const VOTE {"vote"};
      55              : constexpr const char* const UPDATE_PROFILE {"application/update-profile"};
      56              : constexpr const char* const MERGE {"merge"};
      57              : // Jami no longer creates messages of type "application/edited-message", but we
      58              : // still need to be able to parse them for backward compatibility.
      59              : constexpr const char* const EDITED_MESSAGE {"application/edited-message"};
      60              : } // namespace CommitType
      61              : 
      62              : namespace CommitAction {
      63              : constexpr const char* const ADD {"add"};
      64              : constexpr const char* const JOIN {"join"};
      65              : constexpr const char* const REMOVE {"remove"};
      66              : constexpr const char* const BAN {"ban"};
      67              : constexpr const char* const UNBAN {"unban"};
      68              : } // namespace CommitAction
      69              : 
      70              : enum class ConversationMode : int { ONE_TO_ONE = 0, ADMIN_INVITES_ONLY, INVITES_ONLY, PUBLIC };
      71              : 
      72              : /*
      73              :  * Jami conversations are stored as git repositories. Most of the information is contained
      74              :  * in the commit messages. With the exception of merge commits, the commit messages are JSON
      75              :  * objects with a fixed set of possible fields defined in the CommitKey namespace above. The
      76              :  * "type" field is mandatory and determines which other fields can be present as well as their
      77              :  * meaning.
      78              :  */
      79              : struct CommitMessage
      80              : {
      81              :     std::string type {};
      82              :     std::string body {};
      83              :     std::string replyTo {};
      84              :     std::string reactTo {};
      85              :     std::string editedId {};
      86              :     std::string action {};
      87              :     std::string uri {};
      88              :     std::string confId {};
      89              :     std::string device {};
      90              :     std::string duration {};
      91              :     std::string reason {};
      92              :     std::string to {};
      93              :     std::string tid {};
      94              :     std::string displayName {};
      95              :     int64_t totalSize {-1};
      96              :     std::string sha3sum {};
      97              :     int mode {-1};
      98              :     std::string invited {};
      99              : 
     100              :     // User messages are stored as commits of type "text/plain". The message text is in the "body"
     101              :     // field. For example:
     102              :     //
     103              :     //     {"body":"Hello!","type":"text/plain"}
     104              :     //
     105              :     // When a user edits a message, the new message text is stored in the "body" field of a commit
     106              :     // of type "text/plain" (or "application/edited-message" in old versions of Jami), with an
     107              :     // additional "edit" field containing the ID of the commit being edited. For example:
     108              :     //
     109              :     //     {"body":"Hello, how are you?","edit":"7de8a42695da4de31f774df7040893d34de0829d","type":"text/plain"}
     110              :     //     {"body":"Hi!","edit":"81528f849e844b6b0ded23b92ed0fc8d06bc21a2","type":"application/edited-message"}
     111              :     //
     112              :     // If the same message is edited multiple times, the ID in the "edit" field always refers to
     113              :     // the original message, not the previous edit.
     114              :     //
     115              :     // A deleted message is represented by an edit with an empty "body", e.g.:
     116              :     //
     117              :     //     {"body":"","edit":"7de8a42695da4de31f774df7040893d34de0829d","type":"text/plain"}
     118              :     //
     119              :     // Text messages can optionally include a "reply-to" field with the ID of the message being
     120              :     // replied to, e.g.:
     121              :     //
     122              :     //     {"body":"You're right!","reply-to":"200779c99a3f6ed7efc2a83bdfddb6a9e45a4e55","type":"text/plain"}
     123              :     //
     124              :     // The "edit" and "reply-to" fields are mutually exclusive. When replying to an edited message,
     125              :     // the "reply-to" field contains the ID of the original message, not the edit.
     126              :     //
     127              :     // Reactions are also encoded as messages of type "text/plain" with a "react-to" field containing
     128              :     // the ID of the message being reacted to and the reaction itself in the "body" field, e.g.:
     129              :     //
     130              :     //     {"body":"\ud83d\udc4d","react-to":"d433e037b32314bc3de2fad2ca4a12d914ecad57","type":"text/plain"}
     131              :     //
     132              :     // Removing a reaction is done the same way as deleting a message, i.e. as an edit with an
     133              :     // empty "body":
     134              :     //
     135              :     //     {"body":"","edit":"9f5a429073f313d3edb149c61fbd682c6e0fc704","type":"text/plain"}
     136              :     //
     137              :     // The "react-to" field is mutually exclusive with the "edit" and "reply-to" fields.
     138           93 :     static CommitMessage text(const std::string& body, const std::string& replyToId = "")
     139              :     {
     140           93 :         CommitMessage msg;
     141           93 :         msg.type = CommitType::TEXT;
     142           93 :         msg.body = body;
     143           93 :         msg.replyTo = replyToId;
     144           93 :         return msg;
     145            0 :     }
     146            3 :     static CommitMessage reaction(const std::string& reaction, const std::string& reactToId)
     147              :     {
     148            3 :         CommitMessage msg;
     149            3 :         msg.type = CommitType::TEXT;
     150            3 :         msg.body = reaction;
     151            3 :         msg.reactTo = reactToId;
     152            3 :         return msg;
     153            0 :     }
     154            5 :     static CommitMessage edit(const std::string& newBody, const std::string& editedId)
     155              :     {
     156            5 :         CommitMessage msg;
     157            5 :         msg.type = CommitType::TEXT;
     158            5 :         msg.body = newBody;
     159            5 :         msg.editedId = editedId;
     160            5 :         return msg;
     161            0 :     }
     162              : 
     163              :     // Commits of type "member" always have an "action" field and a "uri" field. The "uri" field
     164              :     // contains the Jami ID of the user impacted by the action, which can be one of the following:
     165              :     // - "add": the user was invited to join the conversation
     166              :     // - "join": the user joined the conversation
     167              :     // - "remove": the user left the conversation
     168              :     // - "ban": the user was banned from the conversation
     169              :     // - "unban": the user was unbanned from the conversation
     170              :     // For example:
     171              :     //
     172              :     //     {"action":"join","type":"member","uri":"f32701058c69f8ad6a095c6d14650294a4ba39a3"}
     173          316 :     static CommitMessage member(const std::string& action, const std::string& memberId)
     174              :     {
     175          316 :         CommitMessage msg;
     176          316 :         msg.type = CommitType::MEMBER;
     177          316 :         msg.action = action;
     178          316 :         msg.uri = memberId;
     179          316 :         return msg;
     180            0 :     }
     181              : 
     182              :     // Commits of type "application/call-history+json" represent either the beginning or the end
     183              :     // of a call. Their format differs depending on whether the call was started in a one-to-one
     184              :     // conversation or in a group conversation.
     185              :     //
     186              :     // In a one-to-one conversation, the user who initiated a call creates a commit once it ends.
     187              :     // The "duration" field contains the duration of the call in milliseconds, and the "to" field
     188              :     // contains the Jami ID of the called peer. For example:
     189              :     //
     190              :     //      {"duration":"80805","to":"ff114e1934db7b79e4f7ac676cb943d97ffb6a32","type":"application/call-history+json"}
     191              :     //
     192              :     // If the call failed to start, the "reason" field may provide more information about the cause
     193              :     // of the failure. For example, the call may have been declined by the peer:
     194              :     //
     195              :     //     {"duration":"0","reason":"declined","to":"ff114e1934db7b79e4f7ac676cb943d97ffb6a32","type":"application/call-history+json"}
     196              :     //
     197              :     // In a group conversation, the host creates a commit when the call starts, and another one
     198              :     // when it ends. Both commits include the following fields:
     199              :     // - "confId": a 64-bit unsigned integer identifying the call
     200              :     // - "device": the host's device ID
     201              :     // - "uri": the host's Jami ID
     202              :     // The end call commit additionally includes a "duration" field with the call duration in
     203              :     // milliseconds. A pair of start/end commits for a group call may look like this:
     204              :     //
     205              :     //     {"confId":"6342183642926168","device":"c87dc5b688c0e6a7d1cd30fe5c2b4a24aa68d6387ebba9aa7cbb487419578ea1","type":"application/call-history+json","uri":"079ddd3b04f35f6381f2516315e6aa5b98d43ef4"}
     206              :     //     {"confId":"6342183642926168","device":"c87dc5b688c0e6a7d1cd30fe5c2b4a24aa68d6387ebba9aa7cbb487419578ea1","duration":"9142","type":"application/call-history+json","uri":"079ddd3b04f35f6381f2516315e6aa5b98d43ef4"}
     207            3 :     static CommitMessage outgoingCallEnd(const std::string& peer, uint64_t duration_ms, const std::string& reason = "")
     208              :     {
     209            3 :         CommitMessage msg;
     210            3 :         msg.type = CommitType::CALL_HISTORY;
     211            3 :         msg.to = peer;
     212            3 :         msg.duration = std::to_string(duration_ms);
     213            3 :         msg.reason = reason;
     214            3 :         return msg;
     215            0 :     }
     216           14 :     static CommitMessage conferenceHostingStart(const std::string& confId,
     217              :                                                 const std::string& device,
     218              :                                                 const std::string& hostId)
     219              :     {
     220           14 :         CommitMessage msg;
     221           14 :         msg.type = CommitType::CALL_HISTORY;
     222           14 :         msg.confId = confId;
     223           14 :         msg.device = device;
     224           14 :         msg.uri = hostId;
     225           14 :         return msg;
     226            0 :     }
     227           10 :     static CommitMessage conferenceHostingEnd(const std::string& confId,
     228              :                                               const std::string& device,
     229              :                                               const std::string& hostId,
     230              :                                               uint64_t duration_ms)
     231              :     {
     232           10 :         CommitMessage msg;
     233           10 :         msg.type = CommitType::CALL_HISTORY;
     234           10 :         msg.confId = confId;
     235           10 :         msg.device = device;
     236           10 :         msg.uri = hostId;
     237           10 :         msg.duration = std::to_string(duration_ms);
     238           10 :         return msg;
     239            0 :     }
     240              : 
     241              :     // When a user sends a file in a conversation, a commit of type "application/data-transfer+json"
     242              :     // is created with the following fields:
     243              :     // - "displayName": the file name
     244              :     // - "sha3sum": the SHA3-512 hash of the file content, encoded as a hexadecimal string
     245              :     // - "tid": an ID for the file transfer (currently consists of a nonzero 64-bit unsigned integer
     246              :     //   generated randomly by the sender, encoded as a decimal string)
     247              :     // - "totalSize": the file size in bytes
     248              :     // For example:
     249              :     //
     250              :     //     {"displayName":"some_image.png","sha3sum":"5ce2fb16eb16c9dc42f824218ec0b7be4927d9f9fef9860161159faee1c4236a758aeb4ed98b27bf439364ea3199fce23181be4720c79756cf714271b702efcd","tid":"6147910008623250","totalSize":"581","type":"application/data-transfer+json"}
     251              :     //
     252              :     // File transfers can optionally include a "reply-to" field with the ID of the message being
     253              :     // replied to, e.g.:
     254              :     //
     255              :     //     {"displayName":"aang.jpg","reply-to":"160e330e417401ecdd11094a8dad1355bd734583","sha3sum":"cae439cabde1dd86e15210f1f1486e7bbe0b901e9cb40d087b9673b7827ac5c9b2bf3d5fa3872666d418f205e986e8afa9977ddaba5e4141bdb157fd754656c2","tid":"693561489759880","totalSize":"89040","type":"application/data-transfer+json"}
     256              :     //
     257              :     // A deleted file is represented by a commit of type "application/data-transfer+json" with an "edit"
     258              :     // field containing the ID of the original commit and an empty "tid", e.g.:
     259              :     //
     260              :     //     {"edit":"7fc3b0cba7e0742b0753051a576a5d17a77a57d0","tid":"","type":"application/data-transfer+json"}
     261           14 :     static CommitMessage fileSent(const std::string& displayName,
     262              :                                   const std::string& sha3sum,
     263              :                                   uint64_t tid,
     264              :                                   int64_t totalSize,
     265              :                                   const std::string& replyToId = "")
     266              :     {
     267           14 :         CommitMessage msg;
     268           14 :         msg.type = CommitType::DATA_TRANSFER;
     269           14 :         msg.displayName = displayName;
     270           14 :         msg.sha3sum = sha3sum;
     271           14 :         msg.replyTo = replyToId;
     272           14 :         msg.tid = std::to_string(tid);
     273           14 :         msg.totalSize = totalSize;
     274           14 :         return msg;
     275            0 :     }
     276            2 :     static CommitMessage fileDeleted(const std::string& fileCommitId)
     277              :     {
     278            2 :         CommitMessage msg;
     279            2 :         msg.type = CommitType::DATA_TRANSFER;
     280            2 :         msg.editedId = fileCommitId;
     281            2 :         return msg;
     282            0 :     }
     283              : 
     284              :     // Every Jami conversation starts with a commit of type "initial" containing a "mode" field indicating the
     285              :     // kind of conversation. There are currently two supported values for the mode: 0 (ConversationMode::ONE_TO_ONE)
     286              :     // and 2 (ConversationMode::INVITES_ONLY). In the case of one-to-one conversations (mode 0), there is an
     287              :     // additional "invited" field containing the Jami ID of the other participant. For example:
     288              :     //
     289              :     //     {"invited":"f32701048c59f9ad6a095c6d14650294b4cf30a4","mode":0,"type":"initial"}
     290              :     //     {"mode":2,"type":"initial"}
     291              :     //
     292              :     // Jami allows users to create one-to-one conversations with themselves. In that case, the "invited" field is
     293              :     // still present and contains the user's own Jami ID.
     294          203 :     static CommitMessage initial(ConversationMode mode, const std::string& invitedId = "")
     295              :     {
     296          203 :         CommitMessage msg;
     297          203 :         msg.type = CommitType::INITIAL;
     298          203 :         msg.mode = static_cast<int>(mode);
     299          203 :         if (mode == ConversationMode::ONE_TO_ONE) {
     300           69 :             msg.invited = invitedId;
     301              :         }
     302          203 :         return msg;
     303            0 :     }
     304              : 
     305              :     // Commits of type "vote" are created by admins when voting to ban or unban a user from a
     306              :     // conversation. They have a "uri" field with the Jami ID of the user being voted on, e.g.:
     307              :     //
     308              :     //     {"type":"vote","uri":"f32701048c59f9ad6a095c6d14650294b4cf30a4"}
     309           13 :     static CommitMessage vote(const std::string& userId)
     310              :     {
     311           13 :         CommitMessage msg;
     312           13 :         msg.type = CommitType::VOTE;
     313           13 :         msg.uri = userId;
     314           13 :         return msg;
     315            0 :     }
     316              : 
     317              :     // Commits that modify a conversation's profile (which is stored in the 'profile.vcf' file at
     318              :     // the root of the conversation repository) are of type "application/update-profile". They
     319              :     // do not contain any additional fields:
     320              :     //
     321              :     //      {"type":"application/update-profile"}
     322           10 :     static CommitMessage updateProfile()
     323              :     {
     324           10 :         CommitMessage msg;
     325           10 :         msg.type = CommitType::UPDATE_PROFILE;
     326           10 :         return msg;
     327            0 :     }
     328              : 
     329              :     Json::Value toJson() const;
     330              :     std::string toString() const;
     331              :     static std::optional<CommitMessage> fromString(const std::string& str);
     332              : };
     333              : 
     334              : } // namespace jami
        

Generated by: LCOV version 2.0-1