• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mavlink / MAVSDK / 11041590519

25 Sep 2024 09:43PM UTC coverage: 38.062% (-0.02%) from 38.077%
11041590519

Pull #2405

github

web-flow
Merge 6965341a9 into ae4379225
Pull Request #2405: third_party: update libevents

11454 of 30093 relevant lines covered (38.06%)

260.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.63
/src/mavsdk/core/mavlink_ftp_client.cpp
1
#include "mavlink_ftp_client.h"
2
#include "system_impl.h"
3
#include "plugin_base.h"
4
#include "unused.h"
5
#include <algorithm>
6
#include <fstream>
7
#include <filesystem>
8
#include <algorithm>
9
#include <numeric>
10

11
#include "crc32.h"
12

13
namespace mavsdk {
14

15
namespace fs = std::filesystem;
16

17
MavlinkFtpClient::MavlinkFtpClient(SystemImpl& system_impl) : _system_impl(system_impl)
69✔
18
{
19
    if (const char* env_p = std::getenv("MAVSDK_FTP_DEBUGGING")) {
69✔
20
        if (std::string(env_p) == "1") {
×
21
            LogDebug() << "Ftp debugging is on.";
×
22
            _debugging = true;
×
23
        }
24
    }
25

26
    _system_impl.register_mavlink_message_handler(
69✔
27
        MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL,
28
        [this](const mavlink_message_t& message) { process_mavlink_ftp_message(message); },
1,169✔
29
        this);
30
}
69✔
31

32
MavlinkFtpClient::~MavlinkFtpClient()
69✔
33
{
34
    _system_impl.unregister_all_mavlink_message_handlers(this);
69✔
35
}
69✔
36

37
void MavlinkFtpClient::do_work()
3,062✔
38
{
39
    LockedQueue<Work>::Guard work_queue_guard(_work_queue);
3,062✔
40

41
    auto work = work_queue_guard.get_front();
3,062✔
42
    if (!work) {
3,062✔
43
        return;
2,184✔
44
    }
45

46
    if (work->started) {
878✔
47
        return;
838✔
48
    }
49
    work->started = true;
40✔
50

51
    // We're mainly starting the process here. After that, it continues
52
    // based on returned acks or timeouts.
53

54
    std::visit(
80✔
55
        overloaded{
40✔
56
            [&](DownloadItem& item) {
7✔
57
                if (!download_start(*work, item)) {
7✔
58
                    work_queue_guard.pop_front();
×
59
                }
60
            },
7✔
61
            [&](DownloadBurstItem& item) {
10✔
62
                if (!download_burst_start(*work, item)) {
10✔
63
                    work_queue_guard.pop_front();
×
64
                }
65
            },
10✔
66
            [&](UploadItem& item) {
7✔
67
                if (!upload_start(*work, item)) {
7✔
68
                    work_queue_guard.pop_front();
×
69
                }
70
            },
7✔
71
            [&](RemoveItem& item) {
4✔
72
                if (!remove_start(*work, item)) {
4✔
73
                    work_queue_guard.pop_front();
×
74
                }
75
            },
4✔
76
            [&](RenameItem& item) {
2✔
77
                if (!rename_start(*work, item)) {
2✔
78
                    work_queue_guard.pop_front();
×
79
                }
80
            },
2✔
81
            [&](CreateDirItem& item) {
3✔
82
                if (!create_dir_start(*work, item)) {
3✔
83
                    work_queue_guard.pop_front();
×
84
                }
85
            },
3✔
86
            [&](RemoveDirItem& item) {
2✔
87
                if (!remove_dir_start(*work, item)) {
2✔
88
                    work_queue_guard.pop_front();
×
89
                }
90
            },
2✔
91
            [&](CompareFilesItem& item) {
3✔
92
                if (!compare_files_start(*work, item)) {
3✔
93
                    work_queue_guard.pop_front();
×
94
                }
95
            },
3✔
96
            [&](ListDirItem& item) {
2✔
97
                if (!list_dir_start(*work, item)) {
2✔
98
                    work_queue_guard.pop_front();
×
99
                }
100
            }},
2✔
101
        work->item);
40✔
102
}
103

104
void MavlinkFtpClient::process_mavlink_ftp_message(const mavlink_message_t& msg)
1,169✔
105
{
106
    mavlink_file_transfer_protocol_t ftp_req;
1,169✔
107
    mavlink_msg_file_transfer_protocol_decode(&msg, &ftp_req);
1,169✔
108

109
    if (ftp_req.target_system != 0 && ftp_req.target_system != _system_impl.get_own_system_id()) {
1,169✔
110
        LogWarn() << "Received FTP with wrong target system ID!";
×
111
        return;
×
112
    }
113

114
    if (ftp_req.target_component != 0 &&
2,338✔
115
        ftp_req.target_component != _system_impl.get_own_component_id()) {
1,169✔
116
        LogWarn() << "Received FTP with wrong target component ID!";
9✔
117
        return;
9✔
118
    }
119

120
    PayloadHeader* payload = reinterpret_cast<PayloadHeader*>(&ftp_req.payload[0]);
1,160✔
121

122
    if (payload->size > max_data_length) {
1,160✔
123
        LogWarn() << "Received FTP payload with invalid size";
×
124
        return;
×
125
    } else {
126
        if (_debugging) {
1,160✔
127
            LogDebug() << "FTP: opcode: " << (int)payload->opcode
×
128
                       << ", size: " << (int)payload->size << ", offset: " << (int)payload->offset
×
129
                       << ", seq: " << payload->seq_number;
×
130
        }
131
    }
132

133
    LockedQueue<Work>::Guard work_queue_guard(_work_queue);
1,160✔
134

135
    auto work = work_queue_guard.get_front();
1,160✔
136
    if (!work) {
1,160✔
137
        return;
463✔
138
    }
139

140
    if (work->last_opcode != payload->req_opcode) {
697✔
141
        // Ignore
142
        LogWarn() << "Ignore: last: " << (int)work->last_opcode
12✔
143
                  << ", req: " << (int)payload->req_opcode;
12✔
144
        return;
4✔
145
    }
146
    if (work->last_received_seq_number != 0 &&
1,346✔
147
        work->last_received_seq_number == payload->seq_number) {
653✔
148
        // We have already seen this ack/nak.
149
        LogWarn() << "Already seen";
×
150
        return;
×
151
    }
152

153
    std::visit(
1,386✔
154
        overloaded{
693✔
155
            [&](DownloadItem& item) {
270✔
156
                if (payload->opcode == RSP_ACK) {
270✔
157
                    if (payload->req_opcode == CMD_OPEN_FILE_RO ||
268✔
158
                        payload->req_opcode == CMD_READ_FILE) {
263✔
159
                        // Whenever we do get an ack,
160
                        // reset the retry counter.
161
                        work->retries = RETRIES;
264✔
162

163
                        if (!download_continue(*work, item, payload)) {
264✔
164
                            stop_timer();
×
165
                            work_queue_guard.pop_front();
×
166
                        }
167
                    } else if (payload->req_opcode == CMD_TERMINATE_SESSION) {
4✔
168
                        stop_timer();
4✔
169
                        item.ofstream.close();
4✔
170
                        item.callback(ClientResult::Success, {});
4✔
171
                        work_queue_guard.pop_front();
4✔
172

173
                    } else {
174
                        LogWarn() << "Unexpected ack";
×
175
                    }
176

177
                } else if (payload->opcode == RSP_NAK) {
2✔
178
                    stop_timer();
2✔
179
                    item.callback(result_from_nak(payload), {});
2✔
180
                    terminate_session(*work);
2✔
181
                    work_queue_guard.pop_front();
2✔
182
                }
183
            },
270✔
184
            [&](DownloadBurstItem& item) {
295✔
185
                if (payload->opcode == RSP_ACK) {
295✔
186
                    if (payload->req_opcode == CMD_OPEN_FILE_RO ||
293✔
187
                        payload->req_opcode == CMD_BURST_READ_FILE ||
285✔
188
                        payload->req_opcode == CMD_READ_FILE) {
15✔
189
                        // Whenever we do get an ack,
190
                        // reset the retry counter.
191
                        work->retries = RETRIES;
287✔
192

193
                        if (!download_burst_continue(*work, item, payload)) {
287✔
194
                            stop_timer();
×
195
                            work_queue_guard.pop_front();
×
196
                        }
197
                    } else if (payload->req_opcode == CMD_TERMINATE_SESSION) {
6✔
198
                        stop_timer();
6✔
199
                        item.ofstream.close();
6✔
200
                        item.callback(ClientResult::Success, {});
6✔
201
                        work_queue_guard.pop_front();
6✔
202

203
                    } else {
204
                        LogWarn() << "Unexpected ack";
×
205
                    }
206

207
                } else if (payload->opcode == RSP_NAK) {
2✔
208
                    const ServerResult sr = static_cast<ServerResult>(payload->data[0]);
2✔
209
                    // In case there's no session available, there's another transfer in progress
210
                    // for the given component. Back off and try again later.
211
                    if (sr == ERR_NO_SESSIONS_AVAILABLE) {
2✔
212
                        payload->seq_number = 0; // Ignore this response
×
213
                        start_timer(3.0);
×
214
                        LogDebug() << "No session available, retrying...";
×
215
                    } else {
216
                        LogWarn() << "FTP: NAK received";
2✔
217
                        stop_timer();
2✔
218
                        item.callback(result_from_nak(payload), {});
2✔
219
                        terminate_session(*work);
2✔
220
                        work_queue_guard.pop_front();
2✔
221
                    }
222
                }
223
            },
295✔
224
            [&](UploadItem& item) {
103✔
225
                if (payload->opcode == RSP_ACK) {
103✔
226
                    if (payload->req_opcode == CMD_CREATE_FILE ||
101✔
227
                        payload->req_opcode == CMD_OPEN_FILE_WO ||
96✔
228
                        payload->req_opcode == CMD_WRITE_FILE) {
96✔
229
                        // Whenever we do get an ack,
230
                        // reset the retry counter.
231
                        work->retries = RETRIES;
97✔
232

233
                        if (!upload_continue(*work, item)) {
97✔
234
                            stop_timer();
×
235
                            work_queue_guard.pop_front();
×
236
                        }
237
                    } else if (payload->req_opcode == CMD_TERMINATE_SESSION) {
4✔
238
                        stop_timer();
4✔
239
                        item.ifstream.close();
4✔
240
                        item.callback(ClientResult::Success, {});
4✔
241
                        work_queue_guard.pop_front();
4✔
242

243
                    } else {
244
                        LogWarn() << "Unexpected ack";
×
245
                    }
246

247
                } else if (payload->opcode == RSP_NAK) {
2✔
248
                    stop_timer();
2✔
249
                    item.callback(result_from_nak(payload), {});
2✔
250
                    terminate_session(*work);
2✔
251
                    work_queue_guard.pop_front();
2✔
252
                }
253
            },
103✔
254
            [&](RemoveItem& item) {
4✔
255
                if (payload->opcode == RSP_ACK) {
4✔
256
                    if (payload->req_opcode == CMD_REMOVE_FILE) {
1✔
257
                        stop_timer();
1✔
258
                        item.callback(ClientResult::Success);
1✔
259
                        work_queue_guard.pop_front();
1✔
260

261
                    } else {
262
                        LogWarn() << "Unexpected ack";
×
263
                    }
264

265
                } else if (payload->opcode == RSP_NAK) {
3✔
266
                    stop_timer();
3✔
267
                    item.callback(result_from_nak(payload));
3✔
268
                    terminate_session(*work);
3✔
269
                    work_queue_guard.pop_front();
3✔
270
                }
271
            },
4✔
272
            [&](RenameItem& item) {
2✔
273
                if (payload->opcode == RSP_ACK) {
2✔
274
                    if (payload->req_opcode == CMD_RENAME) {
1✔
275
                        stop_timer();
1✔
276
                        item.callback(ClientResult::Success);
1✔
277
                        work_queue_guard.pop_front();
1✔
278

279
                    } else {
280
                        LogWarn() << "Unexpected ack";
×
281
                    }
282

283
                } else if (payload->opcode == RSP_NAK) {
1✔
284
                    stop_timer();
1✔
285
                    item.callback(result_from_nak(payload));
1✔
286
                    terminate_session(*work);
1✔
287
                    work_queue_guard.pop_front();
1✔
288
                }
289
            },
2✔
290
            [&](CreateDirItem& item) {
3✔
291
                if (payload->opcode == RSP_ACK) {
3✔
292
                    if (payload->req_opcode == CMD_CREATE_DIRECTORY) {
1✔
293
                        stop_timer();
1✔
294
                        item.callback(ClientResult::Success);
1✔
295
                        work_queue_guard.pop_front();
1✔
296

297
                    } else {
298
                        LogWarn() << "Unexpected ack";
×
299
                    }
300

301
                } else if (payload->opcode == RSP_NAK) {
2✔
302
                    stop_timer();
2✔
303
                    item.callback(result_from_nak(payload));
2✔
304
                    terminate_session(*work);
2✔
305
                    work_queue_guard.pop_front();
2✔
306
                }
307
            },
3✔
308
            [&](RemoveDirItem& item) {
2✔
309
                if (payload->opcode == RSP_ACK) {
2✔
310
                    if (payload->req_opcode == CMD_REMOVE_DIRECTORY) {
1✔
311
                        stop_timer();
1✔
312
                        item.callback(ClientResult::Success);
1✔
313
                        work_queue_guard.pop_front();
1✔
314

315
                    } else {
316
                        LogWarn() << "Unexpected ack";
×
317
                    }
318

319
                } else if (payload->opcode == RSP_NAK) {
1✔
320
                    stop_timer();
1✔
321
                    item.callback(result_from_nak(payload));
1✔
322
                    terminate_session(*work);
1✔
323
                    work_queue_guard.pop_front();
1✔
324
                }
325
            },
2✔
326
            [&](CompareFilesItem& item) {
3✔
327
                if (payload->opcode == RSP_ACK) {
3✔
328
                    if (payload->req_opcode == CMD_CALC_FILE_CRC32) {
2✔
329
                        stop_timer();
2✔
330
                        uint32_t remote_crc = *reinterpret_cast<uint32_t*>(payload->data);
2✔
331
                        item.callback(ClientResult::Success, remote_crc == item.local_crc);
2✔
332
                        work_queue_guard.pop_front();
2✔
333

334
                    } else {
335
                        LogWarn() << "Unexpected ack";
×
336
                    }
337

338
                } else if (payload->opcode == RSP_NAK) {
1✔
339
                    stop_timer();
1✔
340
                    item.callback(result_from_nak(payload), false);
1✔
341
                    terminate_session(*work);
1✔
342
                    work_queue_guard.pop_front();
1✔
343
                }
344
            },
3✔
345
            [&](ListDirItem& item) {
11✔
346
                if (payload->opcode == RSP_ACK) {
11✔
347
                    if (payload->req_opcode == CMD_LIST_DIRECTORY) {
9✔
348
                        // Whenever we do get an ack, reset the retry counter.
349
                        work->retries = RETRIES;
9✔
350

351
                        if (!list_dir_continue(*work, item, payload)) {
9✔
352
                            stop_timer();
×
353
                            work_queue_guard.pop_front();
×
354
                        }
355
                    } else {
356
                        LogWarn() << "Unexpected ack";
×
357
                    }
358

359
                } else if (payload->opcode == RSP_NAK) {
2✔
360
                    stop_timer();
2✔
361
                    if (payload->data[0] == ERR_EOF) {
2✔
362
                        std::sort(item.dirs.begin(), item.dirs.end());
1✔
363
                        std::sort(item.files.begin(), item.files.end());
1✔
364
                        item.callback(ClientResult::Success, item.dirs, item.files);
1✔
365
                    } else {
366
                        item.callback(result_from_nak(payload), {}, {});
1✔
367
                    }
368
                    terminate_session(*work);
2✔
369
                    work_queue_guard.pop_front();
2✔
370
                }
371
            }},
11✔
372
        work->item);
693✔
373

374
    work->last_received_seq_number = payload->seq_number;
693✔
375
}
376

377
bool MavlinkFtpClient::download_start(Work& work, DownloadItem& item)
7✔
378
{
379
    fs::path local_path = fs::path(item.local_folder) / fs::path(item.remote_path).filename();
35✔
380

381
    if (_debugging) {
7✔
382
        LogDebug() << "Trying to open write to local path: " << local_path.string();
×
383
    }
384

385
    item.ofstream.open(local_path, std::fstream::trunc | std::fstream::binary);
7✔
386
    if (!item.ofstream) {
7✔
387
        LogErr() << "Could not open it!";
×
388
        item.callback(ClientResult::FileIoError, {});
×
389
        return false;
×
390
    }
391

392
    work.last_opcode = CMD_OPEN_FILE_RO;
7✔
393
    work.payload = {};
7✔
394
    work.payload.seq_number = work.last_sent_seq_number++;
7✔
395
    work.payload.session = _session;
7✔
396
    work.payload.opcode = work.last_opcode;
7✔
397
    work.payload.offset = 0;
7✔
398
    strncpy(
7✔
399
        reinterpret_cast<char*>(work.payload.data), item.remote_path.c_str(), max_data_length - 1);
7✔
400
    work.payload.size = item.remote_path.length() + 1;
7✔
401

402
    start_timer();
7✔
403
    send_mavlink_ftp_message(work.payload, work.target_compid);
7✔
404

405
    return true;
7✔
406
}
407

408
bool MavlinkFtpClient::download_continue(Work& work, DownloadItem& item, PayloadHeader* payload)
264✔
409
{
410
    if (payload->req_opcode == CMD_OPEN_FILE_RO) {
264✔
411
        item.file_size = *(reinterpret_cast<uint32_t*>(payload->data));
5✔
412

413
        if (_debugging) {
5✔
414
            LogWarn() << "Download continue, got file size: " << item.file_size;
×
415
        }
416

417
    } else if (payload->req_opcode == CMD_READ_FILE) {
259✔
418
        if (_debugging) {
259✔
419
            LogWarn() << "Download continue, write: " << std::to_string(payload->size);
×
420
        }
421

422
        if (item.bytes_transferred < item.file_size) {
259✔
423
            item.ofstream.write(reinterpret_cast<const char*>(payload->data), payload->size);
259✔
424
            if (!item.ofstream) {
259✔
425
                item.callback(ClientResult::FileIoError, {});
×
426
                return false;
×
427
            }
428
            item.bytes_transferred += payload->size;
259✔
429

430
            if (_debugging) {
259✔
431
                LogDebug() << "Written " << item.bytes_transferred << " of " << item.file_size
×
432
                           << " bytes";
×
433
            }
434
        }
435
        item.callback(
259✔
436
            ClientResult::Next,
437
            ProgressData{
438
                static_cast<uint32_t>(item.bytes_transferred),
259✔
439
                static_cast<uint32_t>(item.file_size)});
259✔
440
    }
441

442
    if (item.bytes_transferred < item.file_size) {
264✔
443
        work.last_opcode = CMD_READ_FILE;
260✔
444
        work.payload = {};
260✔
445
        work.payload.seq_number = work.last_sent_seq_number++;
260✔
446
        work.payload.session = _session;
260✔
447
        work.payload.opcode = work.last_opcode;
260✔
448
        work.payload.offset = item.bytes_transferred;
260✔
449

450
        work.payload.size =
260✔
451
            std::min(static_cast<size_t>(max_data_length), item.file_size - item.bytes_transferred);
260✔
452

453
        if (_debugging) {
260✔
454
            LogWarn() << "Request size: " << std::to_string(work.payload.size) << " of left "
×
455
                      << int(item.file_size - item.bytes_transferred);
×
456
        }
457

458
        start_timer();
260✔
459
        send_mavlink_ftp_message(work.payload, work.target_compid);
260✔
460

461
        return true;
260✔
462
    } else {
463
        if (_debugging) {
4✔
464
            LogDebug() << "All bytes written, terminating session";
×
465
        }
466

467
        start_timer();
4✔
468
        terminate_session(work);
4✔
469
        return true;
4✔
470
    }
471

472
    return true;
473
}
474

475
bool MavlinkFtpClient::download_burst_start(Work& work, DownloadBurstItem& item)
10✔
476
{
477
    fs::path local_path = fs::path(item.local_folder) / fs::path(item.remote_path).filename();
50✔
478

479
    if (_debugging) {
10✔
480
        LogDebug() << "Trying to open write to local path: " << local_path.string();
×
481
    }
482

483
    item.ofstream.open(local_path, std::fstream::trunc | std::fstream::binary);
10✔
484
    if (!item.ofstream) {
10✔
485
        LogErr() << "Could not open it!";
×
486
        item.callback(ClientResult::FileIoError, {});
×
487
        return false;
×
488
    }
489

490
    work.last_opcode = CMD_OPEN_FILE_RO;
10✔
491
    work.payload = {};
10✔
492
    work.payload.seq_number = work.last_sent_seq_number++;
10✔
493
    work.payload.session = _session;
10✔
494
    work.payload.opcode = work.last_opcode;
10✔
495
    work.payload.offset = 0;
10✔
496
    strncpy(
10✔
497
        reinterpret_cast<char*>(work.payload.data), item.remote_path.c_str(), max_data_length - 1);
10✔
498
    work.payload.size = item.remote_path.length() + 1;
10✔
499

500
    start_timer();
10✔
501
    send_mavlink_ftp_message(work.payload, work.target_compid);
10✔
502

503
    return true;
10✔
504
}
505

506
bool MavlinkFtpClient::download_burst_continue(
287✔
507
    Work& work, DownloadBurstItem& item, PayloadHeader* payload)
508
{
509
    if (payload->req_opcode == CMD_OPEN_FILE_RO) {
287✔
510
        std::memcpy(&(item.file_size), payload->data, sizeof(uint32_t));
8✔
511

512
        if (_debugging) {
8✔
513
            LogDebug() << "Burst Download continue, got file size: " << item.file_size;
×
514
        }
515

516
        request_burst(work, item);
8✔
517

518
    } else if (payload->req_opcode == CMD_BURST_READ_FILE) {
279✔
519
        if (_debugging) {
270✔
520
            LogDebug() << "Burst download continue, at: " << std::to_string(payload->offset)
×
521
                       << " write: " << std::to_string(payload->size);
×
522
        }
523

524
        if (payload->offset != item.current_offset) {
270✔
525
            if (payload->offset < item.current_offset) {
9✔
526
                // Not sure why this would happen but we don't know how to deal with it and ignore
527
                // it.
528
                LogWarn() << "Got payload offset: " << payload->offset
×
529
                          << ", next offset: " << item.current_offset;
×
530
                return false;
×
531
            }
532

533
            // we missed a part
534
            item.missing_data.emplace_back(DownloadBurstItem::MissingData{
9✔
535
                item.current_offset, payload->offset - item.current_offset});
9✔
536
            // write some 0 instead
537
            std::vector<char> empty(payload->offset - item.current_offset);
18✔
538
            item.ofstream.write(empty.data(), empty.size());
9✔
539
            if (!item.ofstream) {
9✔
540
                LogWarn() << "Write failed";
×
541
                item.callback(ClientResult::FileIoError, {});
×
542
                download_burst_end(work);
×
543
                return false;
×
544
            }
545
        }
546

547
        // Write actual data to file.
548
        item.ofstream.write(reinterpret_cast<const char*>(payload->data), payload->size);
270✔
549
        if (!item.ofstream) {
270✔
550
            LogWarn() << "Write failed";
×
551
            item.callback(ClientResult::FileIoError, {});
×
552
            download_burst_end(work);
×
553
            return false;
×
554
        }
555

556
        // Keep track of what was written.
557
        item.current_offset = payload->offset + payload->size;
270✔
558

559
        if (_debugging) {
270✔
560
            LogDebug() << "Received " << payload->offset << " to "
×
561
                       << payload->size + payload->offset;
×
562
        }
563

564
        if (payload->size + payload->offset >= item.file_size) {
270✔
565
            if (_debugging) {
7✔
566
                LogDebug() << "Burst complete";
×
567
            }
568

569
            if (item.missing_data.empty()) {
7✔
570
                // No missing data, we're done.
571

572
                // Final step
573
                download_burst_end(work);
6✔
574
            } else {
575
                // The burst is supposedly complete but we still need data because
576
                // we missed some, so request next without burst.
577
                request_next_rest(work, item);
1✔
578
            }
579
        } else {
580
            item.callback(
526✔
581
                ClientResult::Next,
582
                ProgressData{
583
                    static_cast<uint32_t>(burst_bytes_transferred(item)),
263✔
584
                    static_cast<uint32_t>(item.file_size)});
263✔
585

586
            if (payload->burst_complete) {
263✔
587
                // This burst is complete but the file isn't. we need to start a
588
                // new one
589
                request_burst(work, item);
×
590
            } else {
591
                // There might be more coming, just wait for now.
592
                start_timer();
263✔
593
            }
594
        }
595
    } else if (payload->req_opcode == CMD_READ_FILE) {
9✔
596
        if (_debugging) {
9✔
597
            LogWarn() << "Burst download continue missing pieces, write at " << payload->offset
×
598
                      << " for " << std::to_string(payload->size);
×
599
        }
600

601
        item.ofstream.seekp(payload->offset);
9✔
602
        if (item.ofstream.fail()) {
9✔
603
            LogWarn() << "Seek failed";
×
604
            item.callback(ClientResult::FileIoError, {});
×
605
            download_burst_end(work);
×
606
            return false;
×
607
        }
608

609
        item.ofstream.write(reinterpret_cast<const char*>(payload->data), payload->size);
9✔
610
        if (!item.ofstream) {
9✔
611
            item.callback(ClientResult::FileIoError, {});
×
612
            download_burst_end(work);
×
613
            return false;
×
614
        }
615

616
        auto& missing = item.missing_data.front();
9✔
617
        if (missing.offset != payload->offset) {
9✔
618
            LogErr() << "Offset mismatch";
×
619
            item.callback(ClientResult::ProtocolError, {});
×
620
            download_burst_end(work);
×
621
            return false;
×
622
        }
623

624
        if (missing.size <= payload->size) {
9✔
625
            // we got all needed data for this chunk
626
            item.missing_data.pop_front();
9✔
627
        } else {
628
            missing.offset += payload->size;
×
629
            missing.size -= payload->size;
×
630
        }
631

632
        // Check if this was the last one
633
        if (item.file_size == payload->offset + payload->size) {
9✔
634
            item.current_offset = item.file_size;
×
635
        }
636

637
        const size_t bytes_transferred = burst_bytes_transferred(item);
9✔
638

639
        if (_debugging) {
9✔
640
            LogDebug() << "Written " << bytes_transferred << " of " << item.file_size << " bytes";
×
641
        }
642

643
        if (item.missing_data.empty() && bytes_transferred == item.file_size) {
9✔
644
            // Final step
645
            download_burst_end(work);
1✔
646
        } else {
647
            item.callback(
8✔
648
                ClientResult::Next,
649
                ProgressData{
650
                    static_cast<uint32_t>(bytes_transferred),
651
                    static_cast<uint32_t>(item.file_size)});
8✔
652

653
            request_next_rest(work, item);
8✔
654
        }
655

656
    } else {
657
        LogErr() << "Unexpected req_opcode";
×
658
        download_burst_end(work);
×
659
        return false;
×
660
    }
661

662
    return true;
287✔
663
}
664

665
void MavlinkFtpClient::download_burst_end(Work& work)
8✔
666
{
667
    work.last_opcode = CMD_TERMINATE_SESSION;
8✔
668

669
    work.payload = {};
8✔
670
    work.payload.seq_number = work.last_sent_seq_number++;
8✔
671
    work.payload.session = _session;
8✔
672

673
    work.payload.opcode = work.last_opcode;
8✔
674
    work.payload.offset = 0;
8✔
675
    work.payload.size = 0;
8✔
676

677
    start_timer();
8✔
678
    send_mavlink_ftp_message(work.payload, work.target_compid);
8✔
679
}
8✔
680

681
void MavlinkFtpClient::request_burst(Work& work, DownloadBurstItem& item)
8✔
682
{
683
    UNUSED(item);
8✔
684

685
    work.last_opcode = CMD_BURST_READ_FILE;
8✔
686
    work.payload = {};
8✔
687
    work.payload.seq_number = work.last_sent_seq_number++;
8✔
688
    work.payload.session = _session;
8✔
689
    work.payload.opcode = work.last_opcode;
8✔
690
    work.payload.offset = item.current_offset;
8✔
691

692
    // Fill up the whole packet.
693
    work.payload.size = max_data_length;
8✔
694

695
    start_timer();
8✔
696
    send_mavlink_ftp_message(work.payload, work.target_compid);
8✔
697
}
8✔
698

699
void MavlinkFtpClient::request_next_rest(Work& work, DownloadBurstItem& item)
22✔
700
{
701
    const auto& missing = item.missing_data.front();
22✔
702
    size_t size = std::min(missing.size, size_t(max_data_length));
22✔
703

704
    if (_debugging) {
22✔
705
        LogDebug() << "Re-requesting from " << missing.offset << " with size " << size;
×
706
    }
707

708
    work.last_opcode = CMD_READ_FILE;
22✔
709
    work.payload = {};
22✔
710
    work.payload.seq_number = work.last_sent_seq_number++;
22✔
711
    work.payload.session = _session;
22✔
712
    work.payload.opcode = work.last_opcode;
22✔
713
    work.payload.offset = missing.offset;
22✔
714

715
    work.payload.size = size;
22✔
716

717
    start_timer();
22✔
718
    send_mavlink_ftp_message(work.payload, work.target_compid);
22✔
719
}
22✔
720

721
size_t MavlinkFtpClient::burst_bytes_transferred(DownloadBurstItem& item)
272✔
722
{
723
    return item.current_offset - std::accumulate(
272✔
724
                                     item.missing_data.begin(),
272✔
725
                                     item.missing_data.end(),
544✔
726
                                     size_t(0),
727
                                     [](size_t acc, const DownloadBurstItem::MissingData& missing) {
180✔
728
                                         return acc + missing.size;
180✔
729
                                     });
816✔
730
}
731

732
bool MavlinkFtpClient::upload_start(Work& work, UploadItem& item)
7✔
733
{
734
    std::error_code ec;
7✔
735
    if (!fs::exists(item.local_file_path, ec)) {
7✔
736
        item.callback(ClientResult::FileDoesNotExist, {});
×
737
        return false;
×
738
    }
739

740
    item.ifstream.open(item.local_file_path, std::fstream::binary);
7✔
741
    if (!item.ifstream) {
7✔
742
        item.callback(ClientResult::FileIoError, {});
×
743
        return false;
×
744
    }
745

746
    item.file_size = fs::file_size(item.local_file_path, ec);
7✔
747
    if (ec) {
7✔
748
        LogWarn() << "Could not get file size of '" << item.local_file_path
×
749
                  << "': " << ec.message();
×
750
        return false;
×
751
    }
752

753
    fs::path remote_file_path =
7✔
754
        fs::path(item.remote_folder) / fs::path(item.local_file_path).filename();
35✔
755

756
    if (remote_file_path.string().size() >= max_data_length) {
7✔
757
        item.callback(ClientResult::InvalidParameter, {});
×
758
        return false;
×
759
    }
760

761
    work.last_opcode = CMD_CREATE_FILE;
7✔
762
    work.payload = {};
7✔
763
    work.payload.seq_number = work.last_sent_seq_number++;
7✔
764
    work.payload.session = _session;
7✔
765
    work.payload.opcode = work.last_opcode;
7✔
766
    work.payload.offset = 0;
7✔
767
    strncpy(
14✔
768
        reinterpret_cast<char*>(work.payload.data),
7✔
769
        remote_file_path.string().c_str(),
14✔
770
        max_data_length - 1);
771
    work.payload.size = remote_file_path.string().size() + 1;
7✔
772

773
    start_timer();
7✔
774
    send_mavlink_ftp_message(work.payload, work.target_compid);
7✔
775

776
    return true;
7✔
777
}
778

779
bool MavlinkFtpClient::upload_continue(Work& work, UploadItem& item)
97✔
780
{
781
    if (item.bytes_transferred < item.file_size) {
97✔
782
        work.last_opcode = CMD_WRITE_FILE;
93✔
783

784
        work.payload = {};
93✔
785
        work.payload.seq_number = work.last_sent_seq_number++;
93✔
786
        work.payload.session = _session;
93✔
787

788
        work.payload.opcode = work.last_opcode;
93✔
789
        work.payload.offset = item.bytes_transferred;
93✔
790

791
        std::size_t bytes_to_read =
792
            std::min(item.file_size - item.bytes_transferred, std::size_t(max_data_length));
93✔
793

794
        item.ifstream.read(reinterpret_cast<char*>(work.payload.data), bytes_to_read);
93✔
795

796
        // Get the number of bytes actually read.
797
        int bytes_read = item.ifstream.gcount();
93✔
798

799
        if (!item.ifstream) {
93✔
800
            item.callback(ClientResult::FileIoError, {});
×
801
            return false;
×
802
        }
803

804
        work.payload.size = bytes_read;
93✔
805
        item.bytes_transferred += bytes_read;
93✔
806

807
        start_timer();
93✔
808
        send_mavlink_ftp_message(work.payload, work.target_compid);
93✔
809

810
    } else {
811
        // Final step
812
        work.last_opcode = CMD_TERMINATE_SESSION;
4✔
813

814
        work.payload = {};
4✔
815
        work.payload.seq_number = work.last_sent_seq_number++;
4✔
816
        work.payload.session = _session;
4✔
817

818
        work.payload.opcode = work.last_opcode;
4✔
819
        work.payload.offset = 0;
4✔
820
        work.payload.size = 0;
4✔
821

822
        start_timer();
4✔
823
        send_mavlink_ftp_message(work.payload, work.target_compid);
4✔
824
    }
825

826
    item.callback(
97✔
827
        ClientResult::Next,
828
        ProgressData{
829
            static_cast<uint32_t>(item.bytes_transferred), static_cast<uint32_t>(item.file_size)});
97✔
830

831
    return true;
97✔
832
}
833

834
bool MavlinkFtpClient::remove_start(Work& work, RemoveItem& item)
4✔
835
{
836
    if (item.path.length() >= max_data_length) {
4✔
837
        item.callback(ClientResult::InvalidParameter);
×
838
        return false;
×
839
    }
840

841
    work.last_opcode = CMD_REMOVE_FILE;
4✔
842
    work.payload = {};
4✔
843
    work.payload.seq_number = work.last_sent_seq_number++;
4✔
844
    work.payload.session = _session;
4✔
845
    work.payload.opcode = work.last_opcode;
4✔
846
    work.payload.offset = 0;
4✔
847
    strncpy(reinterpret_cast<char*>(work.payload.data), item.path.c_str(), max_data_length - 1);
4✔
848
    work.payload.size = item.path.length() + 1;
4✔
849

850
    start_timer();
4✔
851
    send_mavlink_ftp_message(work.payload, work.target_compid);
4✔
852

853
    return true;
4✔
854
}
855

856
bool MavlinkFtpClient::rename_start(Work& work, RenameItem& item)
2✔
857
{
858
    if (item.from_path.length() + item.to_path.length() + 1 >= max_data_length) {
2✔
859
        item.callback(ClientResult::InvalidParameter);
×
860
        return false;
×
861
    }
862

863
    work.last_opcode = CMD_RENAME;
2✔
864
    work.payload = {};
2✔
865
    work.payload.seq_number = work.last_sent_seq_number++;
2✔
866
    work.payload.session = _session;
2✔
867
    work.payload.opcode = work.last_opcode;
2✔
868
    work.payload.offset = 0;
2✔
869
    strncpy(
2✔
870
        reinterpret_cast<char*>(work.payload.data), item.from_path.c_str(), max_data_length - 1);
2✔
871
    work.payload.size = item.from_path.length() + 1;
2✔
872
    strncpy(
4✔
873
        reinterpret_cast<char*>(&work.payload.data[work.payload.size]),
2✔
874
        item.to_path.c_str(),
875
        max_data_length - work.payload.size);
2✔
876
    work.payload.size += item.to_path.length() + 1;
2✔
877
    start_timer();
2✔
878

879
    send_mavlink_ftp_message(work.payload, work.target_compid);
2✔
880

881
    return true;
2✔
882
}
883

884
bool MavlinkFtpClient::create_dir_start(Work& work, CreateDirItem& item)
3✔
885
{
886
    if (item.path.length() + 1 >= max_data_length) {
3✔
887
        item.callback(ClientResult::InvalidParameter);
×
888
        return false;
×
889
    }
890

891
    work.last_opcode = CMD_CREATE_DIRECTORY;
3✔
892
    work.payload = {};
3✔
893
    work.payload.seq_number = work.last_sent_seq_number++;
3✔
894
    work.payload.session = _session;
3✔
895
    work.payload.opcode = work.last_opcode;
3✔
896
    work.payload.offset = 0;
3✔
897
    strncpy(reinterpret_cast<char*>(work.payload.data), item.path.c_str(), max_data_length - 1);
3✔
898
    work.payload.size = item.path.length() + 1;
3✔
899
    start_timer();
3✔
900

901
    send_mavlink_ftp_message(work.payload, work.target_compid);
3✔
902

903
    return true;
3✔
904
}
905

906
bool MavlinkFtpClient::remove_dir_start(Work& work, RemoveDirItem& item)
2✔
907
{
908
    if (item.path.length() + 1 >= max_data_length) {
2✔
909
        item.callback(ClientResult::InvalidParameter);
×
910
        return false;
×
911
    }
912

913
    work.last_opcode = CMD_REMOVE_DIRECTORY;
2✔
914
    work.payload = {};
2✔
915
    work.payload.seq_number = work.last_sent_seq_number++;
2✔
916
    work.payload.session = _session;
2✔
917
    work.payload.opcode = work.last_opcode;
2✔
918
    work.payload.offset = 0;
2✔
919
    strncpy(reinterpret_cast<char*>(work.payload.data), item.path.c_str(), max_data_length - 1);
2✔
920
    work.payload.size = item.path.length() + 1;
2✔
921
    start_timer();
2✔
922

923
    send_mavlink_ftp_message(work.payload, work.target_compid);
2✔
924

925
    return true;
2✔
926
}
927

928
bool MavlinkFtpClient::compare_files_start(Work& work, CompareFilesItem& item)
3✔
929
{
930
    if (item.remote_path.length() + 1 >= max_data_length) {
3✔
931
        item.callback(ClientResult::InvalidParameter, false);
×
932
        return false;
×
933
    }
934

935
    auto result_local = calc_local_file_crc32(item.local_path, item.local_crc);
3✔
936
    if (result_local != ClientResult::Success) {
3✔
937
        item.callback(result_local, false);
×
938
        return false;
×
939
    }
940

941
    work.last_opcode = CMD_CALC_FILE_CRC32;
3✔
942
    work.payload = {};
3✔
943
    work.payload.seq_number = work.last_sent_seq_number++;
3✔
944
    work.payload.session = _session;
3✔
945
    work.payload.opcode = work.last_opcode;
3✔
946
    work.payload.offset = 0;
3✔
947
    strncpy(
3✔
948
        reinterpret_cast<char*>(work.payload.data), item.remote_path.c_str(), max_data_length - 1);
3✔
949
    work.payload.size = item.remote_path.length() + 1;
3✔
950
    start_timer();
3✔
951

952
    send_mavlink_ftp_message(work.payload, work.target_compid);
3✔
953

954
    return true;
3✔
955
}
956

957
bool MavlinkFtpClient::list_dir_start(Work& work, ListDirItem& item)
2✔
958
{
959
    if (item.path.length() + 1 >= max_data_length) {
2✔
960
        item.callback(ClientResult::InvalidParameter, {}, {});
×
961
        return false;
×
962
    }
963

964
    work.last_opcode = CMD_LIST_DIRECTORY;
2✔
965
    work.payload = {};
2✔
966
    work.payload.seq_number = work.last_sent_seq_number++;
2✔
967
    work.payload.session = _session;
2✔
968
    work.payload.opcode = work.last_opcode;
2✔
969
    work.payload.offset = 0;
2✔
970
    strncpy(reinterpret_cast<char*>(work.payload.data), item.path.c_str(), max_data_length - 1);
2✔
971
    work.payload.size = item.path.length() + 1;
2✔
972
    start_timer();
2✔
973

974
    send_mavlink_ftp_message(work.payload, work.target_compid);
2✔
975

976
    return true;
2✔
977
}
978

979
bool MavlinkFtpClient::list_dir_continue(Work& work, ListDirItem& item, PayloadHeader* payload)
9✔
980
{
981
    if (_debugging) {
9✔
982
        LogDebug() << "List dir response received, got " << (int)payload->size << " chars";
×
983
    }
984

985
    if (payload->size > max_data_length) {
9✔
986
        LogWarn() << "Received FTP payload with invalid size";
×
987
        return false;
×
988
    }
989

990
    if (payload->size == 0) {
9✔
991
        std::sort(item.dirs.begin(), item.dirs.end());
×
992
        std::sort(item.files.begin(), item.files.end());
×
993
        item.callback(ClientResult::Success, item.dirs, item.files);
×
994
        return false;
×
995
    }
996

997
    // Make sure there is a zero termination.
998
    payload->data[payload->size - 1] = '\0';
9✔
999

1000
    size_t i = 0;
9✔
1001
    while (i + 1 < payload->size) {
209✔
1002
        const int entry_len = std::strlen(reinterpret_cast<char*>(&payload->data[i]));
200✔
1003

1004
        std::string entry;
200✔
1005
        entry.resize(entry_len);
200✔
1006
        std::memcpy(entry.data(), &payload->data[i], entry_len);
200✔
1007

1008
        i += entry_len + 1;
200✔
1009

1010
        ++item.offset;
200✔
1011

1012
        if (entry[0] == 'S') {
200✔
1013
            // Skip skip for now
1014
            continue;
×
1015
        }
1016

1017
        auto tab = entry.find('\t');
200✔
1018
        if (tab != std::string::npos) {
200✔
1019
            entry = entry.substr(0, tab);
100✔
1020
        }
1021

1022
        if (entry[0] == 'D') {
200✔
1023
            item.dirs.push_back(entry.substr(1, entry.size() - 1));
100✔
1024
        } else if (entry[0] == 'F') {
100✔
1025
            item.files.push_back(entry.substr(1, entry.size() - 1));
100✔
1026
        } else {
1027
            LogErr() << "Unknown list_dir entry: " << entry;
×
1028
        }
1029
    }
1030

1031
    work.last_opcode = CMD_LIST_DIRECTORY;
9✔
1032
    work.payload = {};
9✔
1033
    work.payload.seq_number = work.last_sent_seq_number++;
9✔
1034
    work.payload.session = _session;
9✔
1035
    work.payload.opcode = work.last_opcode;
9✔
1036
    work.payload.offset = item.offset;
9✔
1037
    strncpy(reinterpret_cast<char*>(work.payload.data), item.path.c_str(), max_data_length - 1);
9✔
1038
    work.payload.size = item.path.length() + 1;
9✔
1039
    start_timer();
9✔
1040

1041
    send_mavlink_ftp_message(work.payload, work.target_compid);
9✔
1042

1043
    return true;
9✔
1044
}
1045

1046
MavlinkFtpClient::ClientResult MavlinkFtpClient::result_from_nak(PayloadHeader* payload)
15✔
1047
{
1048
    ServerResult sr = static_cast<ServerResult>(payload->data[0]);
15✔
1049

1050
    // PX4 Mavlink FTP returns "File doesn't exist" this way
1051
    if (sr == ServerResult::ERR_FAIL_ERRNO && payload->data[1] == ENOENT) {
15✔
1052
        sr = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
1053
    }
1054

1055
    return translate(sr);
15✔
1056
}
1057

1058
MavlinkFtpClient::ClientResult MavlinkFtpClient::translate(ServerResult result)
15✔
1059
{
1060
    switch (result) {
15✔
1061
        case ServerResult::SUCCESS:
×
1062
            return ClientResult::Success;
×
1063
        case ServerResult::ERR_TIMEOUT:
×
1064
            return ClientResult::Timeout;
×
1065
        case ServerResult::ERR_FILE_IO_ERROR:
×
1066
            return ClientResult::FileIoError;
×
1067
        case ServerResult::ERR_FAIL_FILE_EXISTS:
×
1068
            return ClientResult::FileExists;
×
1069
        case ServerResult::ERR_FAIL_FILE_PROTECTED:
×
1070
            return ClientResult::FileProtected;
×
1071
        case ServerResult::ERR_UNKOWN_COMMAND:
×
1072
            return ClientResult::Unsupported;
×
1073
        case ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST:
1✔
1074
            return ClientResult::FileDoesNotExist;
1✔
1075
        default:
14✔
1076
            LogInfo() << "Unknown error code: " << (int)result;
14✔
1077
            return ClientResult::ProtocolError;
14✔
1078
    }
1079
}
1080

1081
void MavlinkFtpClient::download_async(
17✔
1082
    const std::string& remote_path,
1083
    const std::string& local_folder,
1084
    bool use_burst,
1085
    DownloadCallback callback,
1086
    std::optional<uint8_t> maybe_target_compid)
1087
{
1088
    if (use_burst) {
17✔
1089
        auto item = DownloadBurstItem{};
20✔
1090
        item.remote_path = remote_path;
10✔
1091
        item.local_folder = local_folder;
10✔
1092
        item.callback = callback;
10✔
1093
        auto new_work =
10✔
1094
            Work{std::move(item), maybe_target_compid.value_or(get_target_component_id())};
30✔
1095
        _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
10✔
1096

1097
    } else {
1098
        auto item = DownloadItem{};
14✔
1099
        item.remote_path = remote_path;
7✔
1100
        item.local_folder = local_folder;
7✔
1101
        item.callback = callback;
7✔
1102
        auto new_work =
7✔
1103
            Work{std::move(item), maybe_target_compid.value_or(get_target_component_id())};
21✔
1104
        _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
7✔
1105
    }
1106
}
17✔
1107

1108
void MavlinkFtpClient::upload_async(
7✔
1109
    const std::string& local_file_path, const std::string& remote_folder, UploadCallback callback)
1110
{
1111
    auto item = UploadItem{};
14✔
1112
    item.local_file_path = local_file_path;
7✔
1113
    item.remote_folder = remote_folder;
7✔
1114
    item.callback = callback;
7✔
1115
    auto new_work = Work{std::move(item), get_target_component_id()};
21✔
1116

1117
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
7✔
1118
}
7✔
1119

1120
void MavlinkFtpClient::list_directory_async(const std::string& path, ListDirectoryCallback callback)
2✔
1121
{
1122
    auto item = ListDirItem{};
4✔
1123
    item.path = path;
2✔
1124
    item.callback = callback;
2✔
1125
    auto new_work = Work{std::move(item), get_target_component_id()};
6✔
1126

1127
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
2✔
1128
}
2✔
1129

1130
void MavlinkFtpClient::create_directory_async(const std::string& path, ResultCallback callback)
3✔
1131
{
1132
    auto item = CreateDirItem{};
6✔
1133
    item.path = path;
3✔
1134
    item.callback = callback;
3✔
1135
    auto new_work = Work{std::move(item), get_target_component_id()};
9✔
1136

1137
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
3✔
1138
}
3✔
1139

1140
void MavlinkFtpClient::remove_directory_async(const std::string& path, ResultCallback callback)
2✔
1141
{
1142
    auto item = RemoveDirItem{};
4✔
1143
    item.path = path;
2✔
1144
    item.callback = callback;
2✔
1145
    auto new_work = Work{std::move(item), get_target_component_id()};
6✔
1146

1147
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
2✔
1148
}
2✔
1149

1150
void MavlinkFtpClient::remove_file_async(const std::string& path, ResultCallback callback)
4✔
1151
{
1152
    auto item = RemoveItem{};
8✔
1153
    item.path = path;
4✔
1154
    item.callback = callback;
4✔
1155
    auto new_work = Work{std::move(item), get_target_component_id()};
12✔
1156

1157
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
4✔
1158
}
4✔
1159

1160
void MavlinkFtpClient::rename_async(
2✔
1161
    const std::string& from_path, const std::string& to_path, ResultCallback callback)
1162
{
1163
    auto item = RenameItem{};
4✔
1164
    item.from_path = from_path;
2✔
1165
    item.to_path = to_path;
2✔
1166
    item.callback = callback;
2✔
1167
    auto new_work = Work{std::move(item), get_target_component_id()};
6✔
1168

1169
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
2✔
1170
}
2✔
1171

1172
void MavlinkFtpClient::are_files_identical_async(
3✔
1173
    const std::string& local_path,
1174
    const std::string& remote_path,
1175
    AreFilesIdenticalCallback callback)
1176
{
1177
    auto item = CompareFilesItem{};
6✔
1178
    item.local_path = local_path;
3✔
1179
    item.remote_path = remote_path;
3✔
1180
    item.callback = callback;
3✔
1181
    auto new_work = Work{std::move(item), get_target_component_id()};
9✔
1182

1183
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
3✔
1184
}
3✔
1185

1186
void MavlinkFtpClient::send_mavlink_ftp_message(const PayloadHeader& payload, uint8_t target_compid)
530✔
1187
{
1188
    _system_impl.queue_message([&](MavlinkAddress mavlink_address, uint8_t channel) {
530✔
1189
        mavlink_message_t message;
1190
        mavlink_msg_file_transfer_protocol_pack_chan(
1,060✔
1191
            mavlink_address.system_id,
530✔
1192
            mavlink_address.component_id,
530✔
1193
            channel,
1194
            &message,
1195
            _network_id,
530✔
1196
            _system_impl.get_system_id(),
530✔
1197
            target_compid,
530✔
1198
            reinterpret_cast<const uint8_t*>(&payload));
530✔
1199
        return message;
530✔
1200
    });
1201
}
530✔
1202

1203
void MavlinkFtpClient::start_timer(std::optional<double> duration_s)
777✔
1204
{
1205
    _system_impl.unregister_timeout_handler(_timeout_cookie);
777✔
1206
    _timeout_cookie = _system_impl.register_timeout_handler(
1,554✔
1207
        [this]() { timeout(); }, duration_s.value_or(_system_impl.timeout_s()));
1,638✔
1208
}
777✔
1209

1210
void MavlinkFtpClient::stop_timer()
36✔
1211
{
1212
    _system_impl.unregister_timeout_handler(_timeout_cookie);
36✔
1213
}
36✔
1214

1215
void MavlinkFtpClient::timeout()
84✔
1216
{
1217
    if (_debugging) {
84✔
1218
        LogDebug() << "Timeout!";
×
1219
    }
1220

1221
    LockedQueue<Work>::Guard work_queue_guard(_work_queue);
84✔
1222
    auto work = work_queue_guard.get_front();
84✔
1223
    if (!work) {
84✔
1224
        return;
1✔
1225
    }
1226

1227
    std::visit(
166✔
1228
        overloaded{
83✔
1229
            [&](DownloadItem& item) {
34✔
1230
                if (--work->retries == 0) {
34✔
1231
                    item.callback(ClientResult::Timeout, {});
1✔
1232
                    work_queue_guard.pop_front();
1✔
1233
                    return;
1✔
1234
                }
1235
                if (_debugging) {
33✔
1236
                    LogDebug() << "Retries left: " << work->retries;
×
1237
                }
1238

1239
                start_timer();
33✔
1240
                send_mavlink_ftp_message(work->payload, work->target_compid);
33✔
1241
            },
1242
            [&](DownloadBurstItem& item) {
15✔
1243
                if (--work->retries == 0) {
15✔
1244
                    item.callback(ClientResult::Timeout, {});
1✔
1245
                    work_queue_guard.pop_front();
1✔
1246
                    return;
1✔
1247
                }
1248
                if (_debugging) {
14✔
1249
                    LogDebug() << "Retries left: " << work->retries;
×
1250
                }
1251

1252
                {
1253
                    // This happens when we missed the last ack containing burst complete.
1254
                    // We have already a file size, so we don't need to start at the
1255
                    // beginning any more.
1256
                    if (item.file_size != 0 && item.current_offset != 0) {
14✔
1257
                        // In that case start requesting what we missed.
1258
                        if (item.current_offset == item.file_size && item.missing_data.empty()) {
14✔
1259
                            // We are done anyway.
1260
                            item.ofstream.close();
1✔
1261
                            item.callback(ClientResult::Success, {});
1✔
1262
                            download_burst_end(*work);
1✔
1263
                            work_queue_guard.pop_front();
1✔
1264
                        } else {
1265
                            // The burst is supposedly complete but we still need data because
1266
                            // we missed some, so request next without burst.
1267
                            // We presumably missed the very last chunk.
1268
                            if (item.current_offset < item.file_size) {
13✔
1269
                                item.missing_data.emplace_back(DownloadBurstItem::MissingData{
1✔
1270
                                    item.current_offset, item.file_size - item.current_offset});
1✔
1271
                                item.current_offset = item.file_size;
1✔
1272
                                if (_debugging) {
1✔
1273
                                    LogDebug() << "Adding " << item.current_offset << " with size "
×
1274
                                               << item.file_size - item.current_offset;
×
1275
                                }
1276
                            }
1277
                            request_next_rest(*work, item);
13✔
1278
                        }
1279
                    } else {
1280
                        // Otherwise, start burst again.
1281
                        start_timer();
×
1282
                        send_mavlink_ftp_message(work->payload, work->target_compid);
×
1283
                    }
1284
                }
1285
            },
1286
            [&](UploadItem& item) {
34✔
1287
                if (--work->retries == 0) {
34✔
1288
                    item.callback(ClientResult::Timeout, {});
1✔
1289
                    work_queue_guard.pop_front();
1✔
1290
                    return;
1✔
1291
                }
1292
                if (_debugging) {
33✔
1293
                    LogDebug() << "Retries left: " << work->retries;
×
1294
                }
1295

1296
                start_timer();
33✔
1297
                send_mavlink_ftp_message(work->payload, work->target_compid);
33✔
1298
            },
1299
            [&](RemoveItem& item) {
×
1300
                if (--work->retries == 0) {
×
1301
                    item.callback(ClientResult::Timeout);
×
1302
                    work_queue_guard.pop_front();
×
1303
                    return;
×
1304
                }
1305
                if (_debugging) {
×
1306
                    LogDebug() << "Retries left: " << work->retries;
×
1307
                }
1308

1309
                start_timer();
×
1310
                send_mavlink_ftp_message(work->payload, work->target_compid);
×
1311
            },
1312
            [&](RenameItem& item) {
×
1313
                if (--work->retries == 0) {
×
1314
                    item.callback(ClientResult::Timeout);
×
1315
                    work_queue_guard.pop_front();
×
1316
                    return;
×
1317
                }
1318
                if (_debugging) {
×
1319
                    LogDebug() << "Retries left: " << work->retries;
×
1320
                }
1321

1322
                start_timer();
×
1323
                send_mavlink_ftp_message(work->payload, work->target_compid);
×
1324
            },
1325
            [&](CreateDirItem& item) {
×
1326
                if (--work->retries == 0) {
×
1327
                    item.callback(ClientResult::Timeout);
×
1328
                    work_queue_guard.pop_front();
×
1329
                    return;
×
1330
                }
1331
                if (_debugging) {
×
1332
                    LogDebug() << "Retries left: " << work->retries;
×
1333
                }
1334

1335
                start_timer();
×
1336
                send_mavlink_ftp_message(work->payload, work->target_compid);
×
1337
            },
1338
            [&](RemoveDirItem& item) {
×
1339
                if (--work->retries == 0) {
×
1340
                    item.callback(ClientResult::Timeout);
×
1341
                    work_queue_guard.pop_front();
×
1342
                    return;
×
1343
                }
1344
                if (_debugging) {
×
1345
                    LogDebug() << "Retries left: " << work->retries;
×
1346
                }
1347

1348
                start_timer();
×
1349
                send_mavlink_ftp_message(work->payload, work->target_compid);
×
1350
            },
1351
            [&](CompareFilesItem& item) {
×
1352
                if (--work->retries == 0) {
×
1353
                    item.callback(ClientResult::Timeout, false);
×
1354
                    work_queue_guard.pop_front();
×
1355
                    return;
×
1356
                }
1357
                if (_debugging) {
×
1358
                    LogDebug() << "Retries left: " << work->retries;
×
1359
                }
1360

1361
                start_timer();
×
1362
                send_mavlink_ftp_message(work->payload, work->target_compid);
×
1363
            },
1364
            [&](ListDirItem& item) {
×
1365
                if (--work->retries == 0) {
×
1366
                    item.callback(ClientResult::Timeout, {}, {});
×
1367
                    work_queue_guard.pop_front();
×
1368
                    return;
×
1369
                }
1370
                if (_debugging) {
×
1371
                    LogDebug() << "Retries left: " << work->retries;
×
1372
                }
1373

1374
                start_timer();
×
1375
                send_mavlink_ftp_message(work->payload, work->target_compid);
×
1376
            }},
1377
        work->item);
83✔
1378
}
1379

1380
MavlinkFtpClient::ClientResult
1381
MavlinkFtpClient::calc_local_file_crc32(const std::string& path, uint32_t& csum)
3✔
1382
{
1383
    std::error_code ec;
3✔
1384
    if (!fs::exists(path, ec)) {
3✔
1385
        return ClientResult::FileDoesNotExist;
×
1386
    }
1387

1388
    std::ifstream stream(path, std::fstream::binary);
6✔
1389
    if (!stream) {
3✔
1390
        return ClientResult::FileIoError;
×
1391
    }
1392

1393
    // Read whole file in buffer size chunks
1394
    Crc32 checksum;
3✔
1395
    uint8_t buffer[4096];
3✔
1396
    std::streamsize bytes_read;
1397

1398
    do {
3✔
1399
        stream.read(reinterpret_cast<char*>(buffer), sizeof(buffer));
6✔
1400
        bytes_read = stream.gcount(); // Get the number of bytes actually read
6✔
1401
        checksum.add(reinterpret_cast<const uint8_t*>(buffer), bytes_read);
6✔
1402
    } while (bytes_read > 0);
6✔
1403

1404
    csum = checksum.get();
3✔
1405

1406
    return ClientResult::Success;
3✔
1407
}
1408

1409
void MavlinkFtpClient::terminate_session(Work& work)
20✔
1410
{
1411
    work.last_opcode = CMD_TERMINATE_SESSION;
20✔
1412

1413
    work.payload = {};
20✔
1414
    work.payload.seq_number = work.last_sent_seq_number++;
20✔
1415
    work.payload.session = _session;
20✔
1416

1417
    work.payload.opcode = work.last_opcode;
20✔
1418
    work.payload.offset = 0;
20✔
1419
    work.payload.size = 0;
20✔
1420

1421
    send_mavlink_ftp_message(work.payload, work.target_compid);
20✔
1422
}
20✔
1423

1424
uint8_t MavlinkFtpClient::get_our_compid()
×
1425
{
1426
    return _system_impl.get_own_component_id();
×
1427
}
1428

1429
uint8_t MavlinkFtpClient::get_target_component_id()
40✔
1430
{
1431
    return _target_component_id_set ? _target_component_id : _system_impl.get_autopilot_id();
40✔
1432
}
1433

1434
MavlinkFtpClient::ClientResult MavlinkFtpClient::set_target_compid(uint8_t component_id)
×
1435
{
1436
    _target_component_id = component_id;
×
1437
    _target_component_id_set = true;
×
1438
    return ClientResult::Success;
×
1439
}
1440

1441
std::ostream& operator<<(std::ostream& str, MavlinkFtpClient::ClientResult const& result)
3✔
1442
{
1443
    switch (result) {
3✔
1444
        default:
×
1445
            // Fallthrough
1446
        case MavlinkFtpClient::ClientResult::Unknown:
1447
            return str << "Unknown";
×
1448
        case MavlinkFtpClient::ClientResult::Success:
3✔
1449
            return str << "Success";
3✔
1450
        case MavlinkFtpClient::ClientResult::Next:
×
1451
            return str << "Next";
×
1452
        case MavlinkFtpClient::ClientResult::Timeout:
×
1453
            return str << "Timeout";
×
1454
        case MavlinkFtpClient::ClientResult::Busy:
×
1455
            return str << "Busy";
×
1456
        case MavlinkFtpClient::ClientResult::FileIoError:
×
1457
            return str << "FileIoError";
×
1458
        case MavlinkFtpClient::ClientResult::FileExists:
×
1459
            return str << "FileExists";
×
1460
        case MavlinkFtpClient::ClientResult::FileDoesNotExist:
×
1461
            return str << "FileDoesNotExist";
×
1462
        case MavlinkFtpClient::ClientResult::FileProtected:
×
1463
            return str << "FileProtected";
×
1464
        case MavlinkFtpClient::ClientResult::InvalidParameter:
×
1465
            return str << "InvalidParameter";
×
1466
        case MavlinkFtpClient::ClientResult::Unsupported:
×
1467
            return str << "Unsupported";
×
1468
        case MavlinkFtpClient::ClientResult::ProtocolError:
×
1469
            return str << "ProtocolError";
×
1470
        case MavlinkFtpClient::ClientResult::NoSystem:
×
1471
            return str << "NoSystem";
×
1472
    }
1473
}
1474

1475
} // namespace mavsdk
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc