Browse Source

Support for Open/Read/Terminate FTP commands

Also modified protocol to better support eof’s on list and read
commands.
QGC4.4
Don Gagne 11 years ago
parent
commit
e2e246fe5d
  1. 159
      src/qgcunittest/MockMavlinkFileServer.cc
  2. 18
      src/qgcunittest/MockMavlinkFileServer.h
  3. 64
      src/qgcunittest/QGCUASFileManagerTest.cc
  4. 168
      src/uas/QGCUASFileManager.cc
  5. 57
      src/uas/QGCUASFileManager.h
  6. 2
      src/ui/QGCUASFileView.cc

159
src/qgcunittest/MockMavlinkFileServer.cc

@ -23,20 +23,31 @@
#include "MockMavlinkFileServer.h" #include "MockMavlinkFileServer.h"
const char* MockMavlinkFileServer::smallFilename = "small"; const MockMavlinkFileServer::FileTestCase MockMavlinkFileServer::rgFileTestCases[MockMavlinkFileServer::cFileTestCases] = {
const char* MockMavlinkFileServer::largeFilename = "large"; // File fits one Read Ack packet, partially filling data
{ "partial.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) - 1 },
// FIXME: -2 to avoid eof on full packet // File fits one Read Ack packet, exactly filling all data
const uint8_t MockMavlinkFileServer::smallFileLength = sizeof(((QGCUASFileManager::Request*)0)->data) - 2; { "exact.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) },
const uint8_t MockMavlinkFileServer::largeFileLength = sizeof(((QGCUASFileManager::Request*)0)->data) + 1; // File is larger than a single Read Ack packets, requires multiple Reads
{ "multi.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) + 1 },
};
// We only support a single fixed session
const uint8_t MockMavlinkFileServer::_sessionId = 1; const uint8_t MockMavlinkFileServer::_sessionId = 1;
MockMavlinkFileServer::MockMavlinkFileServer(void)
{
}
/// @brief Handles List command requests. Only supports root folder paths. /// @brief Handles List command requests. Only supports root folder paths.
/// File list returned is set using the setFileList method. /// File list returned is set using the setFileList method.
void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request) void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request)
{ {
// FIXME: Does not support directories that span multiple packets
QGCUASFileManager::Request ackResponse; QGCUASFileManager::Request ackResponse;
QString path; QString path;
@ -56,7 +67,9 @@ void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request)
ackResponse.hdr.magic = 'f'; ackResponse.hdr.magic = 'f';
ackResponse.hdr.opcode = QGCUASFileManager::kRspAck; ackResponse.hdr.opcode = QGCUASFileManager::kRspAck;
ackResponse.hdr.session = 0; ackResponse.hdr.session = 0;
ackResponse.hdr.offset = request->hdr.offset;
ackResponse.hdr.size = 0; ackResponse.hdr.size = 0;
ackResponse.hdr.errCode = QGCUASFileManager::kErrNone;
if (request->hdr.offset == 0) { if (request->hdr.offset == 0) {
// Requesting first batch of file names // Requesting first batch of file names
@ -69,42 +82,35 @@ void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request)
ackResponse.hdr.size += cchFilename + 1; ackResponse.hdr.size += cchFilename + 1;
bufPtr += cchFilename + 1; bufPtr += cchFilename + 1;
} }
// Final double termination
*bufPtr = 0;
ackResponse.hdr.size++;
} else { } else {
// All filenames fit in first ack, send final null terminated ack // FIXME: Does not support directories that span multiple packets
ackResponse.data[0] = 0; _sendNak(QGCUASFileManager::kErrEOF);
ackResponse.hdr.size = 1;
} }
_emitResponse(&ackResponse); _emitResponse(&ackResponse);
} }
/// @brief Handles Open command requests. Two filenames are supported: /// @brief Handles Open command requests.
/// '/small' - file fits in a single packet
/// '/large' - file requires multiple packets to be sent
/// In all cases file contents are one byte data length, followed by a single
/// byte repeating increasing sequence (0x00, 0x01, .. 0xFF) for specified
/// number of bytes.
void MockMavlinkFileServer::_openCommand(QGCUASFileManager::Request* request) void MockMavlinkFileServer::_openCommand(QGCUASFileManager::Request* request)
{ {
QGCUASFileManager::Request response; QGCUASFileManager::Request response;
QString path; QString path;
// Make sure one byte of length is enough to overflow into two packets. size_t cchPath = strnlen((char *)request->data, sizeof(request->data));
Q_ASSERT((sizeof(request->data) & 0xFF) == sizeof(request->data)); Q_ASSERT(cchPath != sizeof(request->data));
path = (char *)request->data;
// Check path against one of our known test cases
path = (char *)&request->data[0]; bool found = false;
if (path == smallFilename) { for (size_t i=0; i<cFileTestCases; i++) {
_readFileLength = smallFileLength; if (path == rgFileTestCases[i].filename) {
qDebug() << "Reading file length" << smallFileLength; found = true;
} else if (path == largeFilename) { _readFileLength = rgFileTestCases[i].length;
_readFileLength = largeFileLength; break;
qDebug() << "Reading file length" << largeFileLength; }
} else { }
if (!found) {
_sendNak(QGCUASFileManager::kErrNotFile); _sendNak(QGCUASFileManager::kErrNotFile);
return; return;
} }
@ -113,6 +119,7 @@ void MockMavlinkFileServer::_openCommand(QGCUASFileManager::Request* request)
response.hdr.opcode = QGCUASFileManager::kRspAck; response.hdr.opcode = QGCUASFileManager::kRspAck;
response.hdr.session = _sessionId; response.hdr.session = _sessionId;
response.hdr.size = 0; response.hdr.size = 0;
response.hdr.errCode = QGCUASFileManager::kErrNone;
_emitResponse(&response); _emitResponse(&response);
} }
@ -120,8 +127,6 @@ void MockMavlinkFileServer::_openCommand(QGCUASFileManager::Request* request)
/// @brief Handles Read command requests. /// @brief Handles Read command requests.
void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request) void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request)
{ {
qDebug() << "Read command:" << request->hdr.offset;
QGCUASFileManager::Request response; QGCUASFileManager::Request response;
if (request->hdr.session != _sessionId) { if (request->hdr.session != _sessionId) {
@ -129,53 +134,70 @@ void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request)
return; return;
} }
if (request->hdr.size > sizeof(response.data)) { uint32_t readOffset = request->hdr.offset; // offset into file for reading
_sendNak(QGCUASFileManager::kErrPerm); uint8_t cDataBytes = 0; // current number of data bytes used
return;
}
uint8_t size = 0;
uint32_t offset = request->hdr.offset;
// Offset check is > instead of >= to take into accoutn extra length byte at beginning of file if (readOffset > _readFileLength) {
if (offset > _readFileLength) { _sendNak(QGCUASFileManager::kErrEOF);
_sendNak(QGCUASFileManager::kErrIO);
return; return;
} }
// Write length byte if needed // Write length byte if needed
if (offset == 0) { if (readOffset == 0) {
response.data[0] = _readFileLength; response.data[0] = _readFileLength;
offset++; readOffset++;
size++; cDataBytes++;
} }
// Write file bytes. Data is a repeating sequence of 0x00, 0x01, .. 0xFF. // Write file bytes. Data is a repeating sequence of 0x00, 0x01, .. 0xFF.
for (; size < sizeof(response.data) && offset <= _readFileLength; offset++, size++) { for (; cDataBytes < sizeof(response.data) && readOffset < _readFileLength; readOffset++, cDataBytes++) {
response.data[size] = (offset - 1) & 0xFF; // Subtract one from readOffset to take into account length byte and start file data a 0x00
response.data[cDataBytes] = (readOffset - 1) & 0xFF;
} }
qDebug() << "_readCommand bytes written" << size; // We should always have written something, otherwise there is something wrong with the code above
Q_ASSERT(cDataBytes);
// If we didn't write any bytes it was a bad request
if (size == 0) {
_sendNak(QGCUASFileManager::kErrEOF);
return;
}
response.hdr.magic = 'f'; response.hdr.magic = 'f';
response.hdr.opcode = QGCUASFileManager::kRspAck; response.hdr.opcode = QGCUASFileManager::kRspAck;
response.hdr.session = _sessionId; response.hdr.session = _sessionId;
response.hdr.size = size; response.hdr.size = cDataBytes;
response.hdr.offset = request->hdr.offset; response.hdr.offset = request->hdr.offset;
if (readOffset >= _readFileLength) {
// Something wrong with the reading code, should not have gone past last byte
Q_ASSERT(readOffset == _readFileLength);
// We have read all the bytes in the file
response.hdr.errCode = QGCUASFileManager::kErrNone;
} else {
// There are still more bytes left to read in the file
response.hdr.errCode = QGCUASFileManager::kErrMore;
}
_emitResponse(&response); _emitResponse(&response);
} }
/// @brief Handles Terminate commands
void MockMavlinkFileServer::_terminateCommand(QGCUASFileManager::Request* request)
{
if (request->hdr.session != _sessionId) {
_sendNak(QGCUASFileManager::kErrNoSession);
return;
}
_sendAck();
// Let our test harness know that we got a terminate command. This is used to validate the a Terminate is correctly
// sent after an Open.
emit terminateCommandReceived();
}
/// @brief Handles messages sent to the FTP server. /// @brief Handles messages sent to the FTP server.
void MockMavlinkFileServer::sendMessage(mavlink_message_t message) void MockMavlinkFileServer::sendMessage(mavlink_message_t message)
{ {
QGCUASFileManager::Request ackResponse; QGCUASFileManager::Request ackResponse;
Q_ASSERT(message.msgid == MAVLINK_MSG_ID_ENCAPSULATED_DATA); Q_ASSERT(message.msgid == MAVLINK_MSG_ID_ENCAPSULATED_DATA);
@ -204,6 +226,7 @@ void MockMavlinkFileServer::sendMessage(mavlink_message_t message)
ackResponse.hdr.session = 0; ackResponse.hdr.session = 0;
ackResponse.hdr.crc32 = 0; ackResponse.hdr.crc32 = 0;
ackResponse.hdr.size = 0; ackResponse.hdr.size = 0;
ackResponse.hdr.errCode = QGCUASFileManager::kErrNone;
_emitResponse(&ackResponse); _emitResponse(&ackResponse);
break; break;
@ -219,10 +242,12 @@ void MockMavlinkFileServer::sendMessage(mavlink_message_t message)
_readCommand(request); _readCommand(request);
break; break;
case QGCUASFileManager::kCmdTerminate:
_terminateCommand(request);
break;
// Remainder of commands are NYI // Remainder of commands are NYI
case QGCUASFileManager::kCmdTerminate:
// releases sessionID, closes file
case QGCUASFileManager::kCmdCreate: case QGCUASFileManager::kCmdCreate:
// creates <path> for writing, returns <session> // creates <path> for writing, returns <session>
case QGCUASFileManager::kCmdWrite: case QGCUASFileManager::kCmdWrite:
@ -236,6 +261,20 @@ void MockMavlinkFileServer::sendMessage(mavlink_message_t message)
} }
} }
/// @brief Sends an Ack
void MockMavlinkFileServer::_sendAck(void)
{
QGCUASFileManager::Request ackResponse;
ackResponse.hdr.magic = 'f';
ackResponse.hdr.opcode = QGCUASFileManager::kRspAck;
ackResponse.hdr.session = 0;
ackResponse.hdr.size = 0;
ackResponse.hdr.errCode = QGCUASFileManager::kErrNone;
_emitResponse(&ackResponse);
}
/// @brief Sends a Nak with the specified error code. /// @brief Sends a Nak with the specified error code.
void MockMavlinkFileServer::_sendNak(QGCUASFileManager::ErrorCode error) void MockMavlinkFileServer::_sendNak(QGCUASFileManager::ErrorCode error)
{ {
@ -244,8 +283,8 @@ void MockMavlinkFileServer::_sendNak(QGCUASFileManager::ErrorCode error)
nakResponse.hdr.magic = 'f'; nakResponse.hdr.magic = 'f';
nakResponse.hdr.opcode = QGCUASFileManager::kRspNak; nakResponse.hdr.opcode = QGCUASFileManager::kRspNak;
nakResponse.hdr.session = 0; nakResponse.hdr.session = 0;
nakResponse.hdr.size = sizeof(nakResponse.data[0]); nakResponse.hdr.size = 0;
nakResponse.data[0] = error; nakResponse.hdr.errCode = error;
_emitResponse(&nakResponse); _emitResponse(&nakResponse);
} }

