QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
FTPManager.cc
Go to the documentation of this file.
1#include "FTPManager.h"
2#include "MAVLinkProtocol.h"
3#include "AppMessages.h"
5#include "Vehicle.h"
7
8#include <QtCore/QFile>
9#include <QtCore/QFileInfo>
10#include <QtCore/QDir>
11#include <limits>
12
13QGC_LOGGING_CATEGORY(FTPManagerLog, "Vehicle.FTPManager")
14
16 : QObject (vehicle)
17 , _vehicle (vehicle)
18{
19 _ackOrNakTimeoutTimer.setSingleShot(true);
20 // Mock link responds immediately if at all, speed up unit tests with faster timeout
21 _ackOrNakTimeoutTimer.setInterval(QGC::runningUnitTests() ? kTestAckTimeoutMs : _ackOrNakTimeoutMsecs);
22 connect(&_ackOrNakTimeoutTimer, &QTimer::timeout, this, &FTPManager::_ackOrNakTimeout);
23
24 // Make sure we don't have bad structure packing
25 Q_ASSERT(sizeof(MavlinkFTP::RequestHeader) == 12);
26
27 _uploadState.reset();
28}
29
30bool FTPManager::download(uint8_t fromCompId, const QString& fromURI, const QString& toDir, const QString& fileName, bool checksize)
31{
32 qCDebug(FTPManagerLog) << "Download fromCompId:" << fromCompId
33 << "fromURI:" << fromURI
34 << "to:" << toDir
35 << "fileName:" << fileName;
36
37 if (!_rgStateMachine.isEmpty()) {
38 qCDebug(FTPManagerLog) << "Cannot download. Already in another operation";
39 return false;
40 }
41
42 static const StateFunctions_t rgDownloadStateMachine[] = {
43 { &FTPManager::_openFileROBegin, &FTPManager::_openFileROAckOrNak, &FTPManager::_openFileROTimeout },
44 { &FTPManager::_burstReadFileBegin, &FTPManager::_burstReadFileAckOrNak, &FTPManager::_burstReadFileTimeout },
45 { &FTPManager::_fillMissingBlocksBegin, &FTPManager::_fillMissingBlocksAckOrNak, &FTPManager::_fillMissingBlocksTimeout },
46 { &FTPManager::_resetSessionsBegin, &FTPManager::_resetSessionsAckOrNak, &FTPManager::_resetSessionsTimeout },
47 { &FTPManager::_downloadCompleteNoError, nullptr, nullptr },
48 };
49 for (size_t i=0; i<sizeof(rgDownloadStateMachine)/sizeof(rgDownloadStateMachine[0]); i++) {
50 _rgStateMachine.append(rgDownloadStateMachine[i]);
51 }
52
53 _downloadState.reset();
54 _downloadState.toDir.setPath(toDir);
55 _downloadState.checksize = checksize;
56
57 if (!_parseURI(fromCompId, fromURI, _downloadState.fullPathOnVehicle, _ftpCompId)) {
58 qCWarning(FTPManagerLog) << "_parseURI failed";
59 return false;
60 }
61
62 // We need to strip off the file name from the fully qualified path. We can't use the usual QDir
63 // routines because this path does not exist locally.
64 int lastDirSlashIndex;
65 for (lastDirSlashIndex=_downloadState.fullPathOnVehicle.size()-1; lastDirSlashIndex>=0; lastDirSlashIndex--) {
66 if (_downloadState.fullPathOnVehicle[lastDirSlashIndex] == '/') {
67 break;
68 }
69 }
70 lastDirSlashIndex++; // move past slash
71
72 if (fileName.isEmpty()) {
73 _downloadState.fileName = _downloadState.fullPathOnVehicle.right(_downloadState.fullPathOnVehicle.size() - lastDirSlashIndex);
74 } else {
75 _downloadState.fileName = fileName;
76 }
77
78 qCDebug(FTPManagerLog) << "_downloadState.fullPathOnVehicle:_downloadState.fileName" << _downloadState.fullPathOnVehicle << _downloadState.fileName;
79
80 _startStateMachine();
81
82 return true;
83}
84
85bool FTPManager::upload(uint8_t toCompId, const QString& toURI, const QString& fromFile)
86{
87 qCDebug(FTPManagerLog) << "upload fromFile:" << fromFile << "toURI:" << toURI << "toCompId:" << toCompId;
88
89 if (!_rgStateMachine.isEmpty()) {
90 qCDebug(FTPManagerLog) << "Cannot upload. Already in another operation";
91 return false;
92 }
93
94 QFileInfo sourceInfo(fromFile);
95 if (!sourceInfo.exists() || !sourceInfo.isFile()) {
96 qCWarning(FTPManagerLog) << "Cannot upload. Source file missing" << fromFile;
97 return false;
98 }
99
100 if (sourceInfo.size() > std::numeric_limits<uint32_t>::max()) {
101 qCWarning(FTPManagerLog) << "Cannot upload. File too large" << fromFile << sourceInfo.size();
102 return false;
103 }
104
105 _uploadState.reset();
106 _uploadState.localFilePath = fromFile;
107 _uploadState.file.setFileName(fromFile);
108 if (!_uploadState.file.open(QFile::ReadOnly)) {
109 qCWarning(FTPManagerLog) << "Cannot upload. Failed to open file" << fromFile << _uploadState.file.errorString();
110 _uploadState.reset();
111 return false;
112 }
113
114 _uploadState.fileSize = static_cast<uint32_t>(sourceInfo.size());
115 _uploadState.totalBytesSent = 0;
116 _uploadState.lastChunkSize = 0;
117 _uploadState.retryCount = 0;
118 _uploadState.sessionId = 0;
119 _uploadState.cancelled = false;
120
121 if (!_parseURI(toCompId, toURI, _uploadState.fullPathOnVehicle, _ftpCompId)) {
122 qCWarning(FTPManagerLog) << "_parseURI failed";
123 _uploadState.reset();
124 return false;
125 }
126
127 static const StateFunctions_t rgUploadStateMachine[] = {
128 { &FTPManager::_createFileBegin, &FTPManager::_createFileAckOrNak, &FTPManager::_createFileTimeout },
129 { &FTPManager::_writeFileBegin, &FTPManager::_writeFileAckOrNak, &FTPManager::_writeFileTimeout },
130 { &FTPManager::_resetSessionsBegin, &FTPManager::_resetSessionsAckOrNak, &FTPManager::_resetSessionsTimeout },
131 { &FTPManager::_uploadFinalize, nullptr, nullptr },
132 };
133 for (size_t i=0; i<sizeof(rgUploadStateMachine)/sizeof(rgUploadStateMachine[0]); i++) {
134 _rgStateMachine.append(rgUploadStateMachine[i]);
135 }
136
137 _startStateMachine();
138
139 return true;
140}
141
142bool FTPManager::listDirectory(uint8_t fromCompId, const QString& fromURI)
143{
144 qCDebug(FTPManagerLog) << "list directory fromURI:" << fromURI << "fromCompId:" << fromCompId;
145
146 if (!_rgStateMachine.isEmpty()) {
147 qCDebug(FTPManagerLog) << "Cannot list directory. Already in another operation";
148 return false;
149 }
150
151 static const StateFunctions_t rgStateMachine[] = {
152 { &FTPManager::_listDirectoryBegin, &FTPManager::_listDirectoryAckOrNak, &FTPManager::_listDirectoryTimeout },
153 { &FTPManager::_listDirectoryCompleteNoError, nullptr, nullptr },
154 };
155 for (size_t i=0; i<sizeof(rgStateMachine)/sizeof(rgStateMachine[0]); i++) {
156 _rgStateMachine.append(rgStateMachine[i]);
157 }
158
159 _listDirectoryState.reset();
160
161 // Prefer the timestamped listing unless we already learned this vehicle doesn't support it.
162 _listDirectoryState.opCode = (_listDirWithTimeSupport == WithTimeSupport_t::Unsupported)
165
166 if (!_parseURI(fromCompId, fromURI, _listDirectoryState.fullPathOnVehicle, _ftpCompId)) {
167 qCWarning(FTPManagerLog) << "_parseURI failed";
168 return false;
169 }
170
171 qCDebug(FTPManagerLog) << "_listDirectoryState.fullPathOnVehicle" << _listDirectoryState.fullPathOnVehicle;
172
173 _startStateMachine();
174
175 return true;
176}
177
178bool FTPManager::deleteFile(uint8_t fromCompId, const QString& fromURI)
179{
180 qCDebug(FTPManagerLog) << "delete file fromURI:" << fromURI << "fromCompId:" << fromCompId;
181
182 if (!_rgStateMachine.isEmpty()) {
183 qCDebug(FTPManagerLog) << "Cannot delete file. Already in another operation";
184 return false;
185 }
186
187 static const StateFunctions_t rgStateMachine[] = {
188 { &FTPManager::_deleteFileBegin, &FTPManager::_deleteFileAckOrNak, &FTPManager::_deleteFileTimeout },
189 { &FTPManager::_deleteCompleteNoError, nullptr, nullptr },
190 };
191 for (size_t i=0; i<sizeof(rgStateMachine)/sizeof(rgStateMachine[0]); i++) {
192 _rgStateMachine.append(rgStateMachine[i]);
193 }
194
195 _deleteState.reset();
196
197 if (!_parseURI(fromCompId, fromURI, _deleteState.fullPathOnVehicle, _ftpCompId)) {
198 qCWarning(FTPManagerLog) << "_parseURI failed";
199 _rgStateMachine.clear();
200 return false;
201 }
202
203 qCDebug(FTPManagerLog) << "_deleteState.fullPathOnVehicle" << _deleteState.fullPathOnVehicle;
204
205 _startStateMachine();
206
207 return true;
208}
209
211{
212 if (!_downloadState.inProgress()) {
213 return;
214 }
215
216 _ackOrNakTimeoutTimer.stop();
217 _rgStateMachine.clear();
218 static const StateFunctions_t rgTerminateStateMachine[] = {
219 { &FTPManager::_terminateSessionBegin, &FTPManager::_terminateSessionAckOrNak, &FTPManager::_terminateSessionTimeout },
220 { &FTPManager::_terminateComplete, nullptr, nullptr },
221 };
222 for (size_t i=0; i<sizeof(rgTerminateStateMachine)/sizeof(rgTerminateStateMachine[0]); i++) {
223 _rgStateMachine.append(rgTerminateStateMachine[i]);
224 }
225 _downloadState.retryCount = 0;
226 _startStateMachine();
227}
228
230{
231 if (!_listDirectoryState.inProgress()) {
232 return;
233 }
234
235 if (_rgStateMachine.isEmpty()) {
236 return;
237 }
238
239 _listDirectoryComplete(tr("Aborted"));
240}
241
243{
244 if (!_uploadState.inProgress()) {
245 return;
246 }
247
248 _uploadState.cancelled = true;
249 _ackOrNakTimeoutTimer.stop();
250 _rgStateMachine.clear();
251
252 if (_uploadState.sessionId != 0) {
253 static const StateFunctions_t rgTerminateStateMachine[] = {
254 { &FTPManager::_terminateUploadSessionBegin, &FTPManager::_terminateUploadSessionAckOrNak, &FTPManager::_terminateUploadSessionTimeout },
255 { &FTPManager::_uploadFinalize, nullptr, nullptr },
256 };
257 for (size_t i=0; i<sizeof(rgTerminateStateMachine)/sizeof(rgTerminateStateMachine[0]); i++) {
258 _rgStateMachine.append(rgTerminateStateMachine[i]);
259 }
260 _uploadState.retryCount = 0;
261 _startStateMachine();
262 } else {
263 _uploadComplete(tr("Aborted"));
264 }
265}
266
268{
269 if (!_deleteState.inProgress()) {
270 return;
271 }
272
273 _deleteComplete(tr("Aborted"));
274}
275
276void FTPManager::_terminateSessionBegin(void)
277{
278 MavlinkFTP::Request request{};
279 request.hdr.session = _downloadState.sessionId;
280 request.hdr.opcode = MavlinkFTP::kCmdTerminateSession;
281 _sendRequestExpectAck(&request);
282}
283
284void FTPManager::_terminateSessionAckOrNak(const MavlinkFTP::Request *ackOrNak)
285{
286 MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
287 if (requestOpCode != MavlinkFTP::kCmdTerminateSession) {
288 qCDebug(FTPManagerLog) << "_terminateSessionAckOrNak: Ack disregarding ack for incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
289 return;
290 }
291 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
292 qCDebug(FTPManagerLog) << "_terminateSessionAckOrNak: Ack disregarding ack for incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
293 return;
294 }
295
296 _ackOrNakTimeoutTimer.stop();
297 _advanceStateMachine();
298}
299
300void FTPManager::_terminateSessionTimeout(void)
301{
302 if (++_downloadState.retryCount > _maxRetry) {
303 qCDebug(FTPManagerLog) << QString("_terminateSessionTimeout retries exceeded");
304 _downloadComplete(tr("Download failed"));
305 } else {
306 // Try again
307 qCDebug(FTPManagerLog) << QString("_terminateSessionTimeout: retrying - retryCount(%1)").arg(_downloadState.retryCount);
308 _terminateSessionBegin();
309 }
310
311}
312
313void FTPManager::_terminateComplete(void)
314{
315 _downloadComplete("Aborted");
316}
317
320void FTPManager::_downloadComplete(const QString& errorMsg)
321{
322 qCDebug(FTPManagerLog) << QString("_downloadComplete: errorMsg(%1)").arg(errorMsg);
323
324 QString downloadFilePath = _downloadState.toDir.absoluteFilePath(_downloadState.fileName);
325 QString error = errorMsg;
326
327 _ackOrNakTimeoutTimer.stop();
328 _rgStateMachine.clear();
329 _currentStateMachineIndex = -1;
330 if (_downloadState.file.isOpen()) {
331 _downloadState.file.close();
332 if (!errorMsg.isEmpty()) {
333 _downloadState.file.remove();
334 }
335 }
336
337 emit downloadComplete(downloadFilePath, errorMsg);
338}
339
342void FTPManager::_listDirectoryComplete(const QString& errorMsg)
343{
344 qCDebug(FTPManagerLog) << QString("_listDirectoryComplete: errorMsg(%1)").arg(errorMsg);
345
346 _ackOrNakTimeoutTimer.stop();
347 _rgStateMachine.clear();
348 _currentStateMachineIndex = -1;
349
350 QStringList rgDirectoryList = _listDirectoryState.rgDirectoryList;
351 if (!errorMsg.isEmpty()) {
352 rgDirectoryList.clear();
353 }
354
355 _listDirectoryState.reset();
356
357 emit listDirectoryComplete(errorMsg.isEmpty() ? rgDirectoryList : QStringList(), errorMsg);
358}
359
360void FTPManager::_deleteFileBegin(void)
361{
362 qCDebug(FTPManagerLog) << "file" << _deleteState.fullPathOnVehicle;
363
364 MavlinkFTP::Request request{};
365 request.hdr.session = 0;
366 request.hdr.opcode = MavlinkFTP::kCmdRemoveFile;
367 request.hdr.offset = 0;
368 request.hdr.size = 0;
369 _fillRequestDataWithString(&request, _deleteState.fullPathOnVehicle);
370 _sendRequestExpectAck(&request);
371}
372
373void FTPManager::_deleteFileAckOrNak(const MavlinkFTP::Request* ackOrNak)
374{
375 MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
376 if (requestOpCode != MavlinkFTP::kCmdRemoveFile) {
377 qCDebug(FTPManagerLog) << "_deleteFileAckOrNak: Ack disregarding ack for incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
378 return;
379 }
380 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
381 qCDebug(FTPManagerLog) << "_deleteFileAckOrNak: Ack disregarding ack for incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
382 return;
383 }
384
385 _ackOrNakTimeoutTimer.stop();
386
387 if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
388 _advanceStateMachine();
389 } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
390 qCDebug(FTPManagerLog) << "_deleteFileAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
391 _deleteComplete(tr("Delete failed") + ": " + _errorMsgFromNak(ackOrNak));
392 }
393}
394
395void FTPManager::_deleteFileTimeout(void)
396{
397 if (++_deleteState.retryCount > _maxRetry) {
398 qCDebug(FTPManagerLog) << QString("_deleteFileTimeout retries exceeded");
399 _deleteComplete(tr("Delete failed"));
400 } else {
401 qCDebug(FTPManagerLog) << QString("_deleteFileTimeout: retrying - retryCount(%1)").arg(_deleteState.retryCount);
402 _deleteFileBegin();
403 }
404}
405
406void FTPManager::_deleteComplete(const QString& errorMsg)
407{
408 qCDebug(FTPManagerLog) << QString("_deleteComplete: errorMsg(%1)").arg(errorMsg);
409
410 const QString deletedPath = _deleteState.fullPathOnVehicle;
411
412 _ackOrNakTimeoutTimer.stop();
413 _rgStateMachine.clear();
414 _currentStateMachineIndex = -1;
415
416 _deleteState.reset();
417
418 emit deleteComplete(deletedPath, errorMsg);
419}
420
421void FTPManager::_createFileBegin(void)
422{
423 qCDebug(FTPManagerLog) << "file" << _uploadState.fullPathOnVehicle;
424
425 MavlinkFTP::Request request{};
426 request.hdr.session = 0;
427 request.hdr.opcode = MavlinkFTP::kCmdCreateFile;
428 request.hdr.offset = 0;
429 request.hdr.size = 0;
430 _fillRequestDataWithString(&request, _uploadState.fullPathOnVehicle);
431 _sendRequestExpectAck(&request);
432}
433
434void FTPManager::_createFileAckOrNak(const MavlinkFTP::Request* ackOrNak)
435{
436 MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
437 if (requestOpCode != MavlinkFTP::kCmdCreateFile) {
438 qCDebug(FTPManagerLog) << "_createFileAckOrNak: Ack disregarding ack for incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
439 return;
440 }
441 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
442 qCDebug(FTPManagerLog) << "_createFileAckOrNak: Ack disregarding ack for incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
443 return;
444 }
445
446 _ackOrNakTimeoutTimer.stop();
447
448 if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
449 qCDebug(FTPManagerLog) << "_createFileAckOrNak: Ack - sessionId" << ackOrNak->hdr.session;
450
451 _uploadState.sessionId = ackOrNak->hdr.session;
452 _advanceStateMachine();
453 } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
454 qCDebug(FTPManagerLog) << "_createFileAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
455 _uploadComplete(tr("Upload failed for: %1 - error: %2").arg(_uploadState.fullPathOnVehicle).arg(_errorMsgFromNak(ackOrNak)));
456 }
457}
458
459void FTPManager::_createFileTimeout(void)
460{
461 qCDebug(FTPManagerLog) << "_createFileTimeout";
462 _uploadComplete(tr("Upload failed for: %1 - no response from vehicle").arg(_uploadState.fullPathOnVehicle));
463}
464
465void FTPManager::_writeFileBegin(void)
466{
467 _writeFileWorker(true /* firstRequest */);
468}
469
470void FTPManager::_writeFileWorker(bool firstRequest)
471{
472 if (!_uploadState.file.isOpen()) {
473 _uploadComplete(tr("Upload failed for: %1 - file not open").arg(_uploadState.fullPathOnVehicle));
474 return;
475 }
476
477 if (_uploadState.totalBytesSent >= _uploadState.fileSize) {
478 _advanceStateMachine();
479 return;
480 }
481
482 qCDebug(FTPManagerLog) << "_writeFileWorker: offset:firstRequest:retryCount" << _uploadState.totalBytesSent << firstRequest << _uploadState.retryCount;
483
484 MavlinkFTP::Request request{};
485 request.hdr.session = _uploadState.sessionId;
486 request.hdr.opcode = MavlinkFTP::kCmdWriteFile;
487 request.hdr.offset = _uploadState.totalBytesSent;
488
489 if (firstRequest) {
490 _uploadState.retryCount = 0;
491 } else {
492 _expectedIncomingSeqNumber -= 2;
493 }
494
495 qint64 bytesRemaining = static_cast<qint64>(_uploadState.fileSize) - static_cast<qint64>(_uploadState.totalBytesSent);
496 qint64 bytesToSend = bytesRemaining;
497 if (bytesToSend > static_cast<qint64>(sizeof(request.data))) {
498 bytesToSend = sizeof(request.data);
499 }
500
501 if (!_uploadState.file.seek(_uploadState.totalBytesSent)) {
502 qCDebug(FTPManagerLog) << "_writeFileWorker: seek failed" << _uploadState.file.errorString();
503 _uploadComplete(tr("Upload failed for: %1 - error reading file").arg(_uploadState.fullPathOnVehicle));
504 return;
505 }
506
507 qint64 bytesRead = _uploadState.file.read(reinterpret_cast<char*>(request.data), bytesToSend);
508 if (bytesRead != bytesToSend) {
509 qCDebug(FTPManagerLog) << "_writeFileWorker: read failed" << _uploadState.file.errorString();
510 _uploadComplete(tr("Upload failed for: %1 - error reading file").arg(_uploadState.fullPathOnVehicle));
511 return;
512 }
513
514 request.hdr.size = static_cast<uint8_t>(bytesRead);
515 _uploadState.lastChunkSize = static_cast<uint32_t>(bytesRead);
516
517 _sendRequestExpectAck(&request);
518}
519
520void FTPManager::_writeFileAckOrNak(const MavlinkFTP::Request* ackOrNak)
521{
522 MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
523
524 if (requestOpCode != MavlinkFTP::kCmdWriteFile) {
525 qCDebug(FTPManagerLog) << "_writeFileAckOrNak: Disregarding due to incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
526 return;
527 }
528 if (ackOrNak->hdr.session != _uploadState.sessionId) {
529 qCDebug(FTPManagerLog) << "_writeFileAckOrNak: Disregarding due to incorrect session id actual:expected" << ackOrNak->hdr.session << _uploadState.sessionId;
530 return;
531 }
532
533 _ackOrNakTimeoutTimer.stop();
534
535 if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
536 if (ackOrNak->hdr.seqNumber < _expectedIncomingSeqNumber) {
537 qCDebug(FTPManagerLog) << "_writeFileAckOrNak: Disregarding Ack due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
538 return;
539 }
540
541 if (ackOrNak->hdr.size != 0) {
542 qCDebug(FTPManagerLog) << "_writeFileAckOrNak: unexpected ack size expected:actual 0" << ackOrNak->hdr.size;
543 }
544
545 _uploadState.totalBytesSent += _uploadState.lastChunkSize;
546 _uploadState.lastChunkSize = 0;
547 _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber;
548
549 if (_uploadState.fileSize != 0) {
550 emit commandProgress(static_cast<float>(_uploadState.totalBytesSent) / static_cast<float>(_uploadState.fileSize));
551 }
552
553 if (_uploadState.totalBytesSent >= _uploadState.fileSize) {
554 _advanceStateMachine();
555 } else {
556 _writeFileWorker(true /* firstRequest */);
557 }
558 } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
559 qCDebug(FTPManagerLog) << "_writeFileAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
560 _uploadComplete(tr("Upload failed for: %1 - error: %2").arg(_uploadState.fullPathOnVehicle).arg(_errorMsgFromNak(ackOrNak)));
561 }
562}
563
564void FTPManager::_writeFileTimeout(void)
565{
566 if (++_uploadState.retryCount > _maxRetry) {
567 qCDebug(FTPManagerLog) << QString("_writeFileTimeout retries exceeded");
568 _uploadComplete(tr("Upload failed for: %1 - no response from vehicle").arg(_uploadState.fullPathOnVehicle));
569 } else {
570 qCDebug(FTPManagerLog) << QString("_writeFileTimeout: retrying - retryCount(%1) offset(%2)").arg(_uploadState.retryCount).arg(_uploadState.totalBytesSent);
571 _writeFileWorker(false /* firstRequest */);
572 }
573}
574
575void FTPManager::_terminateUploadSessionBegin(void)
576{
577 if (_uploadState.sessionId == 0) {
578 qCWarning(FTPManagerLog) << "_terminateUploadSessionBegin: No session to terminate";
579 _advanceStateMachine();
580 return;
581 }
582
583 MavlinkFTP::Request request{};
584 request.hdr.session = _uploadState.sessionId;
585 request.hdr.opcode = MavlinkFTP::kCmdTerminateSession;
586 _sendRequestExpectAck(&request);
587}
588
589void FTPManager::_terminateUploadSessionAckOrNak(const MavlinkFTP::Request* ackOrNak)
590{
591 MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
592 if (requestOpCode != MavlinkFTP::kCmdTerminateSession) {
593 qCDebug(FTPManagerLog) << "_terminateUploadSessionAckOrNak: Ack disregarding ack for incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
594 return;
595 }
596 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
597 qCDebug(FTPManagerLog) << "_terminateUploadSessionAckOrNak: Ack disregarding ack for incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
598 return;
599 }
600
601 _ackOrNakTimeoutTimer.stop();
602
603 if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
604 qCDebug(FTPManagerLog) << "_terminateUploadSessionAckOrNak: Ack";
605 _advanceStateMachine();
606 } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
607 qCDebug(FTPManagerLog) << "_terminateUploadSessionAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
608 _uploadComplete(tr("Upload failed for: %1 - error: %2").arg(_uploadState.fullPathOnVehicle).arg(_errorMsgFromNak(ackOrNak)));
609 }
610}
611
612void FTPManager::_terminateUploadSessionTimeout(void)
613{
614 if (++_uploadState.retryCount > _maxRetry) {
615 qCDebug(FTPManagerLog) << QString("_terminateUploadSessionTimeout retries exceeded");
616 _uploadComplete(tr("Upload failed for: %1 - no response from vehicle").arg(_uploadState.fullPathOnVehicle));
617 } else {
618 qCDebug(FTPManagerLog) << QString("_terminateUploadSessionTimeout: retrying - retryCount(%1)").arg(_uploadState.retryCount);
619 _terminateUploadSessionBegin();
620 }
621}
622
623void FTPManager::_uploadFinalize(void)
624{
625 QString error = _uploadState.cancelled ? tr("Aborted for: %1").arg(_uploadState.fullPathOnVehicle) : QString();
626 _uploadComplete(error);
627}
628
629void FTPManager::_uploadComplete(const QString& errorMsg)
630{
631 qCDebug(FTPManagerLog) << QString("_uploadComplete: errorMsg(%1)").arg(errorMsg)
632 << "local" << _uploadState.localFilePath
633 << "remote" << _uploadState.fullPathOnVehicle;
634
635 QString remotePath = _uploadState.fullPathOnVehicle;
636
637 _ackOrNakTimeoutTimer.stop();
638 _rgStateMachine.clear();
639 _currentStateMachineIndex = -1;
640
641 if (_uploadState.file.isOpen()) {
642 _uploadState.file.close();
643 }
644
645 _uploadState.reset();
646
647 emit uploadComplete(remotePath, errorMsg);
648}
649
650void FTPManager::_mavlinkMessageReceived(const mavlink_message_t& message)
651{
652 if (message.msgid != MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL ||
653 message.sysid != _vehicle->id() || message.compid != _ftpCompId) {
654 return;
655 }
656
657 if (_currentStateMachineIndex == -1) {
658 return;
659 }
660
661 mavlink_file_transfer_protocol_t data;
662 mavlink_msg_file_transfer_protocol_decode(&message, &data);
663
664 // Make sure we are the target system
665 int qgcId = MAVLinkProtocol::instance()->getSystemId();
666 if (data.target_system != qgcId) {
667 return;
668 }
669
670 MavlinkFTP::Request* request = (MavlinkFTP::Request*)&data.payload[0];
671
672 // Reject messages where hdr.size exceeds data array bounds to prevent over-reads
673 if (request->hdr.size > sizeof(request->data)) {
674 qCWarning(FTPManagerLog) << "_mavlinkMessageReceived: hdr.size exceeds data array, discarding." << request->hdr.size;
675 return;
676 }
677
678 // Ignore old/reordered packets (handle wrap-around properly)
679 uint16_t actualIncomingSeqNumber = request->hdr.seqNumber;
680 if ((uint16_t)((_expectedIncomingSeqNumber - 1) - actualIncomingSeqNumber) < (std::numeric_limits<uint16_t>::max()/2)) {
681 qCDebug(FTPManagerLog) << "_mavlinkMessageReceived: Received old packet seqNum expected:actual" << _expectedIncomingSeqNumber << actualIncomingSeqNumber
682 << "hdr.opcode:hdr.req_opcode" << MavlinkFTP::opCodeToString(static_cast<MavlinkFTP::OpCode_t>(request->hdr.opcode)) << MavlinkFTP::opCodeToString(static_cast<MavlinkFTP::OpCode_t>(request->hdr.req_opcode));
683
684 return;
685 }
686
687 qCDebug(FTPManagerLog) << "_mavlinkMessageReceived: hdr.opcode:hdr.req_opcode:seqNumber"
688 << MavlinkFTP::opCodeToString(static_cast<MavlinkFTP::OpCode_t>(request->hdr.opcode)) << MavlinkFTP::opCodeToString(static_cast<MavlinkFTP::OpCode_t>(request->hdr.req_opcode))
689 << request->hdr.seqNumber;
690
691 (this->*_rgStateMachine[_currentStateMachineIndex].ackNakFn)(request);
692}
693
694void FTPManager::_startStateMachine(void)
695{
696 _currentStateMachineIndex = -1;
697 _advanceStateMachine();
698}
699
700void FTPManager::_advanceStateMachine(void)
701{
702 _currentStateMachineIndex++;
703 (this->*_rgStateMachine[_currentStateMachineIndex].beginFn)();
704}
705
706void FTPManager::_ackOrNakTimeout(void)
707{
708 (this->*_rgStateMachine[_currentStateMachineIndex].timeoutFn)();
709}
710
711void FTPManager::_fillRequestDataWithString(MavlinkFTP::Request* request, const QString& str)
712{
713 strncpy((char *)&request->data[0], str.toStdString().c_str(), sizeof(request->data));
714 request->hdr.size = static_cast<uint8_t>(strnlen((const char *)&request->data[0], sizeof(request->data)));
715}
716
717QString FTPManager::_errorMsgFromNak(const MavlinkFTP::Request* nak)
718{
719 QString errorMsg;
720 MavlinkFTP::ErrorCode_t errorCode = static_cast<MavlinkFTP::ErrorCode_t>(nak->data[0]);
721
722 // Nak's normally have 1 byte of data for error code, except for MavlinkFTP::kErrFailErrno which has additional byte for errno
723 if ((errorCode == MavlinkFTP::kErrFailErrno && nak->hdr.size != 2) || ((errorCode != MavlinkFTP::kErrFailErrno) && nak->hdr.size != 1)) {
724 errorMsg = tr("Invalid Nak format");
725 } else if (errorCode == MavlinkFTP::kErrFailErrno) {
726 errorMsg = tr("errno %1").arg(nak->data[1]);
727 } else {
728 errorMsg = MavlinkFTP::errorCodeToString(errorCode);
729 }
730
731 return errorMsg;
732}
733
734void FTPManager::_openFileROBegin(void)
735{
736 MavlinkFTP::Request request{};
737 request.hdr.session = 0;
738 request.hdr.opcode = MavlinkFTP::kCmdOpenFileRO;
739 request.hdr.offset = 0;
740 request.hdr.size = 0;
741 _fillRequestDataWithString(&request, _downloadState.fullPathOnVehicle);
742 _sendRequestExpectAck(&request);
743}
744
745void FTPManager::_openFileROTimeout(void)
746{
747 qCDebug(FTPManagerLog) << "_openFileROTimeout";
748 _downloadComplete(tr("Download failed"));
749}
750
751void FTPManager::_openFileROAckOrNak(const MavlinkFTP::Request* ackOrNak)
752{
753 MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
754 if (requestOpCode != MavlinkFTP::kCmdOpenFileRO) {
755 qCDebug(FTPManagerLog) << "_openFileROAckOrNak: Ack disregarding ack for incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
756 return;
757 }
758 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
759 qCDebug(FTPManagerLog) << "_openFileROAckOrNak: Ack disregarding ack for incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
760 return;
761 }
762
763 _ackOrNakTimeoutTimer.stop();
764
765 if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
766 qCDebug(FTPManagerLog) << "_openFileROAckOrNak: Ack - sessionId:openFileLength" << ackOrNak->hdr.session << ackOrNak->openFileLength;
767
768 if (ackOrNak->hdr.size != sizeof(uint32_t)) {
769 qCDebug(FTPManagerLog) << "_openFileROAckOrNak: Ack ack->hdr.size != sizeof(uint32_t)" << ackOrNak->hdr.size << sizeof(uint32_t);
770 _downloadComplete(tr("Download failed"));
771 return;
772 }
773
774 _downloadState.sessionId = ackOrNak->hdr.session;
775 _downloadState.fileSize = ackOrNak->openFileLength;
776 _downloadState.expectedOffset = 0;
777
778 _downloadState.file.setFileName(_downloadState.toDir.filePath(_downloadState.fileName));
779 if (_downloadState.file.open(QFile::WriteOnly | QFile::Truncate)) {
780 _advanceStateMachine();
781 } else {
782 qCDebug(FTPManagerLog) << "_openFileROAckOrNak: Ack _downloadState.file open failed" << _downloadState.file.errorString();
783 _downloadComplete(tr("Download failed"));
784 }
785 } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
786 qCDebug(FTPManagerLog) << "_handlOpenFileROAck: Nak -" << _errorMsgFromNak(ackOrNak);
787 _downloadComplete(tr("Download failed") + ": " + _errorMsgFromNak(ackOrNak));
788 }
789}
790
791void FTPManager::_burstReadFileWorker(bool firstRequest)
792{
793 qCDebug(FTPManagerLog) << "_burstReadFileWorker: starting burst at offset:firstRequest:retryCount" << _downloadState.expectedOffset << firstRequest << _downloadState.retryCount;
794
795 MavlinkFTP::Request request{};
796 request.hdr.session = _downloadState.sessionId;
797 request.hdr.opcode = MavlinkFTP::kCmdBurstReadFile;
798 request.hdr.offset = _downloadState.expectedOffset;
799 request.hdr.size = sizeof(request.data);
800
801 if (firstRequest) {
802 _downloadState.retryCount = 0;
803 } else {
804 // Must used same sequence number as previous request
805 _expectedIncomingSeqNumber -= 2;
806 }
807
808 _sendRequestExpectAck(&request);
809}
810
811void FTPManager::_burstReadFileBegin(void)
812{
813 _burstReadFileWorker(true /* firstRequestr */);
814}
815
816void FTPManager::_burstReadFileAckOrNak(const MavlinkFTP::Request* ackOrNak)
817{
818 MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
819
820 if (requestOpCode != MavlinkFTP::kCmdBurstReadFile) {
821 qCDebug(FTPManagerLog) << "_burstReadFileAckOrNak: Disregarding due to incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
822 return;
823 }
824 if (ackOrNak->hdr.session != _downloadState.sessionId) {
825 qCDebug(FTPManagerLog) << "_burstReadFileAckOrNak: Disregarding due to incorrect session id actual:expected" << ackOrNak->hdr.session << _downloadState.sessionId;
826 return;
827 }
828
829 _ackOrNakTimeoutTimer.stop();
830
831 if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
832 if (ackOrNak->hdr.seqNumber < _expectedIncomingSeqNumber) {
833 qCDebug(FTPManagerLog) << "_burstReadFileAckOrNak: Disregarding Ack due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
834 return;
835 }
836
837 qCDebug(FTPManagerLog) << QString("_burstReadFileAckOrNak: Ack offset(%1) size(%2) burstComplete(%3)").arg(ackOrNak->hdr.offset).arg(ackOrNak->hdr.size).arg(ackOrNak->hdr.burstComplete);
838
839 if (ackOrNak->hdr.offset != _downloadState.expectedOffset) {
840 if (ackOrNak->hdr.offset > _downloadState.expectedOffset) {
841 // There is a hole in our data, record it as missing and continue on
842 MissingData_t missingData;
843 missingData.offset = _downloadState.expectedOffset;
844 missingData.cBytesMissing = ackOrNak->hdr.offset - _downloadState.expectedOffset;
845 _downloadState.rgMissingData.append(missingData);
846 qCDebug(FTPManagerLog) << "_handleBurstReadFileAck: adding missing data offset:cBytesMissing" << missingData.offset << missingData.cBytesMissing;
847 } else {
848 // Offset is past what we have already seen, disregard and wait for something usefule
849 _ackOrNakTimeoutTimer.start();
850 qCDebug(FTPManagerLog) << "_handleBurstReadFileAck: received offset less than expected offset received:expected" << ackOrNak->hdr.offset << _downloadState.expectedOffset;
851 return;
852 }
853 }
854
855 _downloadState.file.seek(ackOrNak->hdr.offset);
856 int bytesWritten = _downloadState.file.write((const char*)ackOrNak->data, ackOrNak->hdr.size);
857 if (bytesWritten != ackOrNak->hdr.size) {
858 _downloadComplete(tr("Download failed: Error saving file"));
859 return;
860 }
861 _downloadState.bytesWritten += ackOrNak->hdr.size;
862 _downloadState.expectedOffset = ackOrNak->hdr.offset + ackOrNak->hdr.size;
863
864 if (ackOrNak->hdr.burstComplete) {
865 // The current burst is done, request next one in offset sequence
866 _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber;
867 _burstReadFileWorker(true /* firstRequest */);
868 } else {
869 // Still within a burst, next ack should come automatically
870 _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber + 1;
871 _ackOrNakTimeoutTimer.start();
872 }
873
874 // Emit progress last, as cancel could be called in there
875 if (_downloadState.fileSize != 0) {
876 emit commandProgress((float)(_downloadState.bytesWritten) / (float)_downloadState.fileSize);
877 }
878 } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
879 MavlinkFTP::ErrorCode_t errorCode = static_cast<MavlinkFTP::ErrorCode_t>(ackOrNak->data[0]);
880
881 if (errorCode == MavlinkFTP::kErrEOF) {
882 // Burst sequence has gone through the whole file
883 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
884 qCDebug(FTPManagerLog) << "_burstReadFileAckOrNak: EOF Nak"
885 "with incorrect sequence nr actual:expected"
886 << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
887 /* We have received the EOF Nak but out of sequence, i.e. data is missing */
888 _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber;
889 _burstReadFileWorker(true); /* Retry from last expected offset */
890 } else {
891 qCDebug(FTPManagerLog) << "_burstReadFileAckOrNak EOF";
892 _advanceStateMachine();
893 }
894 } else { /* Don't care is this is out of sequence */
895 qCDebug(FTPManagerLog) << "_burstReadFileAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
896 _downloadComplete(tr("Download failed"));
897 }
898 }
899}
900
901void FTPManager::_burstReadFileTimeout(void)
902{
903 if (++_downloadState.retryCount > _maxRetry) {
904 qCDebug(FTPManagerLog) << QString("_burstReadFileTimeout retries exceeded");
905 _downloadComplete(tr("Download failed"));
906 } else {
907 // Try again
908 qCDebug(FTPManagerLog) << QString("_burstReadFileTimeout: retrying - retryCount(%1) offset(%2)").arg(_downloadState.retryCount).arg(_downloadState.expectedOffset);
909 _burstReadFileWorker(false /* firstReqeust */);
910 }
911}
912
913void FTPManager::_listDirectoryWorker(bool firstRequest)
914{
915 qCDebug(FTPManagerLog) << "_listDirectoryWorker: offset:firstRequest:retryCount" << _listDirectoryState.expectedOffset << firstRequest << _listDirectoryState.retryCount;
916
917 MavlinkFTP::Request request{};
918 request.hdr.session = _downloadState.sessionId;
919 request.hdr.opcode = _listDirectoryState.opCode;
920 request.hdr.offset = _listDirectoryState.expectedOffset;
921 request.hdr.size = sizeof(request.data);
922 _fillRequestDataWithString(&request, _listDirectoryState.fullPathOnVehicle);
923
924 if (firstRequest) {
925 _listDirectoryState.retryCount = 0;
926 } else {
927 // Must used same sequence number as previous request
928 _expectedIncomingSeqNumber -= 2;
929 }
930
931 _sendRequestExpectAck(&request);
932}
933
934void FTPManager::_listDirectoryBegin(void)
935{
936 _listDirectoryWorker(true /* firstRequest */);
937}
938
939void FTPManager::_listDirectoryAckOrNak(const MavlinkFTP::Request* ackOrNak)
940{
941 MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
942
943 if (requestOpCode != _listDirectoryState.opCode) {
944 qCDebug(FTPManagerLog) << "_listDirectoryAckOrNak: Disregarding due to incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
945 return;
946 }
947
948 _ackOrNakTimeoutTimer.stop();
949
950 if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
951 if (ackOrNak->hdr.seqNumber < _expectedIncomingSeqNumber) {
952 qCDebug(FTPManagerLog) << "_listDirectoryAckOrNak: Disregarding Ack due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
953 return;
954 }
955
956 if (_listDirectoryState.opCode == MavlinkFTP::kCmdListDirectoryWithTime) {
957 _listDirWithTimeSupport = WithTimeSupport_t::Supported;
958 }
959
960 qCDebug(FTPManagerLog) << QString("_listDirectoryAckOrNak: Ack size(%1)").arg(ackOrNak->hdr.size);
961
962 // Parse entries in ackOrNak->data into _listDirectoryState.rgDirectoryList
963 const char* curDataPtr = (const char*)ackOrNak->data;
964 while (curDataPtr < (const char*)ackOrNak->data + ackOrNak->hdr.size) {
965 QString dirEntry = curDataPtr;
966 curDataPtr += dirEntry.size() + 1;
967 _listDirectoryState.rgDirectoryList.append(dirEntry);
968 _listDirectoryState.expectedOffset++;
969 }
970
971 // Request next set of directory entries
972 _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber;
973 _listDirectoryWorker(true /* firstRequest */);
974 } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
975 MavlinkFTP::ErrorCode_t errorCode = static_cast<MavlinkFTP::ErrorCode_t>(ackOrNak->data[0]);
976
977 if (errorCode == MavlinkFTP::kErrEOF) {
978 // All entries returned
979 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
980 qCDebug(FTPManagerLog) << "_listDirectoryAckOrNak: Disregarding Nak due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
981 _ackOrNakTimeoutTimer.start();
982 return;
983 } else {
984 qCDebug(FTPManagerLog) << "_listDirectoryAckOrNak EOF";
985 if (_listDirectoryState.opCode == MavlinkFTP::kCmdListDirectoryWithTime) {
986 _listDirWithTimeSupport = WithTimeSupport_t::Supported;
987 }
988 _advanceStateMachine();
989 }
990 } else if (errorCode == MavlinkFTP::kErrUnknownCommand && _listDirectoryState.opCode == MavlinkFTP::kCmdListDirectoryWithTime) {
991 // Server doesn't implement kCmdListDirectoryWithTime. Remember that, fall back to the
992 // plain listing and restart from the beginning. The UnknownCommand Nak is a definitive
993 // capability statement so we act on it without a strict sequence check; the restart is
994 // idempotent (offset and accumulated entries are reset).
995 qCDebug(FTPManagerLog) << "_listDirectoryAckOrNak: kCmdListDirectoryWithTime unsupported, falling back to kCmdListDirectory";
996 _listDirWithTimeSupport = WithTimeSupport_t::Unsupported;
997 _listDirectoryState.opCode = MavlinkFTP::kCmdListDirectory;
998 _listDirectoryState.expectedOffset = 0;
999 _listDirectoryState.rgDirectoryList.clear();
1000 _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber;
1001 _listDirectoryWorker(true /* firstRequest */);
1002 } else { /* Don't care is this is out of sequence */
1003 qCDebug(FTPManagerLog) << "_listDirectoryAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
1004 _listDirectoryComplete(tr("List directory failed"));
1005 }
1006 }
1007}
1008
1009void FTPManager::_listDirectoryTimeout(void)
1010{
1011 if (++_listDirectoryState.retryCount > _maxRetry) {
1012 qCDebug(FTPManagerLog) << QString("_listDirectoryTimeout retries exceeded");
1013 _listDirectoryComplete(tr("List directory failed"));
1014 } else {
1015 // Try again
1016 qCDebug(FTPManagerLog) << QString("_listDirectoryTimeout: retrying - retryCount(%1) offset(%2)").arg(_listDirectoryState.retryCount).arg(_listDirectoryState.expectedOffset);
1017 _listDirectoryWorker(false /* firstReqeust */);
1018 }
1019}
1020
1021void FTPManager::_fillMissingBlocksWorker(bool firstRequest)
1022{
1023 if (_downloadState.rgMissingData.count()) {
1024 MavlinkFTP::Request request{};
1025 MissingData_t& missingData = _downloadState.rgMissingData.first();
1026
1027 uint32_t cBytesToRead = qMin((uint32_t)sizeof(request.data), missingData.cBytesMissing);
1028
1029 qCDebug(FTPManagerLog) << "_fillMissingBlocksBegin: offset:cBytesToRead" << missingData.offset << cBytesToRead;
1030
1031 request.hdr.session = _downloadState.sessionId;
1032 request.hdr.opcode = MavlinkFTP::kCmdReadFile;
1033 request.hdr.offset = missingData.offset;
1034 request.hdr.size = cBytesToRead;
1035
1036 if (firstRequest) {
1037 _downloadState.retryCount = 0;
1038 } else {
1039 // Must used same sequence number as previous request
1040 _expectedIncomingSeqNumber -= 2;
1041 }
1042 _downloadState.expectedOffset = request.hdr.offset;
1043
1044 _sendRequestExpectAck(&request);
1045 } else {
1046 // We should have the full file now
1047 if (_downloadState.checksize == false || _downloadState.bytesWritten == _downloadState.fileSize) {
1048 _advanceStateMachine();
1049 } else {
1050 qCDebug(FTPManagerLog) << "_fillMissingBlocksWorker: no missing blocks but file still incomplete - bytesWritten:fileSize" << _downloadState.bytesWritten << _downloadState.fileSize;
1051 _downloadComplete(tr("Download failed"));
1052 }
1053 }
1054}
1055
1056void FTPManager::_fillMissingBlocksBegin(void)
1057{
1058 _fillMissingBlocksWorker(true /* firstRequest */);
1059}
1060
1061void FTPManager::_fillMissingBlocksAckOrNak(const MavlinkFTP::Request* ackOrNak)
1062{
1063 MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
1064
1065 if (requestOpCode != MavlinkFTP::kCmdReadFile) {
1066 qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak: Disregarding due to incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
1067 return;
1068 }
1069 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
1070 qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak: Disregarding due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
1071 return;
1072 }
1073 if (ackOrNak->hdr.session != _downloadState.sessionId) {
1074 qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak: Disregarding due to incorrect session id actual:expected" << ackOrNak->hdr.session << _downloadState.sessionId;
1075 return;
1076 }
1077
1078 _ackOrNakTimeoutTimer.stop();
1079
1080 if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
1081 qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak: Ack offset:size" << ackOrNak->hdr.offset << ackOrNak->hdr.size;
1082
1083 if (ackOrNak->hdr.offset != _downloadState.expectedOffset) {
1084 if (++_downloadState.retryCount > _maxRetry) {
1085 qCDebug(FTPManagerLog) << QString("_fillMissingBlocksAckOrNak: offset mismatch, retries exceeded");
1086 _downloadComplete(tr("Download failed"));
1087 return;
1088 }
1089
1090 // Ask for current offset again
1091 qCDebug(FTPManagerLog) << QString("_fillMissingBlocksAckOrNak: Ack offset mismatch retry, retryCount(%1) offset(%2)").arg(_downloadState.retryCount).arg(_downloadState.expectedOffset);
1092 _fillMissingBlocksWorker(false /* firstReqeust */);
1093 return;
1094 }
1095
1096 _downloadState.file.seek(ackOrNak->hdr.offset);
1097 int bytesWritten = _downloadState.file.write((const char*)ackOrNak->data, ackOrNak->hdr.size);
1098 if (bytesWritten != ackOrNak->hdr.size) {
1099 _downloadComplete(tr("Download failed: Error saving file"));
1100 return;
1101 }
1102 _downloadState.bytesWritten += ackOrNak->hdr.size;
1103
1104 MissingData_t& missingData = _downloadState.rgMissingData.first();
1105 missingData.offset += ackOrNak->hdr.size;
1106 missingData.cBytesMissing -= ackOrNak->hdr.size;
1107 if (missingData.cBytesMissing == 0) {
1108 // This block is finished, remove it
1109 _downloadState.rgMissingData.takeFirst();
1110 }
1111
1112 // Move on to fill in possible next hole
1113 _fillMissingBlocksWorker(true /* firstReqeust */);
1114
1115 // Emit progress last, as cancel could be called in there
1116 if (_downloadState.fileSize != 0) {
1117 emit commandProgress((float)(_downloadState.bytesWritten) / (float)_downloadState.fileSize);
1118 }
1119 } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
1120 MavlinkFTP::ErrorCode_t errorCode = static_cast<MavlinkFTP::ErrorCode_t>(ackOrNak->data[0]);
1121
1122 if (errorCode == MavlinkFTP::kErrEOF) {
1123 qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak EOF";
1124 if (_downloadState.checksize == false || _downloadState.bytesWritten == _downloadState.fileSize) {
1125 // We've successfully complete filling in all missing blocks
1126 _advanceStateMachine();
1127 return;
1128 }
1129 }
1130
1131 qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
1132 _downloadComplete(tr("Download failed"));
1133 }
1134}
1135
1136void FTPManager::_fillMissingBlocksTimeout(void)
1137{
1138 if (++_downloadState.retryCount > _maxRetry) {
1139 qCDebug(FTPManagerLog) << QString("_fillMissingBlocksTimeout retries exceeded");
1140 _downloadComplete(tr("Download failed"));
1141 } else {
1142 // Ask for current offset again
1143 qCDebug(FTPManagerLog) << QString("_fillMissingBlocksTimeout: retrying - retryCount(%1) offset(%2)").arg(_downloadState.retryCount).arg(_downloadState.expectedOffset);
1144 _fillMissingBlocksWorker(false /* firstReqeust */);
1145 }
1146}
1147
1148void FTPManager::_resetSessionsBegin(void)
1149{
1150 MavlinkFTP::Request request{};
1151 request.hdr.opcode = MavlinkFTP::kCmdResetSessions;
1152 request.hdr.size = 0;
1153 _sendRequestExpectAck(&request);
1154}
1155
1156void FTPManager::_resetSessionsAckOrNak(const MavlinkFTP::Request* ackOrNak)
1157{
1158 MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
1159
1160 if (requestOpCode != MavlinkFTP::kCmdResetSessions) {
1161 qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak: Disregarding due to incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
1162 return;
1163 }
1164 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
1165 qCDebug(FTPManagerLog) << "_resetSessionsAckOrNak: Disregarding due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
1166 return;
1167 }
1168
1169 _ackOrNakTimeoutTimer.stop();
1170
1171 if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
1172 qCDebug(FTPManagerLog) << "_resetSessionsAckOrNak: Ack";
1173 _advanceStateMachine();
1174 } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
1175 qCDebug(FTPManagerLog) << "_resetSessionsAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
1176 _downloadComplete(QString());
1177 }
1178}
1179
1180void FTPManager::_resetSessionsTimeout(void)
1181{
1182 qCDebug(FTPManagerLog) << "_resetSessionsTimeout";
1183 _downloadComplete(QString());
1184}
1185
1186void FTPManager::_sendRequestExpectAck(MavlinkFTP::Request* request)
1187{
1188 _ackOrNakTimeoutTimer.start();
1189
1190 SharedLinkInterfacePtr sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
1191 if (sharedLink) {
1192 request->hdr.seqNumber = _expectedIncomingSeqNumber + 1; // Outgoing is 1 past last incoming
1193 _expectedIncomingSeqNumber += 2;
1194
1195 qCDebug(FTPManagerLog) << "_sendRequestExpectAck opcode:" << MavlinkFTP::opCodeToString(static_cast<MavlinkFTP::OpCode_t>(request->hdr.opcode)) << "seqNumber:" << request->hdr.seqNumber;
1196
1197 mavlink_message_t message;
1198 mavlink_msg_file_transfer_protocol_pack_chan(MAVLinkProtocol::instance()->getSystemId(),
1200 sharedLink->mavlinkChannel(),
1201 &message,
1202 0, // Target network, 0=broadcast?
1203 _vehicle->id(),
1204 _ftpCompId,
1205 (uint8_t*)request); // Payload
1206 _vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), message);
1207 } else {
1208 qCDebug(FTPManagerLog) << "_sendRequestExpectAck No primary link. Allowing timeout to fail sequence.";
1209 }
1210}
1211
1212bool FTPManager::_parseURI(uint8_t fromCompId, const QString& uri, QString& parsedURI, uint8_t& compId)
1213{
1214 parsedURI = uri;
1215 compId = (fromCompId == MAV_COMP_ID_ALL) ? (uint8_t)MAV_COMP_ID_AUTOPILOT1 : fromCompId;
1216
1217 // Pull scheme off the front if there
1218 QString ftpPrefix(QStringLiteral("%1://").arg(mavlinkFTPScheme));
1219 if (parsedURI.startsWith(ftpPrefix, Qt::CaseInsensitive)) {
1220 parsedURI = parsedURI.right(parsedURI.length() - ftpPrefix.length() + 1);
1221 }
1222 if (parsedURI.contains("://")) {
1223 qCWarning(FTPManagerLog) << "Incorrect uri scheme or format" << uri;
1224 return false;
1225 }
1226
1227 // Pull component id off the front if there
1228 QRegularExpression regEx("^/??\\[\\;comp\\=(\\d+)\\]");
1229 QRegularExpressionMatch match = regEx.match(parsedURI);
1230 if (match.hasMatch()) {
1231 bool ok;
1232 compId = match.captured(1).toUInt(&ok);
1233 if (!ok) {
1234 qCWarning(FTPManagerLog) << "Incorrect format for component id" << uri;
1235 return false;
1236 }
1237
1238 qCDebug(FTPManagerLog) << "Found compId in MAVLink FTP URI: " << compId;
1239 parsedURI.replace(QRegularExpression("\\[\\;comp\\=\\d+\\]"), "");
1240 }
1241
1242 return true;
1243}
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
Error error
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void cancelDownload()
bool listDirectory(uint8_t fromCompId, const QString &fromURI)
bool deleteFile(uint8_t fromCompId, const QString &fromURI)
bool upload(uint8_t toCompId, const QString &toURI, const QString &fromFile)
Definition FTPManager.cc:85
static constexpr const char * mavlinkFTPScheme
Definition FTPManager.h:74
void uploadComplete(const QString &file, const QString &errorMsg)
void cancelUpload()
void commandProgress(float value)
void downloadComplete(const QString &file, const QString &errorMsg)
void cancelListDirectory()
void deleteComplete(const QString &file, const QString &errorMsg)
void cancelDelete()
bool download(uint8_t fromCompId, const QString &fromURI, const QString &toDir, const QString &fileName="", bool checksize=true)
Definition FTPManager.cc:30
void listDirectoryComplete(const QStringList &dirList, const QString &errorMsg)
static int getComponentId()
static MAVLinkProtocol * instance()
int getSystemId() const
ErrorCode_t
Error codes returned in Nak response PayloadHeader.data[0].
Definition MAVLinkFTP.h:62
@ kErrEOF
Offset past end of file for List and Read commands.
Definition MAVLinkFTP.h:69
@ kErrUnknownCommand
Unknown command opcode.
Definition MAVLinkFTP.h:70
@ kErrFailErrno
errno sent back in PayloadHeader.data[1]
Definition MAVLinkFTP.h:65
static QString errorCodeToString(ErrorCode_t errorCode)
Definition MAVLinkFTP.cc:49
@ kCmdRemoveFile
Remove file at <path>
Definition MAVLinkFTP.h:47
@ kCmdWriteFile
Writes <size> bytes to <offset> in <session>
Definition MAVLinkFTP.h:46
@ kCmdCreateFile
Creates file at <path> for writing, returns <session>
Definition MAVLinkFTP.h:45
@ kCmdOpenFileRO
Opens file at <path> for reading, returns <session>
Definition MAVLinkFTP.h:43
@ kCmdListDirectoryWithTime
List directory as kCmdListDirectory, each entry additionally carrying trailing \t<modification time>
Definition MAVLinkFTP.h:55
@ kCmdReadFile
Reads <size> bytes from <offset> in <session>
Definition MAVLinkFTP.h:44
@ kCmdResetSessions
Terminates all open Read sessions.
Definition MAVLinkFTP.h:41
@ kRspAck
Ack response.
Definition MAVLinkFTP.h:57
@ kCmdBurstReadFile
Burst download session file.
Definition MAVLinkFTP.h:54
@ kCmdTerminateSession
Terminates open Read session.
Definition MAVLinkFTP.h:40
@ kRspNak
Nak response.
Definition MAVLinkFTP.h:58
@ kCmdListDirectory
List files in <path> from <offset>
Definition MAVLinkFTP.h:42
static QString opCodeToString(OpCode_t opCode)
Definition MAVLinkFTP.cc:3
WeakLinkInterfacePtr primaryLink() const
VehicleLinkManager * vehicleLinkManager()
Definition Vehicle.h:579
int id() const
Definition Vehicle.h:429
bool sendMessageOnLinkThreadSafe(LinkInterface *link, mavlink_message_t message)
Definition Vehicle.cc:1390
bool runningUnitTests()