MockLinkFileServer.cc 16.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
/*=====================================================================
 
 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/>.
 
 ======================================================================*/

24 25 26 27 28 29 30 31 32
#include "MockLinkFileServer.h"
#include "MockLink.h"

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

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

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

48
MockLinkFileServer::MockLinkFileServer(uint8_t systemIdServer, uint8_t componentIdServer, MockLink* mockLink) :
49 50
    _errMode(errModeNone),
    _systemIdServer(systemIdServer),
51 52
    _componentIdServer(componentIdServer),
    _mockLink(mockLink)
53 54 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 69
    // We only support root path
    path = (char *)&request->data[0];
    if (!path.isEmpty() && path != "/") {
70
		_sendNak(senderSystemId, senderComponentId, FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdListDirectory);
71 72 73 74 75
        return;
    }
    
    // Offset requested is past the end of the list
    if (request->hdr.offset > (uint32_t)_fileList.size()) {
76
        _sendNak(senderSystemId, senderComponentId, FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdListDirectory);
77 78 79
        return;
    }
    
80
    ackResponse.hdr.opcode = FileManager::kRspAck;
81
    ackResponse.hdr.req_opcode = FileManager::kCmdListDirectory;
82
    ackResponse.hdr.session = 0;
83
    ackResponse.hdr.offset = request->hdr.offset;
84
    ackResponse.hdr.size = 0;
85

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

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

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

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

150
void MockLinkFileServer::_readCommand(uint8_t senderSystemId, uint8_t senderComponentId, FileManager::Request* request, uint16_t seqNumber)
151
{
152 153
    FileManager::Request	response;
    uint16_t				outgoingSeqNumber = _nextSeqNumber(seqNumber);
154 155

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

194
    _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
195 196
}

197
void MockLinkFileServer::_streamCommand(uint8_t senderSystemId, uint8_t senderComponentId, FileManager::Request* request, uint16_t seqNumber)
198 199 200 201 202
{
    uint16_t                outgoingSeqNumber = _nextSeqNumber(seqNumber);
    FileManager::Request    response;

    if (request->hdr.session != _sessionId) {
203
		_sendNak(senderSystemId, senderComponentId, FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdBurstReadFile);
204 205 206 207 208 209 210 211 212 213 214 215 216 217
        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
218
                _sendNak(senderSystemId, senderComponentId, FileManager::kErrFail, outgoingSeqNumber, FileManager::kCmdBurstReadFile);
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
                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;
        
240
        _sendResponse(senderSystemId, senderComponentId, &response, outgoingSeqNumber);
241 242 243 244 245
        
        outgoingSeqNumber = _nextSeqNumber(outgoingSeqNumber);
        ackOffset += cDataAck;
    }
	
246
    _sendNak(senderSystemId, senderComponentId, FileManager::kErrEOF, outgoingSeqNumber, FileManager::kCmdBurstReadFile);
247 248
}

249
void MockLinkFileServer::_terminateCommand(uint8_t senderSystemId, uint8_t senderComponentId, FileManager::Request* request, uint16_t seqNumber)
250
{
251 252
    uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);

253
    if (request->hdr.session != _sessionId) {
254
		_sendNak(senderSystemId, senderComponentId, FileManager::kErrInvalidSession, outgoingSeqNumber, FileManager::kCmdTerminateSession);
255 256 257
        return;
    }
    
258
	_sendAck(senderSystemId, senderComponentId, outgoingSeqNumber, FileManager::kCmdTerminateSession);
259
	
260 261 262
    emit terminateCommandReceived();
}

263
void MockLinkFileServer::_resetCommand(uint8_t senderSystemId, uint8_t senderComponentId, uint16_t seqNumber)
264
{
265 266 267 268 269 270
    uint16_t outgoingSeqNumber = _nextSeqNumber(seqNumber);
    
    _sendAck(senderSystemId, senderComponentId, outgoingSeqNumber, FileManager::kCmdResetSessions);
    
    emit resetCommandReceived();
}
271

272 273 274 275 276
void MockLinkFileServer::handleFTPMessage(const mavlink_message_t& message)
{
    if (message.msgid != MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL) {
        return;
    }
277
    
278
    FileManager::Request  ackResponse;
279

280 281 282 283 284 285
    mavlink_file_transfer_protocol_t requestFTP;
    mavlink_msg_file_transfer_protocol_decode(&message, &requestFTP);
    
    if (requestFTP.target_system != _systemIdServer) {
        return;
    }
286
    
287 288
    FileManager::Request* request = (FileManager::Request*)&requestFTP.payload[0];

289
    uint16_t incomingSeqNumber = request->hdr.seqNumber;
290 291
    uint16_t outgoingSeqNumber = _nextSeqNumber(incomingSeqNumber);
    
292 293 294 295 296 297 298 299 300
    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;
        }
