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

mavlink / MAVSDK / 8712394000

16 Apr 2024 08:53PM UTC coverage: 35.47% (+0.06%) from 35.408%
8712394000

push

github

web-flow
Info fixups (#2284)

* camera/core: remove some Yuneec baggage

I don't think we need to keep maintaining old Yuneec stuff.

Signed-off-by: Julian Oes <julian@oes.ch>

* info: fix received flags

I mixed this up.

Signed-off-by: Julian Oes <julian@oes.ch>

---------

Signed-off-by: Julian Oes <julian@oes.ch>

0 of 11 new or added lines in 1 file covered. (0.0%)

2 existing lines in 2 files now uncovered.

10108 of 28497 relevant lines covered (35.47%)

119.07 hits per line

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

73.16
/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)
68✔
18
{
19
    if (const char* env_p = std::getenv("MAVSDK_FTP_DEBUGGING")) {
68✔
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(
68✔
27
        MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL,
28
        [this](const mavlink_message_t& message) { process_mavlink_ftp_message(message); },
1,103✔
29
        this);
30
}
68✔
31

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

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

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

46
    if (work->started) {
855✔
47
        return;
818✔
48
    }
49
    work->started = true;
37✔
50

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

54
    std::visit(
74✔
55
        overloaded{
37✔
56
            [&](DownloadItem& item) {
7✔
57
                if (!download_start(*work, item)) {
7✔
58
                    work_queue_guard.pop_front();
×
59
                }
60
            },
7✔
61
            [&](DownloadBurstItem& item) {
7✔
62
                if (!download_burst_start(*work, item)) {
7✔
63
                    work_queue_guard.pop_front();
×
64
                }
65
            },
7✔
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);
37✔
102
}
103

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

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

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

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

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

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

140
    if (work->last_opcode != payload->req_opcode) {
671✔
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,305✔
147
        work->last_received_seq_number == payload->seq_number) {
634✔
148
        // We have already seen this ack/nak.
149
        LogWarn() << "Already seen";
×
150
        return;
×
151
    }
152

153
    std::visit(
1,342✔
154
        overloaded{
671✔
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
                        item.callback(ClientResult::Success, {});
4✔
171
                        work_queue_guard.pop_front();
4✔
172

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

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

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

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

206
                } else if (payload->opcode == RSP_NAK) {
2✔
207
                    LogWarn() << "FTP: NAK received";
2✔
208
                    stop_timer();
2✔
209
                    item.callback(result_from_nak(payload), {});
2✔
210
                    work_queue_guard.pop_front();
2✔
211
                }
212
            },
271✔
213
            [&](UploadItem& item) {
103✔
214
                if (payload->opcode == RSP_ACK) {
103✔
215
                    if (payload->req_opcode == CMD_CREATE_FILE ||
101✔
216
                        payload->req_opcode == CMD_OPEN_FILE_WO ||
96✔
217
                        payload->req_opcode == CMD_WRITE_FILE) {
96✔
218
                        // Whenever we do get an ack,
219
                        // reset the retry counter.
220
                        work->retries = RETRIES;
97✔
221

222
                        if (!upload_continue(*work, item)) {
97✔
223
                            stop_timer();
×
224
                            work_queue_guard.pop_front();
×
225
                        }
226
                    } else if (payload->req_opcode == CMD_TERMINATE_SESSION) {
4✔
227
                        stop_timer();
4✔
228
                        item.ifstream.close();
4✔
229
                        item.callback(ClientResult::Success, {});
4✔
230
                        work_queue_guard.pop_front();
4✔
231

232
                    } else {
233
                        LogWarn() << "Unexpected ack";
×
234
                    }
235

236
                } else if (payload->opcode == RSP_NAK) {
2✔
237
                    stop_timer();
2✔
238
                    item.callback(result_from_nak(payload), {});
2✔
239
                    work_queue_guard.pop_front();
2✔
240
                }
241
            },
103✔
242
            [&](RemoveItem& item) {
4✔
243
                if (payload->opcode == RSP_ACK) {
4✔
244
                    if (payload->req_opcode == CMD_REMOVE_FILE) {
1✔
245
                        stop_timer();
1✔
246
                        item.callback(ClientResult::Success);
1✔
247
                        work_queue_guard.pop_front();
1✔
248

249
                    } else {
250
                        LogWarn() << "Unexpected ack";
×
251
                    }
252

253
                } else if (payload->opcode == RSP_NAK) {
3✔
254
                    stop_timer();
3✔
255
                    item.callback(result_from_nak(payload));
3✔
256
                    work_queue_guard.pop_front();
3✔
257
                }
258
            },
4✔
259
            [&](RenameItem& item) {
2✔
260
                if (payload->opcode == RSP_ACK) {
2✔
261
                    if (payload->req_opcode == CMD_RENAME) {
1✔
262
                        stop_timer();
1✔
263
                        item.callback(ClientResult::Success);
1✔
264
                        work_queue_guard.pop_front();
1✔
265

266
                    } else {
267
                        LogWarn() << "Unexpected ack";
×
268
                    }
269

270
                } else if (payload->opcode == RSP_NAK) {
1✔
271
                    stop_timer();
1✔
272
                    item.callback(result_from_nak(payload));
1✔
273
                    work_queue_guard.pop_front();
1✔
274
                }
275
            },
2✔
276
            [&](CreateDirItem& item) {
3✔
277
                if (payload->opcode == RSP_ACK) {
3✔
278
                    if (payload->req_opcode == CMD_CREATE_DIRECTORY) {
1✔
279
                        stop_timer();
1✔
280
                        item.callback(ClientResult::Success);
1✔
281
                        work_queue_guard.pop_front();
1✔
282

283
                    } else {
284
                        LogWarn() << "Unexpected ack";
×
285
                    }
286

287
                } else if (payload->opcode == RSP_NAK) {
2✔
288
                    stop_timer();
2✔
289
                    item.callback(result_from_nak(payload));
2✔
290
                    work_queue_guard.pop_front();
2✔
291
                }
292
            },
3✔
293
            [&](RemoveDirItem& item) {
2✔
294
                if (payload->opcode == RSP_ACK) {
2✔
295
                    if (payload->req_opcode == CMD_REMOVE_DIRECTORY) {
1✔
296
                        stop_timer();
1✔
297
                        item.callback(ClientResult::Success);
1✔
298
                        work_queue_guard.pop_front();
1✔
299

300
                    } else {
301
                        LogWarn() << "Unexpected ack";
×
302
                    }
303

304
                } else if (payload->opcode == RSP_NAK) {
1✔
305
                    stop_timer();
1✔
306
                    item.callback(result_from_nak(payload));
1✔
307
                    work_queue_guard.pop_front();
1✔
308
                }
309
            },
2✔
310
            [&](CompareFilesItem& item) {
3✔
311
                if (payload->opcode == RSP_ACK) {
3✔
312
                    if (payload->req_opcode == CMD_CALC_FILE_CRC32) {
2✔
313
                        stop_timer();
2✔
314
                        uint32_t remote_crc = *reinterpret_cast<uint32_t*>(payload->data);
2✔
315
                        item.callback(ClientResult::Success, remote_crc == item.local_crc);
2✔
316
                        work_queue_guard.pop_front();
2✔
317

318
                    } else {
319
                        LogWarn() << "Unexpected ack";
×
320
                    }
321

322
                } else if (payload->opcode == RSP_NAK) {
1✔
323
                    stop_timer();
1✔
324
                    item.callback(result_from_nak(payload), false);
1✔
325
                    work_queue_guard.pop_front();
1✔
326
                }
327
            },
3✔
328
            [&](ListDirItem& item) {
11✔
329
                if (payload->opcode == RSP_ACK) {
11✔
330
                    if (payload->req_opcode == CMD_LIST_DIRECTORY) {
9✔
331
                        // Whenever we do get an ack, reset the retry counter.
332
                        work->retries = RETRIES;
9✔
333

334
                        if (!list_dir_continue(*work, item, payload)) {
9✔
335
                            stop_timer();
×
336
                            work_queue_guard.pop_front();
×
337
                        }
338
                    } else {
339
                        LogWarn() << "Unexpected ack";
×
340
                    }
341

342
                } else if (payload->opcode == RSP_NAK) {
2✔
343
                    stop_timer();
2✔
344
                    if (payload->data[0] == ERR_EOF) {
2✔
345
                        std::sort(item.dirs.begin(), item.dirs.end());
1✔
346
                        item.callback(ClientResult::Success, item.dirs);
1✔
347
                    } else {
348
                        item.callback(result_from_nak(payload), {});
1✔
349
                    }
350
                    work_queue_guard.pop_front();
2✔
351
                }
352
            }},
11✔
353
        work->item);
671✔
354

355
    work->last_received_seq_number = payload->seq_number;
671✔
356
}
357

358
bool MavlinkFtpClient::download_start(Work& work, DownloadItem& item)
7✔
359
{
360
    fs::path local_path = fs::path(item.local_folder) / fs::path(item.remote_path).filename();
35✔
361

362
    if (_debugging) {
7✔
363
        LogDebug() << "Trying to open write to local path: " << local_path.string();
×
364
    }
365

366
    item.ofstream.open(local_path, std::fstream::trunc | std::fstream::binary);
7✔
367
    if (!item.ofstream) {
7✔
368
        LogErr() << "Could not open it!";
×
369
        item.callback(ClientResult::FileIoError, {});
×
370
        return false;
×
371
    }
372

373
    work.last_opcode = CMD_OPEN_FILE_RO;
7✔
374
    work.payload = {};
7✔
375
    work.payload.seq_number = work.last_sent_seq_number++;
7✔
376
    work.payload.session = 0;
7✔
377
    work.payload.opcode = work.last_opcode;
7✔
378
    work.payload.offset = 0;
7✔
379
    strncpy(
7✔
380
        reinterpret_cast<char*>(work.payload.data), item.remote_path.c_str(), max_data_length - 1);
7✔
381
    work.payload.size = item.remote_path.length() + 1;
7✔
382

383
    start_timer();
7✔
384
    send_mavlink_ftp_message(work.payload);
7✔
385

386
    return true;
7✔
387
}
388

389
bool MavlinkFtpClient::download_continue(Work& work, DownloadItem& item, PayloadHeader* payload)
266✔
390
{
391
    if (payload->req_opcode == CMD_OPEN_FILE_RO) {
266✔
392
        item.file_size = *(reinterpret_cast<uint32_t*>(payload->data));
5✔
393

394
        if (_debugging) {
5✔
395
            LogWarn() << "Download continue, got file size: " << item.file_size;
×
396
        }
397

398
    } else if (payload->req_opcode == CMD_READ_FILE) {
261✔
399
        if (_debugging) {
261✔
400
            LogWarn() << "Download continue, write: " << std::to_string(payload->size);
×
401
        }
402

403
        if (item.bytes_transferred < item.file_size) {
261✔
404
            item.ofstream.write(reinterpret_cast<const char*>(payload->data), payload->size);
261✔
405
            if (!item.ofstream) {
261✔
406
                item.callback(ClientResult::FileIoError, {});
×
407
                return false;
×
408
            }
409
            item.bytes_transferred += payload->size;
261✔
410

411
            if (_debugging) {
261✔
412
                LogDebug() << "Written " << item.bytes_transferred << " of " << item.file_size
×
413
                           << " bytes";
×
414
            }
415
        }
416
        item.callback(
261✔
417
            ClientResult::Next,
418
            ProgressData{
419
                static_cast<uint32_t>(item.bytes_transferred),
261✔
420
                static_cast<uint32_t>(item.file_size)});
261✔
421
    }
422

423
    if (item.bytes_transferred < item.file_size) {
266✔
424
        work.last_opcode = CMD_READ_FILE;
262✔
425
        work.payload = {};
262✔
426
        work.payload.seq_number = work.last_sent_seq_number++;
262✔
427
        work.payload.session = _session;
262✔
428
        work.payload.opcode = work.last_opcode;
262✔
429
        work.payload.offset = item.bytes_transferred;
262✔
430

431
        work.payload.size =
262✔
432
            std::min(static_cast<size_t>(max_data_length), item.file_size - item.bytes_transferred);
262✔
433

434
        if (_debugging) {
262✔
435
            LogWarn() << "Request size: " << std::to_string(work.payload.size) << " of left "
×
436
                      << int(item.file_size - item.bytes_transferred);
×
437
        }
438

439
        start_timer();
262✔
440
        send_mavlink_ftp_message(work.payload);
262✔
441

442
        return true;
262✔
443
    } else {
444
        if (_debugging) {
4✔
445
            LogDebug() << "All bytes written, terminating session";
×
446
        }
447

448
        // Final step
449
        work.last_opcode = CMD_TERMINATE_SESSION;
4✔
450

451
        work.payload = {};
4✔
452
        work.payload.seq_number = work.last_sent_seq_number++;
4✔
453
        work.payload.session = _session;
4✔
454

455
        work.payload.opcode = work.last_opcode;
4✔
456
        work.payload.offset = 0;
4✔
457
        work.payload.size = 0;
4✔
458

459
        start_timer();
4✔
460
        send_mavlink_ftp_message(work.payload);
4✔
461
    }
462

463
    return true;
4✔
464
}
465

466
bool MavlinkFtpClient::download_burst_start(Work& work, DownloadBurstItem& item)
7✔
467
{
468
    fs::path local_path = fs::path(item.local_folder) / fs::path(item.remote_path).filename();
35✔
469

470
    if (_debugging) {
7✔
471
        LogDebug() << "Trying to open write to local path: " << local_path.string();
×
472
    }
473

474
    item.ofstream.open(local_path, std::fstream::trunc | std::fstream::binary);
7✔
475
    if (!item.ofstream) {
7✔
476
        LogErr() << "Could not open it!";
×
477
        item.callback(ClientResult::FileIoError, {});
×
478
        return false;
×
479
    }
480

481
    work.last_opcode = CMD_OPEN_FILE_RO;
7✔
482
    work.payload = {};
7✔
483
    work.payload.seq_number = work.last_sent_seq_number++;
7✔
484
    work.payload.session = 0;
7✔
485
    work.payload.opcode = work.last_opcode;
7✔
486
    work.payload.offset = 0;
7✔
487
    strncpy(
7✔
488
        reinterpret_cast<char*>(work.payload.data), item.remote_path.c_str(), max_data_length - 1);
7✔
489
    work.payload.size = item.remote_path.length() + 1;
7✔
490

491
    start_timer();
7✔
492
    send_mavlink_ftp_message(work.payload);
7✔
493

494
    return true;
7✔
495
}
496

497
bool MavlinkFtpClient::download_burst_continue(
265✔
498
    Work& work, DownloadBurstItem& item, PayloadHeader* payload)
499
{
500
    if (payload->req_opcode == CMD_OPEN_FILE_RO) {
265✔
501
        std::memcpy(&(item.file_size), payload->data, sizeof(uint32_t));
5✔
502

503
        if (_debugging) {
5✔
504
            LogDebug() << "Burst Download continue, got file size: " << item.file_size;
×
505
        }
506

507
        request_burst(work, item);
5✔
508

509
    } else if (payload->req_opcode == CMD_BURST_READ_FILE) {
260✔
510
        if (_debugging) {
247✔
511
            LogDebug() << "Burst download continue, at: " << std::to_string(payload->offset)
×
512
                       << " write: " << std::to_string(payload->size);
×
513
        }
514

515
        if (payload->offset != item.current_offset) {
247✔
516
            if (payload->offset < item.current_offset) {
8✔
517
                // Not sure why this would happen but we don't know how to deal with it and ignore
518
                // it.
519
                LogWarn() << "Got payload offset: " << payload->offset
×
520
                          << ", next offset: " << item.current_offset;
×
521
                return false;
×
522
            }
523

524
            // we missed a part
525
            item.missing_data.emplace_back(DownloadBurstItem::MissingData{
8✔
526
                item.current_offset, payload->offset - item.current_offset});
8✔
527
            // write some 0 instead
528
            std::vector<char> empty(payload->offset - item.current_offset);
16✔
529
            item.ofstream.write(empty.data(), empty.size());
8✔
530
            if (!item.ofstream) {
8✔
531
                LogWarn() << "Write failed";
×
532
                item.callback(ClientResult::FileIoError, {});
×
533
                download_burst_end(work);
×
534
                return false;
×
535
            }
536
        }
537

538
        // Write actual data to file.
539
        item.ofstream.write(reinterpret_cast<const char*>(payload->data), payload->size);
247✔
540
        if (!item.ofstream) {
247✔
541
            LogWarn() << "Write failed";
×
542
            item.callback(ClientResult::FileIoError, {});
×
543
            download_burst_end(work);
×
544
            return false;
×
545
        }
546

547
        // Keep track of what was written.
548
        item.current_offset = payload->offset + payload->size;
247✔
549

550
        if (_debugging) {
247✔
551
            LogDebug() << "Received " << payload->offset << " to "
×
552
                       << payload->size + payload->offset;
×
553
        }
554

555
        if (payload->size + payload->offset >= item.file_size) {
247✔
556
            if (_debugging) {
2✔
557
                LogDebug() << "Burst complete";
×
558
            }
559

560
            if (item.missing_data.empty()) {
2✔
561
                // No missing data, we're done.
562

563
                // Final step
564
                download_burst_end(work);
2✔
565
            } else {
566
                // The burst is supposedly complete but we still need data because
567
                // we missed some, so request next without burst.
UNCOV
568
                request_next_rest(work, item);
×
569
            }
570
        } else {
571
            item.callback(
490✔
572
                ClientResult::Next,
573
                ProgressData{
574
                    static_cast<uint32_t>(burst_bytes_transferred(item)),
245✔
575
                    static_cast<uint32_t>(item.file_size)});
245✔
576

577
            if (payload->burst_complete) {
245✔
578
                // This burst is complete but the file isn't. we need to start a
579
                // new one
580
                request_burst(work, item);
×
581
            } else {
582
                // There might be more coming, just wait for now.
583
                start_timer();
245✔
584
            }
585
        }
586
    } else if (payload->req_opcode == CMD_READ_FILE) {
13✔
587
        if (_debugging) {
13✔
588
            LogWarn() << "Burst download continue missing pieces, write at " << payload->offset
×
589
                      << " for " << std::to_string(payload->size);
×
590
        }
591

592
        item.ofstream.seekp(payload->offset);
13✔
593
        if (item.ofstream.fail()) {
13✔
594
            LogWarn() << "Seek failed";
×
595
            item.callback(ClientResult::FileIoError, {});
×
596
            download_burst_end(work);
×
597
            return false;
×
598
        }
599

600
        item.ofstream.write(reinterpret_cast<const char*>(payload->data), payload->size);
13✔
601
        if (!item.ofstream) {
13✔
602
            item.callback(ClientResult::FileIoError, {});
×
603
            download_burst_end(work);
×
604
            return false;
×
605
        }
606

607
        auto& missing = item.missing_data.front();
13✔
608
        if (missing.offset != payload->offset) {
13✔
609
            LogErr() << "Offset mismatch";
×
610
            item.callback(ClientResult::ProtocolError, {});
×
611
            download_burst_end(work);
×
612
            return false;
×
613
        }
614

615
        if (missing.size <= payload->size) {
13✔
616
            // we got all needed data for this chunk
617
            item.missing_data.pop_front();
10✔
618
        } else {
619
            missing.offset += payload->size;
3✔
620
            missing.size -= payload->size;
3✔
621
        }
622

623
        // Check if this was the last one
624
        if (item.file_size == payload->offset + payload->size) {
13✔
625
            item.current_offset = item.file_size;
2✔
626
        }
627

628
        const size_t bytes_transferred = burst_bytes_transferred(item);
13✔
629

630
        if (_debugging) {
13✔
631
            LogDebug() << "Written " << bytes_transferred << " of " << item.file_size << " bytes";
×
632
        }
633

634
        if (item.missing_data.empty() && bytes_transferred == item.file_size) {
13✔
635
            // Final step
636
            download_burst_end(work);
2✔
637
        } else {
638
            item.callback(
11✔
639
                ClientResult::Next,
640
                ProgressData{
641
                    static_cast<uint32_t>(bytes_transferred),
642
                    static_cast<uint32_t>(item.file_size)});
11✔
643

644
            request_next_rest(work, item);
11✔
645
        }
646

647
    } else {
648
        LogErr() << "Unexpected req_opcode";
×
649
        download_burst_end(work);
×
650
        return false;
×
651
    }
652

653
    return true;
265✔
654
}
655

