MockMavlinkFileServer.cc 12.2 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
const MockMavlinkFileServer::ErrorMode_t MockMavlinkFileServer::rgFailureModes[] = {
    MockMavlinkFileServer::errModeNoResponse,
    MockMavlinkFileServer::errModeNakResponse,
    MockMavlinkFileServer::errModeNoSecondResponse,
    MockMavlinkFileServer::errModeNakSecondResponse,
31
    MockMavlinkFileServer::errModeBadSequence,
32 33 34
};
const size_t MockMavlinkFileServer::cFailureModes = sizeof(MockMavlinkFileServer::rgFailureModes) / sizeof(MockMavlinkFileServer::rgFailureModes[0]);

35 36
const MockMavlinkFileServer::FileTestCase MockMavlinkFileServer::rgFileTestCases[MockMavlinkFileServer::cFileTestCases] = {
    // File fits one Read Ack packet, partially filling data
37
    { "partial.qgc",    sizeof(((QGCUASFileManager::Request*)0)->data) - 1,     false },
38
    // File fits one Read Ack packet, exactly filling all data
39
    { "exact.qgc",      sizeof(((QGCUASFileManager::Request*)0)->data),         true },
40
    // File is larger than a single Read Ack packets, requires multiple Reads
41
    { "multi.qgc",      sizeof(((QGCUASFileManager::Request*)0)->data) + 1,     true },
42
};
43

44
// We only support a single fixed session
45 46
const uint8_t MockMavlinkFileServer::_sessionId = 1;

47 48 49 50
MockMavlinkFileServer::MockMavlinkFileServer(uint8_t systemIdQGC, uint8_t systemIdServer) :
    _errMode(errModeNone),
    _systemIdServer(systemIdServer),
    _systemIdQGC(systemIdQGC)
51 52 53 54
{

}

55 56
/// @brief Handles List command requests. Only supports root folder paths.
///         File list returned is set using the setFileList method.
57
void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request, uint16_t seqNumber)
58
{
59 60
    // FIXME: Does not support directories that span multiple packets
    
Don Gagne's avatar
Don Gagne committed
61 62
    QGCUASFileManager::Request  ackResponse;
    QString                     path;
63
    uint16_t                    outgoingSeqNumber = _nextSeqNumber(seqNumber);
Don Gagne's avatar
Don Gagne committed
64

65 66 67
    // We only support root path
    path = (char *)&request->data[0];
    if (!path.isEmpty() && path != "/") {
68
        _sendNak(QGCUASFileManager::kErrFail, outgoingSeqNumber);
69 70 71 72 73
        return;
    }
    
    // Offset requested is past the end of the list
    if (request->hdr.offset > (uint32_t)_fileList.size()) {
74
        _sendNak(QGCUASFileManager::kErrEOF, outgoingSeqNumber);
75 76 77 78 79
        return;
    }
    
    ackResponse.hdr.opcode = QGCUASFileManager::kRspAck;
    ackResponse.hdr.session = 0;
80
    ackResponse.hdr.offset = request->hdr.offset;
81
    ackResponse.hdr.size = 0;
82

83 84 85 86 87
    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++) {
88
            strcpy(bufPtr, _fileList[i].toStdString().c_str());
Don Gagne's avatar
Don Gagne committed
89 90
            uint8_t cchFilename = static_cast<uint8_t>(strlen(bufPtr));
            Q_ASSERT(cchFilename);
91 92 93
            ackResponse.hdr.size += cchFilename + 1;
            bufPtr += cchFilename + 1;
        }
94

95
        _emitResponse(&ackResponse, outgoingSeqNumber);
96 97
    } else if (_errMode == errModeNakSecondResponse) {
        // Nak error all subsequent requests
98
        _sendNak(QGCUASFileManager::kErrFail, outgoingSeqNumber);
99 100 101 102
        return;
    } else if (_errMode == errModeNoSecondResponse) {
        // No response for all subsequent requests
        return;
103
    } else {
104
        // FIXME: Does not support directories that span multiple packets
105
        _sendNak(QGCUASFileManager::kErrEOF, outgoingSeqNumber);
106 107 108
    }
}

