MockMavlinkFileServer.cc 14.6 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(((FileManager::Request*)0)->data) - 1,     1,    false},
38
    // File fits one Read Ack packet, exactly filling all data
39
    { "exact.qgc",      sizeof(((FileManager::Request*)0)->data),         1,    true },
40
    // File is larger than a single Read Ack packets, requires multiple Reads
41
    { "multi.qgc",      sizeof(((FileManager::Request*)0)->data) + 1,     2,    false },
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(FileManager::Request* request, uint16_t seqNumber)
58
{
59 60
    // FIXME: Does not support directories that span multiple packets
    
61
    FileManager::Request  ackResponse;
Don Gagne's avatar
Don Gagne committed
62
    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(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdListDirectory);
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(FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdListDirectory);
75 76 77
        return;
    }
    
78
    ackResponse.hdr.opcode = FileManager::kRspAck;
79
    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(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdListDirectory);
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(FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdListDirectory);
106 107 108
    }
}

109
/// @brief Handles Open command requests.
110
void MockMavlinkFileServer::_openCommand(FileManager::Request* request, uint16_t seqNumber)
111
{
112
    FileManager::Request  response;
113
    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(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdOpenFileRO);
133 134 135
        return;
    }
    
136 137
    response.hdr.opcode = FileManager::kRspAck;
	response.hdr.req_opcode = FileManager::kCmdOpenFileRO;
138 139
    response.hdr.session = _sessionId;
    
140 141
    // Data contains file length
    response.hdr.size = sizeof(uint32_t);
142
    response.openFileLength = _readFileLength;
143 144
    
    _emitResponse(&response, outgoingSeqNumber);
145 146
}

147
void MockMavlinkFileServer::_readCommand(FileManager::Request* request, uint16_t seqNumber)
148
{
149 150
    FileManager::Request	response;
    uint16_t				outgoingSeqNumber = _nextSeqNumber(seqNumber);
151 152

    if (request->hdr.session != _sessionId) {
153
		_sendNak(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdReadFile);
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(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdReadFile);
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(FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdReadFile);
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 189 190
    response.hdr.opcode = FileManager::kRspAck;
	response.hdr.req_opcode = FileManager::kCmdReadFile;

191
    _emitResponse(&response, outgoingSeqNumber);
192 193
}

194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
void MockMavlinkFileServer::_streamCommand(FileManager::Request* request, uint16_t seqNumber)
{
    uint16_t                outgoingSeqNumber = _nextSeqNumber(seqNumber);
    FileManager::Request    response;

    if (request->hdr.session != _sessionId) {
		_sendNak(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdBurstReadFile);
        return;
    }
    
    uint32_t readOffset = 0;	// offset into file for reading
    uint32_t ackOffset = 0;     // offset for ack
    uint8_t cDataAck;           // number of bytes in ack
    
    while (readOffset < _readFileLength) {
        cDataAck = 0;
        
        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
                _sendNak(FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdBurstReadFile);
                return;
            } else if (_errMode == errModeNoSecondResponse) {
                // No response for all subsequent requests
                return;
            }
        }
        
        // Write file bytes. Data is a repeating sequence of 0x00, 0x01, .. 0xFF.
        for (; cDataAck < sizeof(response.data) && readOffset < _readFileLength; readOffset++, cDataAck++) {
            response.data[cDataAck] = readOffset & 0xFF;
        }
        
        // We should always have written something, otherwise there is something wrong with the code above
        Q_ASSERT(cDataAck);
        
        response.hdr.session = _sessionId;
        response.hdr.size = cDataAck;
        response.hdr.offset = ackOffset;
        response.hdr.opcode = FileManager::kRspAck;
        response.hdr.req_opcode = FileManager::kCmdBurstReadFile;
        
        _emitResponse(&response, outgoingSeqNumber);
        
        outgoingSeqNumber = _nextSeqNumber(outgoingSeqNumber);
        ackOffset += cDataAck;
    }
	
    _sendNak(FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdBurstReadFile);
}

246
/// @brief Handles Terminate commands
247
void MockMavlinkFileServer::_terminateCommand(FileManager::Request* request, uint16_t seqNumber)
248
{
249 250
    uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);

251
    if (request->hdr.session != _sessionId) {
252
		_sendNak(FileManager::kErrInvalidSession, outgoingSeqNumber, FileManager::kCmdTerminateSession);
253 254 255
        return;
    }
    
256 257
	_sendAck(outgoingSeqNumber, FileManager::kCmdTerminateSession);
	
258 259 260 261 262
    // 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();
}

