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
28int
29generateRequest(git_buf* request, const std::string& cmd, const std::string_view& url)
30{
31 if (cmd.empty()) {
32 giterr_set_str(GITERR_NET, "empty command");
33 return -1;
34 }
35 // url format = deviceId/conversationId
36 auto delim = url.find('/');
37 if (delim == std::string::npos) {
38 giterr_set_str(GITERR_NET, "malformed URL");
39 return -1;
40 }
41
42 auto deviceId = url.substr(0, delim);
43 auto conversationId = url.substr(delim, url.size());
44
45 auto nullSeparator = "\0"sv;
46 auto total = 4 /* 4 bytes for the len len */
47 + cmd.size() /* followed by the command */
48 + 1 /* space */
49 + conversationId.size() /* conversation */
50 + 1 /* \0 */
51 + HOST_TAG.size() + deviceId.size() /* device */
52 + nullSeparator.size() /* \0 */;
53
54 std::ostringstream streamed;
55 streamed << std::setw(4) << std::setfill('0') << std::hex << (total & 0x0FFFF) << cmd;
56 streamed << " " << conversationId;
57 streamed << nullSeparator << HOST_TAG << deviceId << nullSeparator;
58 auto str = streamed.str();
59 git_buf_set(request, str.c_str(), str.size());
60 return 0;
61}
62
63int
65{
66 auto res = 0;
67 git_buf request = {};
68 if ((res = generateRequest(&request, s->cmd, s->url)) < 0) {
69 git_buf_dispose(&request);
70 return res;
71 }
72
73 std::error_code ec;
74 auto sock = s->socket.lock();
75 if (!sock) {
76 git_buf_dispose(&request);
77 return -1;
78 }
79 if ((res = sock->write(reinterpret_cast<const unsigned char*>(request.ptr), request.size, ec))) {
80 s->sent_command = 1;
81 git_buf_dispose(&request);
82 return res;
83 }
84
85 s->sent_command = 1;
86 git_buf_dispose(&request);
87 return res;
88}
89
90int
91P2PStreamRead(git_smart_subtransport_stream* stream, char* buffer, size_t buflen, size_t* read)
92{
93 *read = 0;
94 auto* fs = reinterpret_cast<P2PStream*>(stream);
95 auto sock = fs->socket.lock();
96 if (!sock) {
97 giterr_set_str(GITERR_NET, "unavailable socket");
98 return -1;
99 }
100
101 int res = 0;
102 // If it's the first read, we need to send
103 // the upload-pack command
104 if (!fs->sent_command && (res = sendCmd(fs)) < 0)
105 return res;
106
107 std::error_code ec;
108 // TODO ChannelSocket needs a blocking read operation
109 size_t datalen = sock->waitForData(std::chrono::milliseconds(3600 * 1000 * 24), ec);
110 if (datalen > 0)
111 *read = sock->read(reinterpret_cast<unsigned char*>(buffer), std::min(datalen, buflen), ec);
112
113 return res;
114}
115
116int
117P2PStreamWrite(git_smart_subtransport_stream* stream, const char* buffer, size_t len)
118{
119 auto* fs = reinterpret_cast<P2PStream*>(stream);
120 auto sock = fs->socket.lock();
121 if (!sock) {
122 giterr_set_str(GITERR_NET, "unavailable socket");
123 return -1;
124 }
125 std::error_code ec;
126 sock->write(reinterpret_cast<const unsigned char*>(buffer), len, ec);
127 if (ec) {
128 giterr_set_str(GITERR_NET, ec.message().c_str());
129 return -1;
130 }
131 return 0;
132}
133
134void
135P2PStreamFree(git_smart_subtransport_stream*)
136{}
137
138int
139P2PSubTransportAction(git_smart_subtransport_stream** out,
140 git_smart_subtransport* transport,
141 const char* url,
142 git_smart_service_t action)
143{
144 auto* sub = reinterpret_cast<P2PSubTransport*>(transport);
145 if (!sub || !sub->remote) {
146 JAMI_ERROR("Invalid subtransport");
147 return -1;
148 }
149
150 auto repo = git_remote_owner(sub->remote);
151 if (!repo) {
152 JAMI_ERROR("No repository linked to the transport");
153 return -1;
154 }
155
156 const auto* workdir = git_repository_workdir(repo);
157 if (!workdir) {
158 JAMI_ERROR("No working linked to the repository");
159 return -1;
160 }
161 std::string_view path = workdir;
162 auto delimConv = path.rfind("/conversations");
163 if (delimConv == std::string::npos) {
164 JAMI_ERROR("No conversation id found");
165 return -1;
166 }
167 auto delimAccount = path.rfind('/', delimConv - 1);
168 if (delimAccount == std::string::npos && delimConv - 1 - delimAccount == 16) {
169 JAMI_ERROR("No account id found");
170 return -1;
171 }
172 auto accountId = path.substr(delimAccount + 1, delimConv - 1 - delimAccount);
173 std::string_view gitUrl = url + ("git://"sv).size();
174 auto delim = gitUrl.find('/');
175 if (delim == std::string::npos) {
176 JAMI_ERROR("Incorrect url {:s}", gitUrl);
177 return -1;
178 }
179 auto deviceId = gitUrl.substr(0, delim);
180 auto conversationId = gitUrl.substr(delim + 1, gitUrl.size());
181
182 if (action == GIT_SERVICE_UPLOADPACK_LS) {
183 auto gitSocket = jami::Manager::instance().gitSocket(accountId, deviceId, conversationId);
184 if (!gitSocket) {
185 JAMI_ERROR("Unable to find related socket for {:s}, {:s}, {:s}",
186 accountId,
187 deviceId,
188 conversationId);
189 return -1;
190 }
191 auto stream = std::make_unique<P2PStream>();
192 stream->socket = gitSocket;
193 stream->base.read = P2PStreamRead;
194 stream->base.write = P2PStreamWrite;
195 stream->base.free = P2PStreamFree;
196 stream->cmd = UPLOAD_PACK_CMD;
197 stream->url = gitUrl;
198 sub->stream = std::move(stream);
199 *out = &sub->stream->base;
200 return 0;
201 } else if (action == GIT_SERVICE_UPLOADPACK) {
202 if (sub->stream) {
203 *out = &sub->stream->base;
204 return 0;
205 }
206 return -1;
207 }
208 return 0;
209}
210
211int
212P2PSubTransportClose(git_smart_subtransport*)
213{
214 return 0;
215}
216
217void
218P2PSubTransportFree(git_smart_subtransport* transport)
219{
221}
222
223int
224P2PSubTransportNew(P2PSubTransport** out, git_transport*, void* payload)
225{
226 auto sub = std::make_unique<P2PSubTransport>();
227 sub->remote = reinterpret_cast<git_remote*>(payload);
228 auto* base = &sub->base;
229 base->action = P2PSubTransportAction;
230 base->close = P2PSubTransportClose;
231 base->free = P2PSubTransportFree;
232 *out = sub.get();
233 jami::Manager::instance().insertGitTransport(base, std::move(sub));
234 return 0;
235}
236
237int
238p2p_subtransport_cb(git_smart_subtransport** out, git_transport* owner, void* payload)
239{
240 P2PSubTransport* sub;
241
242 if (P2PSubTransportNew(&sub, owner, payload) < 0)
243 return -1;
244
245 *out = &sub->base;
246 return 0;
247}
248
249int
250p2p_transport_cb(git_transport** out, git_remote* owner, void*)
251{
252 git_smart_subtransport_definition def
254 0, /* Because we use an already existing channel socket, we use a permanent transport */
255 reinterpret_cast<void*>(owner)};
256 return git_transport_smart(out, owner, &def);
257}
void insertGitTransport(git_smart_subtransport *tr, std::unique_ptr< P2PSubTransport > &&sub)
Definition manager.cpp:3288
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:676
void eraseGitTransport(git_smart_subtransport *tr)
Definition manager.cpp:3295
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:3203
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.
int generateRequest(git_buf *request, const std::string &cmd, const std::string_view &url)
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.
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