LCOV - code coverage report
Current view: top level - src/jamidht - gitserver.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 81.9 % 265 217
Test Date: 2026-11-09 10:41:14 Functions: 84.4 % 32 27

            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              : #include "gitserver.h"
      18              : 
      19              : #include "fileutils.h"
      20              : #include "logger.h"
      21              : #include "gittransport.h"
      22              : #include "manager.h"
      23              : #include <opendht/thread_pool.h>
      24              : #include <dhtnet/multiplexed_socket.h>
      25              : #include <fmt/compile.h>
      26              : 
      27              : #include <charconv>
      28              : #include <ctime>
      29              : #include <fstream>
      30              : #include <git2.h>
      31              : #include <iomanip>
      32              : 
      33              : using namespace std::string_view_literals;
      34              : constexpr auto FLUSH_PKT = "0000"sv;
      35              : constexpr auto NAK_PKT = "0008NAK\n"sv;
      36              : constexpr auto DONE_CMD = "done\n"sv;
      37              : constexpr auto WANT_CMD = "want"sv;
      38              : constexpr auto HAVE_CMD = "have"sv;
      39              : constexpr auto SERVER_CAPABILITIES = " HEAD\0side-band side-band-64k shallow no-progress include-tag"sv;
      40              : 
      41              : namespace jami {
      42              : 
      43              : class GitServer::Impl
      44              : {
      45              : public:
      46         1529 :     Impl(const std::string& accountId,
      47              :          const std::string& repositoryId,
      48              :          const std::string& repository,
      49              :          const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket)
      50         1529 :         : accountId_(accountId)
      51         1529 :         , repositoryId_(repositoryId)
      52         1529 :         , repository_(repository)
      53         1529 :         , socket_(socket)
      54              :     {
      55              :         // JAMI_DEBUG("[Account {}] [Conversation {}] [GitServer {}] created",
      56              :         //            accountId_, repositoryId_, fmt::ptr(this));
      57              :         // Check at least if repository is correct
      58              :         git_repository* repo;
      59         1529 :         if (git_repository_open(&repo, repository_.c_str()) != 0) {
      60           92 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
      61           46 :             return;
      62              :         }
      63         1483 :         git_repository_free(repo);
      64              : 
      65         1483 :         socket_->setOnRecv([this](const uint8_t* buf, std::size_t len) {
      66         6671 :             if (parseOrder(std::string_view((const char*) buf, len)))
      67        16942 :                 while (parseOrder())
      68              :                     ;
      69         6671 :             return len;
      70              :         });
      71            0 :     }
      72         1529 :     ~Impl()
      73              :     {
      74         1529 :         stop();
      75              :         // JAMI_DEBUG("[Account {}] [Conversation {}] [GitServer {}] destroyed", accountId_, repositoryId_, fmt::ptr(this));
      76         1529 :     }
      77         3785 :     void stop()
      78              :     {
      79         3785 :         if (isDestroying_.exchange(true))
      80         2256 :             return;
      81              :         // Clearing the receive callback waits for an in-flight invocation to
      82              :         // return, so no order can be parsed against a half-destroyed Impl once
      83              :         // this call completes.
      84         1529 :         socket_->setOnRecv({});
      85         3058 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
      86              :     }
      87              :     bool parseOrder(std::string_view buf = {});
      88              : 
      89              :     void sendReferenceCapabilities(bool sendVersion = false);
      90              :     bool NAK();
      91              :     void notifyUpToDate();
      92              :     void ACKCommon();
      93              :     bool ACKFirst();
      94              :     void sendPackData();
      95              :     std::map<std::string, std::string> getParameters(std::string_view pkt_line);
      96              : 
      97              :     std::string accountId_ {};
      98              :     std::string repositoryId_ {};
      99              :     std::string repository_ {};
     100              :     std::shared_ptr<dhtnet::ChannelSocketInterface> socket_ {};
     101              :     std::string wantedReference_ {};
     102              :     // HEAD announced during the reference advertisement, used to acknowledge peers
     103              :     // that are already up to date and therefore never send a want.
     104              :     std::string advertisedHead_ {};
     105              :     bool sawWant_ {false};
     106              :     bool upToDateNotified_ {false};
     107              :     std::string common_ {};
     108              :     std::vector<std::string> haveRefs_ {};
     109              :     std::string cachedPkt_ {};
     110              :     std::atomic_bool isDestroying_ {false};
     111              :     onFetchedCb onFetchedCb_ {};
     112              : };
     113              : 
     114              : bool
     115        23613 : GitServer::Impl::parseOrder(std::string_view buf)
     116              : {
     117        23613 :     std::string pkt = std::move(cachedPkt_);
     118        23613 :     if (!buf.empty())
     119         6671 :         pkt += buf;
     120              : 
     121              :     // Parse pkt len
     122              :     // Reference: https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt#L51
     123              :     // The first four bytes define the length of the packet and 0000 is a FLUSH pkt
     124              : 
     125        23613 :     unsigned int pkt_len = 0;
     126        23613 :     auto [p, ec] = std::from_chars(pkt.data(), pkt.data() + 4, pkt_len, 16);
     127        23613 :     if (ec != std::errc()) {
     128            0 :         JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to parse packet size",
     129              :                    accountId_,
     130              :                    repositoryId_,
     131              :                    fmt::ptr(this));
     132              :     }
     133        23613 :     if (pkt_len != pkt.size()) {
     134              :         // Store next packet part
     135        19742 :         if (pkt_len == 0) {
     136              :             // FLUSH_PKT
     137         4415 :             pkt_len = 4;
     138              :         }
     139        19742 :         cachedPkt_ = pkt.substr(pkt_len);
     140              :     }
     141              : 
     142        23613 :     auto pack = std::string_view(pkt).substr(4, pkt_len - 4);
     143        23613 :     if (pack == DONE_CMD) {
     144              :         // Reference:
     145              :         // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L390 Do
     146              :         // not do multi-ack, just send ACK + pack file
     147              :         // In case of no common base, send NAK
     148         1617 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer negotiation is done. Answering to want order",
     149              :                  accountId_,
     150              :                  repositoryId_,
     151              :                  fmt::ptr(this));
     152              :         bool sendData;
     153         1617 :         if (common_.empty())
     154          430 :             sendData = NAK();
     155              :         else
     156         1187 :             sendData = ACKFirst();
     157         1617 :         if (sendData)
     158         1617 :             sendPackData();
     159         1617 :         return !cachedPkt_.empty();
     160        21996 :     } else if (pack.empty()) {
     161         4415 :         if (!haveRefs_.empty()) {
     162              :             // Reference:
     163              :             // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L390
     164              :             // Do not do multi-ack, just send ACK + pack file In case of no common base ACK
     165          562 :             ACKCommon();
     166          562 :             NAK();
     167              :         } else {
     168              :             // A flush right after the reference advertisement, with neither a want nor a have,
     169              :             // is the peer telling us it already owns everything we announced. No pack is sent,
     170              :             // so the regular fetch acknowledgement never fires and the messages we advertised
     171              :             // would stay marked as sending forever. Acknowledge the announced HEAD instead.
     172         3853 :             notifyUpToDate();
     173              :         }
     174         4415 :         return !cachedPkt_.empty();
     175              :     }
     176              : 
     177        17581 :     auto lim = pack.find(' ');
     178        17581 :     auto cmd = pack.substr(0, lim);
     179        17581 :     auto dat = (lim < pack.size()) ? pack.substr(lim + 1) : std::string_view {};
     180        17581 :     if (cmd == UPLOAD_PACK_CMD) {
     181              :         // Cf: https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     182              :         // References discovery
     183              :         /*JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Upload pack command detected.",
     184              :                  accountId_,
     185              :                  repositoryId_,
     186              :                  fmt::ptr(this));*/
     187         2250 :         auto version = 1;
     188         2250 :         auto parameters = getParameters(dat);
     189         2250 :         auto versionIt = parameters.find("version");
     190         2250 :         bool sendVersion = false;
     191         2250 :         if (versionIt != parameters.end()) {
     192            0 :             auto [p, ec] = std::from_chars(versionIt->second.data(),
     193            0 :                                            versionIt->second.data() + versionIt->second.size(),
     194              :                                            version);
     195            0 :             if (ec == std::errc()) {
     196            0 :                 sendVersion = true;
     197              :             } else {
     198            0 :                 JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Invalid version detected: {}",
     199              :                              accountId_,
     200              :                              repositoryId_,
     201              :                              fmt::ptr(this),
     202              :                              versionIt->second);
     203              :             }
     204              :         }
     205         2250 :         if (version == 1) {
     206         2250 :             sendReferenceCapabilities(sendVersion);
     207              :         } else {
     208            0 :             JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] That protocol version is not yet supported "
     209              :                        "(version: {:d})",
     210              :                        accountId_,
     211              :                        repositoryId_,
     212              :                        fmt::ptr(this),
     213              :                        version);
     214              :         }
     215        17581 :     } else if (cmd == WANT_CMD) {
     216              :         // Reference:
     217              :         // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L229
     218              :         // TODO can have more want
     219         1617 :         wantedReference_ = dat.substr(0, 40);
     220         1617 :         sawWant_ = true;
     221         1617 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer want ref: {}",
     222              :                  accountId_,
     223              :                  repositoryId_,
     224              :                  fmt::ptr(this),
     225              :                  wantedReference_);
     226        13714 :     } else if (cmd == HAVE_CMD) {
     227        13714 :         const auto& commit = haveRefs_.emplace_back(dat.substr(0, 40));
     228        13714 :         if (common_.empty()) {
     229              :             // Detect first common commit
     230              :             // Reference:
     231              :             // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L390
     232              :             // TODO do not open repository every time
     233              :             git_repository* repo;
     234         1187 :             if (git_repository_open(&repo, repository_.c_str()) != 0) {
     235            0 :                 JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open {}",
     236              :                              accountId_,
     237              :                              repositoryId_,
     238              :                              fmt::ptr(this),
     239              :                              repository_);
     240            0 :                 return !cachedPkt_.empty();
     241              :             }
     242         1187 :             GitRepository rep {repo};
     243              :             git_oid commit_id;
     244         1187 :             if (git_oid_fromstr(&commit_id, commit.c_str()) == 0) {
     245              :                 // Reference found
     246         1187 :                 common_ = commit;
     247              :             }
     248         1187 :         }
     249              :     } else {
     250            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unwanted packet received: {}",
     251              :                      accountId_,
     252              :                      repositoryId_,
     253              :                      fmt::ptr(this),
     254              :                      pkt);
     255              :     }
     256        17581 :     return !cachedPkt_.empty();
     257        23613 : }
     258              : 
     259              : std::string
     260        26563 : toGitHex(size_t value)
     261              : {
     262       106252 :     return fmt::format(FMT_COMPILE("{:04x}"), value & 0x0FFFF);
     263              : }
     264              : 
     265              : void
     266         2250 : GitServer::Impl::sendReferenceCapabilities(bool sendVersion)
     267              : {
     268              :     // Get references
     269              :     // First, get the HEAD reference
     270              :     // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     271              :     git_repository* repo;
     272         2250 :     if (git_repository_open(&repo, repository_.c_str()) != 0) {
     273            1 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open {}",
     274              :                      accountId_,
     275              :                      repositoryId_,
     276              :                      fmt::ptr(this),
     277              :                      repository_);
     278            2 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     279            1 :         return;
     280              :     }
     281         2249 :     GitRepository rep {repo};
     282              : 
     283              :     // Answer with the version number
     284              :     // **** When the client initially connects the server will immediately respond
     285              :     // **** with a version number (if "version=1" is sent as an Extra Parameter),
     286         2249 :     std::error_code ec;
     287         2249 :     if (sendVersion) {
     288            0 :         constexpr auto toSend = "000eversion 1\0"sv;
     289            0 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.data()), toSend.size(), ec);
     290            0 :         if (ec) {
     291            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     292              :                          accountId_,
     293              :                          repositoryId_,
     294              :                          fmt::ptr(this),
     295              :                          repository_,
     296              :                          ec.message());
     297            0 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     298            0 :             return;
     299              :         }
     300              :     }
     301              : 
     302              :     git_oid commit_id;
     303         2249 :     if (git_reference_name_to_id(&commit_id, rep.get(), "HEAD") < 0) {
     304            0 :         JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to get reference for HEAD",
     305              :                    accountId_,
     306              :                    repositoryId_,
     307              :                    fmt::ptr(this));
     308            0 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     309            0 :         return;
     310              :     }
     311         2249 :     std::string_view currentHead = git_oid_tostr_s(&commit_id);
     312         2249 :     advertisedHead_ = currentHead;
     313              : 
     314              :     // Send references
     315         2249 :     std::ostringstream packet;
     316         2249 :     packet << toGitHex(5 + currentHead.size() + SERVER_CAPABILITIES.size());
     317         2249 :     packet << currentHead << SERVER_CAPABILITIES << "\n";
     318              : 
     319              :     // Now, add other references
     320              :     git_strarray refs;
     321         2249 :     if (git_reference_list(&refs, rep.get()) == 0) {
     322        24926 :         for (std::size_t i = 0; i < refs.count; ++i) {
     323        22677 :             std::string_view ref = refs.strings[i];
     324        22677 :             if (git_reference_name_to_id(&commit_id, rep.get(), ref.data()) < 0) {
     325            0 :                 JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to get reference for {}",
     326              :                              accountId_,
     327              :                              repositoryId_,
     328              :                              fmt::ptr(this),
     329              :                              ref);
     330            0 :                 continue;
     331              :             }
     332        22677 :             currentHead = git_oid_tostr_s(&commit_id);
     333              : 
     334        22677 :             packet << toGitHex(6 /* size + space + \n */ + currentHead.size() + ref.size());
     335        22677 :             packet << currentHead << " " << ref << "\n";
     336              :         }
     337              :     }
     338         2249 :     git_strarray_dispose(&refs);
     339              : 
     340              :     // And add FLUSH
     341         2249 :     packet << FLUSH_PKT;
     342         2249 :     auto toSend = packet.str();
     343         2249 :     socket_->write(reinterpret_cast<const unsigned char*>(toSend.data()), toSend.size(), ec);
     344         2249 :     if (ec) {
     345            1 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     346              :                      accountId_,
     347              :                      repositoryId_,
     348              :                      fmt::ptr(this),
     349              :                      repository_,
     350              :                      ec.message());
     351            2 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     352              :     }
     353         2249 : }
     354              : 
     355              : void
     356         3853 : GitServer::Impl::notifyUpToDate()
     357              : {
     358         3853 :     if (sawWant_ || upToDateNotified_ || advertisedHead_.empty())
     359         3226 :         return;
     360          627 :     upToDateNotified_ = true;
     361              :     /*JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer is already up to date at {}",
     362              :              accountId_,
     363              :              repositoryId_,
     364              :              fmt::ptr(this),
     365              :              advertisedHead_);*/
     366          627 :     if (onFetchedCb_)
     367          627 :         onFetchedCb_(advertisedHead_);
     368              : }
     369              : 
     370              : void
     371          562 : GitServer::Impl::ACKCommon()
     372              : {
     373          562 :     std::error_code ec;
     374              :     // Ack common base
     375          562 :     if (!common_.empty()) {
     376         1124 :         auto toSend = fmt::format(FMT_COMPILE("{:04x}ACK {} continue\n"),
     377          562 :                                   18 + common_.size() /* size + ACK + space * 2 + continue + \n */,
     378          562 :                                   common_);
     379          562 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.c_str()), toSend.size(), ec);
     380          562 :         if (ec) {
     381            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     382              :                          accountId_,
     383              :                          repositoryId_,
     384              :                          fmt::ptr(this),
     385              :                          repository_,
     386              :                          ec.message());
     387            0 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     388              :         }
     389          562 :     }
     390          562 : }
     391              : 
     392              : bool
     393         1187 : GitServer::Impl::ACKFirst()
     394              : {
     395         1187 :     std::error_code ec;
     396              :     // Ack common base
     397         1187 :     if (!common_.empty()) {
     398         2374 :         auto toSend = fmt::format(FMT_COMPILE("{:04x}ACK {}\n"),
     399         1187 :                                   9 + common_.size() /* size + ACK + space + \n */,
     400         1187 :                                   common_);
     401         1187 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.c_str()), toSend.size(), ec);
     402         1187 :         if (ec) {
     403            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     404              :                          accountId_,
     405              :                          repositoryId_,
     406              :                          fmt::ptr(this),
     407              :                          repository_,
     408              :                          ec.message());
     409            0 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     410            0 :             return false;
     411              :         }
     412         1187 :     }
     413         1187 :     return true;
     414              : }
     415              : 
     416              : bool
     417          992 : GitServer::Impl::NAK()
     418              : {
     419          992 :     std::error_code ec;
     420              :     // NAK
     421          992 :     socket_->write(reinterpret_cast<const unsigned char*>(NAK_PKT.data()), NAK_PKT.size(), ec);
     422          992 :     if (ec) {
     423            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     424              :                      accountId_,
     425              :                      repositoryId_,
     426              :                      fmt::ptr(this),
     427              :                      repository_,
     428              :                      ec.message());
     429            0 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     430            0 :         return false;
     431              :     }
     432          992 :     return true;
     433              : }
     434              : 
     435              : void
     436         1617 : GitServer::Impl::sendPackData()
     437              : {
     438              :     git_repository* repo_ptr;
     439         1617 :     if (git_repository_open(&repo_ptr, repository_.c_str()) != 0) {
     440            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open {}",
     441              :                      accountId_,
     442              :                      repositoryId_,
     443              :                      fmt::ptr(this),
     444              :                      repository_);
     445            0 :         return;
     446              :     }
     447         1617 :     GitRepository repo {repo_ptr};
     448              : 
     449              :     git_packbuilder* pb_ptr;
     450         1617 :     if (git_packbuilder_new(&pb_ptr, repo.get()) != 0) {
     451            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open packbuilder for {}",
     452              :                      accountId_,
     453              :                      repositoryId_,
     454              :                      fmt::ptr(this),
     455              :                      repository_);
     456            0 :         return;
     457              :     }
     458         1617 :     GitPackBuilder pb {pb_ptr};
     459              : 
     460         1617 :     std::string fetched = wantedReference_;
     461              :     git_oid oid;
     462         1617 :     if (git_oid_fromstr(&oid, fetched.c_str()) < 0) {
     463            0 :         JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to get reference for commit {}",
     464              :                    accountId_,
     465              :                    repositoryId_,
     466              :                    fmt::ptr(this),
     467              :                    fetched);
     468            0 :         return;
     469              :     }
     470              : 
     471         1617 :     git_revwalk* walker_ptr = nullptr;
     472         1617 :     if (git_revwalk_new(&walker_ptr, repo.get()) < 0 || git_revwalk_push(walker_ptr, &oid) < 0) {
     473            0 :         if (walker_ptr)
     474            0 :             git_revwalk_free(walker_ptr);
     475            0 :         return;
     476              :     }
     477         1617 :     GitRevWalker walker {walker_ptr};
     478         1617 :     git_revwalk_sorting(walker.get(), GIT_SORT_TOPOLOGICAL);
     479              : 
     480              :     // Hide the commits the peer has. git_packbuilder_insert_walk() then skips
     481              :     // them along with the trees and blobs they reference, rather than sending
     482              :     // the whole tree, every certificate included, with each new commit.
     483         1617 :     git_odb* odb_ptr = nullptr;
     484         1617 :     if (git_repository_odb(&odb_ptr, repo.get()) == 0) {
     485         1617 :         GitOdb odb {odb_ptr};
     486        15331 :         for (const auto& have : haveRefs_) {
     487              :             // Peers also announce commits we lack; skip those without rescanning the packs.
     488              :             git_oid haveOid;
     489        13714 :             if (git_oid_fromstr(&haveOid, have.c_str()) == 0
     490        13714 :                 && git_odb_exists_ext(odb.get(), &haveOid, GIT_ODB_LOOKUP_NO_REFRESH))
     491        13694 :                 git_revwalk_hide(walker.get(), &haveOid);
     492              :         }
     493         1617 :     }
     494         1617 :     if (git_packbuilder_insert_walk(pb.get(), walker.get()) != 0) {
     495            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to insert commits for {}",
     496              :                      accountId_,
     497              :                      repositoryId_,
     498              :                      fmt::ptr(this),
     499              :                      repository_);
     500            0 :         return;
     501              :     }
     502              : 
     503         1617 :     git_buf data = {};
     504         1617 :     if (git_packbuilder_write_buf(&data, pb.get()) != 0) {
     505            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to write pack data for {}",
     506              :                      accountId_,
     507              :                      repositoryId_,
     508              :                      fmt::ptr(this),
     509              :                      repository_);
     510            0 :         return;
     511              :     }
     512              : 
     513         1617 :     std::size_t sent = 0;
     514         1617 :     std::size_t len = data.size;
     515         1617 :     std::error_code ec;
     516         1617 :     std::vector<uint8_t> toSendData;
     517              :     do {
     518              :         // cf https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     519              :         // In 'side-band-64k' mode it will send up to 65519 data bytes plus 1 control code, for a
     520              :         // total of up to 65520 bytes in a pkt-line.
     521         1637 :         std::size_t pkt_size = std::min(static_cast<std::size_t>(65515), len - sent);
     522         1637 :         std::string toSendHeader = toGitHex(pkt_size + 5);
     523         1637 :         toSendData.clear();
     524         1637 :         toSendData.reserve(pkt_size + 5);
     525         1637 :         toSendData.insert(toSendData.end(), toSendHeader.begin(), toSendHeader.end());
     526         1637 :         toSendData.push_back(0x1);
     527         1637 :         toSendData.insert(toSendData.end(), data.ptr + sent, data.ptr + sent + pkt_size);
     528              : 
     529         1637 :         socket_->write(reinterpret_cast<const unsigned char*>(toSendData.data()), toSendData.size(), ec);
     530         1637 :         if (ec) {
     531            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     532              :                          accountId_,
     533              :                          repositoryId_,
     534              :                          fmt::ptr(this),
     535              :                          repository_,
     536              :                          ec.message());
     537            0 :             git_buf_dispose(&data);
     538            0 :             return;
     539              :         }
     540         1637 :         sent += pkt_size;
     541         3274 :     } while (sent < len);
     542         1617 :     git_buf_dispose(&data);
     543         1617 :     toSendData = {};
     544              : 
     545              :     // And finish by a little FLUSH
     546         1617 :     socket_->write(reinterpret_cast<const uint8_t*>(FLUSH_PKT.data()), FLUSH_PKT.size(), ec);
     547         1617 :     if (ec) {
     548            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     549              :                      accountId_,
     550              :                      repositoryId_,
     551              :                      fmt::ptr(this),
     552              :                      repository_,
     553              :                      ec.message());
     554              :     }
     555              : 
     556              :     // Clear sent data
     557         1617 :     haveRefs_.clear();
     558         1617 :     wantedReference_.clear();
     559         1617 :     common_.clear();
     560         1617 :     if (onFetchedCb_)
     561         1615 :         onFetchedCb_(fetched);
     562         1617 : }
     563              : 
     564              : std::map<std::string, std::string>
     565         2250 : GitServer::Impl::getParameters(std::string_view pkt_line)
     566              : {
     567         2250 :     std::map<std::string, std::string> parameters;
     568         2250 :     std::string key, value;
     569         2250 :     auto isKey = true;
     570         2250 :     auto nullChar = 0;
     571       254250 :     for (auto letter : pkt_line) {
     572       252000 :         if (letter == '\0') {
     573              :             // parameters such as host or version are after the first \0
     574         4500 :             if (nullChar != 0 && !key.empty()) {
     575         2250 :                 parameters.try_emplace(std::move(key), std::move(value));
     576              :             }
     577         4500 :             nullChar += 1;
     578         4500 :             isKey = true;
     579         4500 :             key.clear();
     580         4500 :             value.clear();
     581       247500 :         } else if (letter == '=') {
     582         2250 :             isKey = false;
     583       245250 :         } else if (nullChar != 0) {
     584       153000 :             if (isKey) {
     585         9000 :                 key += letter;
     586              :             } else {
     587       144000 :                 value += letter;
     588              :             }
     589              :         }
     590              :     }
     591         4500 :     return parameters;
     592         2250 : }
     593              : 
     594         1529 : GitServer::GitServer(const std::string& accountId,
     595              :                      const std::string& conversationId,
     596         1529 :                      const std::shared_ptr<dhtnet::ChannelSocketInterface>& client)
     597              : {
     598         1529 :     auto path = (fileutils::get_data_dir() / accountId / "conversations" / conversationId).string();
     599         1529 :     pimpl_ = std::make_unique<GitServer::Impl>(accountId, conversationId, path, client);
     600         1529 : }
     601              : 
     602         1529 : GitServer::~GitServer()
     603              : {
     604         1529 :     stop();
     605         1529 :     pimpl_.reset();
     606         1529 : }
     607              : 
     608              : void
     609         1524 : GitServer::setOnFetched(const onFetchedCb& cb)
     610              : {
     611         1524 :     if (!pimpl_)
     612            0 :         return;
     613         1524 :     pimpl_->onFetchedCb_ = cb;
     614              : }
     615              : 
     616              : void
     617         2256 : GitServer::stop()
     618              : {
     619         2256 :     pimpl_->stop();
     620         2256 : }
     621              : 
     622              : } // namespace jami
        

Generated by: LCOV version 2.0-1