656
void MavlinkFtpClient::download_burst_end(Work& work)
4✔
657
{
658
    work.last_opcode = CMD_TERMINATE_SESSION;
4✔
659

660
    work.payload = {};
4✔
661
    work.payload.seq_number = work.last_sent_seq_number++;
4✔
662
    work.payload.session = _session;
4✔
663

664
    work.payload.opcode = work.last_opcode;
4✔
665
    work.payload.offset = 0;
4✔
666
    work.payload.size = 0;
4✔
667

668
    start_timer();
4✔
669
    send_mavlink_ftp_message(work.payload);
4✔
670
}
4✔
671

672
void MavlinkFtpClient::request_burst(Work& work, DownloadBurstItem& item)
5✔
673
{
674
    UNUSED(item);
5✔
675

676
    work.last_opcode = CMD_BURST_READ_FILE;
5✔
677
    work.payload = {};
5✔
678
    work.payload.seq_number = work.last_sent_seq_number++;
5✔
679
    work.payload.session = _session;
5✔
680
    work.payload.opcode = work.last_opcode;
5✔
681
    work.payload.offset = item.current_offset;
5✔
682

683
    // Fill up the whole packet.
684
    work.payload.size = max_data_length;
5✔
685

686
    start_timer();
5✔
687
    send_mavlink_ftp_message(work.payload);
5✔
688
}
5✔
689

690
void MavlinkFtpClient::request_next_rest(Work& work, DownloadBurstItem& item)
27✔
691
{
692
    const auto& missing = item.missing_data.front();
27✔
693
    size_t size = std::min(missing.size, size_t(max_data_length));
27✔
694

695
    if (_debugging) {
27✔
696
        LogDebug() << "Re-requesting from " << missing.offset << " with size " << size;
×
697
    }
698

699
    work.last_opcode = CMD_READ_FILE;
27✔
700
    work.payload = {};
27✔
701
    work.payload.seq_number = work.last_sent_seq_number++;
27✔
702
    work.payload.session = _session;
27✔
703
    work.payload.opcode = work.last_opcode;
27✔
704
    work.payload.offset = missing.offset;
27✔
705

706
    work.payload.size = size;
27✔
707

708
    start_timer();
27✔
709
    send_mavlink_ftp_message(work.payload);
27✔
710
}
27✔
711

712
size_t MavlinkFtpClient::burst_bytes_transferred(DownloadBurstItem& item)
258✔
713
{
714
    return item.current_offset - std::accumulate(
258✔
715
                                     item.missing_data.begin(),
258✔
716
                                     item.missing_data.end(),
516✔
717
                                     size_t(0),
718
                                     [](size_t acc, const DownloadBurstItem::MissingData& missing) {
183✔
719
                                         return acc + missing.size;
183✔
720
                                     });
774✔
721
}
722

723
bool MavlinkFtpClient::upload_start(Work& work, UploadItem& item)
7✔
724
{
725
    std::error_code ec;
7✔
726
    if (!fs::exists(item.local_file_path, ec)) {
7✔
727
        item.callback(ClientResult::FileDoesNotExist, {});
×
728
        return false;
×
729
    }
730

731
    item.ifstream.open(item.local_file_path, std::fstream::binary);
7✔
732
    if (!item.ifstream) {
7✔
733
        item.callback(ClientResult::FileIoError, {});
×
734
        return false;
×
735
    }
736

737
    item.file_size = fs::file_size(item.local_file_path, ec);
7✔
738
    if (ec) {
7✔
739
        LogWarn() << "Could not get file size of '" << item.local_file_path
×
740
                  << "': " << ec.message();
×
741
        return false;
×
742
    }
743

744
    fs::path remote_file_path =
7✔
745
        fs::path(item.remote_folder) / fs::path(item.local_file_path).filename();
35✔
746

747
    if (remote_file_path.string().size() >= max_data_length) {
7✔
748
        item.callback(ClientResult::InvalidParameter, {});
×
749
        return false;
×
750
    }
751

752
    work.last_opcode = CMD_CREATE_FILE;
7✔
753
    work.payload = {};
7✔
754
    work.payload.seq_number = work.last_sent_seq_number++;
7✔
755
    work.payload.session = 0;
7✔
756
    work.payload.opcode = work.last_opcode;
7✔
757
    work.payload.offset = 0;
7✔
758
    strncpy(
14✔
759
        reinterpret_cast<char*>(work.payload.data),
7✔
760
        remote_file_path.string().c_str(),
14✔
761
        max_data_length - 1);
762
    work.payload.size = remote_file_path.string().size() + 1;
7✔
763

764
    start_timer();
7✔
765
    send_mavlink_ftp_message(work.payload);
7✔
766

767
    return true;
7✔
768
}
769

