LCOV - code coverage report
Current view: top level - src/jamidht - gitserver.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 79.4 % 277 220
Test Date: 2026-06-13 09:18:46 Functions: 41.0 % 83 34

            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          967 :     Impl(const std::string& accountId,
      47              :          const std::string& repositoryId,
      48              :          const std::string& repository,
      49              :          const std::shared_ptr<dhtnet::ChannelSocket>& socket)
      50          967 :         : accountId_(accountId)
      51          967 :         , repositoryId_(repositoryId)
      52          967 :         , repository_(repository)
      53          967 :         , socket_(socket)
      54              :     {
      55         3868 :         JAMI_DEBUG("[Account {}] [Conversation {}] [GitServer {}] created", accountId_, repositoryId_, fmt::ptr(this));
      56              :         // Check at least if repository is correct
      57              :         git_repository* repo;
      58          967 :         if (git_repository_open(&repo, repository_.c_str()) != 0) {
      59           38 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
      60           19 :             return;
      61              :         }
      62          948 :         git_repository_free(repo);
      63              : 
      64          948 :         socket_->setOnRecv([this](const uint8_t* buf, std::size_t len) {
      65         5986 :             std::lock_guard lk(destroyMtx_);
      66         5987 :             if (isDestroying_)
      67            0 :                 return len;
      68         5986 :             if (parseOrder(std::string_view((const char*) buf, len)))
      69        15445 :                 while (parseOrder())
      70              :                     ;
      71         5985 :             return len;
      72         5985 :         });
      73            0 :     }
      74          967 :     ~Impl()
      75              :     {
      76          967 :         stop();
      77         3868 :         JAMI_DEBUG("[Account {}] [Conversation {}] [GitServer {}] destroyed", accountId_, repositoryId_, fmt::ptr(this));
      78          967 :     }
      79         2402 :     void stop()
      80              :     {
      81         2402 :         std::lock_guard lk(destroyMtx_);
      82         2401 :         if (isDestroying_.exchange(true)) {
      83         1435 :             socket_->setOnRecv({});
      84         2868 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
      85              :         }
      86         2402 :     }
      87              :     bool parseOrder(std::string_view buf = {});
      88              : 
      89              :     void sendReferenceCapabilities(bool sendVersion = false);
      90              :     bool NAK();
      91              :     void ACKCommon();
      92              :     bool ACKFirst();
      93              :     void sendPackData();
      94              :     std::map<std::string, std::string> getParameters(std::string_view pkt_line);
      95              : 
      96              :     std::string accountId_ {};
      97              :     std::string repositoryId_ {};
      98              :     std::string repository_ {};
      99              :     std::shared_ptr<dhtnet::ChannelSocket> socket_ {};
     100              :     std::string wantedReference_ {};
     101              :     std::string common_ {};
     102              :     std::vector<std::string> haveRefs_ {};
     103              :     std::string cachedPkt_ {};
     104              :     std::mutex destroyMtx_ {};
     105              :     std::atomic_bool isDestroying_ {false};
     106              :     onFetchedCb onFetchedCb_ {};
     107              : };
     108              : 
     109              : bool
     110        21430 : GitServer::Impl::parseOrder(std::string_view buf)
     111              : {
     112        21430 :     std::string pkt = std::move(cachedPkt_);
     113        21412 :     if (!buf.empty())
     114         5987 :         pkt += buf;
     115              : 
     116              :     // Parse pkt len
     117              :     // Reference: https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt#L51
     118              :     // The first four bytes define the length of the packet and 0000 is a FLUSH pkt
     119              : 
     120        21414 :     unsigned int pkt_len = 0;
     121        21414 :     auto [p, ec] = std::from_chars(pkt.data(), pkt.data() + 4, pkt_len, 16);
     122        21443 :     if (ec != std::errc()) {
     123            0 :         JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to parse packet size",
     124              :                    accountId_,
     125              :                    repositoryId_,
     126              :                    fmt::ptr(this));
     127              :     }
     128        21443 :     if (pkt_len != pkt.size()) {
     129              :         // Store next packet part
     130        18166 :         if (pkt_len == 0) {
     131              :             // FLUSH_PKT
     132         3828 :             pkt_len = 4;
     133              :         }
     134        18166 :         cachedPkt_ = pkt.substr(pkt_len);
     135              :     }
     136              : 
     137        21404 :     auto pack = std::string_view(pkt).substr(4, pkt_len - 4);
     138        21425 :     if (pack == DONE_CMD) {
     139              :         // Reference:
     140              :         // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L390 Do
     141              :         // not do multi-ack, just send ACK + pack file
     142              :         // In case of no common base, send NAK
     143         4440 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer negotiation is done. Answering to want order",
     144              :                  accountId_,
     145              :                  repositoryId_,
     146              :                  fmt::ptr(this));
     147              :         bool sendData;
     148         1110 :         if (common_.empty())
     149          190 :             sendData = NAK();
     150              :         else
     151          920 :             sendData = ACKFirst();
     152         1110 :         if (sendData)
     153         1110 :             sendPackData();
     154         1110 :         return !cachedPkt_.empty();
     155        20330 :     } else if (pack.empty()) {
     156         3827 :         if (!haveRefs_.empty()) {
     157              :             // Reference:
     158              :             // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L390
     159              :             // Do not do multi-ack, just send ACK + pack file In case of no common base ACK
     160          560 :             ACKCommon();
     161          560 :             NAK();
     162              :         }
     163         3828 :         return !cachedPkt_.empty();
     164              :     }
     165              : 
     166        16493 :     auto lim = pack.find(' ');
     167        16487 :     auto cmd = pack.substr(0, lim);
     168        16496 :     auto dat = (lim < pack.size()) ? pack.substr(lim + 1) : std::string_view {};
     169        16495 :     if (cmd == UPLOAD_PACK_CMD) {
     170              :         // Cf: https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     171              :         // References discovery
     172         8635 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Upload pack command detected.",
     173              :                  accountId_,
     174              :                  repositoryId_,
     175              :                  fmt::ptr(this));
     176         2159 :         auto version = 1;
     177         2159 :         auto parameters = getParameters(dat);
     178         2159 :         auto versionIt = parameters.find("version");
     179         2159 :         bool sendVersion = false;
     180         2159 :         if (versionIt != parameters.end()) {
     181            0 :             auto [p, ec] = std::from_chars(versionIt->second.data(),
     182            0 :                                            versionIt->second.data() + versionIt->second.size(),
     183              :                                            version);
     184            0 :             if (ec == std::errc()) {
     185            0 :                 sendVersion = true;
     186              :             } else {
     187            0 :                 JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Invalid version detected: {}",
     188              :                              accountId_,
     189              :                              repositoryId_,
     190              :                              fmt::ptr(this),
     191              :                              versionIt->second);
     192              :             }
     193              :         }
     194         2159 :         if (version == 1) {
     195         2159 :             sendReferenceCapabilities(sendVersion);
     196              :         } else {
     197            0 :             JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] That protocol version is not yet supported "
     198              :                        "(version: {:d})",
     199              :                        accountId_,
     200              :                        repositoryId_,
     201              :                        fmt::ptr(this),
     202              :                        version);
     203              :         }
     204        16515 :     } else if (cmd == WANT_CMD) {
     205              :         // Reference:
     206              :         // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L229
     207              :         // TODO can have more want
     208         1108 :         wantedReference_ = dat.substr(0, 40);
     209         4439 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer want ref: {}",
     210              :                  accountId_,
     211              :                  repositoryId_,
     212              :                  fmt::ptr(this),
     213              :                  wantedReference_);
     214        13221 :     } else if (cmd == HAVE_CMD) {
     215        13227 :         const auto& commit = haveRefs_.emplace_back(dat.substr(0, 40));
     216        13229 :         if (common_.empty()) {
     217              :             // Detect first common commit
     218              :             // Reference:
     219              :             // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L390
     220              :             // TODO do not open repository every time
     221              :             git_repository* repo;
     222          920 :             if (git_repository_open(&repo, repository_.c_str()) != 0) {
     223            0 :                 JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open {}",
     224              :                              accountId_,
     225              :                              repositoryId_,
     226              :                              fmt::ptr(this),
     227              :                              repository_);
     228            0 :                 return !cachedPkt_.empty();
     229              :             }
     230          920 :             GitRepository rep {repo};
     231              :             git_oid commit_id;
     232          920 :             if (git_oid_fromstr(&commit_id, commit.c_str()) == 0) {
     233              :                 // Reference found
     234          920 :                 common_ = commit;
     235              :             }
     236          920 :         }
     237              :     } else {
     238            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unwanted packet received: {}",
     239              :                      accountId_,
     240              :                      repositoryId_,
     241              :                      fmt::ptr(this),
     242              :                      pkt);
     243              :     }
     244        16495 :     return !cachedPkt_.empty();
     245        21429 : }
     246              : 
     247              : std::string
     248        35459 : toGitHex(size_t value)
     249              : {
     250       141811 :     return fmt::format(FMT_COMPILE("{:04x}"), value & 0x0FFFF);
     251              : }
     252              : 
     253              : void
     254         2159 : GitServer::Impl::sendReferenceCapabilities(bool sendVersion)
     255              : {
     256              :     // Get references
     257              :     // First, get the HEAD reference
     258              :     // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     259              :     git_repository* repo;
     260         2159 :     if (git_repository_open(&repo, repository_.c_str()) != 0) {
     261            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open {}",
     262              :                      accountId_,
     263              :                      repositoryId_,
     264              :                      fmt::ptr(this),
     265              :                      repository_);
     266            0 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     267            0 :         return;
     268              :     }
     269         2159 :     GitRepository rep {repo};
     270              : 
     271              :     // Answer with the version number
     272              :     // **** When the client initially connects the server will immediately respond
     273              :     // **** with a version number (if "version=1" is sent as an Extra Parameter),
     274         2159 :     std::error_code ec;
     275         2159 :     if (sendVersion) {
     276            0 :         constexpr auto toSend = "000eversion 1\0"sv;
     277            0 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.data()), toSend.size(), ec);
     278            0 :         if (ec) {
     279            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     280              :                          accountId_,
     281              :                          repositoryId_,
     282              :                          fmt::ptr(this),
     283              :                          repository_,
     284              :                          ec.message());
     285            0 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     286            0 :             return;
     287              :         }
     288              :     }
     289              : 
     290              :     git_oid commit_id;
     291         2159 :     if (git_reference_name_to_id(&commit_id, rep.get(), "HEAD") < 0) {
     292            0 :         JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to get reference for HEAD",
     293              :                    accountId_,
     294              :                    repositoryId_,
     295              :                    fmt::ptr(this));
     296            0 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     297            0 :         return;
     298              :     }
     299         2159 :     std::string_view currentHead = git_oid_tostr_s(&commit_id);
     300              : 
     301              :     // Send references
     302         2159 :     std::ostringstream packet;
     303         2159 :     packet << toGitHex(5 + currentHead.size() + SERVER_CAPABILITIES.size());
     304         2159 :     packet << currentHead << SERVER_CAPABILITIES << "\n";
     305              : 
     306              :     // Now, add other references
     307              :     git_strarray refs;
     308         2159 :     if (git_reference_list(&refs, rep.get()) == 0) {
     309        33918 :         for (std::size_t i = 0; i < refs.count; ++i) {
     310        31760 :             std::string_view ref = refs.strings[i];
     311        31719 :             if (git_reference_name_to_id(&commit_id, rep.get(), ref.data()) < 0) {
     312            0 :                 JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to get reference for {}",
     313              :                              accountId_,
     314              :                              repositoryId_,
     315              :                              fmt::ptr(this),
     316              :                              ref);
     317            0 :                 continue;
     318            0 :             }
     319        31757 :             currentHead = git_oid_tostr_s(&commit_id);
     320              : 
     321        31722 :             packet << toGitHex(6 /* size + space + \n */ + currentHead.size() + ref.size());
     322        31672 :             packet << currentHead << " " << ref << "\n";
     323              :         }
     324              :     }
     325         2158 :     git_strarray_dispose(&refs);
     326              : 
     327              :     // And add FLUSH
     328         2159 :     packet << FLUSH_PKT;
     329         2158 :     auto toSend = packet.str();
     330         2159 :     socket_->write(reinterpret_cast<const unsigned char*>(toSend.data()), toSend.size(), ec);
     331         2159 :     if (ec) {
     332            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     333              :                      accountId_,
     334              :                      repositoryId_,
     335              :                      fmt::ptr(this),
     336              :                      repository_,
     337              :                      ec.message());
     338            0 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     339              :     }
     340         2159 : }
     341              : 
     342              : void
     343          560 : GitServer::Impl::ACKCommon()
     344              : {
     345          560 :     std::error_code ec;
     346              :     // Ack common base
     347          560 :     if (!common_.empty()) {
     348         1120 :         auto toSend = fmt::format(FMT_COMPILE("{:04x}ACK {} continue\n"),
     349          560 :                                   18 + common_.size() /* size + ACK + space * 2 + continue + \n */,
     350          560 :                                   common_);
     351          560 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.c_str()), toSend.size(), ec);
     352          560 :         if (ec) {
     353            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     354              :                          accountId_,
     355              :                          repositoryId_,
     356              :                          fmt::ptr(this),
     357              :                          repository_,
     358              :                          ec.message());
     359            0 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     360              :         }
     361          560 :     }
     362          560 : }
     363              : 
     364              : bool
     365          920 : GitServer::Impl::ACKFirst()
     366              : {
     367          920 :     std::error_code ec;
     368              :     // Ack common base
     369          920 :     if (!common_.empty()) {
     370         1840 :         auto toSend = fmt::format(FMT_COMPILE("{:04x}ACK {}\n"),
     371          920 :                                   9 + common_.size() /* size + ACK + space + \n */,
     372          920 :                                   common_);
     373          920 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.c_str()), toSend.size(), ec);
     374          920 :         if (ec) {
     375            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     376              :                          accountId_,
     377              :                          repositoryId_,
     378              :                          fmt::ptr(this),
     379              :                          repository_,
     380              :                          ec.message());
     381            0 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     382            0 :             return false;
     383              :         }
     384          920 :     }
     385          920 :     return true;
     386              : }
     387              : 
     388              : bool
     389          750 : GitServer::Impl::NAK()
     390              : {
     391          750 :     std::error_code ec;
     392              :     // NAK
     393          750 :     socket_->write(reinterpret_cast<const unsigned char*>(NAK_PKT.data()), NAK_PKT.size(), ec);
     394          750 :     if (ec) {
     395            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     396              :                      accountId_,
     397              :                      repositoryId_,
     398              :                      fmt::ptr(this),
     399              :                      repository_,
     400              :                      ec.message());
     401            0 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     402            0 :         return false;
     403              :     }
     404          750 :     return true;
     405              : }
     406              : 
     407              : void
     408         1110 : GitServer::Impl::sendPackData()
     409              : {
     410              :     git_repository* repo_ptr;
     411         1110 :     if (git_repository_open(&repo_ptr, repository_.c_str()) != 0) {
     412            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open {}",
     413              :                      accountId_,
     414              :                      repositoryId_,
     415              :                      fmt::ptr(this),
     416              :                      repository_);
     417            0 :         return;
     418              :     }
     419         1110 :     GitRepository repo {repo_ptr};
     420              : 
     421              :     git_packbuilder* pb_ptr;
     422         1110 :     if (git_packbuilder_new(&pb_ptr, repo.get()) != 0) {
     423            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open packbuilder for {}",
     424              :                      accountId_,
     425              :                      repositoryId_,
     426              :                      fmt::ptr(this),
     427              :                      repository_);
     428            0 :         return;
     429              :     }
     430         1110 :     GitPackBuilder pb {pb_ptr};
     431              : 
     432         1110 :     std::string fetched = wantedReference_;
     433              :     git_oid oid;
     434         1109 :     if (git_oid_fromstr(&oid, fetched.c_str()) < 0) {
     435            0 :         JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to get reference for commit {}",
     436              :                    accountId_,
     437              :                    repositoryId_,
     438              :                    fmt::ptr(this),
     439              :                    fetched);
     440            0 :         return;
     441              :     }
     442              : 
     443         1110 :     git_revwalk* walker_ptr = nullptr;
     444         1110 :     if (git_revwalk_new(&walker_ptr, repo.get()) < 0 || git_revwalk_push(walker_ptr, &oid) < 0) {
     445            0 :         if (walker_ptr)
     446            0 :             git_revwalk_free(walker_ptr);
     447            0 :         return;
     448              :     }
     449         1110 :     GitRevWalker walker {walker_ptr};
     450         1110 :     git_revwalk_sorting(walker.get(), GIT_SORT_TOPOLOGICAL);
     451              :     // Add first commit
     452         1110 :     std::set<std::string> parents;
     453         1110 :     auto haveCommit = false;
     454              : 
     455         3053 :     while (!git_revwalk_next(&oid, walker.get())) {
     456              :         // log until have refs
     457         2864 :         std::string id = git_oid_tostr_s(&oid);
     458         2864 :         haveCommit |= std::find(haveRefs_.begin(), haveRefs_.end(), id) != haveRefs_.end();
     459         2864 :         auto itParents = std::find(parents.begin(), parents.end(), id);
     460         2864 :         if (itParents != parents.end())
     461         1754 :             parents.erase(itParents);
     462         2863 :         if (haveCommit && parents.size() == 0 /* We are sure that all commits are there */)
     463          919 :             break;
     464         1944 :         if (git_packbuilder_insert_commit(pb.get(), &oid) != 0) {
     465            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open insert commit {} for {}",
     466              :                          accountId_,
     467              :                          repositoryId_,
     468              :                          fmt::ptr(this),
     469              :                          git_oid_tostr_s(&oid),
     470              :                          repository_);
     471            0 :             return;
     472              :         }
     473              : 
     474              :         // Get next commit to pack
     475              :         git_commit* commit_ptr;
     476         1944 :         if (git_commit_lookup(&commit_ptr, repo.get(), &oid) < 0) {
     477            0 :             JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to look up current commit",
     478              :                        accountId_,
     479              :                        repositoryId_,
     480              :                        fmt::ptr(this));
     481            0 :             return;
     482              :         }
     483         1944 :         GitCommit commit {commit_ptr};
     484         1944 :         auto parentsCount = git_commit_parentcount(commit.get());
     485         3703 :         for (unsigned int p = 0; p < parentsCount; ++p) {
     486              :             // make sure to explore all branches
     487         1759 :             const git_oid* pid = git_commit_parent_id(commit.get(), p);
     488         1759 :             if (pid)
     489         1759 :                 parents.emplace(git_oid_tostr_s(pid));
     490              :         }
     491         2863 :     }
     492              : 
     493         1109 :     git_buf data = {};
     494         1109 :     if (git_packbuilder_write_buf(&data, pb.get()) != 0) {
     495            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to write pack data for {}",
     496              :                      accountId_,
     497              :                      repositoryId_,
     498              :                      fmt::ptr(this),
     499              :                      repository_);
     500            0 :         return;
     501              :     }
     502              : 
     503         1110 :     std::size_t sent = 0;
     504         1110 :     std::size_t len = data.size;
     505         1110 :     std::error_code ec;
     506         1110 :     std::vector<uint8_t> toSendData;
     507              :     do {
     508              :         // cf https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     509              :         // In 'side-band-64k' mode it will send up to 65519 data bytes plus 1 control code, for a
     510              :         // total of up to 65520 bytes in a pkt-line.
     511         1577 :         std::size_t pkt_size = std::min(static_cast<std::size_t>(65515), len - sent);
     512         1577 :         std::string toSendHeader = toGitHex(pkt_size + 5);
     513         1577 :         toSendData.clear();
     514         1576 :         toSendData.reserve(pkt_size + 5);
     515         1576 :         toSendData.insert(toSendData.end(), toSendHeader.begin(), toSendHeader.end());
     516         1577 :         toSendData.push_back(0x1);
     517         1577 :         toSendData.insert(toSendData.end(), data.ptr + sent, data.ptr + sent + pkt_size);
     518              : 
     519         1577 :         socket_->write(reinterpret_cast<const unsigned char*>(toSendData.data()), toSendData.size(), ec);
     520         1577 :         if (ec) {
     521            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     522              :                          accountId_,
     523              :                          repositoryId_,
     524              :                          fmt::ptr(this),
     525              :                          repository_,
     526              :                          ec.message());
     527            0 :             git_buf_dispose(&data);
     528            0 :             return;
     529              :         }
     530         1577 :         sent += pkt_size;
     531         3154 :     } while (sent < len);
     532         1110 :     git_buf_dispose(&data);
     533         1110 :     toSendData = {};
     534              : 
     535              :     // And finish by a little FLUSH
     536         1110 :     socket_->write(reinterpret_cast<const uint8_t*>(FLUSH_PKT.data()), FLUSH_PKT.size(), ec);
     537         1110 :     if (ec) {
     538            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     539              :                      accountId_,
     540              :                      repositoryId_,
     541              :                      fmt::ptr(this),
     542              :                      repository_,
     543              :                      ec.message());
     544              :     }
     545              : 
     546              :     // Clear sent data
     547         1110 :     haveRefs_.clear();
     548         1110 :     wantedReference_.clear();
     549         1110 :     common_.clear();
     550         1110 :     if (onFetchedCb_)
     551         1110 :         onFetchedCb_(fetched);
     552         1109 : }
     553              : 
     554              : std::map<std::string, std::string>
     555         2159 : GitServer::Impl::getParameters(std::string_view pkt_line)
     556              : {
     557         2159 :     std::map<std::string, std::string> parameters;
     558         2159 :     std::string key, value;
     559         2159 :     auto isKey = true;
     560         2159 :     auto nullChar = 0;
     561       243383 :     for (auto letter : pkt_line) {
     562       241269 :         if (letter == '\0') {
     563              :             // parameters such as host or version are after the first \0
     564         4318 :             if (nullChar != 0 && !key.empty()) {
     565         2159 :                 parameters.try_emplace(std::move(key), std::move(value));
     566              :             }
     567         4318 :             nullChar += 1;
     568         4318 :             isKey = true;
     569         4318 :             key.clear();
     570         4318 :             value.clear();
     571       236951 :         } else if (letter == '=') {
     572         2159 :             isKey = false;
     573       234792 :         } else if (nullChar != 0) {
     574       146359 :             if (isKey) {
     575         8636 :                 key += letter;
     576              :             } else {
     577       137723 :                 value += letter;
     578              :             }
     579              :         }
     580              :     }
     581         4318 :     return parameters;
     582         2159 : }
     583              : 
     584          967 : GitServer::GitServer(const std::string& accountId,
     585              :                      const std::string& conversationId,
     586          967 :                      const std::shared_ptr<dhtnet::ChannelSocket>& client)
     587              : {
     588          967 :     auto path = (fileutils::get_data_dir() / accountId / "conversations" / conversationId).string();
     589          967 :     pimpl_ = std::make_unique<GitServer::Impl>(accountId, conversationId, path, client);
     590          967 : }
     591              : 
     592          967 : GitServer::~GitServer()
     593              : {
     594          967 :     stop();
     595          967 :     pimpl_.reset();
     596          967 : }
     597              : 
     598              : void
     599          966 : GitServer::setOnFetched(const onFetchedCb& cb)
     600              : {
     601          966 :     if (!pimpl_)
     602            0 :         return;
     603          966 :     pimpl_->onFetchedCb_ = cb;
     604              : }
     605              : 
     606              : void
     607         1435 : GitServer::stop()
     608              : {
     609         1435 :     pimpl_->stop();
     610         1435 : }
     611              : 
     612              : } // namespace jami
        

Generated by: LCOV version 2.0-1