MockLinkFileServer.cc 16.9 KB
Newer Older
1 2
/****************************************************************************
 *
Gus Grubba's avatar
Gus Grubba committed
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 5 6 7 8 9
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

10

11 12 13 14 15 16 17 18 19
#include "MockLinkFileServer.h"
#include "MockLink.h"

const MockLinkFileServer::ErrorMode_t MockLinkFileServer::rgFailureModes[] = {
    MockLinkFileServer::errModeNoResponse,
    MockLinkFileServer::errModeNakResponse,
    MockLinkFileServer::errModeNoSecondResponse,
    MockLinkFileServer::errModeNakSecondResponse,
    MockLinkFileServer::errModeBadSequence,
20
};
21
const size_t MockLinkFileServer::cFailureModes = sizeof(MockLinkFileServer::rgFailureModes) / sizeof(MockLinkFileServer::rgFailureModes[0]);
22

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

32
// We only support a single fixed session
33
const uint8_t MockLinkFileServer::_sessionId = 1;
34

35
MockLinkFileServer::MockLinkFileServer(uint8_t systemIdServer, uint8_t componentIdServer, MockLink* mockLink) :
36 37
    _errMode(errModeNone),
    _systemIdServer(systemIdServer),
38
    _componentIdServer(componentIdServer),
39 40 41 42
    _mockLink(mockLink),
    _lastReplyValid(false),
    _lastReplySequence(0),
    _randomDropsEnabled(false)
43
{
44 45 46 47 48 49 50
    srand(0); // make sure unit tests are deterministic
}

void MockLinkFileServer::ensureNullTemination(FileManager::Request* request)
{
    if (request->hdr.size < sizeof(request->data)) {
        request->data[request->hdr.size] = '\0';
51

52 53 54
    } else {
        request->data[sizeof(request->data)-1] = '\0';
    }
55 56
}

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

67 68
    ensureNullTemination(request);

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

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

100
        _sendResponse(senderSystemId, senderComponentId, &ackResponse, outgoingSeqNumber);
101 102
    } else if (_errMode == errModeNakSecondResponse) {
        // Nak error all subsequent requests
103
        _sendNak(senderSystemId, senderComponentId, FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdListDirectory);
104 105 106 107
        return;
    } else if (_errMode == errModeNoSecondResponse) {
        // No response for all subsequent requests
        return;
108
    } else {
109
        // FIXME: Does not support directories that span multiple packets
110
        _sendNak(senderSystemId, senderComponentId, FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdListDirectory);
111 112 113
    }
}

114
/// @brief Handles Open command requests.
115
void MockLinkFileServer::_openCommand(uint8_t senderSystemId, uint8_t senderComponentId, FileManager::Request* request, uint16_t seqNumber)
116
{
117
    FileManager::Request  response;
118
    QString                     path;
119
    uint16_t                    outgoingSeqNumber = _nextSeqNumber(seqNumber);
120
    
121 122
    ensureNullTemination(request);

123 124
    size_t cchPath = strnlen((char *)request->data, sizeof(request->data));
    Q_ASSERT(cchPath != sizeof(request->data));
125
    Q_UNUSED(cchPath); // Fix initialized-but-not-referenced warning on release builds
126 127 128
    path = (char *)request->data;
    
    // Check path against one of our known test cases
129

130 131 132 133 134 135 136 137
    bool found = false;
    for (size_t i=0; i<cFileTestCases; i++) {
        if (path == rgFileTestCases[i].filename) {
            found = true;
            _readFileLength = rgFileTestCases[i].length;
            break;
        }
    }
138 139 140 141 142 143 144

    if (!found) {
        if (path == "/version.json") {
            qDebug() << "Requesting version json";
        }
    }

145
    if (!found) {
146
        _sendNak(senderSystemId, senderComponentId, FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdOpenFileRO);
147 148 149
        return;
    }
    
150 151
    response.hdr.opcode = FileManager::kRspAck;
	response.hdr.req_opcode = FileManager::kCmdOpenFileRO;
152 153
    response.hdr.session = _sessionId;
    
154 155
    // Data contains file length
    response.hdr.size = sizeof(uint32_t);
156
    response.openFileLength = _readFileLength;
157
    
158
    _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
159 160
}

161
void MockLinkFileServer::_readCommand(uint8_t senderSystemId, uint8_t senderComponentId, FileManager::Request* request, uint16_t seqNumber)
162
{
163 164
    FileManager::Request	response;
    uint16_t				outgoingSeqNumber = _nextSeqNumber(seqNumber);
165 166

    if (request->hdr.session != _sessionId) {
167
		_sendNak(senderSystemId, senderComponentId, FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdReadFile);
168 169 170
        return;
    }
    
171 172
    uint32_t readOffset = request->hdr.offset;  // offset into file for reading
    uint8_t cDataBytes = 0;                     // current number of data bytes used
173
    
174 175 176 177
    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
178
            _sendNak(senderSystemId, senderComponentId, FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdReadFile);
179 180 181 182 183 184 185
            return;
        } else if (_errMode == errModeNoSecondResponse) {
            // No rsponse for all subsequent requests
            return;
        }
    }
    
186
    if (readOffset >= _readFileLength) {
187
        _sendNak(senderSystemId, senderComponentId, FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdReadFile);
188 189 190 191
        return;
    }
    
    // Write file bytes. Data is a repeating sequence of 0x00, 0x01, .. 0xFF.
192
    for (; cDataBytes < sizeof(response.data) && readOffset < _readFileLength; readOffset++, cDataBytes++) {
193
        response.data[cDataBytes] = readOffset & 0xFF;
194 195
    }
    
196 197
    // We should always have written something, otherwise there is something wrong with the code above
    Q_ASSERT(cDataBytes);
198 199
    
    response.hdr.session = _sessionId;
200
    response.hdr.size = cDataBytes;
201
    response.hdr.offset = request->hdr.offset;
202 203 204
    response.hdr.opcode = FileManager::kRspAck;
	response.hdr.req_opcode = FileManager::kCmdReadFile;

205
    _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
206 207
}

208
void MockLinkFileServer::_streamCommand(uint8_t senderSystemId, uint8_t senderComponentId, FileManager::Request* request, uint16_t seqNumber)
209 210 211 212 213
{
    uint16_t                outgoingSeqNumber = _nextSeqNumber(seqNumber);
    FileManager::Request    response;

    if (request->hdr.session != _sessionId) {
214
		_sendNak(senderSystemId, senderComponentId, FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdBurstReadFile);
215 216 217 218 219 220 221 222 223 224 225 226 227 228
        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
229
                _sendNak(senderSystemId, senderComponentId, FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdBurstReadFile);
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
                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;
        
251
        _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
252 253 254 255 256
        
        outgoingSeqNumber = _nextSeqNumber(outgoingSeqNumber);
        ackOffset += cDataAck;
    }
	
257
    _sendNak(senderSystemId, senderComponentId, FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdBurstReadFile);
258 259
}

260
void MockLinkFileServer::_terminateCommand(uint8_t senderSystemId, uint8_t senderComponentId, FileManager::Request* request, uint16_t seqNumber)
261
{
262 263
    uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);

264
    if (request->hdr.session != _sessionId) {
265
		_sendNak(senderSystemId, senderComponentId, FileManager::kErrInvalidSession, outgoingSeqNumber, FileManager::kCmdTerminateSession);
266 267 268
        return;
    }
    
269
	_sendAck(senderSystemId, senderComponentId, outgoingSeqNumber, FileManager::kCmdTerminateSession);
270
	
271 272 273
    emit terminateCommandReceived();
}

274
void MockLinkFileServer::_resetCommand(uint8_t senderSystemId, uint8_t senderComponentId, uint16_t seqNumber)
275
{
276 277 278 279 280 281
    uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
    
    _sendAck(senderSystemId, senderComponentId, outgoingSeqNumber, FileManager::kCmdResetSessions);
    
    emit resetCommandReceived();
}
282

283 284 285 286 287
void MockLinkFileServer::handleFTPMessage(const mavlink_message_t& message)
{
    if (message.msgid != MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL) {
        return;
    }
288
    
289
    FileManager::Request  ackResponse;
290

291 292 293 294 295 296
    mavlink_file_transfer_protocol_t requestFTP;
    mavlink_msg_file_transfer_protocol_decode(&message, &requestFTP);
    
    if (requestFTP.target_system != _systemIdServer) {
        return;
    }
297

298 299
    FileManager::Request* request = (FileManager::Request*)&requestFTP.payload[0];

300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
	if (_randomDropsEnabled) {
	    if (rand() % 3 == 0) {
	        qDebug() << "FileServer: Random drop of incoming packet";
	        return;
	    }
	}

	if (_lastReplyValid && request->hdr.seqNumber + 1 == _lastReplySequence) {
	    // this is the same request as the one we replied to last. It means the (n)ack got lost, and the GCS
	    // resent the request
	    qDebug() << "FileServer: resending response";
	    _mockLink->respondWithMavlinkMessage(_lastReply);
	    return;
	}

315
    uint16_t incomingSeqNumber = request->hdr.seqNumber;
316 317
    uint16_t outgoingSeqNumber = _nextSeqNumber(incomingSeqNumber);
    
318 319 320 321 322 323 324 325 326
    if (request->hdr.opcode != FileManager::kCmdResetSessions && request->hdr.opcode != FileManager::kCmdTerminateSession) {
        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
            _sendNak(message.sysid, message.compid, FileManager::kErrFail, outgoingSeqNumber, (FileManager::Opcode)request->hdr.opcode);
            return;
        }
327
    }
328

Don Gagne's avatar
Don Gagne committed
329
    switch (request->hdr.opcode) {
330
        case FileManager::kCmdTestNoAck:
Don Gagne's avatar
Don Gagne committed
331 332 333
            // ignored, ack not sent back, for testing only
            break;
            
334
        case FileManager::kCmdNone:
335
            // ignored, always acked
336
            ackResponse.hdr.opcode = FileManager::kRspAck;
Don Gagne's avatar
Don Gagne committed
337 338
            ackResponse.hdr.session = 0;
            ackResponse.hdr.size = 0;
339
            _sendResponse(message.sysid, message.compid, &ackResponse, outgoingSeqNumber);
Don Gagne's avatar
Don Gagne committed
340 341
            break;

342
        case FileManager::kCmdListDirectory:
343
            _listCommand(message.sysid, message.compid, request, incomingSeqNumber);
344
            break;
345
            
346
        case FileManager::kCmdOpenFileRO:
347
            _openCommand(message.sysid, message.compid, request, incomingSeqNumber);
348
            break;
Don Gagne's avatar
Don Gagne committed
349

350
        case FileManager::kCmdReadFile:
351
            _readCommand(message.sysid, message.compid, request, incomingSeqNumber);
352
            break;
353

354
        case FileManager::kCmdBurstReadFile:
355
            _streamCommand(message.sysid, message.compid, request, incomingSeqNumber);
356 357 358
            break;

        case FileManager::kCmdTerminateSession:
359
            _terminateCommand(message.sysid, message.compid, request, incomingSeqNumber);
360 361
            break;

362 363 364 365
        case FileManager::kCmdResetSessions:
            _resetCommand(message.sysid, message.compid, incomingSeqNumber);
            break;
            
366 367
        default:
            // nack for all NYI opcodes
368
            _sendNak(message.sysid, message.compid, FileManager::kErrUnknownCommand, outgoingSeqNumber, (FileManager::Opcode)request->hdr.opcode);
369 370 371
            break;
    }
}
Don Gagne's avatar
Don Gagne committed
372

373
/// @brief Sends an Ack
374
void MockLinkFileServer::_sendAck(uint8_t targetSystemId, uint8_t targetComponentId, uint16_t seqNumber, FileManager::Opcode reqOpcode)
375
{
376
    FileManager::Request ackResponse;
377
    
378 379
    ackResponse.hdr.opcode = FileManager::kRspAck;
	ackResponse.hdr.req_opcode = reqOpcode;
380 381 382
    ackResponse.hdr.session = 0;
    ackResponse.hdr.size = 0;
    
383
    _sendResponse(targetSystemId, targetComponentId, &ackResponse, seqNumber);
384 385
}

386
/// @brief Sends a Nak with the specified error code.
387
void MockLinkFileServer::_sendNak(uint8_t targetSystemId, uint8_t targetComponentId, FileManager::ErrorCode error, uint16_t seqNumber, FileManager::Opcode reqOpcode)
Don Gagne's avatar
Don Gagne committed
388
{
389
    FileManager::Request nakResponse;
Don Gagne's avatar
Don Gagne committed
390

391 392
    nakResponse.hdr.opcode = FileManager::kRspNak;
	nakResponse.hdr.req_opcode = reqOpcode;
Don Gagne's avatar
Don Gagne committed
393
    nakResponse.hdr.session = 0;
394 395
    nakResponse.hdr.size = 1;
    nakResponse.data[0] = error;
Don Gagne's avatar
Don Gagne committed
396
    
397
    _sendResponse(targetSystemId, targetComponentId, &nakResponse, seqNumber);
Don Gagne's avatar
Don Gagne committed
398 399
}

400
/// @brief Emits a Request through the messageReceived signal.
401
void MockLinkFileServer::_sendResponse(uint8_t targetSystemId, uint8_t targetComponentId, FileManager::Request* request, uint16_t seqNumber)
Don Gagne's avatar
Don Gagne committed
402
{
403
    request->hdr.seqNumber = seqNumber;
404 405
    _lastReplySequence = seqNumber;
    _lastReplyValid = true;
406
    
407 408 409
    mavlink_msg_file_transfer_protocol_pack_chan(_systemIdServer,    // System ID
                                                 0,                  // Component ID
                                                 _mockLink->mavlinkChannel(),
410
                                                 &_lastReply,    // Mavlink Message to pack into
411 412 413 414
                                                 0,                  // Target network
                                                 targetSystemId,
                                                 targetComponentId,
                                                 (uint8_t*)request); // Payload
415 416 417 418 419 420 421

	if (_randomDropsEnabled) {
	    if (rand() % 3 == 0) {
	        qDebug() << "FileServer: Random drop of outgoing packet";
	        return;
	    }
	}
Don Gagne's avatar
Don Gagne committed
422
    
423
    _mockLink->respondWithMavlinkMessage(_lastReply);
Lorenz Meier's avatar
Lorenz Meier committed
424
}
425 426 427

/// @brief Generates the next sequence number given an incoming sequence number. Handles generating
/// bad sequence numbers when errModeBadSequence is set.
428
uint16_t MockLinkFileServer::_nextSeqNumber(uint16_t seqNumber)
429 430 431 432 433 434 435 436
{
    uint16_t outgoingSeqNumber = seqNumber + 1;
    
    if (_errMode == errModeBadSequence) {
        outgoingSeqNumber++;
    }
    return outgoingSeqNumber;
}