770
bool MavlinkFtpClient::upload_continue(Work& work, UploadItem& item)
97✔
771
{
772
    if (item.bytes_transferred < item.file_size) {
97✔
773
        work.last_opcode = CMD_WRITE_FILE;
93✔
774

775
        work.payload = {};
93✔
776
        work.payload.seq_number = work.last_sent_seq_number++;
93✔
777
        work.payload.session = _session;
93✔
778

779
        work.payload.opcode = work.last_opcode;
93✔
780
        work.payload.offset = item.bytes_transferred;
93✔
781

782
        std::size_t bytes_to_read =
783
            std::min(item.file_size - item.bytes_transferred, std::size_t(max_data_length));
93✔
784

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

787
        // Get the number of bytes actually read.
788
        int bytes_read = item.ifstream.gcount();
93✔
789

790
        if (!item.ifstream) {
93✔
791
            item.callback(ClientResult::FileIoError, {});
×
792
            return false;
×
793
        }
794

795
        work.payload.size = bytes_read;
93✔
796
        item.bytes_transferred += bytes_read;
93✔
797

798
        start_timer();
93✔
799
        send_mavlink_ftp_message(work.payload);
93✔
800

801
    } else {
802
        // Final step
803
        work.last_opcode = CMD_TERMINATE_SESSION;
4✔
804

805
        work.payload = {};
4✔
806
        work.payload.seq_number = work.last_sent_seq_number++;
4✔
807
        work.payload.session = _session;
4✔
808

809
        work.payload.opcode = work.last_opcode;
4✔
810
        work.payload.offset = 0;
4✔
811
        work.payload.size = 0;
4✔
812

813
        start_timer();
4✔
814
        send_mavlink_ftp_message(work.payload);
4✔
815
    }
816

817
    item.callback(
97✔
818
        ClientResult::Next,
819
        ProgressData{
820
            static_cast<uint32_t>(item.bytes_transferred), static_cast<uint32_t>(item.file_size)});
97✔
821

822
    return true;
97✔
823
}
824

825
bool MavlinkFtpClient::remove_start(Work& work, RemoveItem& item)
4✔
826
{
827
    if (item.path.length() >= max_data_length) {
4✔
828
        item.callback(ClientResult::InvalidParameter);
×
829
        return false;
×
830
    }
831

832
    work.last_opcode = CMD_REMOVE_FILE;
4✔
833
    work.payload = {};
4✔
834
    work.payload.seq_number = work.last_sent_seq_number++;
4✔
835
    work.payload.session = 0;
4✔
836
    work.payload.opcode = work.last_opcode;
4✔
837
    work.payload.offset = 0;
4✔
838
    strncpy(reinterpret_cast<char*>(work.payload.data), item.path.c_str(), max_data_length - 1);
4✔
839
    work.payload.size = item.path.length() + 1;
4✔
840

841
    start_timer();
4✔
842
    send_mavlink_ftp_message(work.payload);
4✔
843

844
    return true;
4✔
845
}
846

847
bool MavlinkFtpClient::rename_start(Work& work, RenameItem& item)
2✔
848
{
849
    if (item.from_path.length() + item.to_path.length() + 1 >= max_data_length) {
2✔
850
        item.callback(ClientResult::InvalidParameter);
×
851
        return false;
×
852
    }
853

854
    work.last_opcode = CMD_RENAME;
2✔
855
    work.payload = {};
2✔
856
    work.payload.seq_number = work.last_sent_seq_number++;
2✔
857
    work.payload.session = 0;
2✔
858
    work.payload.opcode = work.last_opcode;
2✔
859
    work.payload.offset = 0;
2✔
860
    strncpy(
2✔
861
        reinterpret_cast<char*>(work.payload.data), item.from_path.c_str(), max_data_length - 1);
2✔
862
    work.payload.size = item.from_path.length() + 1;
2✔
863
    strncpy(
4✔
864
        reinterpret_cast<char*>(&work.payload.data[work.payload.size]),
2✔
865
        item.to_path.c_str(),
866
        max_data_length - work.payload.size);
2✔
867
    work.payload.size += item.to_path.length() + 1;
2✔
868
    start_timer();
2✔
869

870
    send_mavlink_ftp_message(work.payload);
2✔
871

872
    return true;
2✔
873
}
874

875
bool MavlinkFtpClient::create_dir_start(Work& work, CreateDirItem& item)
3✔
876
{
877
    if (item.path.length() + 1 >= max_data_length) {
3✔
878
        item.callback(ClientResult::InvalidParameter);
×
879
        return false;
×
880
    }
881

882
    work.last_opcode = CMD_CREATE_DIRECTORY;
3✔
883
    work.payload = {};
3✔
884
    work.payload.seq_number = work.last_sent_seq_number++;
3✔
885
    work.payload.session = 0;
3✔
886
    work.payload.opcode = work.last_opcode;
3✔
887
    work.payload.offset = 0;
3✔
888
    strncpy(reinterpret_cast<char*>(work.payload.data), item.path.c_str(), max_data_length - 1);
3✔
889
    work.payload.size = item.path.length() + 1;
3✔
890
    start_timer();
3✔
891

892
    send_mavlink_ftp_message(work.payload);
3✔
893

894
    return true;
3✔
895
}
896

897
bool MavlinkFtpClient::remove_dir_start(Work& work, RemoveDirItem& item)
2✔
898
{
899
    if (item.path.length() + 1 >= max_data_length) {
2✔
900
        item.callback(ClientResult::InvalidParameter);
×
901
        return false;
×
902
    }
903

904
    work.last_opcode = CMD_REMOVE_DIRECTORY;
2✔
905
    work.payload = {};
2✔
906
    work.payload.seq_number = work.last_sent_seq_number++;
2✔
907
    work.payload.session = 0;
2✔
908
    work.payload.opcode = work.last_opcode;
2✔
909
    work.payload.offset = 0;
2✔
910
    strncpy(reinterpret_cast<char*>(work.payload.data), item.path.c_str(), max_data_length - 1);
2✔
911
    work.payload.size = item.path.length() + 1;
2✔
912
    start_timer();
2✔
913

914
    send_mavlink_ftp_message(work.payload);
2✔
915

916
    return true;
2✔
917
}
918

