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

mavlink / MAVSDK / 16613073909

30 Jul 2025 03:56AM UTC coverage: 44.304% (-2.0%) from 46.31%
16613073909

Pull #2626

github

web-flow
Merge ed7bedb1f into c0a7c02a0
Pull Request #2626: core: flush after each Log* line

188 of 290 new or added lines in 23 files covered. (64.83%)

414 existing lines in 21 files now uncovered.

15335 of 34613 relevant lines covered (44.3%)

286.22 hits per line

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

60.7
/src/mavsdk/core/mavlink_ftp_server.cpp
1
#include <algorithm>
2
#include <fstream>
3
#include <filesystem>
4
#include <future>
5

6
#include "mavlink_ftp_server.h"
7
#include "server_component_impl.h"
8
#include "unused.h"
9
#include "crc32.h"
10

11
namespace mavsdk {
12

13
namespace fs = std::filesystem;
14

15
MavlinkFtpServer::MavlinkFtpServer(ServerComponentImpl& server_component_impl) :
88✔
16
    _server_component_impl(server_component_impl)
88✔
17
{
18
    if (const char* env_p = std::getenv("MAVSDK_FTP_DEBUGGING")) {
88✔
19
        if (std::string(env_p) == "1") {
×
20
            LogDebug() << "Ftp debugging is on.";
×
21
            _debugging = true;
×
22
        }
23
    }
24

25
    _server_component_impl.register_mavlink_message_handler(
88✔
26
        MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL,
27
        [this](const mavlink_message_t& message) { process_mavlink_ftp_message(message); },
1,207✔
28
        this);
29
}
88✔
30

31
void MavlinkFtpServer::process_mavlink_ftp_message(const mavlink_message_t& msg)
1,207✔
32
{
33
    // TODO: implement stream sending
34
    bool stream_send = false;
1,207✔
35
    UNUSED(stream_send);
1,207✔
36

37
    mavlink_file_transfer_protocol_t ftp_req;
1,207✔
38
    mavlink_msg_file_transfer_protocol_decode(&msg, &ftp_req);
1,207✔
39

40
    if (_debugging) {
1,207✔
41
        LogDebug() << "Processing FTP message to target compid: "
×
42
                   << std::to_string(ftp_req.target_component) << ", our compid: "
×
43
                   << std::to_string(_server_component_impl.get_own_component_id());
×
44
    }
45

46
    if (ftp_req.target_system != 0 &&
2,414✔
47
        ftp_req.target_system != _server_component_impl.get_own_system_id()) {
1,207✔
48
        if (_debugging) {
×
49
            LogDebug() << "Received FTP message with wrong target system ID";
×
50
        }
51
        return;
×
52
    }
53

54
    if (ftp_req.target_component != 0 &&
2,396✔
55
        ftp_req.target_component != _server_component_impl.get_own_component_id()) {
1,189✔
56
        if (_debugging) {
9✔
57
            LogDebug() << "Received FTP message with wrong target component ID";
×
58
        }
59
        return;
9✔
60
    }
61

62
    const PayloadHeader& payload = *reinterpret_cast<PayloadHeader*>(&ftp_req.payload[0]);
1,198✔
63

64
    // Basic sanity check: validate length before use.
65
    if (payload.size > max_data_length) {
1,198✔
66
        auto response = PayloadHeader{};
×
67
        response.seq_number = payload.seq_number + 1;
×
68
        response.req_opcode = payload.opcode;
×
69
        response.opcode = Opcode::RSP_NAK;
×
70
        response.data[0] = ServerResult::ERR_INVALID_DATA_SIZE;
×
71
        response.size = 1;
×
72
        _send_mavlink_ftp_message(response);
×
73

74
    } else {
75
        if (_debugging) {
1,198✔
76
            LogDebug() << "FTP opcode: " << (int)payload.opcode << ", size: " << (int)payload.size
×
77
                       << ", offset: " << (int)payload.offset << ", seq: " << payload.seq_number;
×
78
        }
79

80
        _target_system_id = msg.sysid;
1,198✔
81
        _target_component_id = msg.compid;
1,198✔
82

83
        switch (payload.opcode) {
1,198✔
84
            case Opcode::CMD_NONE:
×
85
                if (_debugging) {
×
86
                    LogDebug() << "OPC:CMD_NONE";
×
87
                }
88
                break;
×
89

90
            case Opcode::CMD_TERMINATE_SESSION:
32✔
91
                if (_debugging) {
32✔
92
                    LogDebug() << "OPC:CMD_TERMINATE_SESSION";
×
93
                }
94
                _work_terminate(payload);
32✔
95
                break;
32✔
96

97
            case Opcode::CMD_RESET_SESSIONS:
×
98
                if (_debugging) {
×
99
                    LogDebug() << "OPC:CMD_RESET_SESSIONS";
×
100
                }
101
                _work_reset(payload);
×
102
                break;
×
103

104
            case Opcode::CMD_LIST_DIRECTORY:
11✔
105
                if (_debugging) {
11✔
106
                    LogDebug() << "OPC:CMD_LIST_DIRECTORY";
×
107
                }
108
                _work_list(payload);
11✔
109
                break;
11✔
110

111
            case Opcode::CMD_OPEN_FILE_RO:
18✔
112
                if (_debugging) {
18✔
113
                    LogDebug() << "OPC:CMD_OPEN_FILE_RO";
×
114
                }
115
                _work_open_file_readonly(payload);
18✔
116
                break;
18✔
117

118
            case Opcode::CMD_CREATE_FILE:
7✔
119
                if (_debugging) {
7✔
120
                    LogDebug() << "OPC:CMD_CREATE_FILE";
×
121
                }
122
                _work_create_file(payload);
7✔
123
                break;
7✔
124

125
            case Opcode::CMD_OPEN_FILE_WO:
×
126
                if (_debugging) {
×
127
                    LogDebug() << "OPC:CMD_OPEN_FILE_WO";
×
128
                }
129
                _work_open_file_writeonly(payload);
×
130
                break;
×
131

132
            case Opcode::CMD_READ_FILE:
286✔
133
                if (_debugging) {
286✔
134
                    LogDebug() << "OPC:CMD_READ_FILE";
×
135
                }
136
                _work_read(payload);
286✔
137
                break;
286✔
138

139
            case Opcode::CMD_BURST_READ_FILE:
8✔
140
                if (_debugging) {
8✔
141
                    LogDebug() << "OPC:CMD_BURST_READ_FILE";
×
142
                }
143
                _work_burst(payload);
8✔
144
                break;
8✔
145

146
            case Opcode::CMD_WRITE_FILE:
94✔
147
                if (_debugging) {
94✔
148
                    LogDebug() << "OPC:CMD_WRITE_FILE";
×
149
                }
150
                _work_write(payload);
94✔
151
                break;
94✔
152

153
            case Opcode::CMD_REMOVE_FILE:
4✔
154
                if (_debugging) {
4✔
155
                    LogDebug() << "OPC:CMD_REMOVE_FILE";
×
156
                }
157
                _work_remove_file(payload);
4✔
158
                break;
4✔
159

160
            case Opcode::CMD_RENAME:
2✔
161
                if (_debugging) {
2✔
162
                    LogDebug() << "OPC:CMD_RENAME";
×
163
                }
164
                _work_rename(payload);
2✔
165
                break;
2✔
166

167
            case Opcode::CMD_CREATE_DIRECTORY:
3✔
168
                if (_debugging) {
3✔
169
                    LogDebug() << "OPC:CMD_CREATE_DIRECTORY";
×
170
                }
171
                _work_create_directory(payload);
3✔
172
                break;
3✔
173

174
            case Opcode::CMD_REMOVE_DIRECTORY:
2✔
175
                if (_debugging) {
2✔
176
                    LogDebug() << "OPC:CMD_REMOVE_DIRECTORY";
×
177
                }
178
                _work_remove_directory(payload);
2✔
179
                break;
2✔
180

181
            case Opcode::CMD_CALC_FILE_CRC32:
3✔
182
                if (_debugging) {
3✔
183
                    LogDebug() << "OPC:CMD_CALC_FILE_CRC32";
×
184
                }
185
                _work_calc_file_CRC32(payload);
3✔
186
                break;
3✔
187

188
            default:
728✔
189
                // Not for us, ignore it.
190
                return;
728✔
191
        }
192
    }
193
}
194

195
MavlinkFtpServer::~MavlinkFtpServer()
88✔
196
{
197
    _server_component_impl.unregister_all_mavlink_message_handlers(this);
88✔
198

199
    std::lock_guard<std::mutex> lock(_mutex);
88✔
200
    _reset();
88✔
201
}
176✔
202

203
void MavlinkFtpServer::_send_mavlink_ftp_message(const PayloadHeader& payload)
744✔
204
{
205
    if (uint8_t(payload.opcode) == 0) {
744✔
206
        abort();
×
207
    }
208

209
    _server_component_impl.queue_message([&](MavlinkAddress mavlink_address, uint8_t channel) {
744✔
210
        mavlink_message_t message;
211
        mavlink_msg_file_transfer_protocol_pack_chan(
744✔
212
            mavlink_address.system_id,
744✔
213
            mavlink_address.component_id,
744✔
214
            channel,
215
            &message,
216
            _network_id,
744✔
217
            _target_system_id,
744✔
218
            _target_component_id,
744✔
219
            reinterpret_cast<const uint8_t*>(&payload));
744✔
220
        return message;
744✔
221
    });
222
}
744✔
223

224
std::string MavlinkFtpServer::_data_as_string(const PayloadHeader& payload, size_t entry)
76✔
225
{
226
    size_t start = 0;
76✔
227
    size_t end = 0;
76✔
228
    std::string result;
76✔
229

230
    for (int i = entry; i >= 0; --i) {
153✔
231
        start = end;
77✔
232
        end +=
77✔
233
            strnlen(reinterpret_cast<const char*>(&payload.data[start]), max_data_length - start) +
77✔
234
            1;
235
    }
236

237
    result.resize(end - start);
76✔
238
    std::memcpy(result.data(), &payload.data[start], end - start);
76✔
239

240
    return result;
76✔
241
}
242

243
std::variant<std::string, MavlinkFtpServer::ServerResult>
244
MavlinkFtpServer::_path_from_payload(const PayloadHeader& payload, size_t entry)
51✔
245
{
246
    // Requires lock
247

248
    auto data = _data_as_string(payload, entry);
51✔
249
    return _path_from_string(data);
51✔
250
}
51✔
251

252
std::variant<std::string, MavlinkFtpServer::ServerResult>
253
MavlinkFtpServer::_path_from_string(const std::string& payload_path)
51✔
254
{
255
    // Requires lock
256

257
    // No permission whatsoever if the root dir is not set.
258
    if (_root_dir.empty()) {
51✔
259
        LogWarn() << "Root dir not set!";
9✔
260
        return ServerResult::ERR_FAIL;
9✔
261
    }
262

263
    // Take a copy before we mess with it.
264
    auto temp_path = payload_path;
42✔
265

266
    // We strip leading slashes (like absolute paths).
267
    if (temp_path.size() >= 1 && temp_path[0] == '/') {
42✔
268
        temp_path = temp_path.substr(1, temp_path.size());
×
269
    }
270

271
    fs::path combined_path = (fs::path(_root_dir) / temp_path).lexically_normal();
168✔
272

273
    // Check whether the combined path is inside the root dir.
274
    // From: https://stackoverflow.com/a/61125335/8548472
275
    auto ret = std::mismatch(_root_dir.begin(), _root_dir.end(), combined_path.string().begin());
42✔
276
    if (ret.first != _root_dir.end()) {
42✔
277
        LogWarn() << "Not inside root dir: " << combined_path.string()
12✔
278
                  << ", root dir: " << _root_dir;
12✔
279
        return ServerResult::ERR_FAIL;
4✔
280
    }
281

282
    return combined_path.string();
38✔
283
}
42✔
284

285
void MavlinkFtpServer::set_root_directory(const std::string& root_dir)
27✔
286
{
287
    std::lock_guard<std::mutex> lock(_mutex);
27✔
288

289
    std::error_code ec;
27✔
290
    _root_dir = fs::canonical(fs::path(root_dir), ec).string();
27✔
291
    if (ec) {
27✔
292
        LogWarn() << "Root dir could not be made absolute: " << ec.message();
×
293
    }
294
    if (_debugging) {
27✔
295
        LogDebug() << "Set root dir to: " << _root_dir << " from: " << root_dir;
×
296
    }
297
}
27✔
298

299
void MavlinkFtpServer::_work_list(const PayloadHeader& payload)
11✔
300
{
301
    auto response = PayloadHeader{};
11✔
302
    response.seq_number = payload.seq_number + 1;
11✔
303
    response.req_opcode = payload.opcode;
11✔
304

305
    std::lock_guard<std::mutex> lock(_mutex);
11✔
306

307
    auto maybe_path = _path_from_payload(payload);
11✔
308
    if (std::holds_alternative<ServerResult>(maybe_path)) {
11✔
309
        response.opcode = Opcode::RSP_NAK;
1✔
310
        response.size = 1;
1✔
311
        response.data[0] = std::get<ServerResult>(maybe_path);
1✔
312
        _send_mavlink_ftp_message(response);
1✔
313
        return;
1✔
314
    }
315

316
    fs::path path = std::get<std::string>(maybe_path);
10✔
317

318
    uint8_t offset = 0;
10✔
319

320
    // Move to the requested offset
321
    uint32_t requested_offset = payload.offset;
10✔
322

323
    std::error_code ec;
10✔
324
    if (!fs::exists(path, ec)) {
10✔
325
        LogWarn() << "FTP: can't open path " << path;
×
326
        // this is not an FTP error, abort directory by simulating eof
327
        response.opcode = Opcode::RSP_NAK;
×
328
        response.size = 1;
×
329
        response.data[0] = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
330
        _send_mavlink_ftp_message(response);
×
331
        return;
×
332
    }
333

334
    if (_debugging) {
10✔
335
        LogDebug() << "Opening path: " << path.string();
×
336
    }
337

338
    for (const auto& entry : fs::directory_iterator(fs::canonical(path))) {
1,241✔
339
        if (requested_offset > 0) {
1,219✔
340
            requested_offset--;
1,011✔
341
            continue;
1,011✔
342
        }
343
        const auto name = entry.path().filename();
208✔
344

345
        std::string payload_str;
208✔
346

347
        const auto is_regular_file = entry.is_regular_file(ec);
208✔
348
        if (ec) {
208✔
349
            LogWarn() << "Could not determine whether '" << entry.path().string()
×
350
                      << "' is a file: " << ec.message();
×
351
            continue;
×
352
        }
353

354
        const auto is_directory = entry.is_directory(ec);
208✔
355
        if (ec) {
208✔
356
            LogWarn() << "Could not determine whether '" << entry.path().string()
×
357
                      << "' is a directory: " << ec.message();
×
358
            continue;
×
359
        }
360

361
        if (is_regular_file) {
208✔
362
            const auto filesize = fs::file_size(entry.path(), ec);
106✔
363
            if (ec) {
106✔
364
                LogWarn() << "Could not get file size of '" << entry.path().string()
×
365
                          << "': " << ec.message();
×
366
                continue;
×
367
            }
368

369
            if (_debugging) {
106✔
370
                LogDebug() << "Found file: " << name.string() << ", size: " << filesize << " bytes";
×
371
            }
372

373
            payload_str += 'F';
106✔
374
            payload_str += name.string();
106✔
375
            payload_str += '\t';
106✔
376
            payload_str += std::to_string(filesize);
106✔
377

378
        } else if (is_directory) {
102✔
379
            if (_debugging) {
102✔
380
                LogDebug() << "Found directory: " << name.string();
×
381
            }
382

383
            payload_str += 'D';
102✔
384
            payload_str += name.string();
102✔
385

386
        } else {
387
            // Ignore all other types.
388
            continue;
×
389
        }
390

391
        auto required_len = payload_str.length() + 1;
208✔
392

393
        // Do we have room for the dir entry and the null terminator?
394
        if (offset + required_len > max_data_length) {
208✔
395
            break;
8✔
396
        }
397

398
        std::memcpy(&response.data[offset], payload_str.c_str(), required_len);
200✔
399

400
        offset += static_cast<uint8_t>(required_len);
200✔
401
    }
226✔
402

403
    if (offset == 0) {
10✔
404
        // We are done and need to respond with EOF.
405
        response.opcode = Opcode::RSP_NAK;
1✔
406
        response.size = 1;
1✔
407
        response.data[0] = ServerResult::ERR_EOF;
1✔
408

409
    } else {
410
        response.opcode = Opcode::RSP_ACK;
9✔
411
        response.size = offset;
9✔
412
    }
413

414
    _send_mavlink_ftp_message(response);
10✔
415
}
12✔
416

417
void MavlinkFtpServer::_work_open_file_readonly(const PayloadHeader& payload)
18✔
418
{
419
    auto response = PayloadHeader{};
18✔
420
    response.seq_number = payload.seq_number + 1;
18✔
421
    response.req_opcode = payload.opcode;
18✔
422

423
    std::lock_guard<std::mutex> lock(_mutex);
18✔
424
    if (_session_info.ifstream.is_open()) {
18✔
425
        _reset();
2✔
426
    }
427

428
    std::string path;
18✔
429
    {
430
        std::lock_guard<std::mutex> tmp_lock(_tmp_files_mutex);
18✔
431
        const auto it = _tmp_files.find(_data_as_string(payload));
18✔
432
        if (it != _tmp_files.end()) {
18✔
433
            path = it->second;
×
434
        } else {
435
            auto maybe_path = _path_from_payload(payload);
18✔
436
            if (std::holds_alternative<ServerResult>(maybe_path)) {
18✔
437
                response.opcode = Opcode::RSP_NAK;
4✔
438
                response.size = 1;
4✔
439
                response.data[0] = std::get<ServerResult>(maybe_path);
4✔
440
                _send_mavlink_ftp_message(response);
4✔
441
                return;
4✔
442
            }
443

444
            path = std::get<std::string>(maybe_path);
14✔
445
        }
18✔
446
    }
18✔
447

448
    if (_debugging) {
14✔
449
        LogInfo() << "Finding " << path << " in " << _root_dir;
×
450
    }
451
    if (path.rfind(_root_dir, 0) != 0) {
14✔
452
        LogWarn() << "FTP: invalid path " << path;
×
453
        response.opcode = Opcode::RSP_NAK;
×
454
        response.size = 1;
×
455
        response.data[0] = ServerResult::ERR_FAIL;
×
456
        _send_mavlink_ftp_message(response);
×
457
        return;
×
458
    }
459

460
    if (_debugging) {
14✔
461
        LogDebug() << "Going to open readonly: " << path;
×
462
    }
463

464
    std::error_code ec;
14✔
465
    if (!fs::exists(path, ec)) {
14✔
466
        LogErr() << "FTP: Open failed - file doesn't exist";
×
467
        response.opcode = Opcode::RSP_NAK;
×
468
        response.size = 1;
×
469
        response.data[0] = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
470
        _send_mavlink_ftp_message(response);
×
471
        return;
×
472
    }
473

474
    auto file_size = static_cast<uint32_t>(fs::file_size(path, ec));
14✔
475
    if (ec) {
14✔
476
        LogErr() << "Could not determine file size of '" << path << "': " << ec.message();
×
477
        return;
×
478
    }
479

480
    if (_debugging) {
14✔
481
        LogDebug() << "Determined filesize to be: " << file_size << " bytes";
×
482
    }
483

484
    std::ifstream ifstream;
14✔
485
    ifstream.open(path, std::ios::in | std::ios::binary);
14✔
486

487
    if (!ifstream.is_open()) {
14✔
488
        LogWarn() << "FTP: Open failed";
×
489
        response.opcode = Opcode::RSP_NAK;
×
490
        response.size = 1;
×
491
        response.data[0] = ServerResult::ERR_FAIL;
×
492
        _send_mavlink_ftp_message(response);
×
493
        return;
×
494
    }
495

496
    _session_info.ifstream = std::move(ifstream);
14✔
497
    _session_info.file_size = file_size;
14✔
498

499
    response.opcode = Opcode::RSP_ACK;
14✔
500
    response.session = 0;
14✔
501
    response.seq_number = payload.seq_number + 1;
14✔
502
    response.size = sizeof(uint32_t);
14✔
503
    std::memcpy(response.data, &file_size, response.size);
14✔
504

505
    _send_mavlink_ftp_message(response);
14✔
506
}
22✔
507

508
void MavlinkFtpServer::_work_open_file_writeonly(const PayloadHeader& payload)
×
509
{
510
    auto response = PayloadHeader{};
×
511
    response.seq_number = payload.seq_number + 1;
×
512
    response.req_opcode = payload.opcode;
×
513

514
    std::lock_guard<std::mutex> lock(_mutex);
×
515

516
    if (_session_info.ofstream.is_open()) {
×
517
        _reset();
×
518
    }
519

520
    std::string path;
×
521
    {
522
        std::lock_guard<std::mutex> tmp_lock(_tmp_files_mutex);
×
523
        const auto it = _tmp_files.find(_data_as_string(payload));
×
524
        if (it != _tmp_files.end()) {
×
525
            path = it->second;
×
526
        } else {
527
            auto maybe_path = _path_from_payload(payload);
×
528
            if (std::holds_alternative<ServerResult>(maybe_path)) {
×
529
                response.opcode = Opcode::RSP_NAK;
×
530
                response.size = 1;
×
531
                response.data[0] = std::get<ServerResult>(maybe_path);
×
532
                _send_mavlink_ftp_message(response);
×
533
                return;
×
534
            }
535

536
            path = std::get<std::string>(maybe_path);
×
537
        }
×
538
    }
×
539

540
    if (path.empty()) {
×
541
        response.opcode = Opcode::RSP_NAK;
×
542
        response.size = 1;
×
543
        response.data[0] = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
544
        _send_mavlink_ftp_message(response);
×
545
        return;
×
546
    }
547

548
    if (_debugging) {
×
549
        LogDebug() << "Finding " << path << " in " << _root_dir;
×
550
    }
551
    if (path.rfind(_root_dir, 0) != 0) {
×
552
        LogWarn() << "FTP: invalid path " << path;
×
553
        response.opcode = Opcode::RSP_NAK;
×
554
        response.size = 1;
×
555
        response.data[0] = ServerResult::ERR_FAIL;
×
556
        _send_mavlink_ftp_message(response);
×
557
        return;
×
558
    }
559

560
    if (_debugging) {
×
561
        LogDebug() << "Going to open writeonly: " << path;
×
562
    }
563

564
    // fail only if requested open for read
565
    std::error_code ec;
×
566
    if (!fs::exists(path, ec)) {
×
567
        LogWarn() << "FTP: Open failed - file not found";
×
568
        response.opcode = Opcode::RSP_NAK;
×
569
        response.size = 1;
×
570
        response.data[0] = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
571
        _send_mavlink_ftp_message(response);
×
572
        return;
×
573
    }
574

575
    auto file_size = static_cast<uint32_t>(fs::file_size(path, ec));
×
576
    if (ec) {
×
577
        LogErr() << "Could not determine file size of '" << path << "': " << ec.message();
×
578
        return;
×
579
    }
580

581
    if (_debugging) {
×
582
        LogDebug() << "Determined filesize to be: " << file_size << " bytes";
×
583
    }
584

585
    std::ofstream ofstream;
×
586
    ofstream.open(path, std::ios::out | std::ios::binary);
×
587

588
    if (!ofstream.is_open()) {
×
589
        LogWarn() << "FTP: Open failed";
×
590
        response.opcode = Opcode::RSP_NAK;
×
591
        response.size = 1;
×
592
        response.data[0] = ServerResult::ERR_FAIL;
×
593
        _send_mavlink_ftp_message(response);
×
594
        return;
×
595
    }
596

597
    _session_info.ofstream = std::move(ofstream);
×
598
    _session_info.file_size = file_size;
×
599

600
    response.opcode = Opcode::RSP_ACK;
×
601
    response.session = 0;
×
602
    response.size = sizeof(uint32_t);
×
603
    std::memcpy(response.data, &file_size, response.size);
×
604

605
    _send_mavlink_ftp_message(response);
×
606
}
×
607

608
void MavlinkFtpServer::_work_create_file(const PayloadHeader& payload)
7✔
609
{
610
    auto response = PayloadHeader{};
7✔
611
    response.seq_number = payload.seq_number + 1;
7✔
612
    response.req_opcode = payload.opcode;
7✔
613

614
    std::lock_guard<std::mutex> lock(_mutex);
7✔
615
    if (_session_info.ofstream.is_open()) {
7✔
616
        _reset();
1✔
617
    }
618

619
    std::string path;
7✔
620
    {
621
        std::lock_guard<std::mutex> tmp_lock(_tmp_files_mutex);
7✔
622
        const auto it = _tmp_files.find(_data_as_string(payload));
7✔
623
        if (it != _tmp_files.end()) {
7✔
624
            path = it->second;
×
625
        } else {
626
            auto maybe_path = _path_from_payload(payload);
7✔
627
            if (std::holds_alternative<ServerResult>(maybe_path)) {
7✔
628
                response.opcode = Opcode::RSP_NAK;
2✔
629
                response.size = 1;
2✔
630
                response.data[0] = std::get<ServerResult>(maybe_path);
2✔
631
                _send_mavlink_ftp_message(response);
2✔
632
                return;
2✔
633
            }
634

635
            path = std::get<std::string>(maybe_path);
5✔
636
        }
7✔
637
    }
7✔
638

639
    if (path.empty()) {
5✔
640
        response.opcode = Opcode::RSP_NAK;
×
641
        response.size = 1;
×
642
        response.data[0] = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
643
        _send_mavlink_ftp_message(response);
×
644
        return;
×
645
    }
646

647
    if (_debugging) {
5✔
648
        LogInfo() << "Finding " << path << " in " << _root_dir;
×
649
    }
650
    if (path.rfind(_root_dir, 0) != 0) {
5✔
651
        LogWarn() << "FTP: invalid path " << path;
×
652
        response.opcode = Opcode::RSP_NAK;
×
653
        response.size = 1;
×
654
        response.data[0] = ServerResult::ERR_FAIL;
×
655
        _send_mavlink_ftp_message(response);
×
656
        return;
×
657
    }
658

659
    if (_debugging) {
5✔
660
        LogDebug() << "Creating file: " << path;
×
661
    }
662

663
    std::ofstream ofstream;
5✔
664
    ofstream.open(path, std::ios::out | std::ios::binary);
5✔
665

666
    if (!ofstream.is_open()) {
5✔
667
        LogWarn() << "FTP: Open failed";
×
668
        response.opcode = Opcode::RSP_NAK;
×
669
        response.size = 1;
×
670
        response.data[0] = ServerResult::ERR_FAIL;
×
671
        _send_mavlink_ftp_message(response);
×
672
        return;
×
673
    }
674

675
    _session_info.ofstream = std::move(ofstream);
5✔
676
    _session_info.file_size = 0;
5✔
677

678
    response.session = 0;
5✔
679
    response.size = 0;
5✔
680
    response.opcode = Opcode::RSP_ACK;
5✔
681

682
    _send_mavlink_ftp_message(response);
5✔
683
}
9✔
684

685
void MavlinkFtpServer::_work_read(const PayloadHeader& payload)
286✔
686
{
687
    auto response = PayloadHeader{};
286✔
688
    response.seq_number = payload.seq_number + 1;
286✔
689
    response.req_opcode = payload.opcode;
286✔
690

691
    std::lock_guard<std::mutex> lock(_mutex);
286✔
692
    if (payload.session != 0 || !_session_info.ifstream.is_open()) {
286✔
693
        _reset();
×
694
    }
695

696
    // We have to test seek past EOF ourselves, lseek will allow seek past EOF
697
    if (payload.offset >= _session_info.file_size) {
286✔
698
        response.opcode = Opcode::RSP_NAK;
×
699
        response.size = 1;
×
700
        response.data[0] = ServerResult::ERR_EOF;
×
701
        if (_debugging) {
×
702
            LogDebug() << "Reached EOF reading";
×
703
        }
704
        _send_mavlink_ftp_message(response);
×
705
        return;
×
706
    }
707

708
    _session_info.ifstream.seekg(payload.offset);
286✔
709
    if (_session_info.ifstream.fail()) {
286✔
710
        response.opcode = Opcode::RSP_NAK;
×
711
        response.size = 1;
×
712
        response.data[0] = ServerResult::ERR_FAIL;
×
713
        LogWarn() << "Seek failed";
×
714
        _send_mavlink_ftp_message(response);
×
715
        return;
×
716
    }
717

718
    if (_debugging) {
286✔
719
        LogWarn() << "Read at " << payload.offset << " for " << int(payload.size);
×
720
    }
721

722
    _session_info.ifstream.read(reinterpret_cast<char*>(response.data), payload.size);
286✔
723

724
    if (_session_info.ifstream.fail()) {
286✔
725
        response.opcode = Opcode::RSP_NAK;
×
726
        response.size = 1;
×
727
        response.data[0] = ServerResult::ERR_FAIL;
×
728
        LogWarn() << "Read failed";
×
729
        _send_mavlink_ftp_message(response);
×
730
        return;
×
731
    }
732

733
    const uint32_t bytes_read = _session_info.ifstream.gcount();
286✔
734

735
    response.offset = payload.offset;
286✔
736
    response.size = bytes_read;
286✔
737
    response.opcode = Opcode::RSP_ACK;
286✔
738

739
    _send_mavlink_ftp_message(response);
286✔
740
}
286✔
741

742
void MavlinkFtpServer::_work_burst(const PayloadHeader& payload)
8✔
743
{
744
    auto response = PayloadHeader{};
8✔
745
    response.req_opcode = payload.opcode;
8✔
746

747
    std::lock_guard<std::mutex> lock(_mutex);
8✔
748
    if (payload.session != 0 || !_session_info.ifstream.is_open()) {
8✔
749
        _reset();
×
750
    }
751

752
    // We have to test seek past EOF ourselves, lseek will allow seek past EOF
753
    if (payload.offset >= _session_info.file_size) {
8✔
754
        response.seq_number = payload.seq_number + 1;
×
755
        response.opcode = Opcode::RSP_NAK;
×
756
        response.size = 1;
×
757
        response.data[0] = ServerResult::ERR_EOF;
×
758
        if (_debugging) {
×
759
            LogDebug() << "Reached EOF reading";
×
760
        }
761
        _send_mavlink_ftp_message(response);
×
762
        return;
×
763
    }
764

765
    if (_debugging) {
8✔
766
        LogDebug() << "Seek to " << payload.offset;
×
767
    }
768
    _session_info.ifstream.seekg(payload.offset);
8✔
769
    if (_session_info.ifstream.fail()) {
8✔
770
        response.seq_number = payload.seq_number + 1;
×
771
        response.opcode = Opcode::RSP_NAK;
×
772
        response.size = 1;
×
773
        response.data[0] = ServerResult::ERR_FAIL;
×
774
        LogErr() << "Seek failed";
×
775
        _send_mavlink_ftp_message(response);
×
776
        return;
×
777
    }
778

779
    _session_info.burst_offset = payload.offset;
8✔
780
    _session_info.burst_chunk_size = payload.size;
8✔
781
    _burst_seq = payload.seq_number + 1;
8✔
782

783
    if (_session_info.burst_thread.joinable()) {
8✔
784
        _session_info.burst_stop = true;
×
785
        _session_info.burst_thread.join();
×
786
    }
787

788
    _session_info.burst_stop = false;
8✔
789

790
    // Schedule sending out burst messages.
791
    _session_info.burst_thread = std::thread([this]() {
16✔
792
        while (!_session_info.burst_stop)
282✔
793
            if (_send_burst_packet())
282✔
794
                break;
8✔
795
    });
8✔
796

797
    // Don't send response as that's done in the call every burst call above.
798
}
8✔
799

800
// Returns true if sending is complete
801
bool MavlinkFtpServer::_send_burst_packet()
282✔
802
{
803
    std::lock_guard<std::mutex> lock(_mutex);
282✔
804
    if (!_session_info.ifstream.is_open()) {
282✔
805
        return false;
×
806
    }
807

808
    PayloadHeader burst_packet{};
282✔
809
    burst_packet.req_opcode = Opcode::CMD_BURST_READ_FILE;
282✔
810
    burst_packet.seq_number = _burst_seq++;
282✔
811

812
    _make_burst_packet(burst_packet);
282✔
813

814
    _send_mavlink_ftp_message(burst_packet);
282✔
815

816
    if (burst_packet.burst_complete == 1) {
282✔
817
        return true;
8✔
818
    }
819

820
    return false;
274✔
821
}
282✔
822

823
void MavlinkFtpServer::_make_burst_packet(PayloadHeader& packet)
282✔
824
{
825
    uint32_t bytes_to_read = std::min(
282✔
826
        static_cast<uint32_t>(_session_info.burst_chunk_size),
282✔
827
        _session_info.file_size - _session_info.burst_offset);
564✔
828

829
    if (_debugging) {
282✔
830
        LogDebug() << "Burst read of " << bytes_to_read << " bytes";
×
831
    }
832
    _session_info.ifstream.read(reinterpret_cast<char*>(packet.data), bytes_to_read);
282✔
833

834
    if (_session_info.ifstream.fail()) {
282✔
835
        packet.opcode = Opcode::RSP_NAK;
×
836
        packet.size = 1;
×
837
        packet.data[0] = ServerResult::ERR_FAIL;
×
838
        LogWarn() << "Burst read failed";
×
839
        return;
×
840
    }
841

842
    const uint32_t bytes_read = _session_info.ifstream.gcount();
282✔
843
    packet.size = bytes_read;
282✔
844
    packet.opcode = Opcode::RSP_ACK;
282✔
845

846
    packet.offset = _session_info.burst_offset;
282✔
847
    _session_info.burst_offset += bytes_read;
282✔
848

849
    if (_session_info.burst_offset == _session_info.file_size) {
282✔
850
        // Last read, we are done for this burst.
851
        packet.burst_complete = 1;
8✔
852
        if (_debugging) {
8✔
853
            LogDebug() << "Burst complete";
×
854
        }
855
    }
856
}
857

858
void MavlinkFtpServer::_work_write(const PayloadHeader& payload)
94✔
859
{
860
    auto response = PayloadHeader{};
94✔
861
    response.seq_number = payload.seq_number + 1;
94✔
862
    response.req_opcode = payload.opcode;
94✔
863

864
    std::lock_guard<std::mutex> lock(_mutex);
94✔
865
    if (payload.session != 0 && !_session_info.ofstream.is_open()) {
94✔
866
        _reset();
×
867
    }
868

869
    _session_info.ofstream.seekp(payload.offset);
94✔
870
    if (_session_info.ifstream.fail()) {
94✔
871
        response.opcode = Opcode::RSP_NAK;
×
872
        response.size = 1;
×
873
        response.data[0] = ServerResult::ERR_FAIL;
×
874
        LogWarn() << "Seek failed";
×
875
        _send_mavlink_ftp_message(response);
×
876
        return;
×
877
    }
878

879
    _session_info.ofstream.write(reinterpret_cast<const char*>(payload.data), payload.size);
94✔
880
    if (_session_info.ofstream.fail()) {
94✔
881
        response.opcode = Opcode::RSP_NAK;
×
882
        response.size = 1;
×
883
        response.data[0] = ServerResult::ERR_FAIL;
×
884
        LogWarn() << "Write failed";
×
885
        _send_mavlink_ftp_message(response);
×
886
        return;
×
887
    }
888

889
    response.opcode = Opcode::RSP_ACK;
94✔
890
    response.size = 0;
94✔
891

892
    _send_mavlink_ftp_message(response);
94✔
893
}
94✔
894

895
void MavlinkFtpServer::_work_terminate(const PayloadHeader& payload)
32✔
896
{
897
    {
898
        std::lock_guard<std::mutex> lock(_mutex);
32✔
899
        _reset();
32✔
900
    }
32✔
901

902
    auto response = PayloadHeader{};
32✔
903
    response.seq_number = payload.seq_number + 1;
32✔
904
    response.req_opcode = payload.opcode;
32✔
905
    response.opcode = Opcode::RSP_ACK;
32✔
906
    response.size = 0;
32✔
907
    _send_mavlink_ftp_message(response);
32✔
908
}
32✔
909

910
void MavlinkFtpServer::_reset()
123✔
911
{
912
    // requires lock
913
    if (_session_info.ifstream.is_open()) {
123✔
914
        _session_info.ifstream.close();
14✔
915
    }
916

917
    if (_session_info.ofstream.is_open()) {
123✔
918
        _session_info.ofstream.close();
5✔
919
    }
920

921
    _session_info.burst_stop = true;
123✔
922
    if (_session_info.burst_thread.joinable()) {
123✔
923
        _session_info.burst_thread.join();
8✔
924
    }
925
}
123✔
926

927
void MavlinkFtpServer::_work_reset(const PayloadHeader& payload)
×
928
{
929
    {
930
        std::lock_guard<std::mutex> lock(_mutex);
×
931
        _reset();
×
932
    }
×
933

934
    auto response = PayloadHeader{};
×
935
    response.seq_number = payload.seq_number + 1;
×
936
    response.req_opcode = payload.opcode;
×
937
    response.opcode = Opcode::RSP_ACK;
×
938
    response.size = 0;
×
939
    _send_mavlink_ftp_message(response);
×
940
}
×
941

942
void MavlinkFtpServer::_work_remove_directory(const PayloadHeader& payload)
2✔
943
{
944
    auto response = PayloadHeader{};
2✔
945
    response.seq_number = payload.seq_number + 1;
2✔
946
    response.req_opcode = payload.opcode;
2✔
947

948
    std::lock_guard<std::mutex> lock(_mutex);
2✔
949

950
    auto maybe_path = _path_from_payload(payload);
2✔
951
    if (std::holds_alternative<ServerResult>(maybe_path)) {
2✔
952
        response.opcode = Opcode::RSP_NAK;
×
953
        response.size = 1;
×
954
        response.data[0] = std::get<ServerResult>(maybe_path);
×
955
        _send_mavlink_ftp_message(response);
×
956
        return;
×
957
    }
958

959
    fs::path path = std::get<std::string>(maybe_path);
2✔
960

961
    std::error_code ec;
2✔
962
    if (!fs::exists(path, ec)) {
2✔
963
        response.opcode = Opcode::RSP_NAK;
×
964
        response.size = 1;
×
965
        response.data[0] = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
966
        _send_mavlink_ftp_message(response);
×
967
        return;
×
968
    }
969
    if (ec) {
2✔
NEW
970
        LogErr() << "fs::exists for " << path << " returned error: " << ec.message();
×
UNCOV
971
        response.opcode = Opcode::RSP_NAK;
×
UNCOV
972
        response.size = 1;
×
UNCOV
973
        response.data[0] = ServerResult::ERR_FAIL;
×
NEW
974
        _send_mavlink_ftp_message(response);
×
NEW
975
        return;
×
976
    }
977

978
    if (!fs::remove(path, ec)) {
2✔
979
        LogErr() << "fs::remove returned error: " << ec.message();
1✔
980
        response.opcode = Opcode::RSP_NAK;
1✔
981
        response.size = 1;
1✔
982
        response.data[0] = ServerResult::ERR_FAIL;
1✔
983
        _send_mavlink_ftp_message(response);
1✔
984
        return;
1✔
985
    }
986

987
    response.opcode = Opcode::RSP_ACK;
1✔
988
    response.size = 0;
1✔
989
    _send_mavlink_ftp_message(response);
1✔
990
}
4✔
991

992
void MavlinkFtpServer::_work_create_directory(const PayloadHeader& payload)
3✔
993
{
994
    auto response = PayloadHeader{};
3✔
995
    response.seq_number = payload.seq_number + 1;
3✔
996
    response.req_opcode = payload.opcode;
3✔
997

998
    std::lock_guard<std::mutex> lock(_mutex);
3✔
999

1000
    auto maybe_path = _path_from_payload(payload);
3✔
1001
    if (std::holds_alternative<ServerResult>(maybe_path)) {
3✔
1002
        response.opcode = Opcode::RSP_NAK;
2✔
1003
        response.size = 1;
2✔
1004
        response.data[0] = std::get<ServerResult>(maybe_path);
2✔
1005
        _send_mavlink_ftp_message(response);
2✔
1006
        return;
2✔
1007
    }
1008

1009
    auto path = std::get<std::string>(maybe_path);
1✔
1010

1011
    std::error_code ec;
1✔
1012
    if (fs::exists(path, ec)) {
1✔
1013
        response.opcode = Opcode::RSP_NAK;
×
1014
        response.size = 1;
×
1015
        response.data[0] = ServerResult::ERR_FAIL_FILE_EXISTS;
×
1016
        _send_mavlink_ftp_message(response);
×
1017
        return;
×
1018
    }
1019

1020
    if (fs::create_directory(path, ec)) {
1✔
1021
        response.opcode = Opcode::RSP_ACK;
1✔
1022
    } else {
1023
        response.opcode = Opcode::RSP_NAK;
×
1024
        response.size = 2;
×
1025
        response.data[0] = ServerResult::ERR_FAIL_ERRNO;
×
1026
        response.data[1] = static_cast<uint8_t>(ec.value());
×
1027
    }
1028

1029
    _send_mavlink_ftp_message(response);
1✔
1030
}
5✔
1031

1032
void MavlinkFtpServer::_work_remove_file(const PayloadHeader& payload)
4✔
1033
{
1034
    auto response = PayloadHeader{};
4✔
1035
    response.seq_number = payload.seq_number + 1;
4✔
1036
    response.req_opcode = payload.opcode;
4✔
1037

1038
    std::lock_guard<std::mutex> lock(_mutex);
4✔
1039

1040
    auto maybe_path = _path_from_payload(payload);
4✔
1041
    if (std::holds_alternative<ServerResult>(maybe_path)) {
4✔
1042
        response.opcode = Opcode::RSP_NAK;
2✔
1043
        response.size = 1;
2✔
1044
        response.data[0] = std::get<ServerResult>(maybe_path);
2✔
1045
        _send_mavlink_ftp_message(response);
2✔
1046
        return;
2✔
1047
    }
1048

1049
    auto path = std::get<std::string>(maybe_path);
2✔
1050

1051
    std::error_code ec;
2✔
1052
    if (!fs::exists(path, ec)) {
2✔
1053
        response.opcode = Opcode::RSP_NAK;
1✔
1054
        response.size = 1;
1✔
1055
        response.data[0] = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
1✔
1056
        _send_mavlink_ftp_message(response);
1✔
1057
        return;
1✔
1058
    }
1059
    if (fs::remove(path, ec)) {
1✔
1060
        response.opcode = Opcode::RSP_ACK;
1✔
1061
    } else {
1062
        response.opcode = Opcode::RSP_NAK;
×
1063
        response.size = 1;
×
1064
        response.data[0] = ServerResult::ERR_FAIL;
×
1065
    }
1066

1067
    _send_mavlink_ftp_message(response);
1✔
1068
}
8✔
1069

1070
void MavlinkFtpServer::_work_rename(const PayloadHeader& payload)
2✔
1071
{
1072
    auto response = PayloadHeader{};
2✔
1073
    response.seq_number = payload.seq_number + 1;
2✔
1074
    response.req_opcode = payload.opcode;
2✔
1075

1076
    std::lock_guard<std::mutex> lock(_mutex);
2✔
1077

1078
    auto maybe_old_name = _path_from_payload(payload, 0);
2✔
1079

1080
    if (std::holds_alternative<ServerResult>(maybe_old_name)) {
2✔
1081
        response.opcode = Opcode::RSP_NAK;
1✔
1082
        response.size = 1;
1✔
1083
        response.data[0] = std::get<ServerResult>(maybe_old_name);
1✔
1084
        _send_mavlink_ftp_message(response);
1✔
1085
        return;
1✔
1086
    }
1087

1088
    auto old_name = std::get<std::string>(maybe_old_name);
1✔
1089

1090
    auto maybe_new_name = _path_from_payload(payload, 1);
1✔
1091

1092
    if (std::holds_alternative<ServerResult>(maybe_new_name)) {
1✔
1093
        response.opcode = Opcode::RSP_NAK;
×
1094
        response.size = 1;
×
1095
        response.data[0] = std::get<ServerResult>(maybe_new_name);
×
1096
        _send_mavlink_ftp_message(response);
×
1097
        return;
×
1098
    }
1099

1100
    auto new_name = std::get<std::string>(maybe_new_name);
1✔
1101

1102
    if (_debugging) {
1✔
1103
        LogDebug() << "Rename from old_name " << old_name << " to " << new_name;
×
1104
    }
1105

1106
    std::error_code ec;
1✔
1107
    if (!fs::exists(old_name, ec)) {
1✔
1108
        response.opcode = Opcode::RSP_NAK;
×
1109
        response.size = 1;
×
1110
        response.data[0] = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
1111
        _send_mavlink_ftp_message(response);
×
1112
        return;
×
1113
    }
1114

1115
    fs::rename(old_name, new_name, ec);
1✔
1116
    if (ec) {
1✔
NEW
1117
        LogErr() << "fs::rename from " << old_name << " to " << new_name
×
NEW
1118
                 << " returned error: " << ec.message();
×
1119
        response.opcode = Opcode::RSP_NAK;
×
1120
        response.size = 1;
×
1121
        response.data[0] = ServerResult::ERR_FAIL;
×
NEW
1122
        _send_mavlink_ftp_message(response);
×
NEW
1123
        return;
×
1124
    }
1125

1126
    response.opcode = Opcode::RSP_ACK;
1✔
1127
    _send_mavlink_ftp_message(response);
1✔
1128
}
3✔
1129

1130
MavlinkFtpServer::ServerResult
1131
MavlinkFtpServer::_calc_local_file_crc32(const std::string& path, uint32_t& csum)
2✔
1132
{
1133
    std::error_code ec;
2✔
1134
    if (!fs::exists(path, ec)) {
2✔
1135
        return ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
1136
    }
1137

1138
    std::ifstream ifstream;
2✔
1139
    ifstream.open(path, std::ios::in | std::ios::binary);
2✔
1140

1141
    if (!ifstream.is_open()) {
2✔
1142
        return ServerResult::ERR_FILE_IO_ERROR;
×
1143
    }
1144

1145
    // Read whole file in buffer size chunks
1146
    Crc32 checksum;
2✔
1147
    char buffer[18392];
2✔
1148
    do {
1149
        ifstream.read(buffer, sizeof(buffer));
2✔
1150

1151
        if (ifstream.fail() && !ifstream.eof()) {
2✔
1152
            ifstream.close();
×
1153
            return ServerResult::ERR_FILE_IO_ERROR;
×
1154
        }
1155

1156
        auto bytes_read = ifstream.gcount();
2✔
1157
        checksum.add((uint8_t*)buffer, bytes_read);
2✔
1158

1159
    } while (!ifstream.eof());
2✔
1160

1161
    ifstream.close();
2✔
1162

1163
    csum = checksum.get();
2✔
1164

1165
    return ServerResult::SUCCESS;
2✔
1166
}
2✔
1167

1168
void MavlinkFtpServer::_work_calc_file_CRC32(const PayloadHeader& payload)
3✔
1169
{
1170
    auto response = PayloadHeader{};
3✔
1171
    response.seq_number = payload.seq_number + 1;
3✔
1172
    response.req_opcode = payload.opcode;
3✔
1173

1174
    std::lock_guard<std::mutex> lock(_mutex);
3✔
1175

1176
    auto maybe_path = _path_from_payload(payload);
3✔
1177
    if (std::holds_alternative<ServerResult>(maybe_path)) {
3✔
1178
        response.opcode = Opcode::RSP_NAK;
1✔
1179
        response.size = 1;
1✔
1180
        response.data[0] = std::get<ServerResult>(maybe_path);
1✔
1181
        _send_mavlink_ftp_message(response);
1✔
1182
        return;
1✔
1183
    }
1184

1185
    auto path = std::get<std::string>(maybe_path);
2✔
1186

1187
    std::error_code ec;
2✔
1188
    if (!fs::exists(path, ec)) {
2✔
1189
        response.opcode = Opcode::RSP_NAK;
×
1190
        response.size = 1;
×
1191
        response.data[0] = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
1192
        _send_mavlink_ftp_message(response);
×
1193
        return;
×
1194
    }
1195

1196
    uint32_t checksum;
2✔
1197
    ServerResult res = _calc_local_file_crc32(path, checksum);
2✔
1198
    if (res != ServerResult::SUCCESS) {
2✔
1199
        response.opcode = Opcode::RSP_NAK;
×
1200
        response.size = 1;
×
1201
        response.data[0] = res;
×
1202
        _send_mavlink_ftp_message(response);
×
1203
        return;
×
1204
    }
1205

1206
    response.opcode = Opcode::RSP_ACK;
2✔
1207
    response.size = sizeof(uint32_t);
2✔
1208
    std::memcpy(response.data, &checksum, response.size);
2✔
1209

1210
    _send_mavlink_ftp_message(response);
2✔
1211
}
4✔
1212

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

© 2026 Coveralls, Inc