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

mavlink / MAVSDK / 12113610048

02 Dec 2024 06:03AM UTC coverage: 43.572% (+4.9%) from 38.69%
12113610048

Pull #2386

github

web-flow
Merge e92382a9b into 68856f27a
Pull Request #2386: camera: support multiple cameras within one instance

1368 of 2039 new or added lines in 47 files covered. (67.09%)

50 existing lines in 8 files now uncovered.

13872 of 31837 relevant lines covered (43.57%)

289.73 hits per line

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

61.27
/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,210✔
28
        this);
29
}
88✔
30

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

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

40
    if (_debugging) {
1,210✔
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,420✔
47
        ftp_req.target_system != _server_component_impl.get_own_system_id()) {
1,210✔
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,402✔
55
        ftp_req.target_component != _server_component_impl.get_own_component_id()) {
1,192✔
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,201✔
63

64
    // Basic sanity check: validate length before use.
65
    if (payload.size > max_data_length) {
1,201✔
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,201✔
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,201✔
81
        _target_component_id = msg.compid;
1,201✔
82

83
        switch (payload.opcode) {
1,201✔
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:
288✔
133
                if (_debugging) {
288✔
134
                    LogDebug() << "OPC:CMD_READ_FILE";
×
135
                }
136
                _work_read(payload);
288✔
137
                break;
288✔
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:
95✔
147
                if (_debugging) {
95✔
148
                    LogDebug() << "OPC:CMD_WRITE_FILE";
×
149
                }
150
                _work_write(payload);
95✔
151
                break;
95✔
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)
747✔
204
{
205
    if (uint8_t(payload.opcode) == 0) {
747✔
206
        abort();
×
207
    }
208

209
    _server_component_impl.queue_message([&](MavlinkAddress mavlink_address, uint8_t channel) {
747✔
210
        mavlink_message_t message;
211
        mavlink_msg_file_transfer_protocol_pack_chan(
747✔
212
            mavlink_address.system_id,
747✔
213
            mavlink_address.component_id,
747✔
214
            channel,
215
            &message,
216
            _network_id,
747✔
217
            _target_system_id,
747✔
218
            _target_component_id,
747✔
219
            reinterpret_cast<const uint8_t*>(&payload));
747✔
220
        return message;
747✔
221
    });
222
}
747✔
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✔
NEW
292
        LogWarn() << "Root dir could not be made absolute: " << ec.message();
×
293
    }
294
    if (_debugging) {
27✔
NEW
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,248✔
339
        if (requested_offset > 0) {
1,226✔
340
            requested_offset--;
1,018✔
341
            continue;
1,018✔
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);
104✔
363
            if (ec) {
104✔
364
                LogWarn() << "Could not get file size of '" << entry.path().string()
×
365
                          << "': " << ec.message();
×
366
                continue;
×
367
            }
368

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

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

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

383
            payload_str += 'D';
104✔
384
            payload_str += name.string();
104✔
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)
288✔
686
{
687
    auto response = PayloadHeader{};
288✔
688
    response.seq_number = payload.seq_number + 1;
288✔
689
    response.req_opcode = payload.opcode;
288✔
690

691
    std::lock_guard<std::mutex> lock(_mutex);
288✔
692
    if (payload.session != 0 || !_session_info.ifstream.is_open()) {
288✔
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) {
288✔
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);
288✔
709
    if (_session_info.ifstream.fail()) {
288✔
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) {
288✔
719
        LogWarn() << "Read at " << payload.offset << " for " << int(payload.size);
×
720
    }
721

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

724
    if (_session_info.ifstream.fail()) {
288✔
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();
288✔
734

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

739
    _send_mavlink_ftp_message(response);
288✔
740
}
288✔
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)
95✔
859
{
860
    auto response = PayloadHeader{};
95✔
861
    response.seq_number = payload.seq_number + 1;
95✔
862
    response.req_opcode = payload.opcode;
95✔
863

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

869
    _session_info.ofstream.seekp(payload.offset);
95✔
870
    if (_session_info.ifstream.fail()) {
95✔
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);
95✔
880
    if (_session_info.ofstream.fail()) {
95✔
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;
95✔
890
    response.size = 0;
95✔
891

892
    _send_mavlink_ftp_message(response);
95✔
893
}
95✔
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 (fs::remove(path, ec)) {
2✔
970
        response.opcode = Opcode::RSP_ACK;
1✔
971
        response.size = 0;
1✔
972
    } else {
973
        response.opcode = Opcode::RSP_NAK;
1✔
974
        response.size = 1;
1✔
975
        response.data[0] = ServerResult::ERR_FAIL;
1✔
976
    }
977

978
    _send_mavlink_ftp_message(response);
2✔
979
}
2✔
980

981
void MavlinkFtpServer::_work_create_directory(const PayloadHeader& payload)
3✔
982
{
983
    auto response = PayloadHeader{};
3✔
984
    response.seq_number = payload.seq_number + 1;
3✔
985
    response.req_opcode = payload.opcode;
3✔
986

987
    std::lock_guard<std::mutex> lock(_mutex);
3✔
988

989
    auto maybe_path = _path_from_payload(payload);
3✔
990
    if (std::holds_alternative<ServerResult>(maybe_path)) {
3✔
991
        response.opcode = Opcode::RSP_NAK;
2✔
992
        response.size = 1;
2✔
993
        response.data[0] = std::get<ServerResult>(maybe_path);
2✔
994
        _send_mavlink_ftp_message(response);
2✔
995
        return;
2✔
996
    }
997

998
    auto path = std::get<std::string>(maybe_path);
1✔
999

1000
    std::error_code ec;
1✔
1001
    if (fs::exists(path, ec)) {
1✔
1002
        response.opcode = Opcode::RSP_NAK;
×
1003
        response.size = 1;
×
1004
        response.data[0] = ServerResult::ERR_FAIL_FILE_EXISTS;
×
1005
        _send_mavlink_ftp_message(response);
×
1006
        return;
×
1007
    }
1008

1009
    if (fs::create_directory(path, ec)) {
1✔
1010
        response.opcode = Opcode::RSP_ACK;
1✔
1011
    } else {
1012
        response.opcode = Opcode::RSP_NAK;
×
1013
        response.size = 2;
×
1014
        response.data[0] = ServerResult::ERR_FAIL_ERRNO;
×
1015
        response.data[1] = static_cast<uint8_t>(ec.value());
×
1016
    }
1017

1018
    _send_mavlink_ftp_message(response);
1✔
1019
}
5✔
1020

1021
void MavlinkFtpServer::_work_remove_file(const PayloadHeader& payload)
4✔
1022
{
1023
    auto response = PayloadHeader{};
4✔
1024
    response.seq_number = payload.seq_number + 1;
4✔
1025
    response.req_opcode = payload.opcode;
4✔
1026

1027
    std::lock_guard<std::mutex> lock(_mutex);
4✔
1028

1029
    auto maybe_path = _path_from_payload(payload);
4✔
1030
    if (std::holds_alternative<ServerResult>(maybe_path)) {
4✔
1031
        response.opcode = Opcode::RSP_NAK;
2✔
1032
        response.size = 1;
2✔
1033
        response.data[0] = std::get<ServerResult>(maybe_path);
2✔
1034
        _send_mavlink_ftp_message(response);
2✔
1035
        return;
2✔
1036
    }
1037

1038
    auto path = std::get<std::string>(maybe_path);
2✔
1039

1040
    std::error_code ec;
2✔
1041
    if (!fs::exists(path, ec)) {
2✔
1042
        response.opcode = Opcode::RSP_NAK;
1✔
1043
        response.size = 1;
1✔
1044
        response.data[0] = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
1✔
1045
        _send_mavlink_ftp_message(response);
1✔
1046
        return;
1✔
1047
    }
1048
    if (fs::remove(path, ec)) {
1✔
1049
        response.opcode = Opcode::RSP_ACK;
1✔
1050
    } else {
1051
        response.opcode = Opcode::RSP_NAK;
×
1052
        response.size = 1;
×
1053
        response.data[0] = ServerResult::ERR_FAIL;
×
1054
    }
1055

1056
    _send_mavlink_ftp_message(response);
1✔
1057
}
8✔
1058

1059
void MavlinkFtpServer::_work_rename(const PayloadHeader& payload)
2✔
1060
{
1061
    auto response = PayloadHeader{};
2✔
1062
    response.seq_number = payload.seq_number + 1;
2✔
1063
    response.req_opcode = payload.opcode;
2✔
1064

1065
    std::lock_guard<std::mutex> lock(_mutex);
2✔
1066

1067
    auto maybe_old_name = _path_from_payload(payload, 0);
2✔
1068

1069
    if (std::holds_alternative<ServerResult>(maybe_old_name)) {
2✔
1070
        response.opcode = Opcode::RSP_NAK;
1✔
1071
        response.size = 1;
1✔
1072
        response.data[0] = std::get<ServerResult>(maybe_old_name);
1✔
1073
        _send_mavlink_ftp_message(response);
1✔
1074
        return;
1✔
1075
    }
1076

1077
    auto old_name = std::get<std::string>(maybe_old_name);
1✔
1078

1079
    auto maybe_new_name = _path_from_payload(payload, 1);
1✔
1080

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

1089
    auto new_name = std::get<std::string>(maybe_new_name);
1✔
1090

1091
    if (_debugging) {
1✔
1092
        LogDebug() << "Rename from old_name " << old_name << " to " << new_name;
×
1093
    }
1094

1095
    std::error_code ec;
1✔
1096
    if (!fs::exists(old_name, ec)) {
1✔
1097
        response.opcode = Opcode::RSP_NAK;
×
1098
        response.size = 1;
×
1099
        response.data[0] = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
1100
        _send_mavlink_ftp_message(response);
×
1101
        return;
×
1102
    }
1103

1104
    fs::rename(old_name, new_name, ec);
1✔
1105
    if (!ec) {
1✔
1106
        response.opcode = Opcode::RSP_ACK;
1✔
1107
    } else {
1108
        response.opcode = Opcode::RSP_NAK;
×
1109
        response.size = 1;
×
1110
        response.data[0] = ServerResult::ERR_FAIL;
×
1111
    }
1112

1113
    _send_mavlink_ftp_message(response);
1✔
1114
}
3✔
1115

1116
MavlinkFtpServer::ServerResult
1117
MavlinkFtpServer::_calc_local_file_crc32(const std::string& path, uint32_t& csum)
2✔
1118
{
1119
    std::error_code ec;
2✔
1120
    if (!fs::exists(path, ec)) {
2✔
1121
        return ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
1122
    }
1123

1124
    std::ifstream ifstream;
2✔
1125
    ifstream.open(path, std::ios::in | std::ios::binary);
2✔
1126

1127
    if (!ifstream.is_open()) {
2✔
1128
        return ServerResult::ERR_FILE_IO_ERROR;
×
1129
    }
1130

1131
    // Read whole file in buffer size chunks
1132
    Crc32 checksum;
2✔
1133
    char buffer[18392];
2✔
1134
    do {
1135
        ifstream.read(buffer, sizeof(buffer));
2✔
1136

1137
        if (ifstream.fail() && !ifstream.eof()) {
2✔
1138
            ifstream.close();
×
1139
            return ServerResult::ERR_FILE_IO_ERROR;
×
1140
        }
1141

1142
        auto bytes_read = ifstream.gcount();
2✔
1143
        checksum.add((uint8_t*)buffer, bytes_read);
2✔
1144

1145
    } while (!ifstream.eof());
2✔
1146

1147
    ifstream.close();
2✔
1148

1149
    csum = checksum.get();
2✔
1150

1151
    return ServerResult::SUCCESS;
2✔
1152
}
2✔
1153

1154
void MavlinkFtpServer::_work_calc_file_CRC32(const PayloadHeader& payload)
3✔
1155
{
1156
    auto response = PayloadHeader{};
3✔
1157
    response.seq_number = payload.seq_number + 1;
3✔
1158
    response.req_opcode = payload.opcode;
3✔
1159

1160
    std::lock_guard<std::mutex> lock(_mutex);
3✔
1161

1162
    auto maybe_path = _path_from_payload(payload);
3✔
1163
    if (std::holds_alternative<ServerResult>(maybe_path)) {
3✔
1164
        response.opcode = Opcode::RSP_NAK;
1✔
1165
        response.size = 1;
1✔
1166
        response.data[0] = std::get<ServerResult>(maybe_path);
1✔
1167
        _send_mavlink_ftp_message(response);
1✔
1168
        return;
1✔
1169
    }
1170

1171
    auto path = std::get<std::string>(maybe_path);
2✔
1172

1173
    std::error_code ec;
2✔
1174
    if (!fs::exists(path, ec)) {
2✔
1175
        response.opcode = Opcode::RSP_NAK;
×
1176
        response.size = 1;
×
1177
        response.data[0] = ServerResult::ERR_FAIL_FILE_DOES_NOT_EXIST;
×
1178
        _send_mavlink_ftp_message(response);
×
1179
        return;
×
1180
    }
1181

1182
    uint32_t checksum;
2✔
1183
    ServerResult res = _calc_local_file_crc32(path, checksum);
2✔
1184
    if (res != ServerResult::SUCCESS) {
2✔
1185
        response.opcode = Opcode::RSP_NAK;
×
1186
        response.size = 1;
×
1187
        response.data[0] = res;
×
1188
        _send_mavlink_ftp_message(response);
×
1189
        return;
×
1190
    }
1191

1192
    response.opcode = Opcode::RSP_ACK;
2✔
1193
    response.size = sizeof(uint32_t);
2✔
1194
    std::memcpy(response.data, &checksum, response.size);
2✔
1195

1196
    _send_mavlink_ftp_message(response);
2✔
1197
}
4✔
1198

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