919
bool MavlinkFtpClient::compare_files_start(Work& work, CompareFilesItem& item)
3✔
920
{
921
    if (item.remote_path.length() + 1 >= max_data_length) {
3✔
922
        item.callback(ClientResult::InvalidParameter, false);
×
923
        return false;
×
924
    }
925

926
    auto result_local = calc_local_file_crc32(item.local_path, item.local_crc);
3✔
927
    if (result_local != ClientResult::Success) {
3✔
928
        item.callback(result_local, false);
×
929
        return false;
×
930
    }
931

932
    work.last_opcode = CMD_CALC_FILE_CRC32;
3✔
933
    work.payload = {};
3✔
934
    work.payload.seq_number = work.last_sent_seq_number++;
3✔
935
    work.payload.session = 0;
3✔
936
    work.payload.opcode = work.last_opcode;
3✔
937
    work.payload.offset = 0;
3✔
938
    strncpy(
3✔
939
        reinterpret_cast<char*>(work.payload.data), item.remote_path.c_str(), max_data_length - 1);
3✔
940
    work.payload.size = item.remote_path.length() + 1;
3✔
941
    start_timer();
3✔
942

943
    send_mavlink_ftp_message(work.payload);
3✔
944

945
    return true;
3✔
946
}
947

948
bool MavlinkFtpClient::list_dir_start(Work& work, ListDirItem& item)
2✔
949
{
950
    if (item.path.length() + 1 >= max_data_length) {
2✔
951
        item.callback(ClientResult::InvalidParameter, {});
×
952
        return false;
×
953
    }
954

955
    work.last_opcode = CMD_LIST_DIRECTORY;
2✔
956
    work.payload = {};
2✔
957
    work.payload.seq_number = work.last_sent_seq_number++;
2✔
958
    work.payload.session = 0;
2✔
959
    work.payload.opcode = work.last_opcode;
2✔
960
    work.payload.offset = 0;
2✔
961
    strncpy(reinterpret_cast<char*>(work.payload.data), item.path.c_str(), max_data_length - 1);
2✔
962
    work.payload.size = item.path.length() + 1;
2✔
963
    start_timer();
2✔
964

965
    send_mavlink_ftp_message(work.payload);
2✔
966

967
    return true;
2✔
968
}
969

970
bool MavlinkFtpClient::list_dir_continue(Work& work, ListDirItem& item, PayloadHeader* payload)
9✔
971
{
972
    if (_debugging) {
9✔
973
        LogDebug() << "List dir response received, got " << (int)payload->size << " chars";
×
974
    }
975

976
    if (payload->size > max_data_length) {
9✔
977
        LogWarn() << "Received FTP payload with invalid size";
×
978
        return false;
×
979
    }
980

981
    if (payload->size == 0) {
9✔
982
        std::sort(item.dirs.begin(), item.dirs.end());
×
983
        item.callback(ClientResult::Success, item.dirs);
×
984
        return false;
×
985
    }
986

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

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

994
        std::string entry;
200✔
995
        entry.resize(entry_len);
200✔
996
        std::memcpy(entry.data(), &payload->data[i], entry_len);
200✔
997

998
        i += entry_len + 1;
200✔
999

1000
        ++item.offset;
200✔
1001

1002
        if (entry[0] == 'S') {
200✔
1003
            // Skip skip for now
1004
            continue;
×
1005
        }
1006

1007
        item.dirs.push_back(entry);
200✔
1008
    }
1009

1010
    work.last_opcode = CMD_LIST_DIRECTORY;
9✔
1011
    work.payload = {};
9✔
1012
    work.payload.seq_number = work.last_sent_seq_number++;
9✔
1013
    work.payload.session = 0;
9✔
1014
    work.payload.opcode = work.last_opcode;
9✔
1015
    work.payload.offset = item.offset;
9✔
1016
    strncpy(reinterpret_cast<char*>(work.payload.data), item.path.c_str(), max_data_length - 1);
9✔
1017
    work.payload.size = item.path.length() + 1;
9✔
1018
    start_timer();
9✔
1019

1020
    send_mavlink_ftp_message(work.payload);
9✔
1021

1022
    return true;
9✔
1023
}
1024

1025
MavlinkFtpClient::ClientResult MavlinkFtpClient::result_from_nak(PayloadHeader* payload)
15✔
1026
{
1027
    ServerResult sr = static_cast<ServerResult>(payload->data[0]);
15✔
1028

1029
    // PX4 Mavlink FTP returns "File doesn't exist" this way
1030
    if (sr == ServerResult::ERR_FAIL_ERRNO && payload->data[1] == ENOENT) {
15✔
1031
        sr = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
1032
    }
1033

1034
    return translate(sr);
15✔
1035
}
1036

1037
MavlinkFtpClient::ClientResult MavlinkFtpClient::translate(ServerResult result)
15✔
1038
{
1039
    switch (result) {
15✔
1040
        case ServerResult::SUCCESS:
×
1041
            return ClientResult::Success;
×
1042
        case ServerResult::ERR_TIMEOUT:
×
1043
            return ClientResult::Timeout;
×
1044
        case ServerResult::ERR_FILE_IO_ERROR:
×
1045
            return ClientResult::FileIoError;
×
1046
        case ServerResult::ERR_FAIL_FILE_EXISTS:
×
1047
            return ClientResult::FileExists;
×
1048
        case ServerResult::ERR_FAIL_FILE_PROTECTED:
×
1049
            return ClientResult::FileProtected;
×
1050
        case ServerResult::ERR_UNKOWN_COMMAND:
×
1051
            return ClientResult::Unsupported;
×
1052
        case ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST:
1✔
1053
            return ClientResult::FileDoesNotExist;
1✔
1054
        default:
14✔
1055
            LogInfo() << "Unknown error code: " << (int)result;
14✔
1056
            return ClientResult::ProtocolError;
14✔
1057
    }
1058
}
1059

1060
void MavlinkFtpClient::download_async(
14✔
1061
    const std::string& remote_path,
1062
    const std::string& local_folder,
1063
    bool use_burst,
1064
    DownloadCallback callback)
1065
{
1066
    if (use_burst) {
14✔
1067
        auto item = DownloadBurstItem{};
14✔
1068
        item.remote_path = remote_path;
7✔
1069
        item.local_folder = local_folder;
7✔
1070
        item.callback = callback;
7✔
1071
        auto new_work = Work{std::move(item)};
21✔
1072
        _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
7✔
1073

1074
    } else {
1075
        auto item = DownloadItem{};
14✔
1076
        item.remote_path = remote_path;
7✔
1077
        item.local_folder = local_folder;
7✔
1078
        item.callback = callback;
7✔
1079
        auto new_work = Work{std::move(item)};
21✔
1080
        _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
7✔
1081
    }
1082
}
14✔
1083

