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