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

mavlink / MAVSDK / 9342048164

02 Jun 2024 11:41PM UTC coverage: 37.336% (-0.01%) from 37.346%
9342048164

push

github

web-flow
Merge pull request #2326 from mavlink/pr-rename-lts

CI: remote -lts from filename

11079 of 29674 relevant lines covered (37.34%)

255.41 hits per line

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

75.0
/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,178✔
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,039✔
38
{
39
    LockedQueue<Work>::Guard work_queue_guard(_work_queue);
3,039✔
40

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

46
    if (work->started) {
864✔
47
        return;
824✔
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,178✔
105
{
106
    mavlink_file_transfer_protocol_t ftp_req;
1,178✔
107
    mavlink_msg_file_transfer_protocol_decode(&msg, &ftp_req);
1,178✔
108

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

114
    if (ftp_req.target_component != 0 &&
2,356✔
115
        ftp_req.target_component != _system_impl.get_own_component_id()) {
1,178✔
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,169✔
121

122
    if (payload->size > max_data_length) {
1,169✔
123
        LogWarn() << "Received FTP payload with invalid size";
×
124
        return;
×
125
    } else {
126
        if (_debugging) {
1,169✔
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,169✔
134

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

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

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

163
                        if (!download_continue(*work, item, payload)) {
266✔
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
                        call_callback(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
                    call_callback(item.callback, result_from_nak(payload), {});
2✔
180
                    terminate_session(*work);
2✔
181
                    work_queue_guard.pop_front();
2✔
182
                }
183
            },
272✔
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) {
24✔
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
                        call_callback(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
                        call_callback(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
                        call_callback(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
                    call_callback(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
                        call_callback(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
                    call_callback(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
                        call_callback(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
                    call_callback(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
                        call_callback(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
                    call_callback(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
                        call_callback(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
                    call_callback(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
                        call_callback(
2✔
332
                            item.callback, ClientResult::Success, remote_crc == item.local_crc);
2✔
333
                        work_queue_guard.pop_front();
2✔
334

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

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

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

360
                } else if (payload->opcode == RSP_NAK) {
2✔
361
                    stop_timer();
2✔
362
                    if (payload->data[0] == ERR_EOF) {
2✔
363
                        std::sort(item.dirs.begin(), item.dirs.end());
1✔
364
                        call_callback(item.callback, ClientResult::Success, item.dirs);
1✔
365
                    } else {
366
                        call_callback(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);
695✔
373

374
    work->last_received_seq_number = payload->seq_number;
695✔
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
        call_callback(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)
266✔
409
{
410
    if (payload->req_opcode == CMD_OPEN_FILE_RO) {
266✔
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) {
261✔
418
        if (_debugging) {
261✔
419
            LogWarn() << "Download continue, write: " << std::to_string(payload->size);
×
420
        }
421

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

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

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

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

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

459
        start_timer();
262✔
460
        send_mavlink_ftp_message(work.payload, work.target_compid);
262✔
461

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

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

473
    return true;
474
}
475

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

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

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

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

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

504
    return true;
10✔
505
}
506

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

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

517
        request_burst(work, item);
8✔
518

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

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

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

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

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

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

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

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

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

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

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

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

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

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

634
        // Check if this was the last one
635
        if (item.file_size == payload->offset + payload->size) {
18✔
636
            item.current_offset = item.file_size;
1✔
637
        }
638

639
        const size_t bytes_transferred = burst_bytes_transferred(item);
18✔
640

641
        if (_debugging) {
18✔
642
            LogDebug() << "Written " << bytes_transferred << " of " << item.file_size << " bytes";
×
643
        }
644

645
        if (item.missing_data.empty() && bytes_transferred == item.file_size) {
18✔
646
            // Final step
647
            download_burst_end(work);
2✔
648
        } else {
649
            call_callback(
16✔
650
                item.callback,
16✔
651
                ClientResult::Next,
652
                ProgressData{
16✔
653
                    static_cast<uint32_t>(bytes_transferred),
654
                    static_cast<uint32_t>(item.file_size)});
16✔
655

656
            request_next_rest(work, item);
16✔
657
        }
658

659
    } else {
660
        LogErr() << "Unexpected req_opcode";
×
661
        download_burst_end(work);
×
662
        return false;
×
663
    }
664

665
    return true;
287✔
666
}
667

668
void MavlinkFtpClient::download_burst_end(Work& work)
8✔
669
{
670
    work.last_opcode = CMD_TERMINATE_SESSION;
8✔
671

672
    work.payload = {};
8✔
673
    work.payload.seq_number = work.last_sent_seq_number++;
8✔
674
    work.payload.session = _session;
8✔
675

676
    work.payload.opcode = work.last_opcode;
8✔
677
    work.payload.offset = 0;
8✔
678
    work.payload.size = 0;
8✔
679

680
    start_timer();
8✔
681
    send_mavlink_ftp_message(work.payload, work.target_compid);
8✔
682
}
8✔
683

684
void MavlinkFtpClient::request_burst(Work& work, DownloadBurstItem& item)
8✔
685
{
686
    UNUSED(item);
8✔
687

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

695
    // Fill up the whole packet.
696
    work.payload.size = max_data_length;
8✔
697

698
    start_timer();
8✔
699
    send_mavlink_ftp_message(work.payload, work.target_compid);
8✔
700
}
8✔
701

702
void MavlinkFtpClient::request_next_rest(Work& work, DownloadBurstItem& item)
31✔
703
{
704
    const auto& missing = item.missing_data.front();
31✔
705
    size_t size = std::min(missing.size, size_t(max_data_length));
31✔
706

707
    if (_debugging) {
31✔
708
        LogDebug() << "Re-requesting from " << missing.offset << " with size " << size;
×
709
    }
710

711
    work.last_opcode = CMD_READ_FILE;
31✔
712
    work.payload = {};
31✔
713
    work.payload.seq_number = work.last_sent_seq_number++;
31✔
714
    work.payload.session = _session;
31✔
715
    work.payload.opcode = work.last_opcode;
31✔
716
    work.payload.offset = missing.offset;
31✔
717

718
    work.payload.size = size;
31✔
719

720
    start_timer();
31✔
721
    send_mavlink_ftp_message(work.payload, work.target_compid);
31✔
722
}
31✔
723

724
size_t MavlinkFtpClient::burst_bytes_transferred(DownloadBurstItem& item)
273✔
725
{
726
    return item.current_offset - std::accumulate(
273✔
727
                                     item.missing_data.begin(),
273✔
728
                                     item.missing_data.end(),
546✔
729
                                     size_t(0),
730
                                     [](size_t acc, const DownloadBurstItem::MissingData& missing) {
188✔
731
                                         return acc + missing.size;
188✔
732
                                     });
819✔
733
}
734

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

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

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

756
    fs::path remote_file_path =
7✔
757
        fs::path(item.remote_folder) / fs::path(item.local_file_path).filename();
35✔
758

759
    if (remote_file_path.string().size() >= max_data_length) {
7✔
760
        call_callback(item.callback, ClientResult::InvalidParameter, {});
×
761
        return false;
×
762
    }
763

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

776
    start_timer();
7✔
777
    send_mavlink_ftp_message(work.payload, work.target_compid);
7✔
778

779
    return true;
7✔
780
}
781

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

787
        work.payload = {};
93✔
788
        work.payload.seq_number = work.last_sent_seq_number++;
93✔
789
        work.payload.session = _session;
93✔
790

791
        work.payload.opcode = work.last_opcode;
93✔
792
        work.payload.offset = item.bytes_transferred;
93✔
793

794
        std::size_t bytes_to_read =
795
            std::min(item.file_size - item.bytes_transferred, std::size_t(max_data_length));
93✔
796

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

799
        // Get the number of bytes actually read.
800
        int bytes_read = item.ifstream.gcount();
93✔
801

802
        if (!item.ifstream) {
93✔
803
            call_callback(item.callback, ClientResult::FileIoError, {});
×
804
            return false;
×
805
        }
806

807
        work.payload.size = bytes_read;
93✔
808
        item.bytes_transferred += bytes_read;
93✔
809

810
        start_timer();
93✔
811
        send_mavlink_ftp_message(work.payload, work.target_compid);
93✔
812

813
    } else {
814
        // Final step
815
        work.last_opcode = CMD_TERMINATE_SESSION;
4✔
816

817
        work.payload = {};
4✔
818
        work.payload.seq_number = work.last_sent_seq_number++;
4✔
819
        work.payload.session = _session;
4✔
820

821
        work.payload.opcode = work.last_opcode;
4✔
822
        work.payload.offset = 0;
4✔
823
        work.payload.size = 0;
4✔
824

825
        start_timer();
4✔
826
        send_mavlink_ftp_message(work.payload, work.target_compid);
4✔
827
    }
828

829
    call_callback(
97✔
830
        item.callback,
97✔
831
        ClientResult::Next,
832
        ProgressData{
97✔
833
            static_cast<uint32_t>(item.bytes_transferred), static_cast<uint32_t>(item.file_size)});
97✔
834

835
    return true;
97✔
836
}
837

838
bool MavlinkFtpClient::remove_start(Work& work, RemoveItem& item)
4✔
839
{
840
    if (item.path.length() >= max_data_length) {
4✔
841
        call_callback(item.callback, ClientResult::InvalidParameter);
×
842
        return false;
×
843
    }
844

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

854
    start_timer();
4✔
855
    send_mavlink_ftp_message(work.payload, work.target_compid);
4✔
856

857
    return true;
4✔
858
}
859

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

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

883
    send_mavlink_ftp_message(work.payload, work.target_compid);
2✔
884

885
    return true;
2✔
886
}
887

888
bool MavlinkFtpClient::create_dir_start(Work& work, CreateDirItem& item)
3✔
889
{
890
    if (item.path.length() + 1 >= max_data_length) {
3✔
891
        call_callback(item.callback, ClientResult::InvalidParameter);
×
892
        return false;
×
893
    }
894

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

905
    send_mavlink_ftp_message(work.payload, work.target_compid);
3✔
906

907
    return true;
3✔
908
}
909

910
bool MavlinkFtpClient::remove_dir_start(Work& work, RemoveDirItem& item)
2✔
911
{
912
    if (item.path.length() + 1 >= max_data_length) {
2✔
913
        call_callback(item.callback, ClientResult::InvalidParameter);
×
914
        return false;
×
915
    }
916

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

927
    send_mavlink_ftp_message(work.payload, work.target_compid);
2✔
928

929
    return true;
2✔
930
}
931

932
bool MavlinkFtpClient::compare_files_start(Work& work, CompareFilesItem& item)
3✔
933
{
934
    if (item.remote_path.length() + 1 >= max_data_length) {
3✔
935
        call_callback(item.callback, ClientResult::InvalidParameter, false);
×
936
        return false;
×
937
    }
938

939
    auto result_local = calc_local_file_crc32(item.local_path, item.local_crc);
3✔
940
    if (result_local != ClientResult::Success) {
3✔
941
        call_callback(item.callback, result_local, false);
×
942
        return false;
×
943
    }
944

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

956
    send_mavlink_ftp_message(work.payload, work.target_compid);
3✔
957

958
    return true;
3✔
959
}
960

961
bool MavlinkFtpClient::list_dir_start(Work& work, ListDirItem& item)
2✔
962
{
963
    if (item.path.length() + 1 >= max_data_length) {
2✔
964
        call_callback(item.callback, ClientResult::InvalidParameter, {});
×
965
        return false;
×
966
    }
967

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

978
    send_mavlink_ftp_message(work.payload, work.target_compid);
2✔
979

980
    return true;
2✔
981
}
982

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

989
    if (payload->size > max_data_length) {
9✔
990
        LogWarn() << "Received FTP payload with invalid size";
×
991
        return false;
×
992
    }
993

994
    if (payload->size == 0) {
9✔
995
        std::sort(item.dirs.begin(), item.dirs.end());
×
996
        call_callback(item.callback, ClientResult::Success, item.dirs);
×
997
        return false;
×
998
    }
999

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

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

1007
        std::string entry;
200✔
1008
        entry.resize(entry_len);
200✔
1009
        std::memcpy(entry.data(), &payload->data[i], entry_len);
200✔
1010

1011
        i += entry_len + 1;
200✔
1012

1013
        ++item.offset;
200✔
1014

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

1020
        item.dirs.push_back(entry);
200✔
1021
    }
1022

1023
    work.last_opcode = CMD_LIST_DIRECTORY;
9✔
1024
    work.payload = {};
9✔
1025
    work.payload.seq_number = work.last_sent_seq_number++;
9✔
1026
    work.payload.session = _session;
9✔
1027
    work.payload.opcode = work.last_opcode;
9✔
1028
    work.payload.offset = item.offset;
9✔
1029
    strncpy(reinterpret_cast<char*>(work.payload.data), item.path.c_str(), max_data_length - 1);
9✔
1030
    work.payload.size = item.path.length() + 1;
9✔
1031
    start_timer();
9✔
1032

1033
    send_mavlink_ftp_message(work.payload, work.target_compid);
9✔
1034

1035
    return true;
9✔
1036
}
1037

1038
MavlinkFtpClient::ClientResult MavlinkFtpClient::result_from_nak(PayloadHeader* payload)
15✔
1039
{
1040
    ServerResult sr = static_cast<ServerResult>(payload->data[0]);
15✔
1041

1042
    // PX4 Mavlink FTP returns "File doesn't exist" this way
1043
    if (sr == ServerResult::ERR_FAIL_ERRNO && payload->data[1] == ENOENT) {
15✔
1044
        sr = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
1045
    }
1046

1047
    return translate(sr);
15✔
1048
}
1049

1050
MavlinkFtpClient::ClientResult MavlinkFtpClient::translate(ServerResult result)
15✔
1051
{
1052
    switch (result) {
15✔
1053
        case ServerResult::SUCCESS:
×
1054
            return ClientResult::Success;
×
1055
        case ServerResult::ERR_TIMEOUT:
×
1056
            return ClientResult::Timeout;
×
1057
        case ServerResult::ERR_FILE_IO_ERROR:
×
1058
            return ClientResult::FileIoError;
×
1059
        case ServerResult::ERR_FAIL_FILE_EXISTS:
×
1060
            return ClientResult::FileExists;
×
1061
        case ServerResult::ERR_FAIL_FILE_PROTECTED:
×
1062
            return ClientResult::FileProtected;
×
1063
        case ServerResult::ERR_UNKOWN_COMMAND:
×
1064
            return ClientResult::Unsupported;
×
1065
        case ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST:
1✔
1066
            return ClientResult::FileDoesNotExist;
1✔
1067
        default:
14✔
1068
            LogInfo() << "Unknown error code: " << (int)result;
14✔
1069
            return ClientResult::ProtocolError;
14✔
1070
    }
1071
}
1072

1073
void MavlinkFtpClient::download_async(
17✔
1074
    const std::string& remote_path,
1075
    const std::string& local_folder,
1076
    bool use_burst,
1077
    DownloadCallback callback,
1078
    std::optional<uint8_t> maybe_target_compid)
1079
{
1080
    if (use_burst) {
17✔
1081
        auto item = DownloadBurstItem{};
20✔
1082
        item.remote_path = remote_path;
10✔
1083
        item.local_folder = local_folder;
10✔
1084
        item.callback = callback;
10✔
1085
        auto new_work =
10✔
1086
            Work{std::move(item), maybe_target_compid.value_or(get_target_component_id())};
30✔
1087
        _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
10✔
1088

1089
    } else {
1090
        auto item = DownloadItem{};
14✔
1091
        item.remote_path = remote_path;
7✔
1092
        item.local_folder = local_folder;
7✔
1093
        item.callback = callback;
7✔
1094
        auto new_work =
7✔
1095
            Work{std::move(item), maybe_target_compid.value_or(get_target_component_id())};
21✔
1096
        _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
7✔
1097
    }
1098
}
17✔
1099

1100
void MavlinkFtpClient::upload_async(
7✔
1101
    const std::string& local_file_path, const std::string& remote_folder, UploadCallback callback)
1102
{
1103
    auto item = UploadItem{};
14✔
1104
    item.local_file_path = local_file_path;
7✔
1105
    item.remote_folder = remote_folder;
7✔
1106
    item.callback = callback;
7✔
1107
    auto new_work = Work{std::move(item), get_target_component_id()};
21✔
1108

1109
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
7✔
1110
}
7✔
1111

1112
void MavlinkFtpClient::list_directory_async(const std::string& path, ListDirectoryCallback callback)
2✔
1113
{
1114
    auto item = ListDirItem{};
4✔
1115
    item.path = path;
2✔
1116
    item.callback = callback;
2✔
1117
    auto new_work = Work{std::move(item), get_target_component_id()};
6✔
1118

1119
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
2✔
1120
}
2✔
1121

1122
void MavlinkFtpClient::create_directory_async(const std::string& path, ResultCallback callback)
3✔
1123
{
1124
    auto item = CreateDirItem{};
6✔
1125
    item.path = path;
3✔
1126
    item.callback = callback;
3✔
1127
    auto new_work = Work{std::move(item), get_target_component_id()};
9✔
1128

1129
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
3✔
1130
}
3✔
1131

1132
void MavlinkFtpClient::remove_directory_async(const std::string& path, ResultCallback callback)
2✔
1133
{
1134
    auto item = RemoveDirItem{};
4✔
1135
    item.path = path;
2✔
1136
    item.callback = callback;
2✔
1137
    auto new_work = Work{std::move(item), get_target_component_id()};
6✔
1138

1139
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
2✔
1140
}
2✔
1141

1142
void MavlinkFtpClient::remove_file_async(const std::string& path, ResultCallback callback)
4✔
1143
{
1144
    auto item = RemoveItem{};
8✔
1145
    item.path = path;
4✔
1146
    item.callback = callback;
4✔
1147
    auto new_work = Work{std::move(item), get_target_component_id()};
12✔
1148

1149
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
4✔
1150
}
4✔
1151

1152
void MavlinkFtpClient::rename_async(
2✔
1153
    const std::string& from_path, const std::string& to_path, ResultCallback callback)
1154
{
1155
    auto item = RenameItem{};
4✔
1156
    item.from_path = from_path;
2✔
1157
    item.to_path = to_path;
2✔
1158
    item.callback = callback;
2✔
1159
    auto new_work = Work{std::move(item), get_target_component_id()};
6✔
1160

1161
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
2✔
1162
}
2✔
1163

1164
void MavlinkFtpClient::are_files_identical_async(
3✔
1165
    const std::string& local_path,
1166
    const std::string& remote_path,
1167
    AreFilesIdenticalCallback callback)
1168
{
1169
    auto item = CompareFilesItem{};
6✔
1170
    item.local_path = local_path;
3✔
1171
    item.remote_path = remote_path;
3✔
1172
    item.callback = callback;
3✔
1173
    auto new_work = Work{std::move(item), get_target_component_id()};
9✔
1174

1175
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
3✔
1176
}
3✔
1177

1178
void MavlinkFtpClient::send_mavlink_ftp_message(const PayloadHeader& payload, uint8_t target_compid)
539✔
1179
{
1180
    _system_impl.queue_message([&](MavlinkAddress mavlink_address, uint8_t channel) {
539✔
1181
        mavlink_message_t message;
1182
        mavlink_msg_file_transfer_protocol_pack_chan(
1,078✔
1183
            mavlink_address.system_id,
539✔
1184
            mavlink_address.component_id,
539✔
1185
            channel,
1186
            &message,
1187
            _network_id,
539✔
1188
            _system_impl.get_system_id(),
539✔
1189
            target_compid,
539✔
1190
            reinterpret_cast<const uint8_t*>(&payload));
539✔
1191
        return message;
539✔
1192
    });
1193
}
539✔
1194

1195
void MavlinkFtpClient::start_timer(std::optional<double> duration_s)
778✔
1196
{
1197
    _system_impl.unregister_timeout_handler(_timeout_cookie);
778✔
1198
    _timeout_cookie = _system_impl.register_timeout_handler(
1,556✔
1199
        [this]() { timeout(); }, duration_s.value_or(_system_impl.timeout_s()));
1,639✔
1200
}
778✔
1201

1202
void MavlinkFtpClient::stop_timer()
36✔
1203
{
1204
    _system_impl.unregister_timeout_handler(_timeout_cookie);
36✔
1205
}
36✔
1206

1207
void MavlinkFtpClient::timeout()
83✔
1208
{
1209
    if (_debugging) {
83✔
1210
        LogDebug() << "Timeout!";
×
1211
    }
1212

1213
    LockedQueue<Work>::Guard work_queue_guard(_work_queue);
83✔
1214
    auto work = work_queue_guard.get_front();
83✔
1215
    if (!work) {
83✔
1216
        return;
1✔
1217
    }
1218

1219
    std::visit(
164✔
1220
        overloaded{
82✔
1221
            [&](DownloadItem& item) {
33✔
1222
                if (--work->retries == 0) {
33✔
1223
                    call_callback(item.callback, ClientResult::Timeout, {});
1✔
1224
                    work_queue_guard.pop_front();
1✔
1225
                    return;
1✔
1226
                }
1227
                if (_debugging) {
32✔
1228
                    LogDebug() << "Retries left: " << work->retries;
×
1229
                }
1230

1231
                start_timer();
32✔
1232
                send_mavlink_ftp_message(work->payload, work->target_compid);
32✔
1233
            },
1234
            [&](DownloadBurstItem& item) {
16✔
1235
                if (--work->retries == 0) {
16✔
1236
                    call_callback(item.callback, ClientResult::Timeout, {});
1✔
1237
                    work_queue_guard.pop_front();
1✔
1238
                    return;
1✔
1239
                }
1240
                if (_debugging) {
15✔
1241
                    LogDebug() << "Retries left: " << work->retries;
×
1242
                }
1243

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

1288
                start_timer();
32✔
1289
                send_mavlink_ftp_message(work->payload, work->target_compid);
32✔
1290
            },
1291
            [&](RemoveItem& item) {
×
1292
                if (--work->retries == 0) {
×
1293
                    call_callback(item.callback, ClientResult::Timeout);
×
1294
                    work_queue_guard.pop_front();
×
1295
                    return;
×
1296
                }
1297
                if (_debugging) {
×
1298
                    LogDebug() << "Retries left: " << work->retries;
×
1299
                }
1300

1301
                start_timer();
×
1302
                send_mavlink_ftp_message(work->payload, work->target_compid);
×
1303
            },
1304
            [&](RenameItem& item) {
×
1305
                if (--work->retries == 0) {
×
1306
                    call_callback(item.callback, ClientResult::Timeout);
×
1307
                    work_queue_guard.pop_front();
×
1308
                    return;
×
1309
                }
1310
                if (_debugging) {
×
1311
                    LogDebug() << "Retries left: " << work->retries;
×
1312
                }
1313

1314
                start_timer();
×
1315
                send_mavlink_ftp_message(work->payload, work->target_compid);
×
1316
            },
1317
            [&](CreateDirItem& item) {
×
1318
                if (--work->retries == 0) {
×
1319
                    call_callback(item.callback, ClientResult::Timeout);
×
1320
                    work_queue_guard.pop_front();
×
1321
                    return;
×
1322
                }
1323
                if (_debugging) {
×
1324
                    LogDebug() << "Retries left: " << work->retries;
×
1325
                }
1326

1327
                start_timer();
×
1328
                send_mavlink_ftp_message(work->payload, work->target_compid);
×
1329
            },
1330
            [&](RemoveDirItem& item) {
×
1331
                if (--work->retries == 0) {
×
1332
                    call_callback(item.callback, ClientResult::Timeout);
×
1333
                    work_queue_guard.pop_front();
×
1334
                    return;
×
1335
                }
1336
                if (_debugging) {
×
1337
                    LogDebug() << "Retries left: " << work->retries;
×
1338
                }
1339

1340
                start_timer();
×
1341
                send_mavlink_ftp_message(work->payload, work->target_compid);
×
1342
            },
1343
            [&](CompareFilesItem& item) {
×
1344
                if (--work->retries == 0) {
×
1345
                    call_callback(item.callback, ClientResult::Timeout, false);
×
1346
                    work_queue_guard.pop_front();
×
1347
                    return;
×
1348
                }
1349
                if (_debugging) {
×
1350
                    LogDebug() << "Retries left: " << work->retries;
×
1351
                }
1352

1353
                start_timer();
×
1354
                send_mavlink_ftp_message(work->payload, work->target_compid);
×
1355
            },
1356
            [&](ListDirItem& item) {
×
1357
                if (--work->retries == 0) {
×
1358
                    call_callback(item.callback, ClientResult::Timeout, {});
×
1359
                    work_queue_guard.pop_front();
×
1360
                    return;
×
1361
                }
1362
                if (_debugging) {
×
1363
                    LogDebug() << "Retries left: " << work->retries;
×
1364
                }
1365

1366
                start_timer();
×
1367
                send_mavlink_ftp_message(work->payload, work->target_compid);
×
1368
            }},
1369
        work->item);
82✔
1370
}
1371

1372
MavlinkFtpClient::ClientResult
1373
MavlinkFtpClient::calc_local_file_crc32(const std::string& path, uint32_t& csum)
3✔
1374
{
1375
    std::error_code ec;
3✔
1376
    if (!fs::exists(path, ec)) {
3✔
1377
        return ClientResult::FileDoesNotExist;
×
1378
    }
1379

1380
    std::ifstream stream(path, std::fstream::binary);
6✔
1381
    if (!stream) {
3✔
1382
        return ClientResult::FileIoError;
×
1383
    }
1384

1385
    // Read whole file in buffer size chunks
1386
    Crc32 checksum;
3✔
1387
    uint8_t buffer[4096];
3✔
1388
    std::streamsize bytes_read;
1389

1390
    do {
3✔
1391
        stream.read(reinterpret_cast<char*>(buffer), sizeof(buffer));
6✔
1392
        bytes_read = stream.gcount(); // Get the number of bytes actually read
6✔
1393
        checksum.add(reinterpret_cast<const uint8_t*>(buffer), bytes_read);
6✔
1394
    } while (bytes_read > 0);
6✔
1395

1396
    csum = checksum.get();
3✔
1397

1398
    return ClientResult::Success;
3✔
1399
}
1400

1401
void MavlinkFtpClient::terminate_session(Work& work)
20✔
1402
{
1403
    work.last_opcode = CMD_TERMINATE_SESSION;
20✔
1404

1405
    work.payload = {};
20✔
1406
    work.payload.seq_number = work.last_sent_seq_number++;
20✔
1407
    work.payload.session = _session;
20✔
1408

1409
    work.payload.opcode = work.last_opcode;
20✔
1410
    work.payload.offset = 0;
20✔
1411
    work.payload.size = 0;
20✔
1412

1413
    send_mavlink_ftp_message(work.payload, work.target_compid);
20✔
1414
}
20✔
1415

1416
uint8_t MavlinkFtpClient::get_our_compid()
×
1417
{
1418
    return _system_impl.get_own_component_id();
×
1419
}
1420

1421
uint8_t MavlinkFtpClient::get_target_component_id()
40✔
1422
{
1423
    return _target_component_id_set ? _target_component_id : _system_impl.get_autopilot_id();
40✔
1424
}
1425

1426
MavlinkFtpClient::ClientResult MavlinkFtpClient::set_target_compid(uint8_t component_id)
×
1427
{
1428
    _target_component_id = component_id;
×
1429
    _target_component_id_set = true;
×
1430
    return ClientResult::Success;
×
1431
}
1432

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

1467
template<typename CallbackT>
1468
void MavlinkFtpClient::call_callback(
653✔
1469
    const CallbackT& callback,
1470
    MavlinkFtpClient::ClientResult result,
1471
    const typename CallbackT::second_argument_type& extra_arg)
1472
{
1473
    _system_impl.call_user_callback(
653✔
1474
        [temp_callback = callback, temp_result = result, temp_extra_arg = extra_arg]() {
1475
            temp_callback(temp_result, temp_extra_arg);
1476
        });
1477
}
653✔
1478
template<typename CallbackT>
1479
void MavlinkFtpClient::call_callback(
11✔
1480
    const CallbackT& callback, MavlinkFtpClient::ClientResult result)
1481
{
1482
    _system_impl.call_user_callback(
22✔
1483
        [temp_callback = callback, temp_result = result]() { temp_callback(temp_result); });
1484
}
11✔
1485
} // 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