1084
void MavlinkFtpClient::upload_async(
7✔
1085
    const std::string& local_file_path, const std::string& remote_folder, UploadCallback callback)
1086
{
1087
    auto item = UploadItem{};
14✔
1088
    item.local_file_path = local_file_path;
7✔
1089
    item.remote_folder = remote_folder;
7✔
1090
    item.callback = callback;
7✔
1091
    auto new_work = Work{std::move(item)};
21✔
1092

1093
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
7✔
1094
}
7✔
1095

1096
void MavlinkFtpClient::list_directory_async(const std::string& path, ListDirectoryCallback callback)
2✔
1097
{
1098
    auto item = ListDirItem{};
4✔
1099
    item.path = path;
2✔
1100
    item.callback = callback;
2✔
1101
    auto new_work = Work{std::move(item)};
6✔
1102

1103
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
2✔
1104
}
2✔
1105

1106
void MavlinkFtpClient::create_directory_async(const std::string& path, ResultCallback callback)
3✔
1107
{
1108
    auto item = CreateDirItem{};
6✔
1109
    item.path = path;
3✔
1110
    item.callback = callback;
3✔
1111
    auto new_work = Work{std::move(item)};
9✔
1112

1113
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
3✔
1114
}
3✔
1115

1116
void MavlinkFtpClient::remove_directory_async(const std::string& path, ResultCallback callback)
2✔
1117
{
1118
    auto item = RemoveDirItem{};
4✔
1119
    item.path = path;
2✔
1120
    item.callback = callback;
2✔
1121
    auto new_work = Work{std::move(item)};
6✔
1122

1123
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
2✔
1124
}
2✔
1125

1126
void MavlinkFtpClient::remove_file_async(const std::string& path, ResultCallback callback)
4✔
1127
{
1128
    auto item = RemoveItem{};
8✔
1129
    item.path = path;
4✔
1130
    item.callback = callback;
4✔
1131
    auto new_work = Work{std::move(item)};
12✔
1132

1133
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
4✔
1134
}
4✔
1135

1136
void MavlinkFtpClient::rename_async(
2✔
1137
    const std::string& from_path, const std::string& to_path, ResultCallback callback)