18
src/qgcunittest/MockMavlinkFileServer.h

@ -40,7 +40,7 @@ class MockMavlinkFileServer : public MockMavlinkInterface
Q_OBJECT Q_OBJECT
public: public:
MockMavlinkFileServer(void) { }; MockMavlinkFileServer(void);
/// @brief Sets the list of files returned by the List command. Prepend names with F or D /// @brief Sets the list of files returned by the List command. Prepend names with F or D
/// to indicate (F)ile or (D)irectory. /// to indicate (F)ile or (D)irectory.
@ -49,17 +49,25 @@ public:
// From MockMavlinkInterface // From MockMavlinkInterface
virtual void sendMessage(mavlink_message_t message); virtual void sendMessage(mavlink_message_t message);
static const char* smallFilename; struct FileTestCase {
static const char* largeFilename; const char* filename;
static const uint8_t smallFileLength; uint8_t length;
static const uint8_t largeFileLength; };
static const size_t cFileTestCases = 3;
static const FileTestCase rgFileTestCases[cFileTestCases];
signals:
void terminateCommandReceived(void);
private: private:
void _sendAck(void);
void _sendNak(QGCUASFileManager::ErrorCode error); void _sendNak(QGCUASFileManager::ErrorCode error);
void _emitResponse(QGCUASFileManager::Request* request); void _emitResponse(QGCUASFileManager::Request* request);
void _listCommand(QGCUASFileManager::Request* request); void _listCommand(QGCUASFileManager::Request* request);
void _openCommand(QGCUASFileManager::Request* request); void _openCommand(QGCUASFileManager::Request* request);
void _readCommand(QGCUASFileManager::Request* request); void _readCommand(QGCUASFileManager::Request* request);
void _terminateCommand(QGCUASFileManager::Request* request);
QStringList _fileList; ///< List of files returned by List command QStringList _fileList; ///< List of files returned by List command

