Ring Daemon 16.0.0
Loading...
Searching...
No Matches
gittransport.cpp
Go to the documentation of this file.
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 "gittransport.h"
18
19#include "manager.h"
20
21#include <dhtnet/multiplexed_socket.h>
22#include <dhtnet/connectionmanager.h>
23
24using namespace std::string_view_literals;
25
26// NOTE: THIS MUST BE IN THE ROOT NAMESPACE FOR LIBGIT2
27
28/*
29 * Create a git protocol request.
30 *
31 * For example: 0029git-upload-pack conversation\0host=device\0
32 * @param buf The buffer to fill
33 * @param cmd The wanted command
34 * @param url The repository's URL
35 * @return 0 on success, - 1 on error
36 */
37int
38generateRequest(git_buf* request, const std::string& cmd, std::string_view url)
39{
40 if (cmd.empty()) {
41 giterr_set_str(GITERR_NET, "empty command");
42 return -1;
43 }
44 // url format = deviceId/conversationId
45 auto delim = url.find('/');
46 if (delim == std::string::npos) {
47 giterr_set_str(GITERR_NET, "malformed URL");
48 return -1;
49 }
50
51 auto deviceId = url.substr(0, delim);
52 auto conversationId = url.substr(delim, url.size());
53
54 constexpr auto nullSeparator = "\0"sv;
55 auto total = 4 /* 4 bytes for the len len */
56 + cmd.size() /* followed by the command */
57 + 1 /* space */
58 + conversationId.size() /* conversation */
59 + 1 /* \0 */
60 + HOST_TAG.size() + deviceId.size() /* device */
61 + nullSeparator.size() /* \0 */;
62
63 std::string str;
64 str.reserve(total);
65 str.append("0000"sv);
66 fmt::format_to_n(str.begin(), 4, "{:04x}", total);
67 str.append(cmd)
68 .append(" "sv)
69 .append(conversationId)
70 .append(nullSeparator)
71 .append(HOST_TAG)
72 .append(deviceId)
73 .append(nullSeparator);
74
75 git_buf_set(request, str.data(), str.size());
76 return 0;
77}
78
79int
81{
82 auto res = 0;
83 git_buf request = {};
84 if ((res = generateRequest(&request, s->cmd, s->url)) < 0) {
85 git_buf_dispose(&request);
86 return res;
87 }
88
89 std::error_code ec;
90 auto sock = s->socket.lock();
91 if (!sock) {
92 git_buf_dispose(&request);
93 return -1;
94 }
95 if ((res = sock->write(reinterpret_cast<const unsigned char*>(request.ptr), request.size, ec))) {
96 s->sent_command = 1;
97 git_buf_dispose(&request);
98 return res;
99 }
100
101 s->sent_command = 1;
102 git_buf_dispose(&request);
103 return res;
104}
105
106int
107P2PStreamRead(git_smart_subtransport_stream* stream, char* buffer, size_t buflen, size_t* read)
108{
109 *read = 0;
110 auto* fs = reinterpret_cast<P2PStream*>(stream);
111 auto sock = fs->socket.lock();
112 if (!sock) {
113 giterr_set_str(GITERR_NET, "unavailable socket");
114 return -1;
115 }
116
117 int res = 0;
118 // If it's the first read, we need to send
119 // the upload-pack command
120 if (!fs->sent_command && (res = sendCmd(fs)) < 0)
121 return res;
122
123 std::error_code ec;
124 // TODO ChannelSocket needs a blocking read operation
125 size_t datalen = sock->waitForData(std::chrono::milliseconds(3600 * 1000 * 24), ec);
126 if (datalen > 0)
127 *read = sock->read(reinterpret_cast<unsigned char*>(buffer), std::min(datalen, buflen), ec);
128
129 return res;
130}
131
132int
133P2PStreamWrite(git_smart_subtransport_stream* stream, const char* buffer, size_t len)
134{
135 auto* fs = reinterpret_cast<P2PStream*>(stream);
136 auto sock = fs->socket.lock();
137 if (!sock) {
138 giterr_set_str(GITERR_NET, "unavailable socket");
139 return -1;
140 }
141 std::error_code ec;
142 sock->write(reinterpret_cast<const unsigned char*>(buffer), len, ec);
143 if (ec) {
144 giterr_set_str(GITERR_NET, ec.message().c_str());
145 return -1;
146 }
147 return 0;
148}
149
150void
151P2PStreamFree(git_smart_subtransport_stream*)
152{}
153
154int
155P2PSubTransportAction(git_smart_subtransport_stream** out,
156 git_smart_subtransport* transport,
157 const char* url,
158 git_smart_service_t action)
159{
160 auto* sub = reinterpret_cast<P2PSubTransport*>(transport);
161 if (!sub || !sub->remote) {
162 JAMI_ERROR("Invalid subtransport");
163 return -1;
164 }
165
166 auto repo = git_remote_owner(sub->remote);
167 if (!repo) {
168 JAMI_ERROR("No repository linked to the transport");
169 return -1;
170 }
171
172 const auto* workdir = git_repository_workdir(repo);
173 if (!workdir) {
174 JAMI_ERROR("No working linked to the repository");
175 return -1;
176 }
177 std::string_view path = workdir;
178 auto delimConv = path.rfind("/conversations");
179 if (delimConv == std::string::npos) {
180 JAMI_ERROR("No conversation id found");
181 return -1;
182 }
183 auto delimAccount = path.rfind('/', delimConv - 1);
184 if (delimAccount == std::string::npos && delimConv - 1 - delimAccount == 16) {
185 JAMI_ERROR("No account id found");
186 return -1;
187 }
188 auto accountId = path.substr(delimAccount + 1, delimConv - 1 - delimAccount);
189 std::string_view gitUrl = url + ("git://"sv).size();
190 auto delim = gitUrl.find('/');
191 if (delim == std::string::npos) {
192 JAMI_ERROR("Incorrect url {:s}", gitUrl);
193 return -1;
194 }
195 auto deviceId = gitUrl.substr(0, delim);
196 auto conversationId = gitUrl.substr(delim + 1, gitUrl.size());
197
198 if (action == GIT_SERVICE_UPLOADPACK_LS) {
199 auto gitSocket = jami::Manager::instance().gitSocket(accountId, deviceId, conversationId);
200 if (!gitSocket) {
201 JAMI_ERROR("Unable to find related socket for {:s}, {:s}, {:s}",
202 accountId,
203 deviceId,
204 conversationId);
205 return -1;
206 }
207 auto stream = std::make_unique<P2PStream>();
208 stream->socket = gitSocket;
209 stream->base.read = P2PStreamRead;
210 stream->base.write = P2PStreamWrite;
211 stream->base.free = P2PStreamFree;
212 stream->cmd = UPLOAD_PACK_CMD;
213 stream->url = gitUrl;
214 sub->stream = std::move(stream);
215 *out = &sub->stream->base;
216 return 0;
217 } else if (action == GIT_SERVICE_UPLOADPACK) {
218 if (sub->stream) {
219 *out = &sub->stream->base;
220 return 0;
221 }
222 return -1;
223 }
224 return 0;
225}
226
227int
228P2PSubTransportClose(git_smart_subtransport*)
229{
230 return 0;
231}
232
233void
234P2PSubTransportFree(git_smart_subtransport* transport)
235{
237}
238
239int
240P2PSubTransportNew(P2PSubTransport** out, git_transport*, void* payload)
241{
242 auto sub = std::make_unique<P2PSubTransport>();
243 sub->remote = reinterpret_cast<git_remote*>(payload);
244 auto* base = &sub->base;
245 base->action = P2PSubTransportAction;
246 base->close = P2PSubTransportClose;
247 base->free = P2PSubTransportFree;
248 *out = sub.get();
249 jami::Manager::instance().insertGitTransport(base, std::move(sub));
250 return 0;
251}
252
253int
254p2p_subtransport_cb(git_smart_subtransport** out, git_transport* owner, void* payload)
255{
256 P2PSubTransport* sub;
257
258 if (P2PSubTransportNew(&sub, owner, payload) < 0)
259 return -1;
260
261 *out = &sub->base;
262 return 0;
263}
264
265int
266p2p_transport_cb(git_transport** out, git_remote* owner, void*)
267{
268 git_smart_subtransport_definition def
270 0, /* Because we use an already existing channel socket, we use a permanent transport */
271 reinterpret_cast<void*>(owner)};
272 return git_transport_smart(out, owner, &def);
273}
void insertGitTransport(git_smart_subtransport *tr, std::unique_ptr< P2PSubTransport > &&sub)
Definition manager.cpp:3287
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:676
void eraseGitTransport(git_smart_subtransport *tr)
Definition manager.cpp:3294
std::shared_ptr< dhtnet::ChannelSocket > gitSocket(std::string_view accountId, std::string_view deviceId, std::string_view conversationId)
Return current git socket used for a conversation.
Definition manager.cpp:3202
int P2PStreamRead(git_smart_subtransport_stream *stream, char *buffer, size_t buflen, size_t *read)
Read on a channel socket.
int P2PStreamWrite(git_smart_subtransport_stream *stream, const char *buffer, size_t len)
int P2PSubTransportNew(P2PSubTransport **out, git_transport *, void *payload)
Create a new subtransport.
int P2PSubTransportClose(git_smart_subtransport *)
Close a subtransport Because we use a channel socket, we need to do nothing here.
void P2PStreamFree(git_smart_subtransport_stream *)
Free resources used by the stream.
int P2PSubTransportAction(git_smart_subtransport_stream **out, git_smart_subtransport *transport, const char *url, git_smart_service_t action)
Handles git actions.
int p2p_subtransport_cb(git_smart_subtransport **out, git_transport *owner, void *payload)
Setup the subtransport callback.
int sendCmd(P2PStream *s)
Send a git command on the linked socket.
int p2p_transport_cb(git_transport **out, git_remote *owner, void *)
Setup the transport callback.
int generateRequest(git_buf *request, const std::string &cmd, std::string_view url)
void P2PSubTransportFree(git_smart_subtransport *transport)
Free resources used by a transport.
constexpr auto UPLOAD_PACK_CMD
constexpr auto HOST_TAG
#define JAMI_ERROR(formatstr,...)
Definition logger.h:228
std::string cmd
unsigned sent_command
std::weak_ptr< dhtnet::ChannelSocket > socket
std::string url
git_smart_subtransport base