1138
{
1139
    auto item = RenameItem{};
4✔
1140
    item.from_path = from_path;
2✔
1141
    item.to_path = to_path;
2✔
1142
    item.callback = callback;
2✔
1143
    auto new_work = Work{std::move(item)};
6✔
1144

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

1148
void MavlinkFtpClient::are_files_identical_async(
3✔
1149
    const std::string& local_path,
1150
    const std::string& remote_path,
1151
    AreFilesIdenticalCallback callback)
1152
{
1153
    auto item = CompareFilesItem{};
6✔
1154
    item.local_path = local_path;
3✔
1155
    item.remote_path = remote_path;
3✔
1156
    item.callback = callback;
3✔
1157
    auto new_work = Work{std::move(item)};
9✔
1158

1159
    _work_queue.push_back(std::make_shared<Work>(std::move(new_work)));
3✔
1160
}
3✔
1161

1162
void MavlinkFtpClient::send_mavlink_ftp_message(const PayloadHeader& payload)
507✔
1163
{
1164
    _system_impl.queue_message([&](MavlinkAddress mavlink_address, uint8_t channel) {
507✔
1165
        mavlink_message_t message;
1166
        mavlink_msg_file_transfer_protocol_pack_chan(
1,014✔
1167
            mavlink_address.system_id,
507✔
1168
            mavlink_address.component_id,
507✔
1169
            channel,
1170
            &message,
1171
            _network_id,
507✔
1172
            _system_impl.get_system_id(),
507✔
1173
            get_target_component_id(),
507✔
1174
            reinterpret_cast<const uint8_t*>(&payload));
507✔
1175
        return message;
507✔
1176
    });
1177
}
507✔
1178

1179
void MavlinkFtpClient::start_timer()
752✔
1180
{
1181
    _system_impl.unregister_timeout_handler(_timeout_cookie);
752✔
1182
    _system_impl.register_timeout_handler(
752✔
1183
        [this]() { timeout(); }, _system_impl.timeout_s(), &_timeout_cookie);
833✔
1184
}
752✔
1185

1186
void MavlinkFtpClient::stop_timer()
34✔
1187
{
1188
    _system_impl.unregister_timeout_handler(_timeout_cookie);
34✔
1189
}
34✔
1190

1191
void MavlinkFtpClient::timeout()
81✔
1192
{
1193
    if (_debugging) {
81✔
1194
        LogDebug() << "Timeout!";
×
1195
    }
1196

1197
    LockedQueue<Work>::Guard work_queue_guard(_work_queue);
81✔
1198
    auto work = work_queue_guard.get_front();
81✔
1199
    if (!work) {
81✔
1200
        return;
×
1201
    }
1202

1203
    std::visit(
162✔
1204
        overloaded{
81✔
1205
            [&](DownloadItem& item) {
32✔
1206
                if (--work->retries == 0) {
32✔
1207
                    item.callback(ClientResult::Timeout, {});
1✔
1208
                    work_queue_guard.pop_front();
1✔
1209
                    return;
1✔
1210
                }
1211
                if (_debugging) {
31✔
1212
                    LogDebug() << "Retries left: " << work->retries;
×
1213
                }
1214

1215
                start_timer();
31✔
1216
                send_mavlink_ftp_message(work->payload);
31✔
1217
            },
1218
            [&](DownloadBurstItem& item) {
17✔
1219
                if (--work->retries == 0) {
17✔
1220
                    item.callback(ClientResult::Timeout, {});
1✔
1221
                    work_queue_guard.pop_front();
1✔
1222
                    return;
1✔
1223
                }
1224
                if (_debugging) {
16✔
1225
                    LogDebug() << "Retries left: " << work->retries;
×
1226
                }
1227

1228
                {
1229
                    // This happens when we missed the last ack containing burst complete.
1230
                    // We have already a file size, so we don't need to start at the
1231
                    // beginning any more.
1232
                    if (item.file_size != 0 && item.current_offset != 0) {
16✔
1233
                        // In that case start requesting what we missed.
1234
                        if (item.current_offset == item.file_size && item.missing_data.empty()) {
16✔
1235
                            // We are done anyway.
1236
                            item.callback(ClientResult::Success, {});
×
1237
                            download_burst_end(*work);
×
1238
                            work_queue_guard.pop_front();
×
1239
                        } else {
1240
                            // The burst is supposedly complete but we still need data because
1241
                            // we missed some, so request next without burst.
1242
                            // We presumably missed the very last chunk.
1243
                            if (item.current_offset < item.file_size) {
16✔
1244
                                item.missing_data.emplace_back(DownloadBurstItem::MissingData{
3✔
1245
                                    item.current_offset, item.file_size - item.current_offset});
3✔
1246
                                item.current_offset = item.file_size;
3✔
1247
                                if (_debugging) {
3✔
1248
                                    LogDebug() << "Adding " << item.current_offset << " with size "
×
1249
                                               << item.file_size - item.current_offset;
×
1250
                                }
1251
                            }
1252
                            request_next_rest(*work, item);
16✔
1253
                        }
1254
                    } else {
1255
                        // Otherwise, start burst again.
1256
                        start_timer();
×
1257
                        send_mavlink_ftp_message(work->payload);
×
1258
                    }
1259
                }
1260
            },
1261
            [&](UploadItem& item) {
32✔
1262
                if (--work->retries == 0) {
32✔
1263
                    item.callback(ClientResult::Timeout, {});
1✔
1264
                    work_queue_guard.pop_front();
1✔
1265
                    return;
1✔
1266
                }
1267
                if (_debugging) {
31✔
1268
                    LogDebug() << "Retries left: " << work->retries;
×
1269
                }
1270

1271
                start_timer();
31✔
1272
                send_mavlink_ftp_message(work->payload);
31✔
1273
            },
1274
            [&](RemoveItem& item) {
×
1275
                if (--work->retries == 0) {
×
1276
                    item.callback(ClientResult::Timeout);
×
1277
                    work_queue_guard.pop_front();
×
1278
                    return;
×
1279
                }
1280
                if (_debugging) {
×
1281
                    LogDebug() << "Retries left: " << work->retries;
×
1282
                }
1283

1284
                start_timer();
×
1285
                send_mavlink_ftp_message(work->payload);
×
1286
            },
1287
            [&](RenameItem& item) {
×
1288
                if (--work->retries == 0) {
×
1289
                    item.callback(ClientResult::Timeout);
×
1290
                    work_queue_guard.pop_front();
×
1291
                    return;
×
1292
                }
1293
                if (_debugging) {
×
1294
                    LogDebug() << "Retries left: " << work->retries;
×
1295
                }
1296

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

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

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

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

1349
                start_timer();
×
1350
                send_mavlink_ftp_message(work->payload);
×
1351
            }},
1352
        work->item);
81✔
1353
}
1354

1355
MavlinkFtpClient::ClientResult
1356
MavlinkFtpClient::calc_local_file_crc32(const std::string& path, uint32_t& csum)
3✔
1357
{
1358
    std::error_code ec;
3✔
1359
    if (!fs::exists(path, ec)) {
3✔
1360
        return ClientResult::FileDoesNotExist;
×
1361
    }
1362

1363
    std::ifstream stream(path, std::fstream::binary);
6✔
1364
    if (!stream) {
3✔
1365
        return ClientResult::FileIoError;
×
1366
    }
1367

1368
    // Read whole file in buffer size chunks
1369
    Crc32 checksum;
3✔
1370
    uint8_t buffer[4096];
3✔
1371
    std::streamsize bytes_read;
1372

1373
    do {
3✔
1374
        stream.read(reinterpret_cast<char*>(buffer), sizeof(buffer));
6✔
1375
        bytes_read = stream.gcount(); // Get the number of bytes actually read
6✔
1376
        checksum.add(reinterpret_cast<const uint8_t*>(buffer), bytes_read);
6✔
1377
    } while (bytes_read > 0);
6✔
1378

1379
    csum = checksum.get();
3✔
1380

1381
    return ClientResult::Success;
3✔
1382
}
1383

1384
uint8_t MavlinkFtpClient::get_our_compid()
×
1385
{
1386
    return _system_impl.get_own_component_id();
×
1387
}
1388

1389
uint8_t MavlinkFtpClient::get_target_component_id()
507✔
1390
{
1391
    return _target_component_id_set ? _target_component_id : _system_impl.get_autopilot_id();
507✔
1392
}
1393

1394
MavlinkFtpClient::ClientResult MavlinkFtpClient::set_target_compid(uint8_t component_id)
×
1395
{
1396
    _target_component_id = component_id;
×
1397
    _target_component_id_set = true;
×
1398
    return ClientResult::Success;
×
1399
}
1400

1401
std::ostream& operator<<(std::ostream& str, MavlinkFtpClient::ClientResult const& result)
×
1402
{
1403
    switch (result) {
×
1404
        default:
×
1405
            // Fallthrough
1406
        case MavlinkFtpClient::ClientResult::Unknown:
1407
            return str << "Unknown";
×
1408
        case MavlinkFtpClient::ClientResult::Success:
×
1409
            return str << "Success";
×
1410
        case MavlinkFtpClient::ClientResult::Next:
×
1411
            return str << "Next";
×
1412
        case MavlinkFtpClient::ClientResult::Timeout:
×
1413
            return str << "Timeout";
×
1414
        case MavlinkFtpClient::ClientResult::Busy:
×
1415
            return str << "Busy";
×
1416
        case MavlinkFtpClient::ClientResult::FileIoError:
×
1417
            return str << "FileIoError";
×
1418
        case MavlinkFtpClient::ClientResult::FileExists:
×
1419
            return str << "FileExists";
×
1420
        case MavlinkFtpClient::ClientResult::FileDoesNotExist:
×
1421
            return str << "FileDoesNotExist";
×
1422
        case MavlinkFtpClient::ClientResult::FileProtected:
×
1423
            return str << "FileProtected";
×
1424
        case MavlinkFtpClient::ClientResult::InvalidParameter:
×
1425
            return str << "InvalidParameter";
×
1426
        case MavlinkFtpClient::ClientResult::Unsupported:
×
1427
            return str << "Unsupported";
×
1428
        case MavlinkFtpClient::ClientResult::ProtocolError:
×
1429
            return str << "ProtocolError";
×
1430
        case MavlinkFtpClient::ClientResult::NoSystem:
×
1431
            return str << "NoSystem";
×
1432
    }
1433
}
1434

1435
} // 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