109
/// @brief Handles Open command requests.
110
void MockMavlinkFileServer::_openCommand(QGCUASFileManager::Request* request, uint16_t seqNumber)
111 112 113
{
    QGCUASFileManager::Request  response;
    QString                     path;
114
    uint16_t                    outgoingSeqNumber = _nextSeqNumber(seqNumber);
115
    
116 117
    size_t cchPath = strnlen((char *)request->data, sizeof(request->data));
    Q_ASSERT(cchPath != sizeof(request->data));
118
    Q_UNUSED(cchPath); // Fix initialized-but-not-referenced warning on release builds
119 120 121
    path = (char *)request->data;
    
    // Check path against one of our known test cases
122

123 124 125 126 127 128 129 130 131
    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) {
132
        _sendNak(QGCUASFileManager::kErrFail, outgoingSeqNumber);
133 134 135 136 137 138
        return;
    }
    
    response.hdr.opcode = QGCUASFileManager::kRspAck;
    response.hdr.session = _sessionId;
    
139 140
    // Data contains file length
    response.hdr.size = sizeof(uint32_t);
141
    response.openFileLength = _readFileLength;
142 143
    
    _emitResponse(&response, outgoingSeqNumber);
144 145 146
}

/// @brief Handles Read command requests.
147
void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request, uint16_t seqNumber)
148
{
149 150
    QGCUASFileManager::Request  response;
    uint16_t                    outgoingSeqNumber = _nextSeqNumber(seqNumber);
151 152

    if (request->hdr.session != _sessionId) {
153
        _sendNak(QGCUASFileManager::kErrFail, outgoingSeqNumber);
154 155 156
        return;
    }
    
157 158
    uint32_t readOffset = request->hdr.offset;  // offset into file for reading
    uint8_t cDataBytes = 0;                     // current number of data bytes used
159
    
160 161 162 163
    if (readOffset != 0) {
        // If we get here it means the client is requesting additional data past the first request
        if (_errMode == errModeNakSecondResponse) {
            // Nak error all subsequent requests
164
            _sendNak(QGCUASFileManager::kErrFail, outgoingSeqNumber);
165 166 167 168 169 170 171
            return;
        } else if (_errMode == errModeNoSecondResponse) {
            // No rsponse for all subsequent requests
            return;
        }
    }
    
172
    if (readOffset >= _readFileLength) {
173
        _sendNak(QGCUASFileManager::kErrEOF, outgoingSeqNumber);
174 175 176 177
        return;
    }
    
    // Write file bytes. Data is a repeating sequence of 0x00, 0x01, .. 0xFF.
178
    for (; cDataBytes < sizeof(response.data) && readOffset < _readFileLength; readOffset++, cDataBytes++) {
179
        response.data[cDataBytes] = readOffset & 0xFF;
180 181
    }
    
182 183
    // We should always have written something, otherwise there is something wrong with the code above
    Q_ASSERT(cDataBytes);
184 185
    
    response.hdr.session = _sessionId;
186
    response.hdr.size = cDataBytes;
187
    response.hdr.offset = request->hdr.offset;
188
    response.hdr.opcode = QGCUASFileManager::kRspAck;
189
    
190
    _emitResponse(&response, outgoingSeqNumber);
191 192
}

193
/// @brief Handles Terminate commands
194
void MockMavlinkFileServer::_terminateCommand(QGCUASFileManager::Request* request, uint16_t seqNumber)
195
{
196 197
    uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);

198
    if (request->hdr.session != _sessionId) {
199
        _sendNak(QGCUASFileManager::kErrInvalidSession, outgoingSeqNumber);
200 201 202
        return;
    }
    
203
    _sendAck(outgoingSeqNumber);
204 205 206 207 208 209
    
    // 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();
}