263 264 265
/// @brief Handles messages sent to the FTP server.
void MockMavlinkFileServer::sendMessage(mavlink_message_t message)
{
266
    FileManager::Request  ackResponse;
267

268
    Q_ASSERT(message.msgid == MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL);
269
    
270 271
    mavlink_file_transfer_protocol_t requestFileTransferProtocol;
    mavlink_msg_file_transfer_protocol_decode(&message, &requestFileTransferProtocol);
272
    FileManager::Request* request = (FileManager::Request*)&requestFileTransferProtocol.payload[0];
273

274 275 276
    Q_ASSERT(requestFileTransferProtocol.target_system == _systemIdServer);
    
    uint16_t incomingSeqNumber = request->hdr.seqNumber;
277 278
    uint16_t outgoingSeqNumber = _nextSeqNumber(incomingSeqNumber);
    
279 280 281 282 283
    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
284
		_sendNak(FileManager::kErrFail, outgoingSeqNumber, (FileManager::Opcode)request->hdr.opcode);
285 286
        return;
    }
287

Don Gagne's avatar
Don Gagne committed
288
    switch (request->hdr.opcode) {
289
        case FileManager::kCmdTestNoAck:
Don Gagne's avatar
Don Gagne committed
290 291 292
            // ignored, ack not sent back, for testing only
            break;
            
293
        case FileManager::kCmdResetSessions:
Don Gagne's avatar
Don Gagne committed
294 295 296
            // terminates all sessions
            // Fall through to send back Ack

297
        case FileManager::kCmdNone:
298
            // ignored, always acked
299
            ackResponse.hdr.opcode = FileManager::kRspAck;
Don Gagne's avatar
Don Gagne committed
300 301
            ackResponse.hdr.session = 0;
            ackResponse.hdr.size = 0;
302
            _emitResponse(&ackResponse, outgoingSeqNumber);
Don Gagne's avatar
Don Gagne committed
303 304
            break;

305
        case FileManager::kCmdListDirectory:
306
            _listCommand(request, incomingSeqNumber);
307
            break;
308
            
309
        case FileManager::kCmdOpenFileRO:
310
            _openCommand(request, incomingSeqNumber);
311
            break;
Don Gagne's avatar
Don Gagne committed
312

313
        case FileManager::kCmdReadFile:
314
            _readCommand(request, incomingSeqNumber);
315
            break;
316

317 318 319 320 321
        case FileManager::kCmdBurstReadFile:
            _streamCommand(request, incomingSeqNumber);
            break;

        case FileManager::kCmdTerminateSession:
322
            _terminateCommand(request, incomingSeqNumber);
323 324
            break;

325 326
        default:
            // nack for all NYI opcodes
327
            _sendNak(FileManager::kErrUnknownCommand, outgoingSeqNumber, (FileManager::Opcode)request->hdr.opcode);
328 329 330
            break;
    }
}
Don Gagne's avatar
Don Gagne committed
331

332
/// @brief Sends an Ack
333
void MockMavlinkFileServer::_sendAck(uint16_t seqNumber, FileManager::Opcode reqOpcode)
334
{
335
    FileManager::Request ackResponse;
336
    
337 338
    ackResponse.hdr.opcode = FileManager::kRspAck;
	ackResponse.hdr.req_opcode = reqOpcode;
339 340 341
    ackResponse.hdr.session = 0;
    ackResponse.hdr.size = 0;
    
342
    _emitResponse(&ackResponse, seqNumber);
343 344
}

345
/// @brief Sends a Nak with the specified error code.
346
void MockMavlinkFileServer::_sendNak(FileManager::ErrorCode error, uint16_t seqNumber, FileManager::Opcode reqOpcode)
Don Gagne's avatar
Don Gagne committed
347
{
348
    FileManager::Request nakResponse;
Don Gagne's avatar
Don Gagne committed
349

350 351
    nakResponse.hdr.opcode = FileManager::kRspNak;
	nakResponse.hdr.req_opcode = reqOpcode;
Don Gagne's avatar
Don Gagne committed
352
    nakResponse.hdr.session = 0;
353 354
    nakResponse.hdr.size = 1;
    nakResponse.data[0] = error;
Don Gagne's avatar
Don Gagne committed
355
    
356
    _emitResponse(&nakResponse, seqNumber);
Don Gagne's avatar
Don Gagne committed
357 358
}

359
/// @brief Emits a Request through the messageReceived signal.
360
void MockMavlinkFileServer::_emitResponse(FileManager::Request* request, uint16_t seqNumber)
Don Gagne's avatar
Don Gagne committed
361 362 363
{
    mavlink_message_t   mavlinkMessage;
    
364 365 366 367 368 369 370 371 372
    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
373 374
    
    emit messageReceived(NULL, mavlinkMessage);
Lorenz Meier's avatar
Lorenz Meier committed
375
}
376 377 378 379 380 381 382 383 384 385 386 387

/// @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;
}