Line data Source code
1 : /*
2 : * Copyright (C) 2019-2024 Savoir-faire Linux Inc.
3 : * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
4 : *
5 : * This program is free software; you can redistribute it and/or modify
6 : * it under the terms of the GNU General Public License as published by
7 : * the Free Software Foundation; either version 3 of the License, or
8 : * (at your option) any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 : */
18 : #include "gittransport.h"
19 :
20 : #include "manager.h"
21 :
22 : #include <dhtnet/multiplexed_socket.h>
23 : #include <dhtnet/connectionmanager.h>
24 :
25 : using namespace std::string_view_literals;
26 :
27 : // NOTE: THIS MUST BE IN THE ROOT NAMESPACE FOR LIBGIT2
28 :
29 : int
30 2027 : generateRequest(git_buf* request, const std::string& cmd, const std::string_view& url)
31 : {
32 2027 : if (cmd.empty()) {
33 0 : giterr_set_str(GITERR_NET, "empty command");
34 0 : return -1;
35 : }
36 : // url format = deviceId/conversationId
37 2027 : auto delim = url.find('/');
38 2027 : if (delim == std::string::npos) {
39 0 : giterr_set_str(GITERR_NET, "malformed URL");
40 0 : return -1;
41 : }
42 :
43 2027 : auto deviceId = url.substr(0, delim);
44 2027 : auto conversationId = url.substr(delim, url.size());
45 :
46 2027 : auto nullSeparator = "\0"sv;
47 : auto total = 4 /* 4 bytes for the len len */
48 2027 : + cmd.size() /* followed by the command */
49 : + 1 /* space */
50 2027 : + conversationId.size() /* conversation */
51 : + 1 /* \0 */
52 2027 : + HOST_TAG.size() + deviceId.size() /* device */
53 2027 : + nullSeparator.size() /* \0 */;
54 :
55 2027 : std::ostringstream streamed;
56 2027 : streamed << std::setw(4) << std::setfill('0') << std::hex << (total & 0x0FFFF) << cmd;
57 2027 : streamed << " " << conversationId;
58 2027 : streamed << nullSeparator << HOST_TAG << deviceId << nullSeparator;
59 2027 : auto str = streamed.str();
60 2027 : git_buf_set(request, str.c_str(), str.size());
61 2027 : return 0;
62 2027 : }
63 :
64 : int
65 2027 : sendCmd(P2PStream* s)
66 : {
67 2027 : auto res = 0;
68 2027 : git_buf request = {};
69 2027 : if ((res = generateRequest(&request, s->cmd, s->url)) < 0) {
70 0 : git_buf_dispose(&request);
71 0 : return res;
72 : }
73 :
74 2027 : std::error_code ec;
75 2027 : auto sock = s->socket.lock();
76 2027 : if (!sock) {
77 0 : git_buf_dispose(&request);
78 0 : return -1;
79 : }
80 2027 : if ((res = sock->write(reinterpret_cast<const unsigned char*>(request.ptr), request.size, ec))) {
81 2027 : s->sent_command = 1;
82 2027 : git_buf_dispose(&request);
83 2027 : return res;
84 : }
85 :
86 0 : s->sent_command = 1;
87 0 : git_buf_dispose(&request);
88 0 : return res;
89 2027 : }
90 :
91 : int
92 5766 : P2PStreamRead(git_smart_subtransport_stream* stream, char* buffer, size_t buflen, size_t* read)
93 : {
94 5766 : *read = 0;
95 5766 : auto* fs = reinterpret_cast<P2PStream*>(stream);
96 5766 : auto sock = fs->socket.lock();
97 5767 : if (!sock) {
98 0 : giterr_set_str(GITERR_NET, "unavailable socket");
99 0 : return -1;
100 : }
101 :
102 5767 : int res = 0;
103 : // If it's the first read, we need to send
104 : // the upload-pack command
105 5767 : if (!fs->sent_command && (res = sendCmd(fs)) < 0)
106 3 : return res;
107 :
108 5764 : std::error_code ec;
109 : // TODO ChannelSocket needs a blocking read operation
110 5764 : size_t datalen = sock->waitForData(std::chrono::milliseconds(3600 * 1000 * 24), ec);
111 5764 : if (datalen > 0)
112 5763 : *read = sock->read(reinterpret_cast<unsigned char*>(buffer), std::min(datalen, buflen), ec);
113 :
114 5764 : return res;
115 5767 : }
116 :
117 : int
118 3683 : P2PStreamWrite(git_smart_subtransport_stream* stream, const char* buffer, size_t len)
119 : {
120 3683 : auto* fs = reinterpret_cast<P2PStream*>(stream);
121 3683 : auto sock = fs->socket.lock();
122 3683 : if (!sock) {
123 0 : giterr_set_str(GITERR_NET, "unavailable socket");
124 0 : return -1;
125 : }
126 3683 : std::error_code ec;
127 3682 : sock->write(reinterpret_cast<const unsigned char*>(buffer), len, ec);
128 3683 : if (ec) {
129 0 : giterr_set_str(GITERR_NET, ec.message().c_str());
130 0 : return -1;
131 : }
132 3683 : return 0;
133 3683 : }
134 :
135 : void
136 2027 : P2PStreamFree(git_smart_subtransport_stream*)
137 2027 : {}
138 :
139 : int
140 5713 : P2PSubTransportAction(git_smart_subtransport_stream** out,
141 : git_smart_subtransport* transport,
142 : const char* url,
143 : git_smart_service_t action)
144 : {
145 5713 : auto* sub = reinterpret_cast<P2PSubTransport*>(transport);
146 5713 : if (!sub || !sub->remote) {
147 0 : JAMI_ERROR("Invalid subtransport");
148 0 : return -1;
149 : }
150 :
151 5713 : auto repo = git_remote_owner(sub->remote);
152 5713 : if (!repo) {
153 0 : JAMI_ERROR("No repository linked to the transport");
154 0 : return -1;
155 : }
156 :
157 5713 : const auto* workdir = git_repository_workdir(repo);
158 5713 : if (!workdir) {
159 0 : JAMI_ERROR("No working linked to the repository");
160 0 : return -1;
161 : }
162 5713 : std::string_view path = workdir;
163 5712 : auto delimConv = path.rfind("/conversations");
164 5713 : if (delimConv == std::string::npos) {
165 0 : JAMI_ERROR("No conversation id found");
166 0 : return -1;
167 : }
168 5713 : auto delimAccount = path.rfind('/', delimConv - 1);
169 5713 : if (delimAccount == std::string::npos && delimConv - 1 - delimAccount == 16) {
170 0 : JAMI_ERROR("No account id found");
171 0 : return -1;
172 : }
173 5713 : auto accountId = path.substr(delimAccount + 1, delimConv - 1 - delimAccount);
174 5713 : std::string_view gitUrl = url + ("git://"sv).size();
175 5713 : auto delim = gitUrl.find('/');
176 5712 : if (delim == std::string::npos) {
177 0 : JAMI_ERROR("Incorrect url {:s}", gitUrl);
178 0 : return -1;
179 : }
180 5712 : auto deviceId = gitUrl.substr(0, delim);
181 5712 : auto conversationId = gitUrl.substr(delim + 1, gitUrl.size());
182 :
183 5712 : if (action == GIT_SERVICE_UPLOADPACK_LS) {
184 2030 : auto gitSocket = jami::Manager::instance().gitSocket(accountId, deviceId, conversationId);
185 2030 : if (!gitSocket) {
186 9 : JAMI_ERROR("Can't find related socket for {:s}, {:s}, {:s}",
187 : accountId,
188 : deviceId,
189 : conversationId);
190 3 : return -1;
191 : }
192 2027 : auto stream = std::make_unique<P2PStream>();
193 2027 : stream->socket = gitSocket;
194 2027 : stream->base.read = P2PStreamRead;
195 2027 : stream->base.write = P2PStreamWrite;
196 2027 : stream->base.free = P2PStreamFree;
197 2027 : stream->cmd = UPLOAD_PACK_CMD;
198 2027 : stream->url = gitUrl;
199 2027 : sub->stream = std::move(stream);
200 2027 : *out = &sub->stream->base;
201 2027 : return 0;
202 5712 : } else if (action == GIT_SERVICE_UPLOADPACK) {
203 3683 : if (sub->stream) {
204 3683 : *out = &sub->stream->base;
205 3683 : return 0;
206 : }
207 0 : return -1;
208 : }
209 0 : return 0;
210 : }
211 :
212 : int
213 6083 : P2PSubTransportClose(git_smart_subtransport*)
214 : {
215 6083 : return 0;
216 : }
217 :
218 : void
219 2030 : P2PSubTransportFree(git_smart_subtransport* transport)
220 : {
221 2030 : jami::Manager::instance().eraseGitTransport(transport);
222 2030 : }
223 :
224 : int
225 2030 : P2PSubTransportNew(P2PSubTransport** out, git_transport*, void* payload)
226 : {
227 2030 : auto sub = std::make_unique<P2PSubTransport>();
228 2029 : sub->remote = reinterpret_cast<git_remote*>(payload);
229 2029 : auto* base = &sub->base;
230 2030 : base->action = P2PSubTransportAction;
231 2030 : base->close = P2PSubTransportClose;
232 2030 : base->free = P2PSubTransportFree;
233 2030 : *out = sub.get();
234 2030 : jami::Manager::instance().insertGitTransport(base, std::move(sub));
235 2030 : return 0;
236 2030 : }
237 :
238 : int
239 2030 : p2p_subtransport_cb(git_smart_subtransport** out, git_transport* owner, void* payload)
240 : {
241 : P2PSubTransport* sub;
242 :
243 2030 : if (P2PSubTransportNew(&sub, owner, payload) < 0)
244 0 : return -1;
245 :
246 2030 : *out = &sub->base;
247 2030 : return 0;
248 : }
249 :
250 : int
251 2030 : p2p_transport_cb(git_transport** out, git_remote* owner, void*)
252 : {
253 2030 : git_smart_subtransport_definition def
254 : = {p2p_subtransport_cb,
255 : 0, /* Because we use an already existing channel socket, we use a permanent transport */
256 2030 : reinterpret_cast<void*>(owner)};
257 4060 : return git_transport_smart(out, owner, &def);
258 : }
|