5#include <QtCore/QDataStream>
7#include <QtCore/QTemporaryFile>
8#include <QtCore/QThread>
14 , _systemIdServer(systemIdServer)
15 , _componentIdServer(componentIdServer)
23 if (!_paramPckTempFile.isEmpty()) {
24 QFile::remove(_paramPckTempFile);
26 for (
const QString &tempPath : std::as_const(_logFileTempPaths)) {
27 QFile::remove(tempPath);
31void MockLinkFTP::ensureNullTemination(MavlinkFTP::Request *request)
33 if (request->hdr.size <
sizeof(request->data)) {
34 request->data[request->hdr.size] =
'\0';
36 request->data[
sizeof(request->data) - 1] =
'\0';
40void MockLinkFTP::_listCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber,
bool withTime)
42 MavlinkFTP::Request ackResponse{};
43 ensureNullTemination(request);
45 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
48 if (withTime && !_listDirectoryWithTimeSupported) {
55 const QString path =
reinterpret_cast<char*
>(&request->data[0]);
57 if (path.isEmpty() || path == QStringLiteral(
"/")) {
59 for (uint32_t index = 0; index < 6; index++) {
60 QString entry = QStringLiteral(
"Ffile%1.txt\t%2").arg(index).arg(1024 + index);
64 entries.append(entry);
66 }
else if ((path == QStringLiteral(
"@MAV_LOG")) || (path == QStringLiteral(
"@MAV_LOG/"))) {
67 for (
const LogFile &logFile : std::as_const(_logFiles)) {
68 QString entry = QStringLiteral(
"F%1\t%2").arg(logFile.name).arg(logFile.size);
70 entry += QStringLiteral(
"\t%1").arg(logFile.mtime);
72 entries.append(entry);
75 _sendNak(senderSystemId, senderComponentId,
MavlinkFTP::kErrFail, outgoingSeqNumber, listOpCode);
79 if (request->hdr.offset > 0) {
82 _sendNak(senderSystemId, senderComponentId,
MavlinkFTP::kErrFail, outgoingSeqNumber, listOpCode);
99 ackResponse.hdr.req_opcode = listOpCode;
100 ackResponse.hdr.session = 0;
101 ackResponse.hdr.offset = request->hdr.offset;
102 ackResponse.hdr.size = 0;
106 if (request->hdr.offset <
static_cast<uint32_t
>(entries.count())) {
107 char *bufPtr =
reinterpret_cast<char*
>(&ackResponse.data[0]);
108 for (uint32_t index = request->hdr.offset; (index < request->hdr.offset + 2) && (index <
static_cast<uint32_t
>(entries.count())); index++) {
109 const QByteArray dirEntry = entries[
static_cast<qsizetype
>(index)].toUtf8();
110 if (ackResponse.hdr.size + dirEntry.length() + 1 >
static_cast<int>(
sizeof(ackResponse.data))) {
113 (void) strncpy(bufPtr, dirEntry.constData(), dirEntry.length());
114 ackResponse.hdr.size += dirEntry.length() + 1;
115 bufPtr += dirEntry.length() + 1;
120 ackResponse.hdr.size = 1;
123 _sendResponse(senderSystemId, senderComponentId, &ackResponse, outgoingSeqNumber);
126void MockLinkFTP::_openCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
128 MavlinkFTP::Request response{};
129 ensureNullTemination(request);
130 const QString path =
reinterpret_cast<char*
>(request->data);
132 _uploadSession.reset();
134 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
136 const size_t cchPath = strnlen(
reinterpret_cast<char*
>(request->data),
sizeof(request->data));
137 Q_ASSERT(cchPath !=
sizeof(request->data));
140 _currentFile.close();
144 if (path.startsWith(sizePrefix)) {
145 const QString sizeString = path.right(path.length() - sizePrefix.length());
146 tmpFilename = _createTestTempFile(sizeString.toInt());
147 }
else if (path ==
"/general.json") {
148 tmpFilename = QStringLiteral(
":MockLink/General.MetaData.json");
149 }
else if (path ==
"/general.json.xz") {
150 tmpFilename = QStringLiteral(
":MockLink/General.MetaData.json.xz");
151 }
else if (path ==
"/parameter.json") {
152 tmpFilename = QStringLiteral(
":MockLink/Parameter.MetaData.json");
153 }
else if (path ==
"/parameter.json.xz") {
154 tmpFilename = QStringLiteral(
":MockLink/Parameter.MetaData.json.xz");
155 }
else if (path ==
"@PARAM/param.pck" || path.startsWith(
"@PARAM/param.pck?")) {
156 const bool withDefaults = path.contains(QStringLiteral(
"withdefaults=1"));
157 tmpFilename = _generateParamPck(withDefaults);
158 }
else if (path.startsWith(QStringLiteral(
"@MAV_LOG/"))) {
159 tmpFilename = _logFileTempPath(path.mid(QStringLiteral(
"@MAV_LOG/").length()));
162 if (!tmpFilename.isEmpty()) {
163 _currentFile.setFileName(tmpFilename);
164 if (!_currentFile.open(QIODevice::ReadOnly)) {
175 response.hdr.session = _sessionId;
178 response.hdr.size =
sizeof(uint32_t);
181 response.openFileLength = ((path ==
"@PARAM/param.pck" || path.startsWith(
"@PARAM/param.pck?")) ? qPow(1024, 2) : _currentFile.size());
183 _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
186void MockLinkFTP::_createFileCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
188 ensureNullTemination(request);
190 const QString path =
reinterpret_cast<char*
>(request->data);
191 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
193 if (path.isEmpty()) {
198 _uploadSession.reset();
199 _uploadSession.active =
true;
200 _uploadSession.remotePath = path;
202 MavlinkFTP::Request response{};
205 response.hdr.session = _sessionId;
206 response.hdr.size = 0;
208 _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
211void MockLinkFTP::_openFileWOCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
213 ensureNullTemination(request);
215 const QString path =
reinterpret_cast<char*
>(request->data);
216 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
218 if (path.isEmpty()) {
223 _uploadSession.reset();
224 _uploadSession.active =
true;
225 _uploadSession.remotePath = path;
227 MavlinkFTP::Request response{};
230 response.hdr.session = _sessionId;
231 response.hdr.size = 0;
233 _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
236void MockLinkFTP::_readCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
238 MavlinkFTP::Request response{};
239 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
241 if (request->hdr.session != _sessionId) {
246 const uint32_t readOffset = request->hdr.offset;
247 if (readOffset != 0) {
261 if (readOffset >= _currentFile.size()) {
266 const uint8_t cBytesToRead =
static_cast<uint8_t
>(qMin(
static_cast<qint64
>(
sizeof(response.data)), _currentFile.size() - readOffset));
267 (void) _currentFile.seek(readOffset);
268 const QByteArray bytes = _currentFile.read(cBytesToRead);
269 (void) memcpy(response.data, bytes.constData(), cBytesToRead);
272 Q_ASSERT(cBytesToRead);
274 response.hdr.session = _sessionId;
275 response.hdr.size = cBytesToRead;
276 response.hdr.offset = request->hdr.offset;
280 _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
283void MockLinkFTP::_removeFileCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
285 ensureNullTemination(request);
286 const QString path =
reinterpret_cast<char*
>(request->data);
287 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
289 const QString logPrefix = QStringLiteral(
"@MAV_LOG/");
290 if (path.startsWith(logPrefix)) {
291 const QString name = path.mid(logPrefix.length());
292 for (
int i = 0; i < _logFiles.count(); i++) {
293 if (_logFiles[i].name == name) {
294 _logFiles.removeAt(i);
297 const QString tempPath = _logFileTempPaths.take(name);
298 if (!tempPath.isEmpty()) {
299 QFile::remove(tempPath);
311void MockLinkFTP::_burstReadCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
313 if (_burstReadDelayMs > 0) {
314 QThread::msleep(_burstReadDelayMs);
317 MavlinkFTP::Request response{};
318 uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
320 if (request->hdr.session != _sessionId) {
325 constexpr int burstMax = 10;
327 uint32_t burstOffset = request->hdr.offset;
329 while ((burstOffset < _currentFile.size()) && (burstCount++ < burstMax)) {
330 _currentFile.seek(burstOffset);
332 const uint8_t cBytes =
static_cast<uint8_t
>(qMin(
static_cast<qint64
>(
sizeof(response.data)), _currentFile.size() - burstOffset));
333 const QByteArray bytes = _currentFile.read(cBytes);
336 (void) memcpy(response.data, bytes.constData(), cBytes);
338 response.hdr.session = _sessionId;
339 response.hdr.size = cBytes;
340 response.hdr.offset = burstOffset;
343 response.hdr.burstComplete = (burstCount == burstMax) ? 1 : 0;
345 _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
347 outgoingSeqNumber = _nextSeqNumber(outgoingSeqNumber);
348 burstOffset += cBytes;
351 if (burstOffset >= _currentFile.size()) {
357void MockLinkFTP::_terminateCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
359 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
361 if (request->hdr.session != _sessionId) {
366 _currentFile.close();
369 _finalizeActiveUpload();
374void MockLinkFTP::_resetCommand(uint8_t senderSystemId, uint8_t senderComponentId, uint16_t seqNumber)
376 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
378 _currentFile.close();
381 _finalizeActiveUpload();
386void MockLinkFTP::_writeCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
388 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
390 if ((request->hdr.session != _sessionId) || !_uploadSession.active) {
395 if (request->hdr.offset >
static_cast<uint32_t
>(_uploadSession.buffer.size())) {
400 if (request->hdr.offset != 0) {
416 const uint32_t bytesToWrite = request->hdr.size;
417 if (bytesToWrite >
sizeof(request->data)) {
422 const uint32_t requiredSize = request->hdr.offset + bytesToWrite;
423 if (requiredSize >
static_cast<uint32_t
>(_uploadSession.buffer.size())) {
424 _uploadSession.buffer.resize(requiredSize);
427 if (bytesToWrite > 0) {
428 (void) memcpy(_uploadSession.buffer.data() + request->hdr.offset, request->data, bytesToWrite);
431 MavlinkFTP::Request response{};
434 response.hdr.session = _sessionId;
435 response.hdr.size = 0;
436 response.hdr.offset = request->hdr.offset;
438 _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
441void MockLinkFTP::_finalizeActiveUpload()
443 if (!_uploadSession.active) {
447 if (!_uploadSession.remotePath.isEmpty()) {
448 _uploadedFiles.insert(_uploadSession.remotePath, _uploadSession.buffer);
451 _uploadSession.reset();
456 if (message.msgid != MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL) {
460 mavlink_file_transfer_protocol_t requestFTP{};
461 mavlink_msg_file_transfer_protocol_decode(&message, &requestFTP);
463 if (requestFTP.target_system != _systemIdServer) {
467 MavlinkFTP::Request *request =
reinterpret_cast<MavlinkFTP::Request*
>(&requestFTP.payload[0]);
471 if ((rand() % 5) == 0) {
472 qCDebug(MockLinkFTPLog) <<
"MockLinkFTP: Random drop of incoming packet";
477 if (_lastReplyValid && (request->hdr.seqNumber == (_lastReplySequence - 1))) {
480 qCDebug(MockLinkFTPLog) <<
"MockLinkFTP: resending response";
485 const uint16_t incomingSeqNumber = request->hdr.seqNumber;
486 const uint16_t outgoingSeqNumber = _nextSeqNumber(incomingSeqNumber);
501 MavlinkFTP::Request ackResponse{};
502 switch (request->hdr.opcode) {
506 ackResponse.hdr.session = 0;
507 ackResponse.hdr.size = 0;
508 _sendResponse(message.sysid, message.compid, &ackResponse, outgoingSeqNumber);
511 _listCommand(message.sysid, message.compid, request, incomingSeqNumber,
false );
514 _listCommand(message.sysid, message.compid, request, incomingSeqNumber,
true );
517 _openCommand(message.sysid, message.compid, request, incomingSeqNumber);
520 _createFileCommand(message.sysid, message.compid, request, incomingSeqNumber);
523 _openFileWOCommand(message.sysid, message.compid, request, incomingSeqNumber);
526 _readCommand(message.sysid, message.compid, request, incomingSeqNumber);
529 _burstReadCommand(message.sysid, message.compid, request, incomingSeqNumber);
532 _removeFileCommand(message.sysid, message.compid, request, incomingSeqNumber);
535 _writeCommand(message.sysid, message.compid, request, incomingSeqNumber);
538 _terminateCommand(message.sysid, message.compid, request, incomingSeqNumber);
541 _resetCommand(message.sysid, message.compid, incomingSeqNumber);
550void MockLinkFTP::_sendAck(uint8_t targetSystemId, uint8_t targetComponentId, uint16_t seqNumber,
MavlinkFTP::OpCode_t reqOpcode)
552 MavlinkFTP::Request ackResponse{};
555 ackResponse.hdr.req_opcode = reqOpcode;
556 ackResponse.hdr.session = _sessionId;
557 ackResponse.hdr.size = 0;
559 _sendResponse(targetSystemId, targetComponentId, &ackResponse, seqNumber);
564 MavlinkFTP::Request nakResponse{};
567 nakResponse.hdr.req_opcode = reqOpcode;
568 nakResponse.hdr.session = _sessionId;
569 nakResponse.hdr.size = 1;
570 nakResponse.data[0] =
error;
572 _sendResponse(targetSystemId, targetComponentId, &nakResponse, seqNumber);
575void MockLinkFTP::_sendNakErrno(uint8_t targetSystemId, uint8_t targetComponentId, uint8_t nakErrno, uint16_t seqNumber,
MavlinkFTP::OpCode_t reqOpcode)
577 MavlinkFTP::Request nakResponse{};
580 nakResponse.hdr.req_opcode = reqOpcode;
581 nakResponse.hdr.session = _sessionId;
582 nakResponse.hdr.size = 2;
584 nakResponse.data[1] = nakErrno;
586 _sendResponse(targetSystemId, targetComponentId, &nakResponse, seqNumber);
590void MockLinkFTP::_sendResponse(uint8_t targetSystemId, uint8_t targetComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
592 request->hdr.seqNumber = seqNumber;
593 _lastReplySequence = seqNumber;
594 _lastReplyValid =
true;
596 (void) mavlink_msg_file_transfer_protocol_pack_chan(
604 reinterpret_cast<uint8_t*
>(request)
609 if ((rand() % 5) == 0) {
610 qCDebug(MockLinkFTPLog) <<
"MockLinkFTP: Random drop of outgoing packet";
619uint16_t MockLinkFTP::_nextSeqNumber(uint16_t seqNumber)
const
621 uint16_t outgoingSeqNumber = seqNumber + 1;
627 return outgoingSeqNumber;
630QString MockLinkFTP::_createTestTempFile(
int size)
632 QTemporaryFile tmpFile(QDir::tempPath() + QStringLiteral(
"/MockLinkFTPTestCaseXXXXXX"));
633 tmpFile.setAutoRemove(
false);
635 if (tmpFile.open()) {
636 for (
int i = 0; i < size; i++) {
637 (void) tmpFile.write(QByteArray(1,
static_cast<char>(i % 255)));
642 return tmpFile.fileName();
647 for (
const LogFile &logFile : _logFiles) {
648 if (logFile.name == name) {
649 return _generateLogFileContents(name, logFile.size);
655QByteArray MockLinkFTP::_generateLogFileContents(
const QString &name,
int size)
657 const uint seed =
qHash(name);
658 QByteArray contents(size, Qt::Uninitialized);
659 for (
int i = 0; i < size; i++) {
660 contents[i] =
static_cast<char>((seed +
static_cast<uint
>(i)) & 0xFF);
668 for (
const QString &tempPath : std::as_const(_logFileTempPaths)) {
669 QFile::remove(tempPath);
671 _logFileTempPaths.clear();
676QString MockLinkFTP::_logFileTempPath(
const QString &name)
678 if (_logFileTempPaths.contains(name)) {
679 return _logFileTempPaths.value(name);
682 for (
const LogFile &logFile : std::as_const(_logFiles)) {
683 if (logFile.name != name) {
687 QTemporaryFile tmpFile(QDir::tempPath() + QStringLiteral(
"/MockLinkFTPLogXXXXXX"));
688 tmpFile.setAutoRemove(
false);
689 if (!tmpFile.open()) {
692 (void) tmpFile.write(_generateLogFileContents(name, logFile.size));
695 _logFileTempPaths.insert(name, tmpFile.fileName());
696 return tmpFile.fileName();
702QString MockLinkFTP::_generateParamPck(
bool withDefaults)
705 enum APParamType : quint8 {
714 constexpr quint16 magicStandard = 0x671B;
715 constexpr quint16 magicWithDefaults = 0x671C;
716 constexpr int readSize = 239;
720 constexpr int compId = 1;
721 const auto &valueMap = _mockLink->_mapParamName2Value;
722 const auto &typeMap = _mockLink->_mapParamName2MavParamType;
723 if (!valueMap.contains(compId) || !typeMap.contains(compId)) {
724 qCWarning(MockLinkFTPLog) <<
"_generateParamPck: no parameters for component" << compId;
728 const auto &values = valueMap[compId];
729 const auto &types = typeMap[compId];
732 QStringList paramNames = values.keys();
735 const quint16 numParams =
static_cast<quint16
>(paramNames.size());
738 auto mapType = [](MAV_PARAM_TYPE mavType, quint8 &apType,
int &valueSize) {
740 case MAV_PARAM_TYPE_UINT8:
741 case MAV_PARAM_TYPE_INT8:
742 apType = AP_PARAM_INT8;
745 case MAV_PARAM_TYPE_UINT16:
746 case MAV_PARAM_TYPE_INT16:
747 apType = AP_PARAM_INT16;
750 case MAV_PARAM_TYPE_UINT32:
751 case MAV_PARAM_TYPE_INT32:
752 apType = AP_PARAM_INT32;
755 case MAV_PARAM_TYPE_REAL32:
756 apType = AP_PARAM_FLOAT;
760 apType = AP_PARAM_FLOAT;
767 QDataStream stream(&data, QIODevice::WriteOnly);
768 stream.setByteOrder(QDataStream::LittleEndian);
771 const quint16 magic = withDefaults ? magicWithDefaults : magicStandard;
772 stream << magic << numParams << numParams;
774 QByteArray previousName;
775 constexpr int headerSize = 6;
777 for (
const QString &name : paramNames) {
778 const QVariant &value = values[name];
779 const MAV_PARAM_TYPE mavType = types[name];
780 const QByteArray nameBytes = name.toLatin1();
784 mapType(mavType, apType, valueSize);
787 const int previousNameLen = previousName.size();
788 const int currentNameLen = nameBytes.size();
791 const int maxCommon = std::min({previousNameLen, currentNameLen, 15});
792 while (commonLen < maxCommon && nameBytes[commonLen] == previousName[commonLen]) {
796 int nameLen = currentNameLen - commonLen;
799 if (nameLen == 0 && commonLen > 0) {
804 if (nameLen + commonLen > 16) {
805 commonLen = 16 - nameLen;
817 const bool useZeroDefault = withDefaults
819 && MockLink::kAPMCalOffsetParams.contains(name);
820 const bool addDefault = withDefaults;
821 const quint8 flags = addDefault ? 0x01 : 0x00;
822 const int packedLen = 2 + nameLen + valueSize + (addDefault ? valueSize : 0);
831 const quint32 dataOfs =
static_cast<quint32
>(data.size()) - headerSize;
832 const quint32 endOfs = dataOfs +
static_cast<quint32
>(packedLen);
833 const quint32 endMod = endOfs % readSize;
834 if (endMod > 0 && endMod <
static_cast<quint32
>(valueSize)) {
835 const int pad = valueSize -
static_cast<int>(endMod);
836 for (
int i = 0; i < pad; i++) {
837 stream << static_cast<quint8>(0);
843 stream << static_cast<quint8>(apType | (flags << 4));
845 stream << static_cast<quint8>(((nameLen - 1) << 4) | commonLen);
847 stream.writeRawData(nameBytes.constData() + commonLen, nameLen);
851 case AP_PARAM_INT8: {
852 const qint8 v =
static_cast<qint8
>(value.toInt());
854 if (addDefault) stream << (useZeroDefault ? qint8(0) : v);
857 case AP_PARAM_INT16: {
858 const qint16 v =
static_cast<qint16
>(value.toInt());
860 if (addDefault) stream << (useZeroDefault ? qint16(0) : v);
863 case AP_PARAM_INT32: {
864 const qint32 v = value.toInt();
866 if (addDefault) stream << (useZeroDefault ? qint32(0) : v);
869 case AP_PARAM_FLOAT: {
870 const float f = value.toFloat();
872 memcpy(&raw, &f,
sizeof(raw));
875 if (useZeroDefault) {
885 previousName = nameBytes;
889 if (!_paramPckTempFile.isEmpty()) {
890 QFile::remove(_paramPckTempFile);
891 _paramPckTempFile.clear();
895 QTemporaryFile tmpFile(QDir::temp().filePath(QStringLiteral(
"MockLinkParamPckXXXXXX")));
896 tmpFile.setAutoRemove(
false);
898 if (!tmpFile.open()) {
899 qCWarning(MockLinkFTPLog) <<
"_generateParamPck: failed to create temp file";
905 _paramPckTempFile = tmpFile.fileName();
907 qCDebug(MockLinkFTPLog) <<
"_generateParamPck: generated" << numParams <<
"params,"
908 << data.size() <<
"bytes, withDefaults:" << withDefaults
909 <<
"file:" << tmpFile.fileName();
911 return tmpFile.fileName();
uint qHash(const FirmwareUpgradeController::FirmwareIdentifier &firmwareId)
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
ErrorCode_t
Error codes returned in Nak response PayloadHeader.data[0].
@ kErrFail
Unknown failure.
@ kErrEOF
Offset past end of file for List and Read commands.
@ kErrUnknownCommand
Unknown command opcode.
@ kErrFailErrno
errno sent back in PayloadHeader.data[1]
@ kErrInvalidSession
Session is not currently open.
@ 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>
@ kCmdOpenFileWO
Opens file at <path> for writing, returns <session>
@ kCmdResetSessions
Terminates all open Read sessions.
@ kCmdNone
ignored, always acked
@ kCmdBurstReadFile
Burst download session file.
@ kCmdTerminateSession
Terminates open Read session.
@ kCmdListDirectory
List files in <path> from <offset>
Mock implementation of Mavlink FTP server.
void resetCommandReceived()
You can connect to this signal to be notified when the server receives a Reset command.
static constexpr uint32_t kMockModificationTime
void terminateCommandReceived()
You can connect to this signal to be notified when the server receives a Terminate command.
QList< LogFile > logFiles() const
Returns the log files served from the @MAV_LOG virtual directory.
void setLogFiles(const QList< LogFile > &logFiles)
static constexpr const char * sizeFilenamePrefix
void mavlinkMessageReceived(const mavlink_message_t &message)
Called to handle an FTP message.
QByteArray logFileContents(const QString &name) const
@ errModeBadSequence
Return response with bad sequence number.
@ errModeNoSecondResponseAllowRetry
No response to subsequent request to initial command, error will be cleared after this so retry will ...
@ errModeNakSecondResponse
Nak subsequent request to initial command.
@ errModeNoResponse
No response to any request, client should eventually time out with no Ack.
@ errModeNone
No error, respond correctly.
@ errModeNoSecondResponse
No response to subsequent request to initial command.
@ errModeNakResponse
Nak all requests.
uint8_t outgoingMavlinkChannel() const
Vehicle-side channel used to encode outgoing (mock vehicle -> QGC) messages. Signing state lives here...
MAV_AUTOPILOT getFirmwareType() const
void respondWithMavlinkMessage(const mavlink_message_t &msg)
Sends the specified mavlink message to QGC.
Describes a simulated onboard log file served from the @MAV_LOG virtual directory.