7#include <QtCore/QDataStream>
9#include <QtCore/QTemporaryFile>
10#include <QtCore/QThread>
16 , _systemIdServer(systemIdServer)
17 , _componentIdServer(componentIdServer)
25 if (!_paramPckTempFile.isEmpty()) {
26 QFile::remove(_paramPckTempFile);
28 for (
const QString &tempPath : std::as_const(_logFileTempPaths)) {
29 QFile::remove(tempPath);
33void MockLinkFTP::ensureNullTemination(MavlinkFTP::Request *request)
35 if (request->hdr.size <
sizeof(request->data)) {
36 request->data[request->hdr.size] =
'\0';
38 request->data[
sizeof(request->data) - 1] =
'\0';
42void MockLinkFTP::_listCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber,
bool withTime)
44 MavlinkFTP::Request ackResponse{};
45 ensureNullTemination(request);
47 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
50 if (withTime && !_listDirectoryWithTimeSupported) {
52 _sendNak(senderSystemId, senderComponentId, _listDirectoryWithTimeNakError, outgoingSeqNumber, listOpCode);
57 const QString path =
reinterpret_cast<char*
>(&request->data[0]);
58 const QString logPrefix = QStringLiteral(
"@MAV_LOG/");
60 if (path.isEmpty() || path == QStringLiteral(
"/")) {
62 for (uint32_t index = 0; index < 6; index++) {
63 QString entry = QStringLiteral(
"Ffile%1.txt\t%2").arg(index).arg(1024 + index);
67 entries.append(entry);
69 }
else if ((path == QStringLiteral(
"@MAV_LOG")) || path.startsWith(logPrefix)) {
70 const QString subdir = path.mid(logPrefix.length());
71 const std::optional<QStringList> logEntries = _logDirectoryEntries(subdir, withTime);
73 qCDebug(MockLinkFTPLog) <<
"list of unknown @MAV_LOG directory:" << path;
74 _sendNak(senderSystemId, senderComponentId,
MavlinkFTP::kErrFail, outgoingSeqNumber, listOpCode);
77 entries = *logEntries;
79 _sendNak(senderSystemId, senderComponentId,
MavlinkFTP::kErrFail, outgoingSeqNumber, listOpCode);
83 if (request->hdr.offset > 0) {
86 _sendNak(senderSystemId, senderComponentId,
MavlinkFTP::kErrFail, outgoingSeqNumber, listOpCode);
103 ackResponse.hdr.req_opcode = listOpCode;
104 ackResponse.hdr.session = 0;
105 ackResponse.hdr.offset = request->hdr.offset;
106 ackResponse.hdr.size = 0;
110 if (request->hdr.offset <
static_cast<uint32_t
>(entries.count())) {
111 char *bufPtr =
reinterpret_cast<char*
>(&ackResponse.data[0]);
112 for (uint32_t index = request->hdr.offset; (index < request->hdr.offset + 2) && (index <
static_cast<uint32_t
>(entries.count())); index++) {
113 const QByteArray dirEntry = entries[
static_cast<qsizetype
>(index)].toUtf8();
114 if (ackResponse.hdr.size + dirEntry.length() + 1 >
static_cast<int>(
sizeof(ackResponse.data))) {
117 (void) strncpy(bufPtr, dirEntry.constData(), dirEntry.length());
118 ackResponse.hdr.size += dirEntry.length() + 1;
119 bufPtr += dirEntry.length() + 1;
124 ackResponse.hdr.size = 1;
127 _sendResponse(senderSystemId, senderComponentId, &ackResponse, outgoingSeqNumber);
130void MockLinkFTP::_openCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
132 MavlinkFTP::Request response{};
133 ensureNullTemination(request);
134 const QString path =
reinterpret_cast<char*
>(request->data);
136 _uploadSession.reset();
138 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
140 if (_singleSessionEnforced && _currentFile.isOpen()) {
141 qCDebug(MockLinkFTPLog) <<
"MockLinkFTP: OpenFileRO while session open, NAK No Sessions Available";
146 const size_t cchPath = strnlen(
reinterpret_cast<char*
>(request->data),
sizeof(request->data));
147 Q_ASSERT(cchPath !=
sizeof(request->data));
150 _currentFile.close();
154 if (path.startsWith(sizePrefix)) {
155 const QString sizeString = path.right(path.length() - sizePrefix.length());
156 tmpFilename = _createTestTempFile(sizeString.toInt());
157 }
else if (path ==
"/general.json" || path ==
"general.json") {
158 tmpFilename = QStringLiteral(
":MockLink/General.MetaData.json");
159 }
else if (path ==
"/general.json.xz" || path ==
"general.json.xz") {
160 tmpFilename = QStringLiteral(
":MockLink/General.MetaData.json.xz");
161 }
else if (path ==
"/parameter.json" || path ==
"parameter.json") {
162 tmpFilename = QStringLiteral(
":MockLink/Parameter.MetaData.json");
163 }
else if (path ==
"/parameter.json.xz" || path ==
"parameter.json.xz") {
164 tmpFilename = QStringLiteral(
":MockLink/Parameter.MetaData.json.xz");
165 }
else if (path ==
"@PARAM/param.pck" || path.startsWith(
"@PARAM/param.pck?")) {
166 if (!_paramPckEnabled) {
170 const bool withDefaults = path.contains(QStringLiteral(
"withdefaults=1"));
171 tmpFilename = _generateParamPck(withDefaults);
172 }
else if (path.startsWith(QStringLiteral(
"@MAV_LOG/"))) {
173 tmpFilename = _logFileTempPath(path.mid(QStringLiteral(
"@MAV_LOG/").length()));
176 if (!tmpFilename.isEmpty()) {
177 _currentFile.setFileName(tmpFilename);
178 if (!_currentFile.open(QIODevice::ReadOnly)) {
189 response.hdr.session = _sessionId;
192 response.hdr.size =
sizeof(uint32_t);
195 response.openFileLength = ((path ==
"@PARAM/param.pck" || path.startsWith(
"@PARAM/param.pck?")) ? qPow(1024, 2) : _currentFile.size());
198 _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
201void MockLinkFTP::_createFileCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
203 ensureNullTemination(request);
205 const QString path =
reinterpret_cast<char*
>(request->data);
206 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
208 if (path.isEmpty()) {
213 _uploadSession.reset();
214 _uploadSession.active =
true;
215 _uploadSession.remotePath = path;
217 MavlinkFTP::Request response{};
220 response.hdr.session = _sessionId;
221 response.hdr.size = 0;
223 _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
226void MockLinkFTP::_openFileWOCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
228 ensureNullTemination(request);
230 const QString path =
reinterpret_cast<char*
>(request->data);
231 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
233 if (path.isEmpty()) {
238 _uploadSession.reset();
239 _uploadSession.active =
true;
240 _uploadSession.remotePath = path;
242 MavlinkFTP::Request response{};
245 response.hdr.session = _sessionId;
246 response.hdr.size = 0;
248 _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
251void MockLinkFTP::_readCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
253 MavlinkFTP::Request response{};
254 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
256 _lastReadFileRequestSize = request->hdr.size;
258 if (request->hdr.session != _sessionId) {
263 if (!_currentFile.isOpen()) {
264 qCDebug(MockLinkFTPLog) <<
"MockLinkFTP: ReadFile with no open session, NAK Invalid Session";
269 const uint32_t readOffset = request->hdr.offset;
270 if (readOffset != 0) {
284 if (readOffset >= _currentFile.size()) {
290 const qint64 maxRead = qMin<qint64>(request->hdr.size ? request->hdr.size : sizeof(response.data), sizeof(response.data));
291 const uint8_t cBytesToRead =
static_cast<uint8_t
>(qMin(maxRead, _currentFile.size() - readOffset));
292 (void) _currentFile.seek(readOffset);
293 const QByteArray bytes = _currentFile.read(cBytesToRead);
294 (void) memcpy(response.data, bytes.constData(), cBytesToRead);
297 Q_ASSERT(cBytesToRead);
299 response.hdr.session = _sessionId;
300 response.hdr.size = cBytesToRead;
301 response.hdr.offset = request->hdr.offset;
305 _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
308void MockLinkFTP::_removeFileCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
310 ensureNullTemination(request);
311 const QString path =
reinterpret_cast<char*
>(request->data);
312 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
314 const QString logPrefix = QStringLiteral(
"@MAV_LOG/");
315 if (path.startsWith(logPrefix)) {
316 const QString name = path.mid(logPrefix.length());
317 for (
int i = 0; i < _logFiles.count(); i++) {
318 if (_logFiles[i].name == name) {
319 _logFiles.removeAt(i);
322 const QString tempPath = _logFileTempPaths.take(name);
323 if (!tempPath.isEmpty()) {
324 QFile::remove(tempPath);
336void MockLinkFTP::_burstReadCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
338 _lastBurstReadRequestSize = request->hdr.size;
340 if (_burstReadDelayMs > 0) {
341 QThread::msleep(_burstReadDelayMs);
344 MavlinkFTP::Request response{};
345 uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
347 if (request->hdr.session != _sessionId) {
352 if (!_currentFile.isOpen()) {
353 qCDebug(MockLinkFTPLog) <<
"MockLinkFTP: BurstReadFile with no open session, NAK Invalid Session";
362 constexpr int burstMax = 10;
364 uint32_t burstOffset = request->hdr.offset;
366 const qint64 maxRead = qMin<qint64>(request->hdr.size ? request->hdr.size : sizeof(response.data), sizeof(response.data));
367 MavlinkFTP::Request reorderedResponse{};
368 uint16_t reorderedSeqNumber = 0;
369 bool haveReorderedResponse =
false;
371 while ((burstOffset < _currentFile.size()) && (burstCount++ < burstMax)) {
372 _currentFile.seek(burstOffset);
374 const uint8_t cBytes =
static_cast<uint8_t
>(qMin(maxRead, _currentFile.size() - burstOffset));
375 const QByteArray bytes = _currentFile.read(cBytes);
378 (void) memcpy(response.data, bytes.constData(), cBytes);
380 response.hdr.session = _sessionId;
381 response.hdr.size = cBytes;
382 response.hdr.offset = burstOffset;
385 response.hdr.burstComplete = (burstCount == burstMax) ? 1 : 0;
387 if (_dropBurstPacketPending && (burstOffset == _dropBurstPacketOffset)) {
388 qCDebug(MockLinkFTPLog) <<
"MockLinkFTP: dropping burst packet at offset" << burstOffset;
389 _dropBurstPacketPending =
false;
390 }
else if (_reorderBurstPacketPending && (burstOffset == _reorderBurstPacketOffset)) {
391 qCDebug(MockLinkFTPLog) <<
"MockLinkFTP: holding back burst packet at offset" << burstOffset;
392 _reorderBurstPacketPending =
false;
393 reorderedResponse = response;
394 reorderedSeqNumber = outgoingSeqNumber;
395 haveReorderedResponse =
true;
397 _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
400 outgoingSeqNumber = _nextSeqNumber(outgoingSeqNumber);
401 burstOffset += cBytes;
404 if (haveReorderedResponse) {
405 _sendResponse(senderSystemId, senderComponentId, &reorderedResponse, reorderedSeqNumber);
408 if (burstOffset >= _currentFile.size()) {
413 if ((_expireSessionAfterBursts > 0) && (++_burstsServed >= _expireSessionAfterBursts)) {
414 qCDebug(MockLinkFTPLog) <<
"MockLinkFTP: expiring session after burst" << _burstsServed;
415 _currentFile.close();
416 _expireSessionAfterBursts = 0;
420void MockLinkFTP::_terminateCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
422 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
424 if (request->hdr.session != _sessionId) {
429 _currentFile.close();
432 _finalizeActiveUpload();
437void MockLinkFTP::_resetCommand(uint8_t senderSystemId, uint8_t senderComponentId, uint16_t seqNumber)
441 if (_ignoreResetSessions) {
445 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
447 _currentFile.close();
450 _finalizeActiveUpload();
455 _currentFile.close();
456 _currentFile.setFileName(_createTestTempFile(1024));
457 (void) _currentFile.open(QIODevice::ReadOnly);
460void MockLinkFTP::_writeCommand(uint8_t senderSystemId, uint8_t senderComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
462 const uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
464 if ((request->hdr.session != _sessionId) || !_uploadSession.active) {
469 if (request->hdr.offset >
static_cast<uint32_t
>(_uploadSession.buffer.size())) {
474 if (request->hdr.offset != 0) {
490 const uint32_t bytesToWrite = request->hdr.size;
491 if (bytesToWrite >
sizeof(request->data)) {
496 const uint32_t requiredSize = request->hdr.offset + bytesToWrite;
497 if (requiredSize >
static_cast<uint32_t
>(_uploadSession.buffer.size())) {
498 _uploadSession.buffer.resize(requiredSize);
501 if (bytesToWrite > 0) {
502 (void) memcpy(_uploadSession.buffer.data() + request->hdr.offset, request->data, bytesToWrite);
505 MavlinkFTP::Request response{};
508 response.hdr.session = _sessionId;
509 response.hdr.size = 0;
510 response.hdr.offset = request->hdr.offset;
512 _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
515void MockLinkFTP::_finalizeActiveUpload()
517 if (!_uploadSession.active) {
521 if (!_uploadSession.remotePath.isEmpty()) {
522 _uploadedFiles.insert(_uploadSession.remotePath, _uploadSession.buffer);
525 _uploadSession.reset();
530 if (message.msgid != MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL) {
534 mavlink_file_transfer_protocol_t requestFTP{};
535 mavlink_msg_file_transfer_protocol_decode(&message, &requestFTP);
537 if (requestFTP.target_system != _systemIdServer) {
541 MavlinkFTP::Request *request =
reinterpret_cast<MavlinkFTP::Request*
>(&requestFTP.payload[0]);
545 if ((rand() % 5) == 0) {
546 qCDebug(MockLinkFTPLog) <<
"MockLinkFTP: Random drop of incoming packet";
551 if (_lastReplyValid && (request->hdr.seqNumber == (_lastReplySequence - 1))) {
554 qCDebug(MockLinkFTPLog) <<
"MockLinkFTP: resending response";
559 const uint16_t incomingSeqNumber = request->hdr.seqNumber;
560 const uint16_t outgoingSeqNumber = _nextSeqNumber(incomingSeqNumber);
575 MavlinkFTP::Request ackResponse{};
576 switch (request->hdr.opcode) {
580 ackResponse.hdr.session = 0;
581 ackResponse.hdr.size = 0;
582 _sendResponse(message.sysid, message.compid, &ackResponse, outgoingSeqNumber);
585 _listCommand(message.sysid, message.compid, request, incomingSeqNumber,
false );
588 _listCommand(message.sysid, message.compid, request, incomingSeqNumber,
true );
591 _openCommand(message.sysid, message.compid, request, incomingSeqNumber);
594 _createFileCommand(message.sysid, message.compid, request, incomingSeqNumber);
597 _openFileWOCommand(message.sysid, message.compid, request, incomingSeqNumber);
600 _readCommand(message.sysid, message.compid, request, incomingSeqNumber);
603 _burstReadCommand(message.sysid, message.compid, request, incomingSeqNumber);
606 _removeFileCommand(message.sysid, message.compid, request, incomingSeqNumber);
609 _writeCommand(message.sysid, message.compid, request, incomingSeqNumber);
612 _terminateCommand(message.sysid, message.compid, request, incomingSeqNumber);
615 _resetCommand(message.sysid, message.compid, incomingSeqNumber);
624void MockLinkFTP::_sendAck(uint8_t targetSystemId, uint8_t targetComponentId, uint16_t seqNumber,
MavlinkFTP::OpCode_t reqOpcode)
626 MavlinkFTP::Request ackResponse{};
629 ackResponse.hdr.req_opcode = reqOpcode;
630 ackResponse.hdr.session = _sessionId;
631 ackResponse.hdr.size = 0;
633 _sendResponse(targetSystemId, targetComponentId, &ackResponse, seqNumber);
638 MavlinkFTP::Request nakResponse{};
641 nakResponse.hdr.req_opcode = reqOpcode;
642 nakResponse.hdr.session = _sessionId;
643 nakResponse.hdr.size = 1;
644 nakResponse.data[0] =
error;
646 _sendResponse(targetSystemId, targetComponentId, &nakResponse, seqNumber);
649void MockLinkFTP::_sendNakErrno(uint8_t targetSystemId, uint8_t targetComponentId, uint8_t nakErrno, uint16_t seqNumber,
MavlinkFTP::OpCode_t reqOpcode)
651 MavlinkFTP::Request nakResponse{};
654 nakResponse.hdr.req_opcode = reqOpcode;
655 nakResponse.hdr.session = _sessionId;
656 nakResponse.hdr.size = 2;
658 nakResponse.data[1] = nakErrno;
660 _sendResponse(targetSystemId, targetComponentId, &nakResponse, seqNumber);
664void MockLinkFTP::_sendResponse(uint8_t targetSystemId, uint8_t targetComponentId, MavlinkFTP::Request *request, uint16_t seqNumber)
666 request->hdr.seqNumber = seqNumber;
667 _lastReplySequence = seqNumber;
668 _lastReplyValid =
true;
670 (void) mavlink_msg_file_transfer_protocol_pack_chan(
678 reinterpret_cast<uint8_t*
>(request)
683 if ((rand() % 5) == 0) {
684 qCDebug(MockLinkFTPLog) <<
"MockLinkFTP: Random drop of outgoing packet";
693uint16_t MockLinkFTP::_nextSeqNumber(uint16_t seqNumber)
const
695 uint16_t outgoingSeqNumber = seqNumber + 1;
701 return outgoingSeqNumber;
704QString MockLinkFTP::_createTestTempFile(
int size)
706 QTemporaryFile tmpFile(QDir::tempPath() + QStringLiteral(
"/MockLinkFTPTestCaseXXXXXX"));
707 tmpFile.setAutoRemove(
false);
709 if (tmpFile.open()) {
710 for (
int i = 0; i < size; i++) {
711 (void) tmpFile.write(QByteArray(1,
static_cast<char>(i % 255)));
716 return tmpFile.fileName();
721 for (
const LogFile &logFile : _logFiles) {
722 if (logFile.name == name) {
723 return _generateLogFileContents(name, logFile.size);
729QByteArray MockLinkFTP::_generateLogFileContents(
const QString &name,
int size)
731 const uint seed =
qHash(name);
732 QByteArray contents(size, Qt::Uninitialized);
733 for (
int i = 0; i < size; i++) {
734 contents[i] =
static_cast<char>((seed +
static_cast<uint
>(i)) & 0xFF);
742 for (
const QString &tempPath : std::as_const(_logFileTempPaths)) {
743 QFile::remove(tempPath);
745 _logFileTempPaths.clear();
750QString MockLinkFTP::_logFileTempPath(
const QString &name)
752 if (_logFileTempPaths.contains(name)) {
753 return _logFileTempPaths.value(name);
756 for (
const LogFile &logFile : std::as_const(_logFiles)) {
757 if (logFile.name != name) {
761 QTemporaryFile tmpFile(QDir::tempPath() + QStringLiteral(
"/MockLinkFTPLogXXXXXX"));
762 tmpFile.setAutoRemove(
false);
763 if (!tmpFile.open()) {
766 (void) tmpFile.write(_generateLogFileContents(name, logFile.size));
769 _logFileTempPaths.insert(name, tmpFile.fileName());
770 return tmpFile.fileName();
776std::optional<QStringList> MockLinkFTP::_logDirectoryEntries(
const QString& subdir,
bool withTime)
const
778 const auto dirEntry = [
this, withTime](
const QString& name) {
779 QString entry = QStringLiteral(
"D") + name;
780 if (withTime && _logDirEntriesWithTime) {
787 if (_logDirDotEntries) {
788 entries.append(dirEntry(QStringLiteral(
".")));
789 entries.append(dirEntry(QStringLiteral(
"..")));
792 const QString prefix = subdir.isEmpty() ? QString() : (subdir + QLatin1Char(
'/'));
793 bool found = subdir.isEmpty();
794 QStringList childDirs;
795 for (
const LogFile& logFile : _logFiles) {
796 if (!logFile.name.startsWith(prefix)) {
801 const QString relativeName = logFile.name.mid(prefix.length());
802 const qsizetype slashIdx = relativeName.indexOf(QLatin1Char(
'/'));
804 const QString childDir = relativeName.left(slashIdx);
805 if (!childDirs.contains(childDir)) {
806 childDirs.append(childDir);
807 entries.append(dirEntry(childDir));
812 QString entry = QStringLiteral(
"F%1\t%2").arg(relativeName).arg(logFile.size);
814 entry += QStringLiteral(
"\t%1").arg(logFile.mtime);
816 entries.append(entry);
825QString MockLinkFTP::_generateParamPck(
bool withDefaults)
828 enum APParamType : quint8 {
837 constexpr quint16 magicStandard = 0x671B;
838 constexpr quint16 magicWithDefaults = 0x671C;
839 constexpr int readSize = 239;
843 constexpr int compId = 1;
844 const auto &valueMap = _mockLink->_mapParamName2Value;
845 const auto &typeMap = _mockLink->_mapParamName2MavParamType;
846 if (!valueMap.contains(compId) || !typeMap.contains(compId)) {
847 qCWarning(MockLinkFTPLog) <<
"_generateParamPck: no parameters for component" << compId;
851 const auto &values = valueMap[compId];
852 const auto &types = typeMap[compId];
855 QStringList paramNames = values.keys();
858 const quint16 numParams =
static_cast<quint16
>(paramNames.size());
861 auto mapType = [](MAV_PARAM_TYPE mavType, quint8 &apType,
int &valueSize) {
863 case MAV_PARAM_TYPE_UINT8:
864 case MAV_PARAM_TYPE_INT8:
865 apType = AP_PARAM_INT8;
868 case MAV_PARAM_TYPE_UINT16:
869 case MAV_PARAM_TYPE_INT16:
870 apType = AP_PARAM_INT16;
873 case MAV_PARAM_TYPE_UINT32:
874 case MAV_PARAM_TYPE_INT32:
875 apType = AP_PARAM_INT32;
878 case MAV_PARAM_TYPE_REAL32:
879 apType = AP_PARAM_FLOAT;
883 apType = AP_PARAM_FLOAT;
890 QDataStream stream(&data, QIODevice::WriteOnly);
891 stream.setByteOrder(QDataStream::LittleEndian);
894 const quint16 magic = withDefaults ? magicWithDefaults : magicStandard;
895 stream << magic << numParams << numParams;
897 QByteArray previousName;
898 constexpr int headerSize = 6;
900 for (
const QString &name : paramNames) {
901 const QVariant &value = values[name];
902 const MAV_PARAM_TYPE mavType = types[name];
903 const QByteArray nameBytes = name.toLatin1();
907 mapType(mavType, apType, valueSize);
910 const int previousNameLen = previousName.size();
911 const int currentNameLen = nameBytes.size();
914 const int maxCommon = std::min({previousNameLen, currentNameLen, 15});
915 while (commonLen < maxCommon && nameBytes[commonLen] == previousName[commonLen]) {
919 int nameLen = currentNameLen - commonLen;
922 if (nameLen == 0 && commonLen > 0) {
927 if (nameLen + commonLen > 16) {
928 commonLen = 16 - nameLen;
940 const bool useZeroDefault = withDefaults
942 && MockLink::kAPMCalOffsetParams.contains(name);
943 const bool addDefault = withDefaults;
944 const quint8 flags = addDefault ? 0x01 : 0x00;
945 const int packedLen = 2 + nameLen + valueSize + (addDefault ? valueSize : 0);
954 const quint32 dataOfs =
static_cast<quint32
>(data.size()) - headerSize;
955 const quint32 endOfs = dataOfs +
static_cast<quint32
>(packedLen);
956 const quint32 endMod = endOfs % readSize;
957 if (endMod > 0 && endMod <
static_cast<quint32
>(valueSize)) {
958 const int pad = valueSize -
static_cast<int>(endMod);
959 for (
int i = 0; i < pad; i++) {
960 stream << static_cast<quint8>(0);
966 stream << static_cast<quint8>(apType | (flags << 4));
968 stream << static_cast<quint8>(((nameLen - 1) << 4) | commonLen);
970 stream.writeRawData(nameBytes.constData() + commonLen, nameLen);
974 case AP_PARAM_INT8: {
975 const qint8 v =
static_cast<qint8
>(value.toInt());
977 if (addDefault) stream << (useZeroDefault ? qint8(0) : v);
980 case AP_PARAM_INT16: {
981 const qint16 v =
static_cast<qint16
>(value.toInt());
983 if (addDefault) stream << (useZeroDefault ? qint16(0) : v);
986 case AP_PARAM_INT32: {
987 const qint32 v = value.toInt();
989 if (addDefault) stream << (useZeroDefault ? qint32(0) : v);
992 case AP_PARAM_FLOAT: {
993 const float f = value.toFloat();
995 memcpy(&raw, &f,
sizeof(raw));
998 if (useZeroDefault) {
1008 previousName = nameBytes;
1012 if (!_paramPckTempFile.isEmpty()) {
1013 QFile::remove(_paramPckTempFile);
1014 _paramPckTempFile.clear();
1018 QTemporaryFile tmpFile(QDir::temp().filePath(QStringLiteral(
"MockLinkParamPckXXXXXX")));
1019 tmpFile.setAutoRemove(
false);
1021 if (!tmpFile.open()) {
1022 qCWarning(MockLinkFTPLog) <<
"_generateParamPck: failed to create temp file";
1026 tmpFile.write(data);
1028 _paramPckTempFile = tmpFile.fileName();
1030 qCDebug(MockLinkFTPLog) <<
"_generateParamPck: generated" << numParams <<
"params,"
1031 << data.size() <<
"bytes, withDefaults:" << withDefaults
1032 <<
"file:" << tmpFile.fileName();
1034 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].
@ kErrNoSessionsAvailable
All available Sessions in use.
@ 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)
void openStaleSessionForTest()
Opens a read session server-side without any client request, simulating a session left behind by a cr...
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.