MockMavlinkFileServer.cc 9.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*=====================================================================
 
 QGroundControl Open Source Ground Control Station
 
 (c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 
 This file is part of the QGROUNDCONTROL project
 
 QGROUNDCONTROL is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 QGROUNDCONTROL is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
 
 ======================================================================*/

#include "MockMavlinkFileServer.h"

26 27 28 29 30 31 32 33
const MockMavlinkFileServer::FileTestCase MockMavlinkFileServer::rgFileTestCases[MockMavlinkFileServer::cFileTestCases] = {
    // File fits one Read Ack packet, partially filling data
    { "partial.qgc",    sizeof(((QGCUASFileManager::Request*)0)->data) - 1 },
    // File fits one Read Ack packet, exactly filling all data
    { "exact.qgc",      sizeof(((QGCUASFileManager::Request*)0)->data) },
    // File is larger than a single Read Ack packets, requires multiple Reads
    { "multi.qgc",      sizeof(((QGCUASFileManager::Request*)0)->data) + 1 },
};
34

35
// We only support a single fixed session
36 37
const uint8_t MockMavlinkFileServer::_sessionId = 1;

38 39 40 41 42 43
MockMavlinkFileServer::MockMavlinkFileServer(void)
{

}


44 45 46 47

/// @brief Handles List command requests. Only supports root folder paths.
///         File list returned is set using the setFileList method.
void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request)
48
{
49 50
    // FIXME: Does not support directories that span multiple packets
    
Don Gagne's avatar
Don Gagne committed
51 52 53
    QGCUASFileManager::Request  ackResponse;
    QString                     path;

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
    // We only support root path
    path = (char *)&request->data[0];
    if (!path.isEmpty() && path != "/") {
        _sendNak(QGCUASFileManager::kErrNotDir);
        return;
    }
    
    // Offset requested is past the end of the list
    if (request->hdr.offset > (uint32_t)_fileList.size()) {
        _sendNak(QGCUASFileManager::kErrEOF);
        return;
    }
    
    ackResponse.hdr.magic = 'f';
    ackResponse.hdr.opcode = QGCUASFileManager::kRspAck;
    ackResponse.hdr.session = 0;
70
    ackResponse.hdr.offset = request->hdr.offset;
71 72 73 74 75 76 77 78 79 80 81 82 83
    ackResponse.hdr.size = 0;
    
    if (request->hdr.offset == 0) {
        // Requesting first batch of file names
        Q_ASSERT(_fileList.size());
        char *bufPtr = (char *)&ackResponse.data[0];
        for (int i=0; i<_fileList.size(); i++) {
            const char *filename = _fileList[i].toStdString().c_str();
            size_t cchFilename = strlen(filename);
            strcpy(bufPtr, filename);
            ackResponse.hdr.size += cchFilename + 1;
            bufPtr += cchFilename + 1;
        }
84 85

        _emitResponse(&ackResponse);
86
    } else {
87 88
        // FIXME: Does not support directories that span multiple packets
        _sendNak(QGCUASFileManager::kErrEOF);
89 90 91
    }
}

92
/// @brief Handles Open command requests.
93 94 95 96 97
void MockMavlinkFileServer::_openCommand(QGCUASFileManager::Request* request)
{
    QGCUASFileManager::Request  response;
    QString                     path;
    
98 99 100 101 102
    size_t cchPath = strnlen((char *)request->data, sizeof(request->data));
    Q_ASSERT(cchPath != sizeof(request->data));
    path = (char *)request->data;
    
    // Check path against one of our known test cases
103

104 105 106 107 108 109 110 111 112
    bool found = false;
    for (size_t i=0; i<cFileTestCases; i++) {
        if (path == rgFileTestCases[i].filename) {
            found = true;
            _readFileLength = rgFileTestCases[i].length;
            break;
        }
    }
    if (!found) {
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
        _sendNak(QGCUASFileManager::kErrNotFile);
        return;
    }
    
    response.hdr.magic = 'f';
    response.hdr.opcode = QGCUASFileManager::kRspAck;
    response.hdr.session = _sessionId;
    response.hdr.size = 0;
    
    _emitResponse(&response);
}

/// @brief Handles Read command requests.
void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request)
{
    QGCUASFileManager::Request response;

    if (request->hdr.session != _sessionId) {
        _sendNak(QGCUASFileManager::kErrNoSession);
        return;
    }
    
135 136
    uint32_t readOffset = request->hdr.offset;  // offset into file for reading
    uint8_t cDataBytes = 0;                     // current number of data bytes used
137
    
138
    if (readOffset >= _readFileLength) {
139
        _sendNak(QGCUASFileManager::kErrEOF);
140 141 142 143
        return;
    }
    
    // Write length byte if needed
144
    if (readOffset == 0) {
145
        response.data[0] = _readFileLength;
146 147
        readOffset++;
        cDataBytes++;
148 149 150
    }
    
    // Write file bytes. Data is a repeating sequence of 0x00, 0x01, .. 0xFF.
151 152 153
    for (; cDataBytes < sizeof(response.data) && readOffset < _readFileLength; readOffset++, cDataBytes++) {
        // Subtract one from readOffset to take into account length byte and start file data a 0x00
        response.data[cDataBytes] = (readOffset - 1) & 0xFF;
154 155
    }
    
156 157
    // We should always have written something, otherwise there is something wrong with the code above
    Q_ASSERT(cDataBytes);
158 159 160
    
    response.hdr.magic = 'f';
    response.hdr.session = _sessionId;
161
    response.hdr.size = cDataBytes;
162
    response.hdr.offset = request->hdr.offset;
163
    response.hdr.opcode = QGCUASFileManager::kRspAck;
164
    
165 166 167
    _emitResponse(&response);
}

