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

mavlink / MAVSDK / 11452150858

22 Oct 2024 02:32AM UTC coverage: 37.403% (+0.02%) from 37.381%
11452150858

push

github

web-flow
camera_server: prevent double ack+message (#2430)

It turns out we were sending the ack and message for storage information
as well as capture status twice, once directly in the request handler
callback, and once the MAVSDK user would call the respond function.

We should only call it in the respond function, not in the callback.

11105 of 29690 relevant lines covered (37.4%)

255.6 hits per line

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

74.68
/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,172✔
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()
2,997✔
38
{
39
    LockedQueue<Work>::Guard work_queue_guard(_work_queue);
2,997✔
40

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

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

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

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

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

135
    auto work = work_queue_guard.get_front();
1,163✔
136
    if (!work) {
1,163✔
137
        return;
468✔
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) {
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
                        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) {
270✔
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) {
270✔
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);
270✔
550
        if (!item.ofstream) {
270✔
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;
270✔
559

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

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

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

573
                // Final step
574
                download_burst_end(work);
6✔
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(
263✔
582
                item.callback,
263✔
583
                ClientResult::Next,
584
                ProgressData{
263✔
585
                    static_cast<uint32_t>(burst_bytes_transferred(item)),
263✔
586
                    static_cast<uint32_t>(item.file_size)});
263✔
587

588
            if (payload->burst_complete) {
263✔
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();
263✔
595
            }
596
        }
597
    } else if (payload->req_opcode == CMD_READ_FILE) {
9✔
598
        if (_debugging) {
9✔
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);
9✔
604
        if (item.ofstream.fail()) {
9✔
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);
9✔
612
        if (!item.ofstream) {
9✔
613
            call_callback(item.callback, ClientResult::FileIoError, {});
×
614
            download_burst_end(work);
×
615
            return false;
×
616
        }
617

618
        auto& missing = item.missing_data.front();
9✔
619
        if (missing.offset != payload->offset) {
9✔
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) {
9✔
627
            // we got all needed data for this chunk
628
            item.missing_data.pop_front();
9✔
629
        } else {
630
            missing.offset += payload->size;
×
631
            missing.size -= payload->size;
×
632
        }
633

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

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

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

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

656
            request_next_rest(work, item);
8✔
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)
22✔
703
{
704
    const auto& missing = item.missing_data.front();
22✔
705
    size_t size = std::min(missing.size, size_t(max_data_length));
22✔
706

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

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

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

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

724
size_t MavlinkFtpClient::burst_bytes_transferred(DownloadBurstItem& item)
272✔
725
{
726
    return item.current_offset - std::accumulate(
272✔
727
                                     item.missing_data.begin(),
272✔
728
                                     item.missing_data.end(),
544✔
729
                                     size_t(0),
730
                                     [](size_t acc, const DownloadBurstItem::MissingData& missing) {
180✔
731
                                         return acc + missing.size;
180✔
732
                                     });
816✔
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)
529✔
1179
{
1180
    _system_impl.queue_message([&](MavlinkAddress mavlink_address, uint8_t channel) {
529✔
1181
        mavlink_message_t message;
1182
        mavlink_msg_file_transfer_protocol_pack_chan(
1,058✔
1183
            mavlink_address.system_id,
529✔
1184
            mavlink_address.component_id,
529✔
1185
            channel,
1186
            &message,
1187
            _network_id,
529✔
1188
            _system_impl.get_system_id(),
529✔
1189
            target_compid,
529✔
1190
            reinterpret_cast<const uint8_t*>(&payload));
529✔
1191
        return message;
529✔
1192
    });
1193
}
529✔
1194

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

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

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

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

1219
    std::visit(
160✔
1220
        overloaded{
80✔
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) {
15✔
1235
                if (--work->retries == 0) {
15✔
1236
                    call_callback(item.callback, ClientResult::Timeout, {});
1✔
1237
                    work_queue_guard.pop_front();
1✔
1238
                    return;
1✔
1239
                }
1240
                if (_debugging) {
14✔
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) {
14✔
1249
                        // In that case start requesting what we missed.
1250
                        if (item.current_offset == item.file_size && item.missing_data.empty()) {
14✔
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) {
13✔
1261
                                item.missing_data.emplace_back(DownloadBurstItem::MissingData{
1✔
1262
                                    item.current_offset, item.file_size - item.current_offset});
1✔
1263
                                item.current_offset = item.file_size;
1✔
1264
                                if (_debugging) {
1✔
1265
                                    LogDebug() << "Adding " << item.current_offset << " with size "
×
1266
                                               << item.file_size - item.current_offset;
×
1267
                                }
1268
                            }
1269
                            request_next_rest(*work, item);
13✔
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) {
32✔
1279
                if (--work->retries == 0) {
32✔
1280
                    call_callback(item.callback, ClientResult::Timeout, {});
1✔
1281
                    work_queue_guard.pop_front();
1✔
1282
                    return;
1✔
1283
                }
1284
                if (_debugging) {
31✔
1285
                    LogDebug() << "Retries left: " << work->retries;
×
1286
                }
1287

1288
                start_timer();
31✔
1289
                send_mavlink_ftp_message(work->payload, work->target_compid);
31✔
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);
80✔
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