64
src/qgcunittest/QGCUASFileManagerTest.cc

@ -132,7 +132,7 @@ void QGCUASFileManagerUnitTest::_listTest(void)
// Send a bogus path // Send a bogus path
// We should get a single resetStatusMessages signal // We should get a single resetStatusMessages signal
// We should get a single errorMessage signal // We should get a single errorMessage signal
_fileManager->listRecursively("/bogus"); _fileManager->listDirectory("/bogus");
QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask | resetStatusMessagesSignalMask), true); QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask | resetStatusMessagesSignalMask), true);
_multiSpy->clearAllSignals(); _multiSpy->clearAllSignals();
@ -151,7 +151,7 @@ void QGCUASFileManagerUnitTest::_listTest(void)
_fileListReceived.clear(); _fileListReceived.clear();
_fileManager->listRecursively("/"); _fileManager->listDirectory("/");
QCOMPARE(_multiSpy->checkSignalByMask(resetStatusMessagesSignalMask), true); // We should be told to reset status messages QCOMPARE(_multiSpy->checkSignalByMask(resetStatusMessagesSignalMask), true); // We should be told to reset status messages
QCOMPARE(_multiSpy->checkNoSignalByMask(errorMessageSignalMask), true); // We should not get an error signals QCOMPARE(_multiSpy->checkNoSignalByMask(errorMessageSignalMask), true); // We should not get an error signals
QVERIFY(_fileListReceived == fileListExpected); QVERIFY(_fileListReceived == fileListExpected);
@ -160,16 +160,20 @@ void QGCUASFileManagerUnitTest::_listTest(void)
void QGCUASFileManagerUnitTest::_validateFileContents(const QString& filePath, uint8_t length) void QGCUASFileManagerUnitTest::_validateFileContents(const QString& filePath, uint8_t length)
{ {
QFile file(filePath); QFile file(filePath);
// Make sure file size is correct
QCOMPARE(file.size(), (qint64)length);
// Read data
QVERIFY(file.open(QIODevice::ReadOnly)); QVERIFY(file.open(QIODevice::ReadOnly));
QByteArray bytes = file.readAll(); QByteArray bytes = file.readAll();
file.close(); file.close();
QCOMPARE(bytes.length(), length + 1); // +1 for length byte
// Validate length byte // Validate length byte
QCOMPARE((uint8_t)bytes[0], length); QCOMPARE((uint8_t)bytes[0], length);
// Validate file contents // Validate file contents:
// Repeating 0x00, 0x01 .. 0xFF until file is full
for (uint8_t i=1; i<bytes.length(); i++) { for (uint8_t i=1; i<bytes.length(); i++) {
QCOMPARE((uint8_t)bytes[i], (uint8_t)((i-1) & 0xFF)); QCOMPARE((uint8_t)bytes[i], (uint8_t)((i-1) & 0xFF));
} }
@ -189,30 +193,32 @@ void QGCUASFileManagerUnitTest::_openTest(void)
_multiSpy->clearAllSignals(); _multiSpy->clearAllSignals();
// Clean previous downloads // Clean previous downloads
for (size_t i=0; i<MockMavlinkFileServer::cFileTestCases; i++) {
QString smallFilePath; QString filePath = QDir::temp().absoluteFilePath(MockMavlinkFileServer::rgFileTestCases[i].filename);
smallFilePath = QDir::temp().absoluteFilePath(MockMavlinkFileServer::smallFilename); if (QFile::exists(filePath)) {
if (QFile::exists(smallFilePath)) { Q_ASSERT(QFile::remove(filePath));
Q_ASSERT(QFile::remove(smallFilePath)); }
}
QString largeFilePath;
largeFilePath = QDir::temp().absoluteFilePath(MockMavlinkFileServer::largeFilename);
if (QFile::exists(largeFilePath)) {
Q_ASSERT(QFile::remove(largeFilePath));
} }
// Send a valid file to download
// We should get a single resetStatusMessages signal
// We should get a single statusMessage signal, which indicated download completion
_fileManager->downloadPath(MockMavlinkFileServer::smallFilename, QDir::temp());
QCOMPARE(_multiSpy->checkOnlySignalByMask(statusMessageSignalMask | resetStatusMessagesSignalMask), true);
_multiSpy->clearAllSignals();
_validateFileContents(smallFilePath, MockMavlinkFileServer::smallFileLength);
_fileManager->downloadPath(MockMavlinkFileServer::largeFilename, QDir::temp()); // Run through the set of file test cases
QCOMPARE(_multiSpy->checkOnlySignalByMask(statusMessageSignalMask | resetStatusMessagesSignalMask), true);
_multiSpy->clearAllSignals(); // We setup a spy on the terminate command signal so that we can determine that a Terminate command was
_validateFileContents(largeFilePath, MockMavlinkFileServer::largeFileLength); // correctly sent after the Open/Read commands complete.
QSignalSpy terminateSpy(&_mockFileServer, SIGNAL(terminateCommandReceived()));
for (size_t i=0; i<MockMavlinkFileServer::cFileTestCases; i++) {
_fileManager->downloadPath(MockMavlinkFileServer::rgFileTestCases[i].filename, QDir::temp());
// We should get a single resetStatusMessages signal
// We should get a single statusMessage signal, which indicated download completion
QCOMPARE(_multiSpy->checkOnlySignalByMask(statusMessageSignalMask | resetStatusMessagesSignalMask), true);
_multiSpy->clearAllSignals();
// We should get a single Terminate command
QCOMPARE(terminateSpy.count(), 1);
terminateSpy.clear();
QString filePath = QDir::temp().absoluteFilePath(MockMavlinkFileServer::rgFileTestCases[i].filename);
_validateFileContents(filePath, MockMavlinkFileServer::rgFileTestCases[i].length);
}
} }

