9#include <QtCore/QFileInfo>
19 _ackOrNakTimeoutTimer.setSingleShot(
true);
21 _ackOrNakTimeoutTimer.setInterval(
QGC::runningUnitTests() ? kTestAckTimeoutMs : _ackOrNakTimeoutMsecs);
22 connect(&_ackOrNakTimeoutTimer, &QTimer::timeout,
this, &FTPManager::_ackOrNakTimeout);
25 Q_ASSERT(
sizeof(MavlinkFTP::RequestHeader) == 12);
30bool FTPManager::download(uint8_t fromCompId,
const QString& fromURI,
const QString& toDir,
const QString& fileName,
bool checksize)
32 qCDebug(FTPManagerLog) <<
"Download fromCompId:" << fromCompId
33 <<
"fromURI:" << fromURI
35 <<
"fileName:" << fileName;
37 if (!_rgStateMachine.isEmpty()) {
38 qCDebug(FTPManagerLog) <<
"Cannot download. Already in another operation";
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 },
49 for (
size_t i=0; i<
sizeof(rgDownloadStateMachine)/
sizeof(rgDownloadStateMachine[0]); i++) {
50 _rgStateMachine.append(rgDownloadStateMachine[i]);
53 _downloadState.reset();
54 _downloadState.toDir.setPath(toDir);
55 _downloadState.checksize = checksize;
57 if (!_parseURI(fromCompId, fromURI, _downloadState.fullPathOnVehicle, _ftpCompId)) {
58 qCWarning(FTPManagerLog) <<
"_parseURI failed";
64 int lastDirSlashIndex;
65 for (lastDirSlashIndex=_downloadState.fullPathOnVehicle.size()-1; lastDirSlashIndex>=0; lastDirSlashIndex--) {
66 if (_downloadState.fullPathOnVehicle[lastDirSlashIndex] ==
'/') {
72 if (fileName.isEmpty()) {
73 _downloadState.fileName = _downloadState.fullPathOnVehicle.right(_downloadState.fullPathOnVehicle.size() - lastDirSlashIndex);
75 _downloadState.fileName = fileName;
78 qCDebug(FTPManagerLog) <<
"_downloadState.fullPathOnVehicle:_downloadState.fileName" << _downloadState.fullPathOnVehicle << _downloadState.fileName;
87 qCDebug(FTPManagerLog) <<
"upload fromFile:" << fromFile <<
"toURI:" << toURI <<
"toCompId:" << toCompId;
89 if (!_rgStateMachine.isEmpty()) {
90 qCDebug(FTPManagerLog) <<
"Cannot upload. Already in another operation";
94 QFileInfo sourceInfo(fromFile);
95 if (!sourceInfo.exists() || !sourceInfo.isFile()) {
96 qCWarning(FTPManagerLog) <<
"Cannot upload. Source file missing" << fromFile;
100 if (sourceInfo.size() > std::numeric_limits<uint32_t>::max()) {
101 qCWarning(FTPManagerLog) <<
"Cannot upload. File too large" << fromFile << sourceInfo.size();
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();
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;
121 if (!_parseURI(toCompId, toURI, _uploadState.fullPathOnVehicle, _ftpCompId)) {
122 qCWarning(FTPManagerLog) <<
"_parseURI failed";
123 _uploadState.reset();
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 },
133 for (
size_t i=0; i<
sizeof(rgUploadStateMachine)/
sizeof(rgUploadStateMachine[0]); i++) {
134 _rgStateMachine.append(rgUploadStateMachine[i]);
137 _startStateMachine();
144 qCDebug(FTPManagerLog) <<
"list directory fromURI:" << fromURI <<
"fromCompId:" << fromCompId;
146 if (!_rgStateMachine.isEmpty()) {
147 qCDebug(FTPManagerLog) <<
"Cannot list directory. Already in another operation";
151 static const StateFunctions_t rgStateMachine[] = {
152 { &FTPManager::_listDirectoryBegin, &FTPManager::_listDirectoryAckOrNak, &FTPManager::_listDirectoryTimeout },
153 { &FTPManager::_listDirectoryCompleteNoError,
nullptr,
nullptr },
155 for (
size_t i=0; i<
sizeof(rgStateMachine)/
sizeof(rgStateMachine[0]); i++) {
156 _rgStateMachine.append(rgStateMachine[i]);
159 _listDirectoryState.reset();
162 _listDirectoryState.opCode = (_listDirWithTimeSupport == WithTimeSupport_t::Unsupported)
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";
172 <<
"-" << supportStr;
174 if (!_parseURI(fromCompId, fromURI, _listDirectoryState.fullPathOnVehicle, _ftpCompId)) {
175 qCWarning(FTPManagerLog) <<
"_parseURI failed";
179 qCDebug(FTPManagerLog) <<
"_listDirectoryState.fullPathOnVehicle" << _listDirectoryState.fullPathOnVehicle;
181 _startStateMachine();
188 qCDebug(FTPManagerLog) <<
"delete file fromURI:" << fromURI <<
"fromCompId:" << fromCompId;
190 if (!_rgStateMachine.isEmpty()) {
191 qCDebug(FTPManagerLog) <<
"Cannot delete file. Already in another operation";
195 static const StateFunctions_t rgStateMachine[] = {
196 { &FTPManager::_deleteFileBegin, &FTPManager::_deleteFileAckOrNak, &FTPManager::_deleteFileTimeout },
197 { &FTPManager::_deleteCompleteNoError,
nullptr,
nullptr },
199 for (
size_t i=0; i<
sizeof(rgStateMachine)/
sizeof(rgStateMachine[0]); i++) {
200 _rgStateMachine.append(rgStateMachine[i]);
203 _deleteState.reset();
205 if (!_parseURI(fromCompId, fromURI, _deleteState.fullPathOnVehicle, _ftpCompId)) {
206 qCWarning(FTPManagerLog) <<
"_parseURI failed";
207 _rgStateMachine.clear();
211 qCDebug(FTPManagerLog) <<
"_deleteState.fullPathOnVehicle" << _deleteState.fullPathOnVehicle;
213 _startStateMachine();
220 if (!_downloadState.inProgress()) {
224 _ackOrNakTimeoutTimer.stop();
225 _rgStateMachine.clear();
226 static const StateFunctions_t rgTerminateStateMachine[] = {
227 { &FTPManager::_terminateSessionBegin, &FTPManager::_terminateSessionAckOrNak, &FTPManager::_terminateSessionTimeout },
228 { &FTPManager::_terminateComplete,
nullptr,
nullptr },
230 for (
size_t i=0; i<
sizeof(rgTerminateStateMachine)/
sizeof(rgTerminateStateMachine[0]); i++) {
231 _rgStateMachine.append(rgTerminateStateMachine[i]);
233 _downloadState.retryCount = 0;
234 _startStateMachine();
239 if (!_listDirectoryState.inProgress()) {
243 if (_rgStateMachine.isEmpty()) {
247 _listDirectoryComplete(tr(
"Aborted"));
252 if (!_uploadState.inProgress()) {
256 _uploadState.cancelled =
true;
257 _ackOrNakTimeoutTimer.stop();
258 _rgStateMachine.clear();
260 if (_uploadState.sessionId != 0) {
261 static const StateFunctions_t rgTerminateStateMachine[] = {
262 { &FTPManager::_terminateUploadSessionBegin, &FTPManager::_terminateUploadSessionAckOrNak, &FTPManager::_terminateUploadSessionTimeout },
263 { &FTPManager::_uploadFinalize,
nullptr,
nullptr },
265 for (
size_t i=0; i<
sizeof(rgTerminateStateMachine)/
sizeof(rgTerminateStateMachine[0]); i++) {
266 _rgStateMachine.append(rgTerminateStateMachine[i]);
268 _uploadState.retryCount = 0;
269 _startStateMachine();
271 _uploadComplete(tr(
"Aborted"));
277 if (!_deleteState.inProgress()) {
281 _deleteComplete(tr(
"Aborted"));
284void FTPManager::_terminateSessionBegin(
void)
286 MavlinkFTP::Request request{};
287 request.hdr.session = _downloadState.sessionId;
289 _sendRequestExpectAck(&request);
292void FTPManager::_terminateSessionAckOrNak(
const MavlinkFTP::Request *ackOrNak)
296 qCDebug(FTPManagerLog) <<
"_terminateSessionAckOrNak: Ack disregarding ack for incorrect requestOpCode" <<
MavlinkFTP::opCodeToString(requestOpCode);
299 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
300 qCDebug(FTPManagerLog) <<
"_terminateSessionAckOrNak: Ack disregarding ack for incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
304 _ackOrNakTimeoutTimer.stop();
305 _advanceStateMachine();
308void FTPManager::_terminateSessionTimeout(
void)
310 if (++_downloadState.retryCount > _maxRetry) {
311 qCDebug(FTPManagerLog) << QString(
"_terminateSessionTimeout retries exceeded");
312 _downloadComplete(tr(
"Download failed"));
315 qCDebug(FTPManagerLog) << QString(
"_terminateSessionTimeout: retrying - retryCount(%1)").arg(_downloadState.retryCount);
316 _terminateSessionBegin();
321void FTPManager::_terminateComplete(
void)
323 _downloadComplete(
"Aborted");
328void FTPManager::_downloadComplete(
const QString& errorMsg)
330 qCDebug(FTPManagerLog) << QString(
"_downloadComplete: errorMsg(%1)").arg(errorMsg);
332 QString downloadFilePath = _downloadState.toDir.absoluteFilePath(_downloadState.fileName);
333 QString
error = errorMsg;
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();
350void FTPManager::_listDirectoryComplete(
const QString& errorMsg)
352 qCDebug(FTPManagerLog) << QString(
"_listDirectoryComplete: errorMsg(%1)").arg(errorMsg);
354 _ackOrNakTimeoutTimer.stop();
355 _rgStateMachine.clear();
356 _currentStateMachineIndex = -1;
358 QStringList rgDirectoryList = _listDirectoryState.rgDirectoryList;
359 if (!errorMsg.isEmpty()) {
360 rgDirectoryList.clear();
363 _listDirectoryState.reset();
368void FTPManager::_deleteFileBegin(
void)
370 qCDebug(FTPManagerLog) <<
"file" << _deleteState.fullPathOnVehicle;
372 MavlinkFTP::Request request{};
373 request.hdr.session = 0;
375 request.hdr.offset = 0;
376 request.hdr.size = 0;
377 _fillRequestDataWithString(&request, _deleteState.fullPathOnVehicle);
378 _sendRequestExpectAck(&request);
381void FTPManager::_deleteFileAckOrNak(
const MavlinkFTP::Request* ackOrNak)
385 qCDebug(FTPManagerLog) <<
"_deleteFileAckOrNak: Ack disregarding ack for incorrect requestOpCode" <<
MavlinkFTP::opCodeToString(requestOpCode);
388 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
389 qCDebug(FTPManagerLog) <<
"_deleteFileAckOrNak: Ack disregarding ack for incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
393 _ackOrNakTimeoutTimer.stop();
396 _advanceStateMachine();
398 qCDebug(FTPManagerLog) <<
"_deleteFileAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
399 _deleteComplete(tr(
"Delete failed") +
": " + _errorMsgFromNak(ackOrNak));
403void FTPManager::_deleteFileTimeout(
void)
405 if (++_deleteState.retryCount > _maxRetry) {
406 qCDebug(FTPManagerLog) << QString(
"_deleteFileTimeout retries exceeded");
407 _deleteComplete(tr(
"Delete failed"));
409 qCDebug(FTPManagerLog) << QString(
"_deleteFileTimeout: retrying - retryCount(%1)").arg(_deleteState.retryCount);
414void FTPManager::_deleteComplete(
const QString& errorMsg)
416 qCDebug(FTPManagerLog) << QString(
"_deleteComplete: errorMsg(%1)").arg(errorMsg);
418 const QString deletedPath = _deleteState.fullPathOnVehicle;
420 _ackOrNakTimeoutTimer.stop();
421 _rgStateMachine.clear();
422 _currentStateMachineIndex = -1;
424 _deleteState.reset();
429void FTPManager::_createFileBegin(
void)
431 qCDebug(FTPManagerLog) <<
"file" << _uploadState.fullPathOnVehicle;
433 MavlinkFTP::Request request{};
434 request.hdr.session = 0;
436 request.hdr.offset = 0;
437 request.hdr.size = 0;
438 _fillRequestDataWithString(&request, _uploadState.fullPathOnVehicle);
439 _sendRequestExpectAck(&request);
442void FTPManager::_createFileAckOrNak(
const MavlinkFTP::Request* ackOrNak)
446 qCDebug(FTPManagerLog) <<
"_createFileAckOrNak: Ack disregarding ack for incorrect requestOpCode" <<
MavlinkFTP::opCodeToString(requestOpCode);
449 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
450 qCDebug(FTPManagerLog) <<
"_createFileAckOrNak: Ack disregarding ack for incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
454 _ackOrNakTimeoutTimer.stop();
457 qCDebug(FTPManagerLog) <<
"_createFileAckOrNak: Ack - sessionId" << ackOrNak->hdr.session;
459 _uploadState.sessionId = ackOrNak->hdr.session;
460 _advanceStateMachine();
462 qCDebug(FTPManagerLog) <<
"_createFileAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
463 _uploadComplete(tr(
"Upload failed for: %1 - error: %2").arg(_uploadState.fullPathOnVehicle).arg(_errorMsgFromNak(ackOrNak)));
467void FTPManager::_createFileTimeout(
void)
469 qCDebug(FTPManagerLog) <<
"_createFileTimeout";
470 _uploadComplete(tr(
"Upload failed for: %1 - no response from vehicle").arg(_uploadState.fullPathOnVehicle));
473void FTPManager::_writeFileBegin(
void)
475 _writeFileWorker(
true );
478void FTPManager::_writeFileWorker(
bool firstRequest)
480 if (!_uploadState.file.isOpen()) {
481 _uploadComplete(tr(
"Upload failed for: %1 - file not open").arg(_uploadState.fullPathOnVehicle));
485 if (_uploadState.totalBytesSent >= _uploadState.fileSize) {
486 _advanceStateMachine();
490 qCDebug(FTPManagerLog) <<
"_writeFileWorker: offset:firstRequest:retryCount" << _uploadState.totalBytesSent << firstRequest << _uploadState.retryCount;
492 MavlinkFTP::Request request{};
493 request.hdr.session = _uploadState.sessionId;
495 request.hdr.offset = _uploadState.totalBytesSent;
498 _uploadState.retryCount = 0;
500 _expectedIncomingSeqNumber -= 2;
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);
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));
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));
522 request.hdr.size =
static_cast<uint8_t
>(bytesRead);
523 _uploadState.lastChunkSize =
static_cast<uint32_t
>(bytesRead);
525 _sendRequestExpectAck(&request);
528void FTPManager::_writeFileAckOrNak(
const MavlinkFTP::Request* ackOrNak)
533 qCDebug(FTPManagerLog) <<
"_writeFileAckOrNak: Disregarding due to incorrect requestOpCode" <<
MavlinkFTP::opCodeToString(requestOpCode);
536 if (ackOrNak->hdr.session != _uploadState.sessionId) {
537 qCDebug(FTPManagerLog) <<
"_writeFileAckOrNak: Disregarding due to incorrect session id actual:expected" << ackOrNak->hdr.session << _uploadState.sessionId;
541 _ackOrNakTimeoutTimer.stop();
544 if (ackOrNak->hdr.seqNumber < _expectedIncomingSeqNumber) {
545 qCDebug(FTPManagerLog) <<
"_writeFileAckOrNak: Disregarding Ack due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
549 if (ackOrNak->hdr.size != 0) {
550 qCDebug(FTPManagerLog) <<
"_writeFileAckOrNak: unexpected ack size expected:actual 0" << ackOrNak->hdr.size;
553 _uploadState.totalBytesSent += _uploadState.lastChunkSize;
554 _uploadState.lastChunkSize = 0;
555 _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber;
557 if (_uploadState.fileSize != 0) {
558 emit
commandProgress(
static_cast<float>(_uploadState.totalBytesSent) /
static_cast<float>(_uploadState.fileSize));
561 if (_uploadState.totalBytesSent >= _uploadState.fileSize) {
562 _advanceStateMachine();
564 _writeFileWorker(
true );
567 qCDebug(FTPManagerLog) <<
"_writeFileAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
568 _uploadComplete(tr(
"Upload failed for: %1 - error: %2").arg(_uploadState.fullPathOnVehicle).arg(_errorMsgFromNak(ackOrNak)));
572void FTPManager::_writeFileTimeout(
void)
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));
578 qCDebug(FTPManagerLog) << QString(
"_writeFileTimeout: retrying - retryCount(%1) offset(%2)").arg(_uploadState.retryCount).arg(_uploadState.totalBytesSent);
579 _writeFileWorker(
false );
583void FTPManager::_terminateUploadSessionBegin(
void)
585 if (_uploadState.sessionId == 0) {
586 qCWarning(FTPManagerLog) <<
"_terminateUploadSessionBegin: No session to terminate";
587 _advanceStateMachine();
591 MavlinkFTP::Request request{};
592 request.hdr.session = _uploadState.sessionId;
594 _sendRequestExpectAck(&request);
597void FTPManager::_terminateUploadSessionAckOrNak(
const MavlinkFTP::Request* ackOrNak)
601 qCDebug(FTPManagerLog) <<
"_terminateUploadSessionAckOrNak: Ack disregarding ack for incorrect requestOpCode" <<
MavlinkFTP::opCodeToString(requestOpCode);
604 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
605 qCDebug(FTPManagerLog) <<
"_terminateUploadSessionAckOrNak: Ack disregarding ack for incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
609 _ackOrNakTimeoutTimer.stop();
612 qCDebug(FTPManagerLog) <<
"_terminateUploadSessionAckOrNak: Ack";
613 _advanceStateMachine();
615 qCDebug(FTPManagerLog) <<
"_terminateUploadSessionAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
616 _uploadComplete(tr(
"Upload failed for: %1 - error: %2").arg(_uploadState.fullPathOnVehicle).arg(_errorMsgFromNak(ackOrNak)));
620void FTPManager::_terminateUploadSessionTimeout(
void)
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));
626 qCDebug(FTPManagerLog) << QString(
"_terminateUploadSessionTimeout: retrying - retryCount(%1)").arg(_uploadState.retryCount);
627 _terminateUploadSessionBegin();
631void FTPManager::_uploadFinalize(
void)
633 QString
error = _uploadState.cancelled ? tr(
"Aborted for: %1").arg(_uploadState.fullPathOnVehicle) : QString();
634 _uploadComplete(
error);
637void FTPManager::_uploadComplete(
const QString& errorMsg)
639 qCDebug(FTPManagerLog) << QString(
"_uploadComplete: errorMsg(%1)").arg(errorMsg)
640 <<
"local" << _uploadState.localFilePath
641 <<
"remote" << _uploadState.fullPathOnVehicle;
643 QString remotePath = _uploadState.fullPathOnVehicle;
645 _ackOrNakTimeoutTimer.stop();
646 _rgStateMachine.clear();
647 _currentStateMachineIndex = -1;
649 if (_uploadState.file.isOpen()) {
650 _uploadState.file.close();
653 _uploadState.reset();
660 if (message.msgid != MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL ||
661 message.sysid != _vehicle->
id() || message.compid != _ftpCompId) {
665 if (_currentStateMachineIndex == -1) {
669 mavlink_file_transfer_protocol_t data;
670 mavlink_msg_file_transfer_protocol_decode(&message, &data);
674 if (data.target_system != qgcId) {
678 MavlinkFTP::Request* request = (MavlinkFTP::Request*)&data.payload[0];
681 if (request->hdr.size >
sizeof(request->data)) {
682 qCWarning(FTPManagerLog) <<
"_mavlinkMessageReceived: hdr.size exceeds data array, discarding." << request->hdr.size;
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
695 qCDebug(FTPManagerLog) <<
"_mavlinkMessageReceived: hdr.opcode:hdr.req_opcode:seqNumber"
697 << request->hdr.seqNumber;
699 (this->*_rgStateMachine[_currentStateMachineIndex].ackNakFn)(request);
702void FTPManager::_startStateMachine(
void)
704 _currentStateMachineIndex = -1;
705 _advanceStateMachine();
708void FTPManager::_advanceStateMachine(
void)
710 _currentStateMachineIndex++;
711 (this->*_rgStateMachine[_currentStateMachineIndex].beginFn)();
714void FTPManager::_ackOrNakTimeout(
void)
716 (this->*_rgStateMachine[_currentStateMachineIndex].timeoutFn)();
719void FTPManager::_fillRequestDataWithString(MavlinkFTP::Request* request,
const QString& str)
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)));
725QString FTPManager::_errorMsgFromNak(
const MavlinkFTP::Request* nak)
732 errorMsg = tr(
"Invalid Nak format");
734 errorMsg = tr(
"errno %1").arg(nak->data[1]);
742void FTPManager::_openFileROBegin(
void)
744 MavlinkFTP::Request request{};
745 request.hdr.session = 0;
747 request.hdr.offset = 0;
748 request.hdr.size = 0;
749 _fillRequestDataWithString(&request, _downloadState.fullPathOnVehicle);
750 _sendRequestExpectAck(&request);
753void FTPManager::_openFileROTimeout(
void)
755 qCDebug(FTPManagerLog) <<
"_openFileROTimeout";
756 _downloadComplete(tr(
"Download failed"));
759void FTPManager::_openFileROAckOrNak(
const MavlinkFTP::Request* ackOrNak)
763 qCDebug(FTPManagerLog) <<
"_openFileROAckOrNak: Ack disregarding ack for incorrect requestOpCode" <<
MavlinkFTP::opCodeToString(requestOpCode);
766 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
767 qCDebug(FTPManagerLog) <<
"_openFileROAckOrNak: Ack disregarding ack for incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
771 _ackOrNakTimeoutTimer.stop();
774 qCDebug(FTPManagerLog) <<
"_openFileROAckOrNak: Ack - sessionId:openFileLength" << ackOrNak->hdr.session << ackOrNak->openFileLength;
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"));
782 _downloadState.sessionId = ackOrNak->hdr.session;
783 _downloadState.fileSize = ackOrNak->openFileLength;
784 _downloadState.expectedOffset = 0;
786 _downloadState.file.setFileName(_downloadState.toDir.filePath(_downloadState.fileName));
787 if (_downloadState.file.open(QFile::WriteOnly | QFile::Truncate)) {
788 _advanceStateMachine();
790 qCDebug(FTPManagerLog) <<
"_openFileROAckOrNak: Ack _downloadState.file open failed" << _downloadState.file.errorString();
791 _downloadComplete(tr(
"Download failed"));
794 qCDebug(FTPManagerLog) <<
"_handlOpenFileROAck: Nak -" << _errorMsgFromNak(ackOrNak);
795 _downloadComplete(tr(
"Download failed") +
": " + _errorMsgFromNak(ackOrNak));
799void FTPManager::_burstReadFileWorker(
bool firstRequest)
801 qCDebug(FTPManagerLog) <<
"_burstReadFileWorker: starting burst at offset:firstRequest:retryCount" << _downloadState.expectedOffset << firstRequest << _downloadState.retryCount;
803 MavlinkFTP::Request request{};
804 request.hdr.session = _downloadState.sessionId;
806 request.hdr.offset = _downloadState.expectedOffset;
807 request.hdr.size =
sizeof(request.data);
810 _downloadState.retryCount = 0;
813 _expectedIncomingSeqNumber -= 2;
816 _sendRequestExpectAck(&request);
819void FTPManager::_burstReadFileBegin(
void)
821 _burstReadFileWorker(
true );
824void FTPManager::_burstReadFileAckOrNak(
const MavlinkFTP::Request* ackOrNak)
829 qCDebug(FTPManagerLog) <<
"_burstReadFileAckOrNak: Disregarding due to incorrect requestOpCode" <<
MavlinkFTP::opCodeToString(requestOpCode);
832 if (ackOrNak->hdr.session != _downloadState.sessionId) {
833 qCDebug(FTPManagerLog) <<
"_burstReadFileAckOrNak: Disregarding due to incorrect session id actual:expected" << ackOrNak->hdr.session << _downloadState.sessionId;
837 _ackOrNakTimeoutTimer.stop();
840 if (ackOrNak->hdr.seqNumber < _expectedIncomingSeqNumber) {
841 qCDebug(FTPManagerLog) <<
"_burstReadFileAckOrNak: Disregarding Ack due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
845 qCDebug(FTPManagerLog) << QString(
"_burstReadFileAckOrNak: Ack offset(%1) size(%2) burstComplete(%3)").arg(ackOrNak->hdr.offset).arg(ackOrNak->hdr.size).arg(ackOrNak->hdr.burstComplete);
847 if (ackOrNak->hdr.offset != _downloadState.expectedOffset) {
848 if (ackOrNak->hdr.offset > _downloadState.expectedOffset) {
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;
857 _ackOrNakTimeoutTimer.start();
858 qCDebug(FTPManagerLog) <<
"_handleBurstReadFileAck: received offset less than expected offset received:expected" << ackOrNak->hdr.offset << _downloadState.expectedOffset;
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"));
869 _downloadState.bytesWritten += ackOrNak->hdr.size;
870 _downloadState.expectedOffset = ackOrNak->hdr.offset + ackOrNak->hdr.size;
872 if (ackOrNak->hdr.burstComplete) {
874 _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber;
875 _burstReadFileWorker(
true );
878 _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber + 1;
879 _ackOrNakTimeoutTimer.start();
883 if (_downloadState.fileSize != 0) {
884 emit
commandProgress((
float)(_downloadState.bytesWritten) / (
float)_downloadState.fileSize);
891 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
892 qCDebug(FTPManagerLog) <<
"_burstReadFileAckOrNak: EOF Nak"
893 "with incorrect sequence nr actual:expected"
894 << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
896 _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber;
897 _burstReadFileWorker(
true);
899 qCDebug(FTPManagerLog) <<
"_burstReadFileAckOrNak EOF";
900 _advanceStateMachine();
903 qCDebug(FTPManagerLog) <<
"_burstReadFileAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
904 _downloadComplete(tr(
"Download failed"));
909void FTPManager::_burstReadFileTimeout(
void)
911 if (++_downloadState.retryCount > _maxRetry) {
912 qCDebug(FTPManagerLog) << QString(
"_burstReadFileTimeout retries exceeded");
913 _downloadComplete(tr(
"Download failed"));
916 qCDebug(FTPManagerLog) << QString(
"_burstReadFileTimeout: retrying - retryCount(%1) offset(%2)").arg(_downloadState.retryCount).arg(_downloadState.expectedOffset);
917 _burstReadFileWorker(
false );
921void FTPManager::_listDirectoryWorker(
bool firstRequest)
923 qCDebug(FTPManagerLog) <<
"_listDirectoryWorker: offset:firstRequest:retryCount" << _listDirectoryState.expectedOffset << firstRequest << _listDirectoryState.retryCount;
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);
933 _listDirectoryState.retryCount = 0;
936 _expectedIncomingSeqNumber -= 2;
939 _sendRequestExpectAck(&request);
942void FTPManager::_listDirectoryBegin(
void)
944 _listDirectoryWorker(
true );
947void FTPManager::_listDirectoryAckOrNak(
const MavlinkFTP::Request* ackOrNak)
951 if (requestOpCode != _listDirectoryState.opCode) {
952 qCDebug(FTPManagerLog) <<
"_listDirectoryAckOrNak: Disregarding due to incorrect requestOpCode" <<
MavlinkFTP::opCodeToString(requestOpCode);
956 _ackOrNakTimeoutTimer.stop();
959 if (ackOrNak->hdr.seqNumber < _expectedIncomingSeqNumber) {
960 qCDebug(FTPManagerLog) <<
"_listDirectoryAckOrNak: Disregarding Ack due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
965 _listDirWithTimeSupport = WithTimeSupport_t::Supported;
968 qCDebug(FTPManagerLog) << QString(
"_listDirectoryAckOrNak: Ack size(%1)").arg(ackOrNak->hdr.size);
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++;
980 _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber;
981 _listDirectoryWorker(
true );
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();
992 qCDebug(FTPManagerLog) <<
"_listDirectoryAckOrNak EOF";
994 _listDirWithTimeSupport = WithTimeSupport_t::Supported;
996 _advanceStateMachine();
1003 qCDebug(FTPManagerLog) <<
"_listDirectoryAckOrNak: kCmdListDirectoryWithTime unsupported, falling back to kCmdListDirectory";
1004 _listDirWithTimeSupport = WithTimeSupport_t::Unsupported;
1006 _listDirectoryState.expectedOffset = 0;
1007 _listDirectoryState.rgDirectoryList.clear();
1008 _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber;
1009 _listDirectoryWorker(
true );
1011 qCDebug(FTPManagerLog) <<
"_listDirectoryAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
1012 _listDirectoryComplete(tr(
"List directory failed"));
1017void FTPManager::_listDirectoryTimeout(
void)
1019 if (++_listDirectoryState.retryCount > _maxRetry) {
1020 qCDebug(FTPManagerLog) << QString(
"_listDirectoryTimeout retries exceeded");
1021 _listDirectoryComplete(tr(
"List directory failed"));
1024 qCDebug(FTPManagerLog) << QString(
"_listDirectoryTimeout: retrying - retryCount(%1) offset(%2)").arg(_listDirectoryState.retryCount).arg(_listDirectoryState.expectedOffset);
1025 _listDirectoryWorker(
false );
1029void FTPManager::_fillMissingBlocksWorker(
bool firstRequest)
1031 if (_downloadState.rgMissingData.count()) {
1032 MavlinkFTP::Request request{};
1033 MissingData_t& missingData = _downloadState.rgMissingData.first();
1035 uint32_t cBytesToRead = qMin((uint32_t)
sizeof(request.data), missingData.cBytesMissing);
1037 qCDebug(FTPManagerLog) <<
"_fillMissingBlocksBegin: offset:cBytesToRead" << missingData.offset << cBytesToRead;
1039 request.hdr.session = _downloadState.sessionId;
1041 request.hdr.offset = missingData.offset;
1042 request.hdr.size = cBytesToRead;
1045 _downloadState.retryCount = 0;
1048 _expectedIncomingSeqNumber -= 2;
1050 _downloadState.expectedOffset = request.hdr.offset;
1052 _sendRequestExpectAck(&request);
1055 if (_downloadState.checksize ==
false || _downloadState.bytesWritten == _downloadState.fileSize) {
1056 _advanceStateMachine();
1058 qCDebug(FTPManagerLog) <<
"_fillMissingBlocksWorker: no missing blocks but file still incomplete - bytesWritten:fileSize" << _downloadState.bytesWritten << _downloadState.fileSize;
1059 _downloadComplete(tr(
"Download failed"));
1064void FTPManager::_fillMissingBlocksBegin(
void)
1066 _fillMissingBlocksWorker(
true );
1069void FTPManager::_fillMissingBlocksAckOrNak(
const MavlinkFTP::Request* ackOrNak)
1074 qCDebug(FTPManagerLog) <<
"_fillMissingBlocksAckOrNak: Disregarding due to incorrect requestOpCode" <<
MavlinkFTP::opCodeToString(requestOpCode);
1077 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
1078 qCDebug(FTPManagerLog) <<
"_fillMissingBlocksAckOrNak: Disregarding due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
1081 if (ackOrNak->hdr.session != _downloadState.sessionId) {
1082 qCDebug(FTPManagerLog) <<
"_fillMissingBlocksAckOrNak: Disregarding due to incorrect session id actual:expected" << ackOrNak->hdr.session << _downloadState.sessionId;
1086 _ackOrNakTimeoutTimer.stop();
1089 qCDebug(FTPManagerLog) <<
"_fillMissingBlocksAckOrNak: Ack offset:size" << ackOrNak->hdr.offset << ackOrNak->hdr.size;
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"));
1099 qCDebug(FTPManagerLog) << QString(
"_fillMissingBlocksAckOrNak: Ack offset mismatch retry, retryCount(%1) offset(%2)").arg(_downloadState.retryCount).arg(_downloadState.expectedOffset);
1100 _fillMissingBlocksWorker(
false );
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"));
1110 _downloadState.bytesWritten += ackOrNak->hdr.size;
1112 MissingData_t& missingData = _downloadState.rgMissingData.first();
1113 missingData.offset += ackOrNak->hdr.size;
1114 missingData.cBytesMissing -= ackOrNak->hdr.size;
1115 if (missingData.cBytesMissing == 0) {
1117 _downloadState.rgMissingData.takeFirst();
1121 _fillMissingBlocksWorker(
true );
1124 if (_downloadState.fileSize != 0) {
1125 emit
commandProgress((
float)(_downloadState.bytesWritten) / (
float)_downloadState.fileSize);
1131 qCDebug(FTPManagerLog) <<
"_fillMissingBlocksAckOrNak EOF";
1132 if (_downloadState.checksize ==
false || _downloadState.bytesWritten == _downloadState.fileSize) {
1134 _advanceStateMachine();
1139 qCDebug(FTPManagerLog) <<
"_fillMissingBlocksAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
1140 _downloadComplete(tr(
"Download failed"));
1144void FTPManager::_fillMissingBlocksTimeout(
void)
1146 if (++_downloadState.retryCount > _maxRetry) {
1147 qCDebug(FTPManagerLog) << QString(
"_fillMissingBlocksTimeout retries exceeded");
1148 _downloadComplete(tr(
"Download failed"));
1151 qCDebug(FTPManagerLog) << QString(
"_fillMissingBlocksTimeout: retrying - retryCount(%1) offset(%2)").arg(_downloadState.retryCount).arg(_downloadState.expectedOffset);
1152 _fillMissingBlocksWorker(
false );
1156void FTPManager::_resetSessionsBegin(
void)
1158 MavlinkFTP::Request request{};
1160 request.hdr.size = 0;
1161 _sendRequestExpectAck(&request);
1164void FTPManager::_resetSessionsAckOrNak(
const MavlinkFTP::Request* ackOrNak)
1169 qCDebug(FTPManagerLog) <<
"_fillMissingBlocksAckOrNak: Disregarding due to incorrect requestOpCode" <<
MavlinkFTP::opCodeToString(requestOpCode);
1172 if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
1173 qCDebug(FTPManagerLog) <<
"_resetSessionsAckOrNak: Disregarding due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
1177 _ackOrNakTimeoutTimer.stop();
1180 qCDebug(FTPManagerLog) <<
"_resetSessionsAckOrNak: Ack";
1181 _advanceStateMachine();
1183 qCDebug(FTPManagerLog) <<
"_resetSessionsAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
1184 _downloadComplete(QString());
1188void FTPManager::_resetSessionsTimeout(
void)
1190 qCDebug(FTPManagerLog) <<
"_resetSessionsTimeout";
1191 _downloadComplete(QString());
1194void FTPManager::_sendRequestExpectAck(MavlinkFTP::Request* request)
1196 _ackOrNakTimeoutTimer.start();
1200 request->hdr.seqNumber = _expectedIncomingSeqNumber + 1;
1201 _expectedIncomingSeqNumber += 2;
1208 sharedLink->mavlinkChannel(),
1216 qCDebug(FTPManagerLog) <<
"_sendRequestExpectAck No primary link. Allowing timeout to fail sequence.";
1220bool FTPManager::_parseURI(uint8_t fromCompId,
const QString& uri, QString& parsedURI, uint8_t& compId)
1223 compId = (fromCompId == MAV_COMP_ID_ALL) ? (uint8_t)MAV_COMP_ID_AUTOPILOT1 : fromCompId;
1227 if (parsedURI.startsWith(ftpPrefix, Qt::CaseInsensitive)) {
1228 parsedURI = parsedURI.right(parsedURI.length() - ftpPrefix.length() + 1);
1230 if (parsedURI.contains(
"://")) {
1231 qCWarning(FTPManagerLog) <<
"Incorrect uri scheme or format" << uri;
1236 QRegularExpression regEx(
"^/??\\[\\;comp\\=(\\d+)\\]");
1237 QRegularExpressionMatch match = regEx.match(parsedURI);
1238 if (match.hasMatch()) {
1240 compId = match.captured(1).toUInt(&ok);
1242 qCWarning(FTPManagerLog) <<
"Incorrect format for component id" << uri;
1246 qCDebug(FTPManagerLog) <<
"Found compId in MAVLink FTP URI: " << compId;
1247 parsedURI.replace(QRegularExpression(
"\\[\\;comp\\=\\d+\\]"),
"");
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
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)
static constexpr const char * mavlinkFTPScheme
void uploadComplete(const QString &file, const QString &errorMsg)
void commandProgress(float value)
void downloadComplete(const QString &file, const QString &errorMsg)
void cancelListDirectory()
void deleteComplete(const QString &file, const QString &errorMsg)
bool download(uint8_t fromCompId, const QString &fromURI, const QString &toDir, const QString &fileName="", bool checksize=true)
void listDirectoryComplete(const QStringList &dirList, const QString &errorMsg)
static int getComponentId()
static MAVLinkProtocol * instance()
ErrorCode_t
Error codes returned in Nak response PayloadHeader.data[0].
@ kErrEOF
Offset past end of file for List and Read commands.
@ kErrUnknownCommand
Unknown command opcode.
@ kErrFailErrno
errno sent back in PayloadHeader.data[1]
static QString errorCodeToString(ErrorCode_t errorCode)
@ kCmdRemoveFile
Remove file at <path>
@ kCmdWriteFile
Writes <size> bytes to <offset> in <session>
@ kCmdCreateFile
Creates file at <path> for writing, returns <session>
@ kCmdOpenFileRO
Opens file at <path> for reading, returns <session>
@ kCmdListDirectoryWithTime
List directory as kCmdListDirectory, each entry additionally carrying trailing \t<modification time>
@ kCmdReadFile
Reads <size> bytes from <offset> in <session>
@ kCmdResetSessions
Terminates all open Read sessions.
@ kCmdBurstReadFile
Burst download session file.
@ kCmdTerminateSession
Terminates open Read session.
@ kCmdListDirectory
List files in <path> from <offset>
static QString opCodeToString(OpCode_t opCode)
WeakLinkInterfacePtr primaryLink() const
VehicleLinkManager * vehicleLinkManager()
bool sendMessageOnLinkThreadSafe(LinkInterface *link, mavlink_message_t message)