Skip to content
Snippets Groups Projects
FTPManager.cc 23.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • DonLakeFlyer's avatar
    DonLakeFlyer committed
    /****************************************************************************
     *
     * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
     *
     * QGroundControl is licensed according to the terms in the file
     * COPYING.md in the root of the source code directory.
     *
     ****************************************************************************/
    
    #include "FTPManager.h"
    #include "QGC.h"
    #include "MAVLinkProtocol.h"
    #include "Vehicle.h"
    #include "QGCApplication.h"
    
    #include <QFile>
    #include <QDir>
    #include <string>
    
    QGC_LOGGING_CATEGORY(FTPManagerLog, "FTPManagerLog")
    
    FTPManager::FTPManager(Vehicle* vehicle)
        : QObject   (vehicle)
        , _vehicle  (vehicle)
    {
    
        _ackOrNakTimeoutTimer.setSingleShot(true);
        // Mock link responds immediately if at all, speed up unit tests with faster timoue
        _ackOrNakTimeoutTimer.setInterval(qgcApp()->runningUnitTests() ? 10 : _ackOrNakTimeoutMsecs);
        connect(&_ackOrNakTimeoutTimer, &QTimer::timeout, this, &FTPManager::_ackOrNakTimeout);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        
        // Make sure we don't have bad structure packing
        Q_ASSERT(sizeof(MavlinkFTP::RequestHeader) == 12);
    }
    
    
    bool FTPManager::download(const QString& from, const QString& toDir)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    {
    
        qCDebug(FTPManagerLog) << "download from:" << from << "to:" << toDir;
    
        if (!_rgStateMachine.isEmpty()) {
            qCDebug(FTPManagerLog) << "Cannot download. Already in another operation";
            return false;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        }
    
    
        static const StateFunctions_t rgDownloadStateMachine[] = {
            { &FTPManager::_openFileROBegin,            &FTPManager::_openFileROAckOrNak,           &FTPManager::_openFileROTimeout },
            { &FTPManager::_burstReadFileBegin,         &FTPManager::_burstReadFileAckOrNak,        &FTPManager::_burstReadFileTimeout },
            { &FTPManager::_fillMissingBlocksBegin,     &FTPManager::_fillMissingBlocksAckOrNak,    &FTPManager::_fillMissingBlocksTimeout },
            { &FTPManager::_resetSessionsBegin,         &FTPManager::_resetSessionsAckOrNak,        &FTPManager::_resetSessionsTimeout },
            { &FTPManager::_downloadCompleteNoError,    nullptr,                                    nullptr },
        };
        for (size_t i=0; i<sizeof(rgDownloadStateMachine)/sizeof(rgDownloadStateMachine[0]); i++) {
            _rgStateMachine.append(rgDownloadStateMachine[i]);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        }
    
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
        _downloadState.reset();
    
        _downloadState.toDir.setPath(toDir);
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    
    
        QString ftpPrefix("mavlinkftp://");
        if (from.startsWith(ftpPrefix, Qt::CaseInsensitive)) {
            _downloadState.fullPathOnVehicle = from.right(from.length() - ftpPrefix.length() + 1);
        } else {
            _downloadState.fullPathOnVehicle = from;
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
        }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
        // We need to strip off the file name from the fully qualified path. We can't use the usual QDir
        // routines because this path does not exist locally.
        int lastDirSlashIndex;
        for (lastDirSlashIndex=_downloadState.fullPathOnVehicle.size()-1; lastDirSlashIndex>=0; lastDirSlashIndex--) {
            if (_downloadState.fullPathOnVehicle[lastDirSlashIndex] == '/') {
                break;
            }
        }
        lastDirSlashIndex++; // move past slash
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    
    
        _downloadState.fileName = _downloadState.fullPathOnVehicle.right(_downloadState.fullPathOnVehicle.size() - lastDirSlashIndex);
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    
    
        qDebug() << _downloadState.fullPathOnVehicle << _downloadState.fileName;
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    }
    
    /// Closes out a download session by writing the file and doing cleanup.
    
    ///     @param errorMsg Error message, empty if no error
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    void FTPManager::_downloadComplete(const QString& errorMsg)
    {
        qCDebug(FTPManagerLog) << QString("_downloadComplete: errorMsg(%1)").arg(errorMsg);
        
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
        QString downloadFilePath    = _downloadState.toDir.absoluteFilePath(_downloadState.fileName);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        QString error               = errorMsg;
    
    
        _ackOrNakTimeoutTimer.stop();
        _rgStateMachine.clear();
        _currentStateMachineIndex = -1;
        if (_downloadState.file.isOpen()) {
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
            _downloadState.file.close();
    
            if (!errorMsg.isEmpty()) {
                _downloadState.file.remove();
            }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        }
    
    
        emit downloadComplete(downloadFilePath, errorMsg);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    }
    
    
    void FTPManager::_mavlinkMessageReceived(const mavlink_message_t& message)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    {
    
        if (message.msgid != MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL) {
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
            return;
        }
    
    
        if (_currentStateMachineIndex == -1) {
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
            return;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        }
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    
    
        mavlink_file_transfer_protocol_t data;
        mavlink_msg_file_transfer_protocol_decode(&message, &data);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
        // Make sure we are the target system
        int qgcId = qgcApp()->toolbox()->mavlinkProtocol()->getSystemId();
        if (data.target_system != qgcId) {
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
            return;
        }
    
        
        MavlinkFTP::Request* request = (MavlinkFTP::Request*)&data.payload[0];
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    
    
        // Ignore old/reordered packets (handle wrap-around properly)
        uint16_t actualIncomingSeqNumber = request->hdr.seqNumber;
        if ((uint16_t)((_expectedIncomingSeqNumber - 1) - actualIncomingSeqNumber) < (std::numeric_limits<uint16_t>::max()/2)) {
            qCDebug(FTPManagerLog) << "_mavlinkMessageReceived: Received old packet seqNum expected:actual" << _expectedIncomingSeqNumber << actualIncomingSeqNumber
                                   << "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));
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    
            return;
        }
    
    
        qCDebug(FTPManagerLog) << "_mavlinkMessageReceived: hdr.opcode:hdr.req_opcode:seqNumber"
                               << MavlinkFTP::opCodeToString(static_cast<MavlinkFTP::OpCode_t>(request->hdr.opcode)) <<  MavlinkFTP::opCodeToString(static_cast<MavlinkFTP::OpCode_t>(request->hdr.req_opcode))
                               << request->hdr.seqNumber;
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    
    
        (this->*_rgStateMachine[_currentStateMachineIndex].ackNakFn)(request);
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    }
    
    
    void FTPManager::_startStateMachine(void)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    {
    
        _currentStateMachineIndex = -1;
        _advanceStateMachine();
    }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
    void FTPManager::_advanceStateMachine(void)
    {
        _currentStateMachineIndex++;
        (this->*_rgStateMachine[_currentStateMachineIndex].beginFn)();
    }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
    void FTPManager::_ackOrNakTimeout(void)
    {
        (this->*_rgStateMachine[_currentStateMachineIndex].timeoutFn)();
    }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
    void FTPManager::_fillRequestDataWithString(MavlinkFTP::Request* request, const QString& str)
    {
        strncpy((char *)&request->data[0], str.toStdString().c_str(), sizeof(request->data));
        request->hdr.size = static_cast<uint8_t>(strnlen((const char *)&request->data[0], sizeof(request->data)));
    }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
    QString FTPManager::_errorMsgFromNak(const MavlinkFTP::Request* nak)
    {
        QString errorMsg;
        MavlinkFTP::ErrorCode_t errorCode = static_cast<MavlinkFTP::ErrorCode_t>(nak->data[0]);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
        // Nak's normally have 1 byte of data for error code, except for MavlinkFTP::kErrFailErrno which has additional byte for errno
        if ((errorCode == MavlinkFTP::kErrFailErrno && nak->hdr.size != 2) || ((errorCode != MavlinkFTP::kErrFailErrno) && nak->hdr.size != 1)) {
            errorMsg = tr("Invalid Nak format");
        } else if (errorCode == MavlinkFTP::kErrFailErrno) {
            errorMsg = tr("errno %1").arg(nak->data[1]);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        } else {
    
            errorMsg = MavlinkFTP::errorCodeToString(errorCode);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    }
    
    
    void FTPManager::_openFileROBegin(void)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    {
    
        MavlinkFTP::Request request;
        request.hdr.session = 0;
        request.hdr.opcode  = MavlinkFTP::kCmdOpenFileRO;
        request.hdr.offset  = 0;
        request.hdr.size    = 0;
        _fillRequestDataWithString(&request, _downloadState.fullPathOnVehicle);
        _sendRequestExpectAck(&request);
    }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
    void FTPManager::_openFileROTimeout(void)
    {
        qCDebug(FTPManagerLog) << "_openFileROTimeout";
        _downloadComplete(tr("Download failed"));
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    }
    
    
    void FTPManager::_openFileROAckOrNak(const MavlinkFTP::Request* ackOrNak)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    {
    
        MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
        if (requestOpCode != MavlinkFTP::kCmdOpenFileRO) {
            qCDebug(FTPManagerLog) << "_openFileROAckOrNak: Ack disregarding ack for incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            return;
        }
    
        if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
            qCDebug(FTPManagerLog) << "_openFileROAckOrNak: Ack disregarding ack for incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            return;
        }
    
    
        _ackOrNakTimeoutTimer.stop();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
        if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
            qCDebug(FTPManagerLog) << "_openFileROAckOrNak: Ack  - sessionId:openFileLength" << ackOrNak->hdr.session << ackOrNak->openFileLength;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            if (ackOrNak->hdr.size != sizeof(uint32_t)) {
                qCDebug(FTPManagerLog) << "_openFileROAckOrNak: Ack ack->hdr.size != sizeof(uint32_t)" << ackOrNak->hdr.size << sizeof(uint32_t);
                _downloadComplete(tr("Download failed"));
                return;
            }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            _downloadState.sessionId        = ackOrNak->hdr.session;
            _downloadState.fileSize         = ackOrNak->openFileLength;
            _downloadState.expectedOffset   = 0;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            _downloadState.file.setFileName(_downloadState.toDir.filePath(_downloadState.fileName));
            if (_downloadState.file.open(QFile::WriteOnly | QFile::Truncate)) {
                _advanceStateMachine();
            } else {
                qCDebug(FTPManagerLog) << "_openFileROAckOrNak: Ack _downloadState.file open failed" << _downloadState.file.errorString();
                _downloadComplete(tr("Download failed"));
            }
        } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
            qCDebug(FTPManagerLog) << "_handlOpenFileROAck: Nak -" << _errorMsgFromNak(ackOrNak);
            _downloadComplete(tr("Download failed"));
        }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    }
    
    
    void FTPManager::_burstReadFileWorker(bool firstRequest)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    {
    
        qCDebug(FTPManagerLog) << "_burstReadFileWorker: starting burst at offset:firstRequest:retryCount" << _downloadState.expectedOffset << firstRequest << _downloadState.retryCount;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
        MavlinkFTP::Request request;
    
        request.hdr.session = _downloadState.sessionId;
        request.hdr.opcode  = MavlinkFTP::kCmdBurstReadFile;
        request.hdr.offset  = _downloadState.expectedOffset;
        request.hdr.size    = sizeof(request.data);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
        if (firstRequest) {
            _downloadState.retryCount = 0;
        } else {
            // Must used same sequence number as previous request
            _expectedIncomingSeqNumber -= 2;
        }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
        _sendRequestExpectAck(&request);
    }
    
    
    void FTPManager::_burstReadFileBegin(void)
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    {
    
        _burstReadFileWorker(true /* firstRequestr */);
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    }
    
    
    void FTPManager::_burstReadFileAckOrNak(const MavlinkFTP::Request* ackOrNak)
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    {
    
        MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
    
    DonLakeFlyer's avatar
     
    DonLakeFlyer committed
    
    
        if (requestOpCode != MavlinkFTP::kCmdBurstReadFile) {
            qCDebug(FTPManagerLog) << "_burstReadFileAckOrNak: Disregarding due to incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            return;
        }
    
        if (ackOrNak->hdr.session != _downloadState.sessionId) {
            qCDebug(FTPManagerLog) << "_burstReadFileAckOrNak: Disregarding due to incorrect session id actual:expected" << ackOrNak->hdr.session << _downloadState.sessionId;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            return;
        }
    
    
        _ackOrNakTimeoutTimer.stop();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
        if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
            if (ackOrNak->hdr.seqNumber < _expectedIncomingSeqNumber) {
                qCDebug(FTPManagerLog) << "_burstReadFileAckOrNak: Disregarding Ack due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
                return;
            }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            qCDebug(FTPManagerLog) << QString("_burstReadFileAckOrNak: Ack offset(%1) size(%2) burstComplete(%3)").arg(ackOrNak->hdr.offset).arg(ackOrNak->hdr.size).arg(ackOrNak->hdr.burstComplete);
    
            if (ackOrNak->hdr.offset != _downloadState.expectedOffset) {
                if (ackOrNak->hdr.offset > _downloadState.expectedOffset) {
                    // There is a hole in our data, record it as missing and continue on
                    MissingData_t missingData;
                    missingData.offset          = _downloadState.expectedOffset;
                    missingData.cBytesMissing   = ackOrNak->hdr.offset - _downloadState.expectedOffset;
                    _downloadState.rgMissingData.append(missingData);
                    qCDebug(FTPManagerLog) << "_handleBurstReadFileAck: adding missing data offset:cBytesMissing" << missingData.offset << missingData.cBytesMissing;
                } else {
                    // Offset is past what we have already seen, disregard and wait for something usefule
                    _ackOrNakTimeoutTimer.start();
                    qCDebug(FTPManagerLog) << "_handleBurstReadFileAck: received offset less than expected offset received:expected" << ackOrNak->hdr.offset << _downloadState.expectedOffset;
                    return;
                }
            }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            _downloadState.file.seek(ackOrNak->hdr.offset);
            int bytesWritten = _downloadState.file.write((const char*)ackOrNak->data, ackOrNak->hdr.size);
            if (bytesWritten != ackOrNak->hdr.size) {
                _downloadComplete(tr("Download failed: Error saving file"));
                return;
            }
            _downloadState.bytesWritten += ackOrNak->hdr.size;
            _downloadState.expectedOffset = ackOrNak->hdr.offset + ackOrNak->hdr.size;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            if (_downloadState.fileSize != 0) {
                emit commandProgress(100 * ((float)(_downloadState.bytesWritten) / (float)_downloadState.fileSize));
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            }
    
    
            if (ackOrNak->hdr.burstComplete) {
                // The current burst is done, request next one in offset sequence
                _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber;
                _burstReadFileWorker(true /* firstRequest */);
            } else {
                // Still within a burst, next ack should come automatically
                _expectedIncomingSeqNumber = ackOrNak->hdr.seqNumber + 1;
                _ackOrNakTimeoutTimer.start();
            }
        } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
            if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
                qCDebug(FTPManagerLog) << "_burstReadFileAckOrNak: Disregarding Nak due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
                return;
            }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            MavlinkFTP::ErrorCode_t errorCode = static_cast<MavlinkFTP::ErrorCode_t>(ackOrNak->data[0]);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            if (errorCode == MavlinkFTP::kErrEOF) {
                // Burst sequence has gone through the whole file
                qCDebug(FTPManagerLog) << "_burstReadFileAckOrNak EOF";
                _advanceStateMachine();
            } else {
                qCDebug(FTPManagerLog) << "_burstReadFileAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
                _downloadComplete(tr("Download failed"));
            }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        }
    }
    
    
    void FTPManager::_burstReadFileTimeout(void)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    {
    
        if (++_downloadState.retryCount > _maxRetry) {
            qCDebug(FTPManagerLog) << QString("_burstReadFileTimeout retries exceeded");
            _downloadComplete(tr("Download failed"));
        } else {
            // Try again
            qCDebug(FTPManagerLog) << QString("_burstReadFileTimeout: retrying - retryCount(%1) offset(%2)").arg(_downloadState.retryCount).arg(_downloadState.expectedOffset);
            _burstReadFileWorker(false /* firstReqeust */);
        }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    }
    
    
    void FTPManager::_fillMissingBlocksWorker(bool firstRequest)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    {
    
        if (_downloadState.rgMissingData.count()) {
            MavlinkFTP::Request request;
            MissingData_t&      missingData = _downloadState.rgMissingData.first();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            uint32_t cBytesToRead = qMin((uint32_t)sizeof(request.data), missingData.cBytesMissing);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            qCDebug(FTPManagerLog) << "_fillMissingBlocksBegin: offset:cBytesToRead" << missingData.offset << cBytesToRead;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            request.hdr.session                 = _downloadState.sessionId;
            request.hdr.opcode                  = MavlinkFTP::kCmdReadFile;
            request.hdr.offset                  = missingData.offset;
            request.hdr.size                    = cBytesToRead;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            if (firstRequest) {
                _downloadState.retryCount = 0;
            } else {
                // Must used same sequence number as previous request
                _expectedIncomingSeqNumber -= 2;
            }
            _downloadState.expectedOffset = request.hdr.offset;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            _sendRequestExpectAck(&request);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        } else {
    
            // We should have the full file now
            if (_downloadState.bytesWritten == _downloadState.fileSize) {
                _advanceStateMachine();
            } else {
                qCDebug(FTPManagerLog) << "_fillMissingBlocksWorker: no missing blocks but file still incomplete - bytesWritten:fileSize" << _downloadState.bytesWritten << _downloadState.fileSize;
                _downloadComplete(tr("Download failed"));
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            }
        }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
    void FTPManager::_fillMissingBlocksBegin(void)
    {
        _fillMissingBlocksWorker(true /* firstRequest */);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    }
    
    
    void FTPManager::_fillMissingBlocksAckOrNak(const MavlinkFTP::Request* ackOrNak)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    {
    
        MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
        if (requestOpCode != MavlinkFTP::kCmdReadFile) {
            qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak: Disregarding due to incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            return;
        }
    
        if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
            qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak: Disregarding due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            return;
        }
    
        if (ackOrNak->hdr.session != _downloadState.sessionId) {
            qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak: Disregarding due to incorrect session id actual:expected" << ackOrNak->hdr.session << _downloadState.sessionId;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            return;
        }
    
    
        _ackOrNakTimeoutTimer.stop();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
        if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
            qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak: Ack offset:size" << ackOrNak->hdr.offset << ackOrNak->hdr.size;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            if (ackOrNak->hdr.offset != _downloadState.expectedOffset) {
                if (++_downloadState.retryCount > _maxRetry) {
                    qCDebug(FTPManagerLog) << QString("_fillMissingBlocksAckOrNak: offset mismatch, retries exceeded");
                    _downloadComplete(tr("Download failed"));
                    return;
                }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
                // Ask for current offset again
                qCDebug(FTPManagerLog) << QString("_fillMissingBlocksAckOrNak: Ack offset mismatch retry, retryCount(%1) offset(%2)").arg(_downloadState.retryCount).arg(_downloadState.expectedOffset);
                _fillMissingBlocksWorker(false /* firstReqeust */);
                return;
            }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            _downloadState.file.seek(ackOrNak->hdr.offset);
            int bytesWritten = _downloadState.file.write((const char*)ackOrNak->data, ackOrNak->hdr.size);
            if (bytesWritten != ackOrNak->hdr.size) {
                _downloadComplete(tr("Download failed: Error saving file"));
                return;
            }
            _downloadState.bytesWritten += ackOrNak->hdr.size;
    
            MissingData_t& missingData = _downloadState.rgMissingData.first();
            missingData.offset += ackOrNak->hdr.size;
            missingData.cBytesMissing -= ackOrNak->hdr.size;
            if (missingData.cBytesMissing == 0) {
                // This block is finished, remove it
                _downloadState.rgMissingData.takeFirst();
            }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            if (_downloadState.fileSize != 0) {
                emit commandProgress(100 * ((float)(_downloadState.bytesWritten) / (float)_downloadState.fileSize));
            }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            // Move on to fill in possible next hole
            _fillMissingBlocksWorker(true /* firstReqeust */);
        } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
            MavlinkFTP::ErrorCode_t errorCode = static_cast<MavlinkFTP::ErrorCode_t>(ackOrNak->data[0]);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
            if (errorCode == MavlinkFTP::kErrEOF) {
                qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak EOF";
                if (_downloadState.bytesWritten == _downloadState.fileSize) {
                    // We've successfully complete filling in all missing blocks
                    _advanceStateMachine();
                    return;
                }
            }
    
            qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
            _downloadComplete(tr("Download failed"));
        }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    }
    
    
    void FTPManager::_fillMissingBlocksTimeout(void)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    {
    
        if (++_downloadState.retryCount > _maxRetry) {
            qCDebug(FTPManagerLog) << QString("_fillMissingBlocksTimeout retries exceeded");
            _downloadComplete(tr("Download failed"));
        } else {
            // Ask for current offset again
            qCDebug(FTPManagerLog) << QString("_fillMissingBlocksTimeout: retrying - retryCount(%1) offset(%2)").arg(_downloadState.retryCount).arg(_downloadState.expectedOffset);
            _fillMissingBlocksWorker(false /* firstReqeust */);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
    void FTPManager::_resetSessionsBegin(void)
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        MavlinkFTP::Request request;
    
        request.hdr.opcode  = MavlinkFTP::kCmdResetSessions;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        request.hdr.size    = 0;
        _sendRequestExpectAck(&request);
    }
    
    
    void FTPManager::_resetSessionsAckOrNak(const MavlinkFTP::Request* ackOrNak)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    {
    
        MavlinkFTP::OpCode_t requestOpCode = static_cast<MavlinkFTP::OpCode_t>(ackOrNak->hdr.req_opcode);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
        if (requestOpCode != MavlinkFTP::kCmdResetSessions) {
            qCDebug(FTPManagerLog) << "_fillMissingBlocksAckOrNak: Disregarding due to incorrect requestOpCode" << MavlinkFTP::opCodeToString(requestOpCode);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            return;
        }
    
        if (ackOrNak->hdr.seqNumber != _expectedIncomingSeqNumber) {
            qCDebug(FTPManagerLog) << "_resetSessionsAckOrNak: Disregarding due to incorrect sequence actual:expected" << ackOrNak->hdr.seqNumber << _expectedIncomingSeqNumber;
            return;
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        }
    
    
        _ackOrNakTimeoutTimer.stop();
    
        if (ackOrNak->hdr.opcode == MavlinkFTP::kRspAck) {
            qCDebug(FTPManagerLog) << "_resetSessionsAckOrNak: Ack";
            _advanceStateMachine();
        } else if (ackOrNak->hdr.opcode == MavlinkFTP::kRspNak) {
            qCDebug(FTPManagerLog) << "_resetSessionsAckOrNak: Nak -" << _errorMsgFromNak(ackOrNak);
            _downloadComplete(QString());
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        }
    }
    
    
    void FTPManager::_resetSessionsTimeout(void)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    {
    
        qCDebug(FTPManagerLog) << "_resetSessionsTimeout";
        _downloadComplete(QString());
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    }
    
    void FTPManager::_emitErrorMessage(const QString& msg)
    {
        qCDebug(FTPManagerLog) << "Error:" << msg;
        emit commandError(msg);
    }
    
    void FTPManager::_sendRequestExpectAck(MavlinkFTP::Request* request)
    {
    
        LinkInterface* primaryLink = _vehicle->vehicleLinkManager()->primaryLink();
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
    
    
        _ackOrNakTimeoutTimer.start();
        
        if (primaryLink) {
            request->hdr.seqNumber = _expectedIncomingSeqNumber + 1;    // Outgoing is 1 past last incoming
            _expectedIncomingSeqNumber += 2;
    
            qCDebug(FTPManagerLog) << "_sendRequestExpectAck opcode:" << MavlinkFTP::opCodeToString(static_cast<MavlinkFTP::OpCode_t>(request->hdr.opcode)) << "seqNumber:" << request->hdr.seqNumber;
    
            mavlink_message_t message;
            mavlink_msg_file_transfer_protocol_pack_chan(qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
                                                         qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
                                                         primaryLink->mavlinkChannel(),
                                                         &message,
                                                         0,                                                     // Target network, 0=broadcast?
                                                         _vehicle->id(),
                                                         MAV_COMP_ID_AUTOPILOT1,
                                                         (uint8_t*)request);                                    // Payload
            _vehicle->sendMessageOnLinkThreadSafe(primaryLink, message);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        } else {
    
            qCDebug(FTPManagerLog) << "_sendRequestExpectAck No primary link. Allowing timeout to fail sequence.";
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        }
    }