168
src/uas/QGCUASFileManager.cc

@ -104,13 +104,14 @@ void QGCUASFileManager::nothingMessage()
// FIXME: Connect ui correctly // FIXME: Connect ui correctly
} }
/// @brief Respond to the Ack associated with the Open command. /// @brief Respond to the Ack associated with the Open command with the next Read command.
void QGCUASFileManager::_openResponse(Request* openAck) void QGCUASFileManager::_openAckResponse(Request* openAck)
{ {
_currentOperation = kCORead; _currentOperation = kCORead;
_activeSession = openAck->hdr.session; _activeSession = openAck->hdr.session;
_readOffset = 0;
_readFileAccumulator.clear(); _readOffset = 0; // Start reading at beginning of file
_readFileAccumulator.clear(); // Start with an empty file
Request request; Request request;
request.hdr.magic = 'f'; request.hdr.magic = 'f';
@ -123,7 +124,7 @@ void QGCUASFileManager::_openResponse(Request* openAck)
} }
/// @brief Respond to the Ack associated with the Read command. /// @brief Respond to the Ack associated with the Read command.
void QGCUASFileManager::_readResponse(Request* readAck) void QGCUASFileManager::_readAckResponse(Request* readAck)
{ {
if (readAck->hdr.session != _activeSession) { if (readAck->hdr.session != _activeSession) {
_currentOperation = kCOIdle; _currentOperation = kCOIdle;
@ -139,24 +140,27 @@ void QGCUASFileManager::_readResponse(Request* readAck)
return; return;
} }
qDebug() << "Accumulator size" << readAck->hdr.size;
_readFileAccumulator.append((const char*)readAck->data, readAck->hdr.size); _readFileAccumulator.append((const char*)readAck->data, readAck->hdr.size);
if (readAck->hdr.size == sizeof(readAck->data)) { if (readAck->hdr.errCode == kErrMore) {
// Still more data to read // Still more data to read, send next read request
_currentOperation = kCORead; _currentOperation = kCORead;
_readOffset += sizeof(readAck->data); _readOffset += readAck->hdr.size;
Request request; Request request;
request.hdr.magic = 'f'; request.hdr.magic = 'f';
request.hdr.session = _activeSession; request.hdr.session = _activeSession;
request.hdr.opcode = kCmdRead; request.hdr.opcode = kCmdRead;
request.hdr.offset = _readOffset; request.hdr.offset = _readOffset;
request.hdr.size = sizeof(request.data);
_sendRequest(&request); _sendRequest(&request);
} else { } else {
// We're at the end of the file, we can write it out now
Q_ASSERT(readAck->hdr.errCode == kErrNone);
_currentOperation = kCOIdle; _currentOperation = kCOIdle;
QString downloadFilePath = _readFileDownloadDir.absoluteFilePath(_readFileDownloadFilename); QString downloadFilePath = _readFileDownloadDir.absoluteFilePath(_readFileDownloadFilename);
@ -176,6 +180,70 @@ void QGCUASFileManager::_readResponse(Request* readAck)
file.close(); file.close();
_emitStatusMessage(tr("Download complete '%1'").arg(downloadFilePath)); _emitStatusMessage(tr("Download complete '%1'").arg(downloadFilePath));
// Close the open session
_sendTerminateCommand();
}
}
/// @brief Respond to the Ack associated with the List command.
void QGCUASFileManager::_listAckResponse(Request* listAck)
{
if (listAck->hdr.offset != _listOffset) {
_currentOperation = kCOIdle;
_emitErrorMessage(tr("List: Offset returned (%1) differs from offset requested (%2)").arg(listAck->hdr.offset).arg(_listOffset));
return;
}
uint8_t offset = 0;
uint8_t cListEntries = 0;
uint8_t cBytes = listAck->hdr.size;
// parse filenames out of the buffer
while (offset < cBytes) {
const char * ptr = ((const char *)listAck->data) + offset;
// get the length of the name
uint8_t cBytesLeft = cBytes - offset;
size_t nlen = strnlen(ptr, cBytesLeft);
if (nlen < 2) {
_currentOperation = kCOIdle;
_emitErrorMessage(tr("Incorrectly formed list entry: '%1'").arg(ptr));
return;
} else if (nlen == cBytesLeft) {
_currentOperation = kCOIdle;
_emitErrorMessage(tr("Missing NULL termination in list entry"));
return;
}
// Returned names are prepended with D for directory or F for file
QString s(ptr + 1);
if (*ptr == 'D') {
s.append('/');
} else if (*ptr != 'F') {
_currentOperation = kCOIdle;
_emitErrorMessage(tr("Unknown prefix list entry: '%1'").arg(ptr));
return;
}
// put it in the view
_emitStatusMessage(s);
// account for the name + NUL
offset += nlen + 1;
cListEntries++;
}
if (listAck->hdr.errCode == kErrMore) {
// Still more data to read, send next list request
_currentOperation = kCOList;
_listOffset += cListEntries;
_sendListCommand();
} else {
// We've gotten the last list entries we can go back to idle
Q_ASSERT(listAck->hdr.errCode == kErrNone);
_currentOperation = kCOIdle;
} }
} }
@ -200,7 +268,6 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
switch (_currentOperation) { switch (_currentOperation) {
case kCOIdle: case kCOIdle:
// we should not be seeing anything here.. shut the other guy up // we should not be seeing anything here.. shut the other guy up
qDebug() << "FTP resetting file transfer session";
_sendCmdReset(); _sendCmdReset();
break; break;
@ -210,15 +277,15 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
break; break;
case kCOList: case kCOList:
listDecode(&request->data[0], request->hdr.size); _listAckResponse(request);
break; break;
case kCOOpen: case kCOOpen:
_openResponse(request); _openAckResponse(request);
break; break;
case kCORead: case kCORead:
_readResponse(request); _readAckResponse(request);
break; break;
default: default:
@ -227,7 +294,7 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
} }
} else if (request->hdr.opcode == kRspNak) { } else if (request->hdr.opcode == kRspNak) {
_clearAckTimeout(); _clearAckTimeout();
_emitErrorMessage(QString("Nak received, error: ").append(errorString(request->data[0]))); _emitErrorMessage(tr("Nak received, error: %1").arg(request->hdr.errCode));
_currentOperation = kCOIdle; _currentOperation = kCOIdle;
} else { } else {
// Note that we don't change our operation state. If something goes wrong beyond this, the operation // Note that we don't change our operation state. If something goes wrong beyond this, the operation
@ -236,7 +303,7 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
} }
} }
void QGCUASFileManager::listRecursively(const QString &from) void QGCUASFileManager::listDirectory(const QString& dirPath)
{ {
if (_currentOperation != kCOIdle) { if (_currentOperation != kCOIdle) {
_emitErrorMessage(tr("Command not sent. Waiting for previous command to complete.")); _emitErrorMessage(tr("Command not sent. Waiting for previous command to complete."));
@ -247,55 +314,12 @@ void QGCUASFileManager::listRecursively(const QString &from)
emit resetStatusMessages(); emit resetStatusMessages();
// initialise the lister // initialise the lister
_listPath = from; _listPath = dirPath;
_listOffset = 0; _listOffset = 0;
_currentOperation = kCOList; _currentOperation = kCOList;
// and send the initial request // and send the initial request
sendList(); _sendListCommand();
}
void QGCUASFileManager::listDecode(const uint8_t *data, unsigned len)
{
unsigned offset = 0;
unsigned files = 0;
// parse filenames out of the buffer
while (offset < len) {
const char * ptr = (const char *)data + offset;
// get the length of the name
unsigned nlen = strnlen(ptr, len - offset);
if (nlen < 2) {
break;
}
// Returned names are prepended with D for directory or F for file
QString s(ptr + 1);
if (*ptr == 'D') {
s.append('/');
}
// put it in the view
_emitStatusMessage(s);
// account for the name + NUL
offset += nlen + 1;
// account for the file
files++;
}
// we have run out of files to list
if (files == 0) {
_currentOperation = kCOIdle;
} else {
// update our state
_listOffset += files;
// and send another request
sendList();
}
} }
void QGCUASFileManager::_fillRequestWithString(Request* request, const QString& str) void QGCUASFileManager::_fillRequestWithString(Request* request, const QString& str)
@ -304,7 +328,7 @@ void QGCUASFileManager::_fillRequestWithString(Request* request, const QString&
request->hdr.size = strnlen((const char *)&request->data[0], sizeof(request->data)); request->hdr.size = strnlen((const char *)&request->data[0], sizeof(request->data));
} }
void QGCUASFileManager::sendList() void QGCUASFileManager::_sendListCommand(void)
{ {
Request request; Request request;
@ -432,8 +456,26 @@ void QGCUASFileManager::_clearAckTimeout(void)
/// @brief Called when ack timeout timer fires /// @brief Called when ack timeout timer fires
void QGCUASFileManager::_ackTimeout(void) void QGCUASFileManager::_ackTimeout(void)
{ {
_currentOperation = kCOIdle;
_emitErrorMessage(tr("Timeout waiting for ack")); _emitErrorMessage(tr("Timeout waiting for ack"));
switch (_currentOperation) {
case kCORead:
_currentOperation = kCOAck;
_sendTerminateCommand();
break;
default:
_currentOperation = kCOIdle;
break;
}
}
void QGCUASFileManager::_sendTerminateCommand(void)
{
Request request;
request.hdr.magic = 'f';
request.hdr.session = _activeSession;
request.hdr.opcode = kCmdTerminate;
_sendRequest(&request);
} }
void QGCUASFileManager::_emitErrorMessage(const QString& msg) void QGCUASFileManager::_emitErrorMessage(const QString& msg)

57
src/uas/QGCUASFileManager.h

@ -48,18 +48,19 @@ signals:
public slots: public slots:
void receiveMessage(LinkInterface* link, mavlink_message_t message); void receiveMessage(LinkInterface* link, mavlink_message_t message);
void nothingMessage(); void nothingMessage();
void listRecursively(const QString &from); void listDirectory(const QString& dirPath);
void downloadPath(const QString& from, const QDir& downloadDir); void downloadPath(const QString& from, const QDir& downloadDir);
protected: protected:
struct RequestHeader struct RequestHeader
{ {
uint8_t magic; uint8_t magic; ///> Magic byte 'f' to idenitfy FTP protocol
uint8_t session; uint8_t session; ///> Session id for read and write commands
uint8_t opcode; uint8_t opcode; ///> Command opcode
uint8_t size; uint8_t size; ///> Size of data
uint32_t crc32; uint32_t crc32; ///> CRC for entire Request structure, with crc32 set to 0
uint32_t offset; uint32_t offset; ///> Offsets for List and Read commands
uint8_t errCode; ///> Error code from Ack and Naks (ignored for commands)
}; };
struct Request struct Request
@ -72,25 +73,29 @@ protected:
enum Opcode enum Opcode
{ {
kCmdNone, // ignored, always acked // Commands
kCmdTerminate, // releases sessionID, closes file kCmdNone, ///> ignored, always acked
kCmdReset, // terminates all sessions kCmdTerminate, ///> releases sessionID, closes file
kCmdList, // list files in <path> from <offset> kCmdReset, ///> terminates all sessions
kCmdOpen, // opens <path> for reading, returns <session> kCmdList, ///> list files in <path> from <offset>
kCmdRead, // reads <size> bytes from <offset> in <session> kCmdOpen, ///> opens <path> for reading, returns <session>
kCmdCreate, // creates <path> for writing, returns <session> kCmdRead, ///> reads <size> bytes from <offset> in <session>
kCmdWrite, // appends <size> bytes at <offset> in <session> kCmdCreate, ///> creates <path> for writing, returns <session>
kCmdRemove, // remove file (only if created by server?) kCmdWrite, ///> appends <size> bytes at <offset> in <session>
kCmdRemove, ///> remove file (only if created by server?)
kRspAck,
kRspNak, // Responses
kRspAck, ///> positive acknowledgement of previous command
kRspNak, ///> negative acknowledgement of previous command
kCmdTestNoAck, // ignored, ack not sent back, for testing only, should timeout waiting for ack // Used for testing only, not part of protocol
kCmdTestNoAck, // ignored, ack not sent back, should timeout waiting for ack
}; };
enum ErrorCode enum ErrorCode
{ {
kErrNone, kErrNone,
kErrMore,
kErrNoRequest, kErrNoRequest,
kErrNoSession, kErrNoSession,
kErrSequence, kErrSequence,
@ -127,12 +132,12 @@ protected:
void _emitStatusMessage(const QString& msg); void _emitStatusMessage(const QString& msg);
void _sendRequest(Request* request); void _sendRequest(Request* request);
void _fillRequestWithString(Request* request, const QString& str); void _fillRequestWithString(Request* request, const QString& str);
void _openResponse(Request* openAck); void _openAckResponse(Request* openAck);
void _readResponse(Request* readAck); void _readAckResponse(Request* readAck);
void _listAckResponse(Request* listAck);
void sendList(); void _sendListCommand(void);
void listDecode(const uint8_t *data, unsigned len); void _sendTerminateCommand(void);
static quint32 crc32(Request* request, unsigned state = 0); static quint32 crc32(Request* request, unsigned state = 0);
static QString errorString(uint8_t errorCode); static QString errorString(uint8_t errorCode);

2
src/ui/QGCUASFileView.cc

@ -28,7 +28,7 @@ QGCUASFileView::~QGCUASFileView()
void QGCUASFileView::listFiles() void QGCUASFileView::listFiles()
{ {
_manager->listRecursively(ui->pathLineEdit->text()); _manager->listDirectory(ui->pathLineEdit->text());
} }
void QGCUASFileView::downloadFiles() void QGCUASFileView::downloadFiles()

Loading…
Cancel
Save