168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
/// @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();
}

183 184 185
/// @brief Handles messages sent to the FTP server.
void MockMavlinkFileServer::sendMessage(mavlink_message_t message)
{
186
    QGCUASFileManager::Request ackResponse;
187

188 189
    Q_ASSERT(message.msgid == MAVLINK_MSG_ID_ENCAPSULATED_DATA);

Don Gagne's avatar
Don Gagne committed
190 191 192
    mavlink_encapsulated_data_t requestEncapsulatedData;
    mavlink_msg_encapsulated_data_decode(&message, &requestEncapsulatedData);
    QGCUASFileManager::Request* request = (QGCUASFileManager::Request*)&requestEncapsulatedData.data[0];
193
    
Don Gagne's avatar
Don Gagne committed
194 195 196 197
    // Validate CRC
    if (request->hdr.crc32 != QGCUASFileManager::crc32(request)) {
        _sendNak(QGCUASFileManager::kErrCrc);
    }
198

Don Gagne's avatar
Don Gagne committed
199 200 201 202 203 204 205 206 207 208
    switch (request->hdr.opcode) {
        case QGCUASFileManager::kCmdTestNoAck:
            // ignored, ack not sent back, for testing only
            break;
            
        case QGCUASFileManager::kCmdReset:
            // terminates all sessions
            // Fall through to send back Ack

        case QGCUASFileManager::kCmdNone:
209
            // ignored, always acked
Don Gagne's avatar
Don Gagne committed
210 211 212 213 214 215 216 217 218
            ackResponse.hdr.magic = 'f';
            ackResponse.hdr.opcode = QGCUASFileManager::kRspAck;
            ackResponse.hdr.session = 0;
            ackResponse.hdr.crc32 = 0;
            ackResponse.hdr.size = 0;
            _emitResponse(&ackResponse);
            break;

        case QGCUASFileManager::kCmdList:
219 220
            _listCommand(request);
            break;
221
            
222 223 224
        case QGCUASFileManager::kCmdOpen:
            _openCommand(request);
            break;
Don Gagne's avatar
Don Gagne committed
225

226 227
        case QGCUASFileManager::kCmdRead:
            _readCommand(request);
228
            break;
229

230 231 232 233
        case QGCUASFileManager::kCmdTerminate:
            _terminateCommand(request);
            break;

Don Gagne's avatar
Don Gagne committed
234
        // Remainder of commands are NYI
235

Don Gagne's avatar
Don Gagne committed
236
        case QGCUASFileManager::kCmdCreate:
237
            // creates <path> for writing, returns <session>
Don Gagne's avatar
Don Gagne committed
238
        case QGCUASFileManager::kCmdWrite:
239
            // appends <size> bytes at <offset> in <session>
Don Gagne's avatar
Don Gagne committed
240
        case QGCUASFileManager::kCmdRemove:
241 242 243
            // remove file (only if created by server?)
        default:
            // nack for all NYI opcodes
Don Gagne's avatar
Don Gagne committed
244
            _sendNak(QGCUASFileManager::kErrUnknownCommand);
245 246 247
            break;
    }
}
Don Gagne's avatar
Don Gagne committed
248

249 250 251 252 253 254 255 256 257 258 259 260 261
/// @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;
    
    _emitResponse(&ackResponse);
}

262
/// @brief Sends a Nak with the specified error code.
Don Gagne's avatar
Don Gagne committed
263 264 265 266 267 268 269
void MockMavlinkFileServer::_sendNak(QGCUASFileManager::ErrorCode error)
{
    QGCUASFileManager::Request nakResponse;

    nakResponse.hdr.magic = 'f';
    nakResponse.hdr.opcode = QGCUASFileManager::kRspNak;
    nakResponse.hdr.session = 0;
270 271
    nakResponse.hdr.size = 1;
    nakResponse.data[0] = error;
Don Gagne's avatar
Don Gagne committed
272 273 274 275
    
    _emitResponse(&nakResponse);
}

276
/// @brief Emits a Request through the messageReceived signal.
Don Gagne's avatar
Don Gagne committed
277 278 279 280 281 282 283 284 285 286
void MockMavlinkFileServer::_emitResponse(QGCUASFileManager::Request* request)
{
    mavlink_message_t   mavlinkMessage;
    
    request->hdr.crc32 = QGCUASFileManager::crc32(request);
    
    mavlink_msg_encapsulated_data_pack(250, 0, &mavlinkMessage, 0 /*_encdata_seq*/, (uint8_t*)request);
    
    emit messageReceived(NULL, mavlinkMessage);
}