301
    }
302

Don Gagne's avatar
Don Gagne committed
303
    switch (request->hdr.opcode) {
304
        case FileManager::kCmdTestNoAck:
Don Gagne's avatar
Don Gagne committed
305 306 307
            // ignored, ack not sent back, for testing only
            break;
            
308
        case FileManager::kCmdNone:
309
            // ignored, always acked
310
            ackResponse.hdr.opcode = FileManager::kRspAck;
Don Gagne's avatar
Don Gagne committed
311 312
            ackResponse.hdr.session = 0;
            ackResponse.hdr.size = 0;
313
            _sendResponse(message.sysid, message.compid, &ackResponse, outgoingSeqNumber);
Don Gagne's avatar
Don Gagne committed
314 315
            break;

316
        case FileManager::kCmdListDirectory:
317
            _listCommand(message.sysid, message.compid, request, incomingSeqNumber);
318
            break;
319
            
320
        case FileManager::kCmdOpenFileRO:
321
            _openCommand(message.sysid, message.compid, request, incomingSeqNumber);
322
            break;
Don Gagne's avatar
Don Gagne committed
323

324
        case FileManager::kCmdReadFile:
325
            _readCommand(message.sysid, message.compid, request, incomingSeqNumber);
326
            break;
327

328
        case FileManager::kCmdBurstReadFile:
329
            _streamCommand(message.sysid, message.compid, request, incomingSeqNumber);
330 331 332
            break;

        case FileManager::kCmdTerminateSession:
333
            _terminateCommand(message.sysid, message.compid, request, incomingSeqNumber);
334 335
            break;

336 337 338 339
        case FileManager::kCmdResetSessions:
            _resetCommand(message.sysid, message.compid, incomingSeqNumber);
            break;
            
340 341
        default:
            // nack for all NYI opcodes
342
            _sendNak(message.sysid, message.compid, FileManager::kErrUnknownCommand, outgoingSeqNumber, (FileManager::Opcode)request->hdr.opcode);
343 344 345
            break;
    }
}
Don Gagne's avatar
Don Gagne committed
346

347
/// @brief Sends an Ack
348
void MockLinkFileServer::_sendAck(uint8_t targetSystemId, uint8_t targetComponentId, uint16_t seqNumber, FileManager::Opcode reqOpcode)
349
{
350
    FileManager::Request ackResponse;
351
    
352 353
    ackResponse.hdr.opcode = FileManager::kRspAck;
	ackResponse.hdr.req_opcode = reqOpcode;
354 355 356
    ackResponse.hdr.session = 0;
    ackResponse.hdr.size = 0;
    
357
    _sendResponse(targetSystemId, targetComponentId, &ackResponse, seqNumber);
358 359
}

360
/// @brief Sends a Nak with the specified error code.
361
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
362
{
363
    FileManager::Request nakResponse;
Don Gagne's avatar
Don Gagne committed
364

365 366
    nakResponse.hdr.opcode = FileManager::kRspNak;
	nakResponse.hdr.req_opcode = reqOpcode;
Don Gagne's avatar
Don Gagne committed
367
    nakResponse.hdr.session = 0;
368 369
    nakResponse.hdr.size = 1;
    nakResponse.data[0] = error;
Don Gagne's avatar
Don Gagne committed
370
    
371
    _sendResponse(targetSystemId, targetComponentId, &nakResponse, seqNumber);
Don Gagne's avatar
Don Gagne committed
372 373
}

374
/// @brief Emits a Request through the messageReceived signal.
375
void MockLinkFileServer::_sendResponse(uint8_t targetSystemId, uint8_t targetComponentId, FileManager::Request* request, uint16_t seqNumber)
Don Gagne's avatar
Don Gagne committed
376 377 378
{
    mavlink_message_t   mavlinkMessage;
    
379 380 381 382 383 384
    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
385 386
                                            targetSystemId,
                                            targetComponentId,
387
                                            (uint8_t*)request); // Payload
Don Gagne's avatar
Don Gagne committed
388
    
389
    _mockLink->respondWithMavlinkMessage(mavlinkMessage);
Lorenz Meier's avatar
Lorenz Meier committed
390
}
391 392 393

/// @brief Generates the next sequence number given an incoming sequence number. Handles generating
/// bad sequence numbers when errModeBadSequence is set.
394
uint16_t MockLinkFileServer::_nextSeqNumber(uint16_t seqNumber)
395 396 397 398 399 400 401 402
{
    uint16_t outgoingSeqNumber = seqNumber + 1;
    
    if (_errMode == errModeBadSequence) {
        outgoingSeqNumber++;
    }
    return outgoingSeqNumber;
}