LCOV - code coverage report
Current view: top level - foo/src/jamidht - gitserver.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 218 278 78.4 %
Date: 2025-12-18 10:07:43 Functions: 33 83 39.8 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2025 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         848 :     Impl(const std::string& accountId,
      47             :          const std::string& repositoryId,
      48             :          const std::string& repository,
      49             :          const std::shared_ptr<dhtnet::ChannelSocket>& socket)
      50         848 :         : accountId_(accountId)
      51         848 :         , repositoryId_(repositoryId)
      52         848 :         , repository_(repository)
      53         848 :         , socket_(socket)
      54             :     {
      55        3392 :         JAMI_DEBUG("[Account {}] [Conversation {}] [GitServer {}] created", accountId_, repositoryId_, fmt::ptr(this));
      56             :         // Check at least if repository is correct
      57             :         git_repository* repo;
      58         848 :         if (git_repository_open(&repo, repository_.c_str()) != 0) {
      59           0 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
      60           0 :             return;
      61             :         }
      62         848 :         git_repository_free(repo);
      63             : 
      64         848 :         socket_->setOnRecv([this](const uint8_t* buf, std::size_t len) {
      65        5593 :             std::lock_guard lk(destroyMtx_);
      66        5591 :             if (isDestroying_)
      67           0 :                 return len;
      68        5590 :             if (parseOrder(std::string_view((const char*) buf, len)))
      69       15492 :                 while (parseOrder())
      70             :                     ;
      71        5593 :             return len;
      72        5593 :         });
      73           0 :     }
      74         848 :     ~Impl()
      75             :     {
      76         848 :         stop();
      77        3392 :         JAMI_DEBUG("[Account {}] [Conversation {}] [GitServer {}] destroyed", accountId_, repositoryId_, fmt::ptr(this));
      78         848 :     }
      79        2123 :     void stop()
      80             :     {
      81        2123 :         std::lock_guard lk(destroyMtx_);
      82        2122 :         if (isDestroying_.exchange(true)) {
      83        1275 :             socket_->setOnRecv({});
      84        2548 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
      85             :         }
      86        2123 :     }
      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       21081 : GitServer::Impl::parseOrder(std::string_view buf)
     111             : {
     112       21081 :     std::string pkt = std::move(cachedPkt_);
     113       21085 :     if (!buf.empty())
     114        5592 :         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       21086 :     unsigned int pkt_len = 0;
     121       21086 :     auto [p, ec] = std::from_chars(pkt.data(), pkt.data() + 4, pkt_len, 16);
     122       21081 :     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       21081 :     if (pkt_len != pkt.size()) {
     129             :         // Store next packet part
     130       18006 :         if (pkt_len == 0) {
     131             :             // FLUSH_PKT
     132        3632 :             pkt_len = 4;
     133             :         }
     134       18006 :         cachedPkt_ = pkt.substr(pkt_len);
     135             :     }
     136             : 
     137       21090 :     auto pack = std::string_view(pkt).substr(4, pkt_len - 4);
     138       21088 :     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        4456 :         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        1114 :         if (common_.empty())
     149         190 :             sendData = NAK();
     150             :         else
     151         924 :             sendData = ACKFirst();
     152        1114 :         if (sendData)
     153        1114 :             sendPackData();
     154        1114 :         return !cachedPkt_.empty();
     155       19971 :     } else if (pack.empty()) {
     156        3633 :         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        3632 :         return !cachedPkt_.empty();
     164             :     }
     165             : 
     166       16337 :     auto lim = pack.find(' ');
     167       16339 :     auto cmd = pack.substr(0, lim);
     168       16338 :     auto dat = (lim < pack.size()) ? pack.substr(lim + 1) : std::string_view {};
     169       16336 :     if (cmd == UPLOAD_PACK_CMD) {
     170             :         // Cf: https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     171             :         // References discovery
     172        7844 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Upload pack command detected.",
     173             :                  accountId_,
     174             :                  repositoryId_,
     175             :                  fmt::ptr(this));
     176        1961 :         auto version = 1;
     177        1961 :         auto parameters = getParameters(dat);
     178        1961 :         auto versionIt = parameters.find("version");
     179        1961 :         bool sendVersion = false;
     180        1961 :         if (versionIt != parameters.end()) {
     181           0 :             auto [p, ec] = std::from_chars(versionIt->second.data(),
     182           0 :                                            versionIt->second.data() + versionIt->second.size(),
     183           0 :                                            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        1961 :         if (version == 1) {
     195        1961 :             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       16337 :     } 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        1114 :         wantedReference_ = dat.substr(0, 40);
     209        4456 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer want ref: {}",
     210             :                  accountId_,
     211             :                  repositoryId_,
     212             :                  fmt::ptr(this),
     213             :                  wantedReference_);
     214       13264 :     } else if (cmd == HAVE_CMD) {
     215       13267 :         const auto& commit = haveRefs_.emplace_back(dat.substr(0, 40));
     216       13262 :         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         924 :             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         924 :             GitRepository rep {repo, git_repository_free};
     231             :             git_oid commit_id;
     232         924 :             if (git_oid_fromstr(&commit_id, commit.c_str()) == 0) {
     233             :                 // Reference found
     234         924 :                 common_ = commit;
     235             :             }
     236         924 :         }
     237             :     } else {
     238           0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unwanted packet received: {}",
     239             :                      accountId_,
     240             :                      repositoryId_,
     241             :                      fmt::ptr(this),
     242             :                      pkt);
     243             :     }
     244       16337 :     return !cachedPkt_.empty();
     245       21083 : }
     246             : 
     247             : std::string
     248       27453 : toGitHex(size_t value)
     249             : {
     250      109812 :     return fmt::format(FMT_COMPILE("{:04x}"), value & 0x0FFFF);
     251             : }
     252             : 
     253             : void
     254        1961 : 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        1961 :     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        1961 :     GitRepository rep {repo, git_repository_free};
     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        1961 :     std::error_code ec;
     275        1961 :     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        1961 :     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        1961 :     std::string_view currentHead = git_oid_tostr_s(&commit_id);
     300             : 
     301             :     // Send references
     302        1961 :     std::ostringstream packet;
     303        1961 :     packet << toGitHex(5 + currentHead.size() + SERVER_CAPABILITIES.size());
     304        1961 :     packet << currentHead << SERVER_CAPABILITIES << "\n";
     305             : 
     306             :     // Now, add other references
     307             :     git_strarray refs;
     308        1961 :     if (git_reference_list(&refs, rep.get()) == 0) {
     309       25884 :         for (std::size_t i = 0; i < refs.count; ++i) {
     310       23925 :             std::string_view ref = refs.strings[i];
     311       23923 :             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       23924 :             currentHead = git_oid_tostr_s(&commit_id);
     320             : 
     321       23917 :             packet << toGitHex(6 /* size + space + \n */ + currentHead.size() + ref.size());
     322       23921 :             packet << currentHead << " " << ref << "\n";
     323             :         }
     324             :     }
     325        1959 :     git_strarray_dispose(&refs);
     326             : 
     327             :     // And add FLUSH
     328        1961 :     packet << FLUSH_PKT;
     329        1961 :     auto toSend = packet.str();
     330        1961 :     socket_->write(reinterpret_cast<const unsigned char*>(toSend.data()), toSend.size(), ec);
     331        1961 :     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        1961 : }
     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         924 : GitServer::Impl::ACKFirst()
     366             : {
     367         924 :     std::error_code ec;
     368             :     // Ack common base
     369         924 :     if (!common_.empty()) {
     370        1848 :         auto toSend = fmt::format(FMT_COMPILE("{:04x}ACK {}\n"),
     371         924 :                                   9 + common_.size() /* size + ACK + space + \n */,
     372         924 :                                   common_);
     373         924 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.c_str()), toSend.size(), ec);
     374         924 :         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         924 :     }
     385         924 :     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        1114 : GitServer::Impl::sendPackData()
     409             : {
     410             :     git_repository* repo_ptr;
     411        1114 :     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        1114 :     GitRepository repo {repo_ptr, git_repository_free};
     420             : 
     421             :     git_packbuilder* pb_ptr;
     422        1114 :     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        1114 :     GitPackBuilder pb {pb_ptr, git_packbuilder_free};
     431             : 
     432        1114 :     std::string fetched = wantedReference_;
     433             :     git_oid oid;
     434        1114 :     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        1114 :     git_revwalk* walker_ptr = nullptr;
     444        1114 :     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        1114 :     GitRevWalker walker {walker_ptr, git_revwalk_free};
     450        1114 :     git_revwalk_sorting(walker.get(), GIT_SORT_TOPOLOGICAL);
     451             :     // Add first commit
     452        1114 :     std::set<std::string> parents;
     453        1114 :     auto haveCommit = false;
     454             : 
     455        3062 :     while (!git_revwalk_next(&oid, walker.get())) {
     456             :         // log until have refs
     457        2871 :         std::string id = git_oid_tostr_s(&oid);
     458        2872 :         haveCommit |= std::find(haveRefs_.begin(), haveRefs_.end(), id) != haveRefs_.end();
     459        2872 :         auto itParents = std::find(parents.begin(), parents.end(), id);
     460        2871 :         if (itParents != parents.end())
     461        1758 :             parents.erase(itParents);
     462        2870 :         if (haveCommit && parents.size() == 0 /* We are sure that all commits are there */)
     463         924 :             break;
     464        1947 :         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        1948 :         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        1948 :         GitCommit commit {commit_ptr, git_commit_free};
     484        1947 :         auto parentsCount = git_commit_parentcount(commit.get());
     485        3712 :         for (unsigned int p = 0; p < parentsCount; ++p) {
     486             :             // make sure to explore all branches
     487        1764 :             const git_oid* pid = git_commit_parent_id(commit.get(), p);
     488        1764 :             if (pid)
     489        1764 :                 parents.emplace(git_oid_tostr_s(pid));
     490             :         }
     491        2872 :     }
     492             : 
     493        1114 :     git_buf data = {};
     494        1114 :     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        1114 :     std::size_t sent = 0;
     504        1114 :     std::size_t len = data.size;
     505        1114 :     std::error_code ec;
     506        1114 :     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        1580 :         std::size_t pkt_size = std::min(static_cast<std::size_t>(65515), len - sent);
     512        1580 :         std::string toSendHeader = toGitHex(pkt_size + 5);
     513        1581 :         toSendData.clear();
     514        1579 :         toSendData.reserve(pkt_size + 5);
     515        1581 :         toSendData.insert(toSendData.end(), toSendHeader.begin(), toSendHeader.end());
     516        1581 :         toSendData.push_back(0x1);
     517        1581 :         toSendData.insert(toSendData.end(), data.ptr + sent, data.ptr + sent + pkt_size);
     518             : 
     519        1581 :         socket_->write(reinterpret_cast<const unsigned char*>(toSendData.data()), toSendData.size(), ec);
     520        1581 :         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        1581 :         sent += pkt_size;
     531        3162 :     } while (sent < len);
     532        1114 :     git_buf_dispose(&data);
     533        1114 :     toSendData = {};
     534             : 
     535             :     // And finish by a little FLUSH
     536        1114 :     socket_->write(reinterpret_cast<const uint8_t*>(FLUSH_PKT.data()), FLUSH_PKT.size(), ec);
     537        1114 :     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        1114 :     haveRefs_.clear();
     548        1114 :     wantedReference_.clear();
     549        1114 :     common_.clear();
     550        1114 :     if (onFetchedCb_)
     551        1114 :         onFetchedCb_(fetched);
     552        1114 : }
     553             : 
     554             : std::map<std::string, std::string>
     555        1961 : GitServer::Impl::getParameters(std::string_view pkt_line)
     556             : {
     557        1961 :     std::map<std::string, std::string> parameters;
     558        1961 :     std::string key, value;
     559        1961 :     auto isKey = true;
     560        1961 :     auto nullChar = 0;
     561      221593 :     for (auto letter : pkt_line) {
     562      219632 :         if (letter == '\0') {
     563             :             // parameters such as host or version are after the first \0
     564        3922 :             if (nullChar != 0 && !key.empty()) {
     565        1961 :                 parameters.try_emplace(std::move(key), std::move(value));
     566             :             }
     567        3922 :             nullChar += 1;
     568        3922 :             isKey = true;
     569        3922 :             key.clear();
     570        3922 :             value.clear();
     571      215710 :         } else if (letter == '=') {
     572        1961 :             isKey = false;
     573      213749 :         } else if (nullChar != 0) {
     574      133348 :             if (isKey) {
     575        7844 :                 key += letter;
     576             :             } else {
     577      125504 :                 value += letter;
     578             :             }
     579             :         }
     580             :     }
     581        3922 :     return parameters;
     582        1961 : }
     583             : 
     584         848 : GitServer::GitServer(const std::string& accountId,
     585             :                      const std::string& conversationId,
     586         848 :                      const std::shared_ptr<dhtnet::ChannelSocket>& client)
     587             : {
     588        1696 :     auto path = (fileutils::get_data_dir() / accountId / "conversations" / conversationId).string();
     589         848 :     pimpl_ = std::make_unique<GitServer::Impl>(accountId, conversationId, path, client);
     590         848 : }
     591             : 
     592         848 : GitServer::~GitServer()
     593             : {
     594         848 :     stop();
     595         848 :     pimpl_.reset();
     596         848 : }
     597             : 
     598             : void
     599         848 : GitServer::setOnFetched(const onFetchedCb& cb)
     600             : {
     601         848 :     if (!pimpl_)
     602           0 :         return;
     603         848 :     pimpl_->onFetchedCb_ = cb;
     604             : }
     605             : 
     606             : void
     607        1275 : GitServer::stop()
     608             : {
     609        1275 :     pimpl_->stop();
     610        1275 : }
     611             : 
     612             : } // namespace jami

Generated by: LCOV version 1.14