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