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 :
18 : #include "fileutils.h"
19 : #include "manager.h"
20 :
21 : #include "jamidht/jamiaccount.h"
22 : #include "../../test_runner.h"
23 : #include "jami.h"
24 : #include "data_transfer.h"
25 : #include "jami/datatransfer_interface.h"
26 : #include "account_const.h"
27 : #include "common.h"
28 :
29 : #include <dhtnet/connectionmanager.h>
30 :
31 : #include <cppunit/TestAssert.h>
32 : #include <cppunit/TestFixture.h>
33 : #include <cppunit/extensions/HelperMacros.h>
34 :
35 : #include <condition_variable>
36 : #include <string>
37 : #include <filesystem>
38 :
39 : using namespace std::literals::chrono_literals;
40 : using namespace libjami::Account;
41 : using namespace std::literals::chrono_literals;
42 :
43 : namespace jami {
44 : namespace test {
45 :
46 : struct UserData {
47 : std::string conversationId;
48 : bool removed {false};
49 : bool requestReceived {false};
50 : bool registered {false};
51 : bool stopped {false};
52 : bool deviceAnnounced {false};
53 : int code {0};
54 : std::vector<libjami::SwarmMessage> messages;
55 : std::vector<libjami::SwarmMessage> messagesUpdated;
56 : };
57 :
58 : class FileTransferTest : public CppUnit::TestFixture
59 : {
60 : public:
61 11 : FileTransferTest()
62 11 : {
63 : // Init daemon
64 11 : libjami::init(
65 : libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
66 11 : if (not Manager::instance().initialized)
67 1 : CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
68 11 : }
69 22 : ~FileTransferTest() { libjami::fini(); }
70 2 : static std::string name() { return "Call"; }
71 : bool compare(const std::string& fileA, const std::string& fileB) const;
72 : void setUp();
73 : void tearDown();
74 :
75 : std::string aliceId;
76 : UserData aliceData;
77 : std::string bobId;
78 : UserData bobData;
79 : std::string carlaId;
80 : UserData carlaData;
81 :
82 : std::filesystem::path sendPath {std::filesystem::current_path() / "SEND"};
83 : std::filesystem::path recvPath {std::filesystem::current_path() / "RECV"};
84 : std::filesystem::path recv2Path {std::filesystem::current_path() / "RECV2"};
85 :
86 : std::mutex mtx;
87 : std::unique_lock<std::mutex> lk {mtx};
88 : std::condition_variable cv;
89 :
90 : void connectSignals();
91 :
92 : private:
93 : void testConversationFileTransfer();
94 : void testFileTransferInConversation();
95 : void testVcfFileTransferInConversation();
96 : void testBadSha3sumOut();
97 : void testBadSha3sumIn();
98 : void testAskToMultipleParticipants();
99 : void testCancelInTransfer();
100 : void testCancelOutTransfer();
101 : void testTransferInfo();
102 : void testRemoveHardLink();
103 : void testTooLarge();
104 : void testDeleteFile();
105 :
106 2 : CPPUNIT_TEST_SUITE(FileTransferTest);
107 1 : CPPUNIT_TEST(testConversationFileTransfer);
108 1 : CPPUNIT_TEST(testFileTransferInConversation);
109 1 : CPPUNIT_TEST(testVcfFileTransferInConversation);
110 1 : CPPUNIT_TEST(testBadSha3sumOut);
111 1 : CPPUNIT_TEST(testBadSha3sumIn);
112 1 : CPPUNIT_TEST(testAskToMultipleParticipants);
113 1 : CPPUNIT_TEST(testCancelInTransfer);
114 1 : CPPUNIT_TEST(testTransferInfo);
115 1 : CPPUNIT_TEST(testRemoveHardLink);
116 1 : CPPUNIT_TEST(testTooLarge);
117 1 : CPPUNIT_TEST(testDeleteFile);
118 4 : CPPUNIT_TEST_SUITE_END();
119 : };
120 :
121 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(FileTransferTest, FileTransferTest::name());
122 :
123 : bool
124 0 : FileTransferTest::compare(const std::string& fileA, const std::string& fileB) const
125 : {
126 0 : std::ifstream f1(fileA, std::ifstream::binary | std::ifstream::ate);
127 0 : std::ifstream f2(fileB, std::ifstream::binary | std::ifstream::ate);
128 :
129 0 : if (f1.fail() || f2.fail() || f1.tellg() != f2.tellg()) {
130 0 : return false;
131 : }
132 :
133 0 : f1.seekg(0, std::ifstream::beg);
134 0 : f2.seekg(0, std::ifstream::beg);
135 0 : return std::equal(std::istreambuf_iterator<char>(f1.rdbuf()),
136 : std::istreambuf_iterator<char>(),
137 0 : std::istreambuf_iterator<char>(f2.rdbuf()));
138 0 : }
139 :
140 : void
141 11 : FileTransferTest::setUp()
142 : {
143 22 : auto actors = load_actors_and_wait_for_announcement("actors/alice-bob-carla.yml");
144 11 : aliceId = actors["alice"];
145 11 : bobId = actors["bob"];
146 11 : carlaId = actors["carla"];
147 11 : aliceData = {};
148 11 : bobData = {};
149 11 : carlaData = {};
150 11 : }
151 :
152 : void
153 11 : FileTransferTest::tearDown()
154 : {
155 11 : std::filesystem::remove(sendPath);
156 11 : std::filesystem::remove(recvPath);
157 11 : std::filesystem::remove(recv2Path);
158 44 : wait_for_removal_of({aliceId, bobId, carlaId});
159 11 : }
160 :
161 : void
162 10 : FileTransferTest::connectSignals()
163 : {
164 10 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
165 10 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
166 21 : [&](const std::string& accountId, const std::string& conversationId) {
167 21 : if (accountId == aliceId) {
168 10 : aliceData.conversationId = conversationId;
169 11 : } else if (accountId == bobId) {
170 9 : bobData.conversationId = conversationId;
171 2 : } else if (accountId == carlaId) {
172 2 : carlaData.conversationId = conversationId;
173 : }
174 21 : cv.notify_one();
175 21 : }));
176 10 : confHandlers.insert(
177 20 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
178 11 : [&](const std::string& accountId,
179 : const std::string& /* conversationId */,
180 : std::map<std::string, std::string> /*metadatas*/) {
181 11 : if (accountId == aliceId) {
182 0 : aliceData.requestReceived = true;
183 11 : } else if (accountId == bobId) {
184 9 : bobData.requestReceived = true;
185 2 : } else if (accountId == carlaId) {
186 2 : carlaData.requestReceived = true;
187 : }
188 11 : cv.notify_one();
189 11 : }));
190 10 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
191 45 : [&](const std::string& accountId,
192 : const std::string& /* conversationId */,
193 : libjami::SwarmMessage message) {
194 45 : std::unique_lock<std::mutex> lk {mtx};
195 45 : if (accountId == aliceId) {
196 32 : aliceData.messages.emplace_back(message);
197 13 : } else if (accountId == bobId) {
198 11 : bobData.messages.emplace_back(message);
199 2 : } else if (accountId == carlaId) {
200 2 : carlaData.messages.emplace_back(message);
201 : }
202 45 : cv.notify_one();
203 45 : }));
204 10 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageUpdated>(
205 0 : [&](const std::string& accountId,
206 : const std::string& /* conversationId */,
207 : libjami::SwarmMessage message) {
208 0 : if (accountId == aliceId) {
209 0 : aliceData.messagesUpdated.emplace_back(message);
210 0 : } else if (accountId == bobId) {
211 0 : bobData.messagesUpdated.emplace_back(message);
212 0 : } else if (accountId == carlaId) {
213 0 : carlaData.messagesUpdated.emplace_back(message);
214 : }
215 0 : cv.notify_one();
216 0 : }));
217 10 : confHandlers.insert(
218 20 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
219 1 : [&](const std::string& accountId, const std::string&) {
220 1 : if (accountId == aliceId)
221 1 : aliceData.removed = true;
222 0 : else if (accountId == bobId)
223 0 : bobData.removed = true;
224 1 : cv.notify_one();
225 1 : }));
226 10 : confHandlers.insert(libjami::exportable_callback<libjami::DataTransferSignal::DataTransferEvent>(
227 43 : [&](const std::string& accountId,
228 : const std::string& conversationId,
229 : const std::string&,
230 : const std::string& fileId,
231 : int code) {
232 43 : if (conversationId.empty())
233 0 : return;
234 43 : if (accountId == aliceId)
235 20 : aliceData.code = code;
236 23 : else if (accountId == bobId)
237 18 : bobData.code = code;
238 5 : else if (accountId == carlaId)
239 5 : carlaData.code = code;
240 43 : cv.notify_one();
241 : }));
242 10 : libjami::registerSignalHandlers(confHandlers);
243 10 : }
244 :
245 : void
246 1 : FileTransferTest::testConversationFileTransfer()
247 : {
248 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
249 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
250 1 : auto bobUri = bobAccount->getUsername();
251 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
252 1 : auto carlaUri = carlaAccount->getUsername();
253 1 : aliceAccount->trackBuddyPresence(carlaUri, true);
254 :
255 : // Enable carla
256 1 : Manager::instance().sendRegister(carlaId, true);
257 1 : wait_for_announcement_of(carlaId);
258 :
259 1 : connectSignals();
260 :
261 1 : auto convId = libjami::startConversation(aliceId);
262 :
263 1 : libjami::addConversationMember(aliceId, convId, bobUri);
264 1 : libjami::addConversationMember(aliceId, convId, carlaUri);
265 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return bobData.requestReceived && carlaData.requestReceived; }));
266 :
267 1 : auto aliceMsgSize = aliceData.messages.size();
268 1 : libjami::acceptConversationRequest(bobId, convId);
269 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
270 1 : aliceMsgSize = aliceData.messages.size();
271 1 : auto bobMsgSize = bobData.messages.size();
272 1 : libjami::acceptConversationRequest(carlaId, convId);
273 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size() && bobMsgSize + 1 == bobData.messages.size(); }));
274 :
275 : // Send file
276 1 : std::ofstream sendFile(sendPath);
277 1 : CPPUNIT_ASSERT(sendFile.is_open());
278 1 : sendFile << std::string(64000, 'A');
279 1 : sendFile.close();
280 :
281 1 : bobMsgSize = bobData.messages.size();
282 1 : auto carlaMsgSize = carlaData.messages.size();
283 1 : libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
284 :
285 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { return bobData.messages.size() == bobMsgSize + 1 && carlaData.messages.size() == carlaMsgSize + 1; }));
286 1 : auto id = bobData.messages.rbegin()->id;
287 2 : auto fileId = bobData.messages.rbegin()->body["fileId"];
288 :
289 1 : libjami::downloadFile(bobId, convId, id, fileId, recvPath);
290 1 : libjami::downloadFile(carlaId, convId, id, fileId, recv2Path);
291 :
292 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { return carlaData.code == static_cast<int>(libjami::DataTransferEventCode::finished) && bobData.code == static_cast<int>(libjami::DataTransferEventCode::finished); }));
293 1 : }
294 :
295 : void
296 1 : FileTransferTest::testFileTransferInConversation()
297 : {
298 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
299 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
300 1 : auto bobUri = bobAccount->getUsername();
301 1 : auto aliceUri = aliceAccount->getUsername();
302 1 : connectSignals();
303 :
304 1 : auto convId = libjami::startConversation(aliceId);
305 :
306 1 : libjami::addConversationMember(aliceId, convId, bobUri);
307 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
308 :
309 1 : auto aliceMsgSize = aliceData.messages.size();
310 1 : libjami::acceptConversationRequest(bobId, convId);
311 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
312 :
313 : // Create file to send
314 1 : std::ofstream sendFile(sendPath);
315 1 : CPPUNIT_ASSERT(sendFile.is_open());
316 1 : sendFile << std::string(64000, 'A');
317 1 : sendFile.close();
318 :
319 1 : auto bobMsgSize = bobData.messages.size();
320 1 : libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
321 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1== bobData.messages.size(); }));
322 :
323 1 : auto id = bobData.messages.rbegin()->id;
324 2 : auto fileId = bobData.messages.rbegin()->body["fileId"];
325 1 : libjami::downloadFile(bobId, convId, id, fileId, recvPath);
326 :
327 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { return aliceData.code == static_cast<int>(libjami::DataTransferEventCode::finished) && bobData.code == static_cast<int>(libjami::DataTransferEventCode::finished); }));
328 1 : }
329 :
330 : void
331 1 : FileTransferTest::testVcfFileTransferInConversation()
332 : {
333 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
334 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
335 1 : auto bobUri = bobAccount->getUsername();
336 1 : auto aliceUri = aliceAccount->getUsername();
337 1 : connectSignals();
338 1 : auto convId = libjami::startConversation(aliceId);
339 :
340 1 : libjami::addConversationMember(aliceId, convId, bobUri);
341 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
342 :
343 1 : auto aliceMsgSize = aliceData.messages.size();
344 1 : libjami::acceptConversationRequest(bobId, convId);
345 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
346 :
347 : // Create file to send
348 1 : std::ofstream sendFile(sendPath);
349 1 : CPPUNIT_ASSERT(sendFile.is_open());
350 1 : sendFile << std::string(64000, 'A');
351 1 : sendFile.close();
352 :
353 1 : auto bobMsgSize = bobData.messages.size();
354 1 : libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
355 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
356 :
357 1 : auto id = bobData.messages.rbegin()->id;
358 2 : auto fileId = bobData.messages.rbegin()->body["fileId"];
359 1 : libjami::downloadFile(bobId, convId, id, fileId, recvPath);
360 :
361 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { return aliceData.code == static_cast<int>(libjami::DataTransferEventCode::finished) && bobData.code == static_cast<int>(libjami::DataTransferEventCode::finished); }));
362 1 : }
363 :
364 : void
365 1 : FileTransferTest::testBadSha3sumOut()
366 : {
367 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
368 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
369 1 : auto bobUri = bobAccount->getUsername();
370 1 : auto aliceUri = aliceAccount->getUsername();
371 1 : connectSignals();
372 1 : auto convId = libjami::startConversation(aliceId);
373 :
374 1 : libjami::addConversationMember(aliceId, convId, bobUri);
375 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
376 :
377 1 : auto aliceMsgSize = aliceData.messages.size();
378 1 : libjami::acceptConversationRequest(bobId, convId);
379 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
380 :
381 : // Create file to send
382 1 : std::ofstream sendFile(sendPath);
383 1 : CPPUNIT_ASSERT(sendFile.is_open());
384 1 : sendFile << std::string(64000, 'A');
385 1 : sendFile.close();
386 :
387 1 : auto bobMsgSize = bobData.messages.size();
388 1 : libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
389 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
390 :
391 : // modifiy file
392 1 : sendFile = std::ofstream(sendPath, std::ios::trunc);
393 1 : CPPUNIT_ASSERT(sendFile.is_open());
394 1 : sendFile << std::string(64000, 'B');
395 1 : sendFile.close();
396 :
397 1 : auto id = bobData.messages.rbegin()->id;
398 2 : auto fileId = bobData.messages.rbegin()->body["fileId"];
399 1 : libjami::downloadFile(bobId, convId, id, fileId, recvPath);
400 :
401 3 : CPPUNIT_ASSERT(!cv.wait_for(lk, 45s, [&]() { return aliceData.code == static_cast<int>(libjami::DataTransferEventCode::finished) || bobData.code == static_cast<int>(libjami::DataTransferEventCode::finished); }));
402 1 : }
403 :
404 : void
405 1 : FileTransferTest::testBadSha3sumIn()
406 : {
407 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
408 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
409 1 : auto bobUri = bobAccount->getUsername();
410 1 : auto aliceUri = aliceAccount->getUsername();
411 1 : connectSignals();
412 1 : auto convId = libjami::startConversation(aliceId);
413 :
414 1 : libjami::addConversationMember(aliceId, convId, bobUri);
415 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
416 :
417 1 : auto aliceMsgSize = aliceData.messages.size();
418 1 : libjami::acceptConversationRequest(bobId, convId);
419 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
420 :
421 : // Create file to send
422 1 : std::ofstream sendFile(sendPath);
423 1 : CPPUNIT_ASSERT(sendFile.is_open());
424 1 : sendFile << std::string(64000, 'A');
425 1 : sendFile.close();
426 :
427 1 : aliceAccount->noSha3sumVerification(true);
428 1 : auto bobMsgSize = bobData.messages.size();
429 1 : libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
430 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
431 :
432 : // modifiy file
433 1 : sendFile = std::ofstream(sendPath);
434 1 : CPPUNIT_ASSERT(sendFile.is_open());
435 : // Avoid ASAN error on big alloc sendFile << std::string("B", 64000);
436 1 : sendFile << std::string(64000, 'B');
437 1 : sendFile.close();
438 :
439 1 : auto id = bobData.messages.rbegin()->id;
440 2 : auto fileId = bobData.messages.rbegin()->body["fileId"];
441 1 : libjami::downloadFile(bobId, convId, id, fileId, recvPath);
442 :
443 : // The file transfer will be sent but refused by bob
444 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.code == static_cast<int>(libjami::DataTransferEventCode::finished); }));
445 5 : CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return bobData.code == static_cast<int>(libjami::DataTransferEventCode::finished); }));
446 :
447 1 : std::filesystem::remove(sendPath);
448 1 : }
449 :
450 : void
451 1 : FileTransferTest::testAskToMultipleParticipants()
452 : {
453 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
454 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
455 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
456 1 : auto aliceUri = aliceAccount->getUsername();
457 1 : auto bobUri = bobAccount->getUsername();
458 1 : auto carlaUri = carlaAccount->getUsername();
459 1 : connectSignals();
460 1 : auto convId = libjami::startConversation(aliceId);
461 :
462 1 : libjami::addConversationMember(aliceId, convId, bobUri);
463 1 : libjami::addConversationMember(aliceId, convId, carlaUri);
464 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return bobData.requestReceived && carlaData.requestReceived; }));
465 :
466 1 : auto aliceMsgSize = aliceData.messages.size();
467 1 : libjami::acceptConversationRequest(bobId, convId);
468 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
469 1 : aliceMsgSize = aliceData.messages.size();
470 1 : auto bobMsgSize = bobData.messages.size();
471 1 : libjami::acceptConversationRequest(carlaId, convId);
472 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size() && bobMsgSize + 1 == bobData.messages.size(); }));
473 :
474 : // Create file to send
475 1 : std::ofstream sendFile(sendPath);
476 1 : CPPUNIT_ASSERT(sendFile.is_open());
477 1 : sendFile << std::string(64000, 'A');
478 1 : sendFile.close();
479 :
480 1 : bobMsgSize = bobData.messages.size();
481 1 : auto carlaMsgSize = carlaData.messages.size();
482 1 : libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
483 :
484 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { return bobData.messages.size() == bobMsgSize + 1 && carlaData.messages.size() == carlaMsgSize + 1; }));
485 1 : auto id = bobData.messages.rbegin()->id;
486 2 : auto fileId = bobData.messages.rbegin()->body["fileId"];
487 :
488 1 : libjami::downloadFile(carlaId, convId, id, fileId, recv2Path);
489 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.code == static_cast<int>(libjami::DataTransferEventCode::finished); }));
490 1 : CPPUNIT_ASSERT(dhtnet::fileutils::isFile(recv2Path));
491 :
492 1 : libjami::downloadFile(bobId, convId, id, fileId, recvPath);
493 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.code == static_cast<int>(libjami::DataTransferEventCode::finished); }));
494 1 : CPPUNIT_ASSERT(dhtnet::fileutils::isFile(recvPath));
495 1 : }
496 :
497 : void
498 1 : FileTransferTest::testCancelInTransfer()
499 : {
500 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
501 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
502 1 : auto bobUri = bobAccount->getUsername();
503 1 : auto aliceUri = aliceAccount->getUsername();
504 1 : connectSignals();
505 1 : auto convId = libjami::startConversation(aliceId);
506 :
507 1 : libjami::addConversationMember(aliceId, convId, bobUri);
508 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
509 :
510 1 : auto aliceMsgSize = aliceData.messages.size();
511 1 : libjami::acceptConversationRequest(bobId, convId);
512 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
513 :
514 : // Create file to send
515 1 : std::ofstream sendFile(sendPath);
516 1 : CPPUNIT_ASSERT(sendFile.is_open());
517 1 : sendFile << std::string(640000, 'A');
518 1 : sendFile.close();
519 :
520 1 : auto bobMsgSize = bobData.messages.size();
521 1 : libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
522 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
523 1 : auto id = bobData.messages.rbegin()->id;
524 2 : auto fileId = bobData.messages.rbegin()->body["fileId"];
525 :
526 1 : libjami::downloadFile(bobId, convId, id, fileId, recvPath);
527 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.code == static_cast<int>(libjami::DataTransferEventCode::ongoing); }));
528 :
529 1 : libjami::cancelDataTransfer(bobId, convId, fileId);
530 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.code == static_cast<int>(libjami::DataTransferEventCode::closed_by_peer); }));
531 1 : CPPUNIT_ASSERT(!dhtnet::fileutils::isFile(recvPath));
532 1 : CPPUNIT_ASSERT(!bobAccount->dataTransfer(convId)->isWaiting(fileId));
533 1 : }
534 :
535 : void
536 1 : FileTransferTest::testTransferInfo()
537 : {
538 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
539 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
540 1 : auto bobUri = bobAccount->getUsername();
541 1 : auto aliceUri = aliceAccount->getUsername();
542 1 : connectSignals();
543 1 : auto convId = libjami::startConversation(aliceId);
544 :
545 1 : libjami::addConversationMember(aliceId, convId, bobUri);
546 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
547 :
548 1 : auto aliceMsgSize = aliceData.messages.size();
549 1 : libjami::acceptConversationRequest(bobId, convId);
550 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
551 :
552 : // Create file to send
553 1 : std::ofstream sendFile(sendPath);
554 1 : CPPUNIT_ASSERT(sendFile.is_open());
555 1 : sendFile << std::string(640000, 'A');
556 1 : sendFile.close();
557 :
558 1 : auto bobMsgSize = bobData.messages.size();
559 1 : libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
560 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
561 1 : auto id = bobData.messages.rbegin()->id;
562 2 : auto fileId = bobData.messages.rbegin()->body["fileId"];
563 :
564 : int64_t totalSize, bytesProgress;
565 1 : std::string path;
566 1 : CPPUNIT_ASSERT(libjami::fileTransferInfo(bobId, convId, fileId, path, totalSize, bytesProgress)
567 : == libjami::DataTransferError::invalid_argument);
568 1 : CPPUNIT_ASSERT(bytesProgress == 0);
569 1 : CPPUNIT_ASSERT(!std::filesystem::is_regular_file(path));
570 : // No check for total as not started
571 :
572 1 : libjami::downloadFile(bobId, convId, id, fileId, recvPath);
573 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.code == static_cast<int>(libjami::DataTransferEventCode::finished); }));
574 1 : CPPUNIT_ASSERT(libjami::fileTransferInfo(bobId, convId, fileId, path, totalSize, bytesProgress)
575 : == libjami::DataTransferError::success);
576 1 : CPPUNIT_ASSERT(bytesProgress == 640000);
577 1 : CPPUNIT_ASSERT(totalSize == 640000);
578 1 : CPPUNIT_ASSERT(dhtnet::fileutils::isFile(path));
579 1 : }
580 :
581 : void
582 1 : FileTransferTest::testRemoveHardLink()
583 : {
584 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
585 1 : connectSignals();
586 1 : auto convId = libjami::startConversation(aliceId);
587 :
588 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
589 :
590 : // Send file
591 1 : std::ofstream sendFile(sendPath);
592 1 : CPPUNIT_ASSERT(sendFile.is_open());
593 1 : sendFile << std::string(64000, 'A');
594 1 : sendFile.close();
595 :
596 1 : libjami::sendFile(aliceId, convId, sendPath, std::filesystem::absolute("SEND"), "");
597 :
598 1 : auto aliceMsgSize = aliceData.messages.size();
599 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 45s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
600 :
601 1 : CPPUNIT_ASSERT(libjami::removeConversation(aliceId, convId));
602 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.removed; }));
603 :
604 1 : auto content = fileutils::loadTextFile(sendPath);
605 1 : CPPUNIT_ASSERT(content.find("AAA") != std::string::npos);
606 1 : }
607 :
608 : void
609 1 : FileTransferTest::testTooLarge()
610 : {
611 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
612 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
613 1 : auto bobUri = bobAccount->getUsername();
614 1 : connectSignals();
615 1 : auto convId = libjami::startConversation(aliceId);
616 :
617 1 : libjami::addConversationMember(aliceId, convId, bobUri);
618 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
619 :
620 1 : auto aliceMsgSize = aliceData.messages.size();
621 1 : libjami::acceptConversationRequest(bobId, convId);
622 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
623 : return aliceMsgSize + 1 == aliceData.messages.size(); }));
624 :
625 : // Send file
626 1 : std::ofstream sendFile(sendPath);
627 1 : CPPUNIT_ASSERT(sendFile.is_open());
628 1 : sendFile << std::string(64000, 'A');
629 1 : sendFile.close();
630 :
631 1 : auto bobMsgSize = bobData.messages.size();
632 1 : libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
633 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
634 1 : auto id = bobData.messages.rbegin()->id;
635 2 : auto fileId = bobData.messages.rbegin()->body["fileId"];
636 :
637 : // Add some data for the reception. This will break the final shasum
638 2 : std::ofstream recvFile(recvPath + std::string(".tmp"));
639 1 : CPPUNIT_ASSERT(recvFile.is_open());
640 1 : recvFile << std::string(1000, 'B');
641 1 : recvFile.close();
642 1 : libjami::downloadFile(bobId, convId, id, fileId, recvPath);
643 :
644 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return bobData.code == static_cast<int>(libjami::DataTransferEventCode::closed_by_host); }));
645 1 : CPPUNIT_ASSERT(!dhtnet::fileutils::isFile(recvPath));
646 1 : }
647 :
648 : void
649 1 : FileTransferTest::testDeleteFile()
650 : {
651 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
652 1 : auto convId = libjami::startConversation(aliceId);
653 :
654 1 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
655 1 : bool conversationReady = false;
656 1 : std::string iid, tid;
657 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
658 1 : [&](const std::string& accountId,
659 : const std::string& /* conversationId */,
660 : libjami::SwarmMessage message) {
661 1 : if (message.type == "application/data-transfer+json") {
662 1 : if (accountId == aliceId) {
663 1 : iid = message.id;
664 1 : tid = message.body["tid"];
665 : }
666 : }
667 1 : cv.notify_one();
668 1 : }));
669 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
670 0 : [&](const std::string& accountId, const std::string& /* conversationId */) {
671 0 : if (accountId == bobId) {
672 0 : conversationReady = true;
673 0 : cv.notify_one();
674 : }
675 0 : }));
676 1 : bool messageUpdated = false;
677 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageUpdated>(
678 1 : [&](const std::string& accountId,
679 : const std::string& /* conversationId */,
680 : libjami::SwarmMessage message) {
681 1 : if (accountId == aliceId && message.type == "application/data-transfer+json" && message.body["tid"].empty()) {
682 1 : messageUpdated = true;
683 : }
684 1 : cv.notify_one();
685 1 : }));
686 1 : libjami::registerSignalHandlers(confHandlers);
687 :
688 : // Create file to send
689 1 : std::ofstream sendFile(sendPath);
690 1 : CPPUNIT_ASSERT(sendFile.is_open());
691 1 : sendFile << std::string(64000, 'A');
692 1 : sendFile.close();
693 :
694 1 : libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
695 :
696 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !iid.empty(); }));
697 2 : auto dataPath = fileutils::get_data_dir() / aliceId / "conversation_data" / convId;
698 2 : CPPUNIT_ASSERT(dhtnet::fileutils::isFile(dataPath / fmt::format("{}_{}", iid, tid)));
699 :
700 : // Delete file
701 1 : libjami::sendMessage(aliceId, convId, ""s, iid, 1);
702 :
703 : // Verify message is updated
704 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageUpdated; }));
705 : // Verify file is deleted
706 2 : CPPUNIT_ASSERT(!dhtnet::fileutils::isFile(dataPath / fmt::format("{}_{}", iid, tid)));
707 :
708 1 : libjami::unregisterSignalHandlers();
709 1 : std::this_thread::sleep_for(5s);
710 1 : }
711 :
712 : } // namespace test
713 : } // namespace jami
714 :
715 1 : RING_TEST_RUNNER(jami::test::FileTransferTest::name())
|