210 211 212
/// @brief Handles messages sent to the FTP server.
void MockMavlinkFileServer::sendMessage(mavlink_message_t message)
{
213
    QGCUASFileManager::Request  ackResponse;
214

215
    Q_ASSERT(message.msgid == MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL);
216
    
217 218 219
    mavlink_file_transfer_protocol_t requestFileTransferProtocol;
    mavlink_msg_file_transfer_protocol_decode(&message, &requestFileTransferProtocol);
    QGCUASFileManager::Request* request = (QGCUASFileManager::Request*)&requestFileTransferProtocol.payload[0];
220

221 222 223
    Q_ASSERT(requestFileTransferProtocol.target_system == _systemIdServer);
    
    uint16_t incomingSeqNumber = request->hdr.seqNumber;
224 225
    uint16_t outgoingSeqNumber = _nextSeqNumber(incomingSeqNumber);
    
226 227 228 229 230
    if (_errMode == errModeNoResponse) {
        // Don't respond to any requests, this shold cause the client to eventually timeout waiting for the ack
        return;
    } else if (_errMode == errModeNakResponse) {
        // Nak all requests, the actual error send back doesn't really matter as long as it's an error
231
        _sendNak(QGCUASFileManager::kErrFail, outgoingSeqNumber);
232 233
        return;
    }
234

Don Gagne's avatar
Don Gagne committed
235 236 237 238 239
    switch (request->hdr.opcode) {
        case QGCUASFileManager::kCmdTestNoAck:
            // ignored, ack not sent back, for testing only
            break;
            
240
        case QGCUASFileManager::kCmdResetSessions:
Don Gagne's avatar
Don Gagne committed
241 242 243 244
            // terminates all sessions
            // Fall through to send back Ack

        case QGCUASFileManager::kCmdNone:
245
            // ignored, always acked
Don Gagne's avatar
Don Gagne committed
246 247 248
            ackResponse.hdr.opcode = QGCUASFileManager::kRspAck;
            ackResponse.hdr.session = 0;
            ackResponse.hdr.size = 0;
249
            _emitResponse(&ackResponse, outgoingSeqNumber);
Don Gagne's avatar
Don Gagne committed
250 251
            break;

252
        case QGCUASFileManager::kCmdListDirectory:
253
            _listCommand(request, incomingSeqNumber);
254
            break;
255
            
256
        case QGCUASFileManager::kCmdOpenFile:
257
            _openCommand(request, incomingSeqNumber);
258
            break;
Don Gagne's avatar
Don Gagne committed
259

260
        case QGCUASFileManager::kCmdReadFile:
261
            _readCommand(request, incomingSeqNumber);
262
            break;
263

264
        case QGCUASFileManager::kCmdTerminateSession:
265
            _terminateCommand(request, incomingSeqNumber);
266 267
            break;

268 269
        default:
            // nack for all NYI opcodes
270
            _sendNak(QGCUASFileManager::kErrUnknownCommand, outgoingSeqNumber);
271 272 273
            break;
    }
}
Don Gagne's avatar
Don Gagne committed
274

275
/// @brief Sends an Ack
276
void MockMavlinkFileServer::_sendAck(uint16_t seqNumber)
277 278 279 280 281 282 283
{
    QGCUASFileManager::Request ackResponse;
    
    ackResponse.hdr.opcode = QGCUASFileManager::kRspAck;
    ackResponse.hdr.session = 0;
    ackResponse.hdr.size = 0;
    
284
    _emitResponse(&ackResponse, seqNumber);
285 286
}

287
/// @brief Sends a Nak with the specified error code.
288
void MockMavlinkFileServer::_sendNak(QGCUASFileManager::ErrorCode error, uint16_t seqNumber)
Don Gagne's avatar
Don Gagne committed
289 290 291 292 293
{
    QGCUASFileManager::Request nakResponse;

    nakResponse.hdr.opcode = QGCUASFileManager::kRspNak;
    nakResponse.hdr.session = 0;
294 295
    nakResponse.hdr.size = 1;
    nakResponse.data[0] = error;
Don Gagne's avatar
Don Gagne committed
296
    
297
    _emitResponse(&nakResponse, seqNumber);
Don Gagne's avatar
Don Gagne committed
298 299
}

300
/// @brief Emits a Request through the messageReceived signal.
301
void MockMavlinkFileServer::_emitResponse(QGCUASFileManager::Request* request, uint16_t seqNumber)
Don Gagne's avatar
Don Gagne committed
302 303 304
{
    mavlink_message_t   mavlinkMessage;
    
305 306 307 308 309 310 311 312 313
    request->hdr.seqNumber = seqNumber;
    
    mavlink_msg_file_transfer_protocol_pack(_systemIdServer,    // System ID
                                            0,                  // Component ID
                                            &mavlinkMessage,    // Mavlink Message to pack into
                                            0,                  // Target network
                                            _systemIdQGC,       // QGC Target System ID
                                            0,                  // Target component
                                            (uint8_t*)request); // Payload
Don Gagne's avatar
Don Gagne committed
314 315
    
    emit messageReceived(NULL, mavlinkMessage);
Lorenz Meier's avatar
Lorenz Meier committed
316
}
317 318 319 320 321 322 323 324 325 326 327 328

/// @brief Generates the next sequence number given an incoming sequence number. Handles generating
/// bad sequence numbers when errModeBadSequence is set.
uint16_t MockMavlinkFileServer::_nextSeqNumber(uint16_t seqNumber)
{
    uint16_t outgoingSeqNumber = seqNumber + 1;
    
    if (_errMode == errModeBadSequence) {
        outgoingSeqNumber++;
    }
    return outgoingSeqNumber;
}