LCOV - code coverage report
Current view: top level - GCS_MAVLink - GCS_FTP.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 358 424 84.4 %
Date: 2026-09-09 11:02:08 Functions: 13 13 100.0 %

          Line data    Source code
       1             : /*
       2             :    GCS MAVLink functions related to FTP
       3             : 
       4             :    This program is free software: you can redistribute it and/or modify
       5             :    it under the terms of the GNU General Public License as published by
       6             :    the Free Software Foundation, either version 3 of the License, or
       7             :    (at your option) any later version.
       8             : 
       9             :    This program is distributed in the hope that it will be useful,
      10             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :    GNU General Public License for more details.
      13             : 
      14             :    You should have received a copy of the GNU General Public License
      15             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "GCS_config.h"
      19             : 
      20             : #if AP_MAVLINK_FTP_ENABLED
      21             : 
      22             : #include "GCS_FTP.h"
      23             : 
      24             : #include <AP_HAL/AP_HAL.h>
      25             : 
      26             : #include "GCS.h"
      27             : 
      28             : #include <AP_Filesystem/AP_Filesystem.h>
      29             : #include <AP_HAL/utility/sparse-endian.h>
      30             : #include <AP_BoardConfig/AP_BoardConfig.h>
      31             : 
      32             : extern const AP_HAL::HAL& hal;
      33             : 
      34             : GCS_FTP *GCS_FTP::ftp;
      35             : 
      36             : // timeout for session inactivity, when we will kill the session if
      37             : // the session slot is needed
      38             : #define FTP_SESSION_TIMEOUT 3000
      39             : 
      40             : // timeout for session inactivity, when we will kill an idle session
      41             : #define FTP_SESSION_KILL_TIMEOUT 20000
      42             : 
      43         125 : bool GCS_FTP::init(void)
      44             : {
      45         125 :     if (initialised) {
      46         118 :         return true;
      47             :     }
      48             : 
      49           7 :     initialised = hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&GCS_FTP::worker, void),
      50             :                                                "FTP", 2560, AP_HAL::Scheduler::PRIORITY_IO, 0);
      51           7 :     if (!initialised) {
      52           0 :         GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "failed to initialize MAVFTP");
      53             :     }
      54             : 
      55           7 :     return initialised;
      56             : }
      57             : 
      58             : /*
      59             :   handle a FILE_TRANSFER_PROTOCOL message
      60             :  */
      61         125 : void GCS_FTP::handle_file_transfer_protocol(const mavlink_message_t &msg, mavlink_channel_t chan)
      62             : {
      63             : #if !defined(HAL_BUILD_AP_PERIPH)
      64             :     // check if ftp is disabled for memory savings
      65         125 :     if (AP_BoardConfig::ftp_disabled()) {
      66           0 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "FTP disabled");
      67           0 :         return;
      68             :     }
      69             : #endif
      70         125 :     if (ftp == nullptr) {
      71           7 :         ftp = NEW_NOTHROW GCS_FTP;
      72           7 :         if (ftp == nullptr) {
      73           0 :             return;
      74             :         }
      75             :     }
      76         125 :     if (ftp->init()) {
      77             :         mavlink_file_transfer_protocol_t packet;
      78         125 :         mavlink_msg_file_transfer_protocol_decode(&msg, &packet);
      79             : 
      80             :         Transaction request;
      81             : 
      82         125 :         request.chan = chan;
      83         125 :         request.seq_number = le16toh_ptr(packet.payload);
      84             : 
      85         125 :         request.session = packet.payload[2];
      86         125 :         request.opcode = static_cast<FTP_OP>(packet.payload[3]);
      87         125 :         request.size = packet.payload[4];
      88         125 :         request.req_opcode = static_cast<FTP_OP>(packet.payload[5]);
      89         125 :         request.burst_complete = packet.payload[6];
      90         125 :         request.offset = le32toh_ptr(&packet.payload[8]);
      91         125 :         request.sysid = msg.sysid;
      92         125 :         request.compid = msg.compid;
      93         125 :         memcpy(request.data, &packet.payload[12], sizeof(packet.payload) - 12);
      94             : 
      95             :         // if the push fails we drop the message
      96             :         // we could NACK it, but that can lead to GCS
      97             :         // confusion, so we're treating it like lost data
      98         125 :         ftp->requests.push(request);
      99             :     }
     100             : }
     101             : 
     102         771 : bool GCS_FTP::send_reply(const Transaction &reply)
     103             : {
     104         771 :     if (!GCS_MAVLINK::last_txbuf_is_greater(33)) { // It helps avoid GCS timeout if this is less than the threshold where we slow down normal streams (<=49)
     105           0 :         return false;
     106             :     }
     107         771 :     WITH_SEMAPHORE(comm_chan_lock(reply.chan));
     108         771 :     if (!HAVE_PAYLOAD_SPACE(reply.chan, FILE_TRANSFER_PROTOCOL)) {
     109           0 :         return false;
     110             :     }
     111         771 :     mavlink_file_transfer_protocol_t pkt {};
     112         771 :     pkt.target_network = 0;
     113         771 :     pkt.target_system = reply.sysid;
     114         771 :     pkt.target_component = reply.compid;
     115         771 :     uint8_t *payload = pkt.payload;
     116         771 :     put_le16_ptr(payload, reply.seq_number);
     117         771 :     payload[2] = reply.session;
     118         771 :     payload[3] = static_cast<uint8_t>(reply.opcode);
     119         771 :     payload[4] = reply.size;
     120         771 :     payload[5] = static_cast<uint8_t>(reply.req_opcode);
     121         771 :     payload[6] = reply.burst_complete ? 1 : 0;
     122         771 :     put_le32_ptr(&payload[8], reply.offset);
     123         771 :     memcpy(&pkt.payload[12], reply.data, sizeof(reply.data));
     124         771 :     mavlink_msg_file_transfer_protocol_send_struct(reply.chan, &pkt);
     125         771 :     return true;
     126         771 : }
     127             : 
     128             : /*
     129             :   check a name length for validity
     130             :  */
     131          39 : bool GCS_FTP::Session::check_name_len(const Transaction &request)
     132             : {
     133          39 :     const size_t file_name_len = strnlen((char *)request.data, sizeof(request.data));
     134          39 :     if (request.size == 0) {
     135           2 :         return false;
     136             :     }
     137          37 :     if (file_name_len == request.size) {
     138          10 :         return true;
     139             :     }
     140          27 :     return (request.size - file_name_len == 1) && (request.data[sizeof(request.data) - 1] == 0);
     141             : }
     142             : 
     143             : // send our response back out to the system
     144         752 : void GCS_FTP::Session::push_reply(Transaction &reply)
     145             : {
     146         752 :     last_send_ms = AP_HAL::millis(); // Used to detect active FTP session
     147             : 
     148         752 :     while (!send_reply(reply)) {
     149           0 :         hal.scheduler->delay_microseconds(100);
     150             :     }
     151             : 
     152         752 :     if (reply.req_opcode == FTP_OP::TerminateSession) {
     153          21 :         last_send_ms = 0;
     154             :     }
     155         752 : }
     156             : 
     157             : // calculates how much string length is needed to fit this in a list response
     158         294 : int GCS_FTP::Session::gen_dir_entry(char *dest, size_t space, const char *path, const struct dirent * entry)
     159             : {
     160             : #if AP_FILESYSTEM_HAVE_DIRENT_DTYPE
     161         294 :     const bool is_file = entry->d_type == DT_REG || entry->d_type == DT_LNK;
     162             : #else
     163             :     // assume true initially, then handle below
     164             :     const bool is_file = true;
     165             : #endif
     166             : 
     167         294 :     if (space < 3) {
     168           0 :         return -1;
     169             :     }
     170         294 :     dest[0] = 0;
     171             : 
     172             : #if AP_FILESYSTEM_HAVE_DIRENT_DTYPE
     173         294 :     if (!is_file && entry->d_type != DT_DIR) {
     174           0 :         return -1; // this just forces it so we can't send this back, it's easier then sending skips to a GCS
     175             :     }
     176             : #endif
     177             : 
     178         294 :     if (is_file) {
     179             : #ifdef MAX_NAME_LEN
     180         430 :         const uint8_t max_name_len = MIN(unsigned(MAX_NAME_LEN), 255U);
     181             : #else
     182             :         const uint8_t max_name_len = 255U;
     183             : #endif
     184         215 :         const size_t full_path_len = strlen(path) + strnlen(entry->d_name, max_name_len);
     185         215 :         char full_path[full_path_len + 2];
     186         215 :         hal.util->snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
     187             :         struct stat st;
     188         215 :         if (AP::FS().stat(full_path, &st)) {
     189         141 :             return -1;
     190             :         }
     191             : 
     192             : #if !AP_FILESYSTEM_HAVE_DIRENT_DTYPE
     193             :         if (S_ISDIR(st.st_mode)) {
     194             :             return hal.util->snprintf(dest, space, "D%s%c", entry->d_name, (char)0);
     195             :         }
     196             : #endif
     197          74 :         return hal.util->snprintf(dest, space, "F%s\t%u%c", entry->d_name, (unsigned)st.st_size, (char)0);
     198         215 :     } else {
     199          79 :         return hal.util->snprintf(dest, space, "D%s%c", entry->d_name, (char)0);
     200             :     }
     201             : }
     202             : 
     203             : // list the contents of a directory, skip the offset number of entries before providing data
     204          12 : void GCS_FTP::Session::list_dir(Transaction &request, Transaction &response)
     205             : {
     206          12 :     response.offset = request.offset; // this should be set for any failure condition for debugging
     207             : 
     208             :     // sanity check that our the request looks well formed
     209          12 :     if (!check_name_len(request)) {
     210           1 :         GCS_FTP::error(response, FTP_ERROR::InvalidDataSize);
     211           1 :         return;
     212             :     }
     213             : 
     214          11 :     request.data[sizeof(request.data) - 1] = 0; // ensure the path is null terminated
     215             : 
     216             :     // Strip trailing /
     217          11 :     const size_t dir_len = strlen((char *)request.data);
     218          11 :     if ((dir_len > 1) && (request.data[dir_len - 1] == '/')) {
     219           2 :         request.data[dir_len - 1] = 0;
     220             :     }
     221             : 
     222             :     // open the dir
     223          11 :     auto *dir = AP::FS().opendir((char *)request.data);
     224          11 :     if (dir == nullptr) {
     225           1 :         GCS_FTP::error(response, FTP_ERROR::FailErrno);
     226           1 :         return;
     227             :     }
     228             : 
     229             :     // burn the entries we don't care about
     230         167 :     while (request.offset > 0) {
     231         158 :         const struct dirent *entry = AP::FS().readdir(dir);
     232         158 :         if(entry == nullptr) {
     233           1 :             GCS_FTP::error(response, FTP_ERROR::EndOfFile);
     234           1 :             AP::FS().closedir(dir);
     235           1 :             return;
     236             :         }
     237             : 
     238             :         // check how much space would be needed to emit the listing
     239         157 :         const int needed_space = gen_dir_entry((char *)response.data, sizeof(request.data), (char *)request.data, entry);
     240             : 
     241             :         // an entry needing the whole packet still does not fit, as the
     242             :         // packing loop below only takes an entry which leaves the index
     243             :         // inside the buffer. both loops must agree on which entries are
     244             :         // skipped or the offsets they are counting drift apart
     245         157 :         if (needed_space < 0 || needed_space >= (int)sizeof(request.data)) {
     246          86 :             continue;
     247             :         }
     248             : 
     249          71 :         request.offset--;
     250             :     }
     251             : 
     252             :     // start packing in entries that fit
     253           9 :     uint8_t index = 0;
     254             :     struct dirent *entry;
     255         144 :     while ((entry = AP::FS().readdir(dir))) {
     256             :         // figure out if we can fit the file
     257         137 :         const int required_space = gen_dir_entry((char *)(response.data + index), sizeof(response.data) - index, (char *)request.data, entry);
     258             : 
     259             :         // couldn't ever send this so drop it
     260         137 :         if (required_space < 0) {
     261          71 :             continue;
     262             :         }
     263             : 
     264             :         // this entry doesn't fit in a packet of its own, so no later list
     265             :         // will be able to send it either. dropping it loses one file from
     266             :         // the listing; breaking here would end the listing at an EndOfFile
     267             :         // and lose every file after it as well
     268          66 :         if (required_space >= (int)sizeof(response.data)) {
     269           8 :             continue;
     270             :         }
     271             : 
     272             :         // can't fit it in this one, leave it for the next list to send
     273          58 :         if ((required_space + index) >= (int)sizeof(request.data)) {
     274           2 :             break;
     275             :         }
     276             : 
     277             :         // step the index forward and keep going. required_space already
     278             :         // includes the entry's null terminator (the trailing "%c" with a 0),
     279             :         // so we must not add another - a stray second null would appear as an
     280             :         // empty entry to clients, inflating their offset across paged listings.
     281          56 :         index += required_space;
     282             :     }
     283             : 
     284           9 :     if (index == 0) {
     285           3 :         GCS_FTP::error(response, FTP_ERROR::EndOfFile);
     286           3 :         AP::FS().closedir(dir);
     287           3 :         return;
     288             :     }
     289             :     
     290             :     // strip any bad temp data from our response as it can confuse a GCS, and defeats 0 trimming
     291           6 :     if (index < sizeof(response.data)) {
     292           6 :         memset(response.data + index, 0, MAX(0, (int)(sizeof(response.data)) - index));
     293             :     }
     294             : 
     295           6 :     response.opcode = FTP_OP::Ack;
     296           6 :     response.size = index;
     297             : 
     298           6 :     AP::FS().closedir(dir);
     299             : }
     300             : 
     301             : /*
     302             :   close a session
     303             : 
     304             :   returns the error code friom the underlying close() call, or zero (no error) if the
     305             :   file was closed already
     306             :  */
     307          53 : int GCS_FTP::Session::close(void)
     308             : {
     309          53 :     int result = 0;
     310             : 
     311          53 :     if (fd != -1) {
     312          15 :         result = AP::FS().close(fd);
     313          15 :         fd = -1;
     314             :     }
     315          53 :     last_send_ms = 0;
     316             : 
     317          53 :     return result;
     318             : }
     319             : 
     320             : /*
     321             :   handle one request on a session
     322             : 
     323             :   return true if the main loop should skip the push_replies() call
     324             :  */
     325         105 : bool GCS_FTP::Session::handle_request(Transaction &request, Transaction &reply)
     326             : {
     327         105 :     bool skip_push_reply = false;
     328             : 
     329             :     // sanity check the request size
     330         105 :     if (request.size > sizeof(request.data)) {
     331           1 :         GCS_FTP::error(reply, FTP_ERROR::InvalidDataSize);
     332           1 :         return false;
     333             :     }
     334             : 
     335         104 :     const uint32_t now = AP_HAL::millis();
     336             : 
     337             :     // dispatch the command as needed
     338         104 :     switch (request.opcode) {
     339           0 :     case FTP_OP::None:
     340           0 :         reply.opcode = FTP_OP::Ack;
     341           0 :         break;
     342          21 :     case FTP_OP::TerminateSession:
     343          21 :         if (close()) {
     344             :             // close() operation indicated an error, errno
     345             :             // was set by close() itself
     346           0 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     347             :         } else {
     348          21 :             reply.opcode = FTP_OP::Ack;
     349             :         }
     350          21 :         break;
     351          12 :     case FTP_OP::ListDirectory:
     352          12 :         list_dir(request, reply);
     353          12 :         break;
     354          13 :     case FTP_OP::OpenFileRO:
     355             :     {
     356             :         // only allow one file to be open per session
     357          13 :         if (fd != -1 && now - last_send_ms > FTP_SESSION_TIMEOUT) {
     358             :             // no activity for 3s, assume client has
     359             :             // timed out receiving open reply, close
     360             :             // the file
     361           0 :             close();    // error code ignored
     362           0 :             fd = -1;
     363             :         }
     364          13 :         if (fd != -1) {
     365           0 :             GCS_FTP::error(reply, FTP_ERROR::Fail);
     366           0 :             break;
     367             :         }
     368             : 
     369             :         // sanity check that the request looks well formed
     370          13 :         if (!check_name_len(request)) {
     371           0 :             GCS_FTP::error(reply, FTP_ERROR::InvalidDataSize);
     372           0 :             break;
     373             :         }
     374             : 
     375          13 :         request.data[sizeof(request.data) - 1] = 0; // ensure the path is null terminated
     376             : 
     377             :         // get the file size
     378             :         struct stat st;
     379          13 :         if (AP::FS().stat((char *)request.data, &st)) {
     380           0 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     381           0 :             break;
     382             :         }
     383          13 :         const size_t file_size = st.st_size;
     384             : 
     385             :         // actually open the file
     386          13 :         fd = AP::FS().open((char *)request.data, O_RDONLY);
     387          13 :         if (fd == -1) {
     388           0 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     389           0 :             break;
     390             :         }
     391          13 :         mode = FTP_FILE_MODE::Read;
     392             : 
     393          13 :         reply.opcode = FTP_OP::Ack;
     394          13 :         reply.size = sizeof(uint32_t);
     395          13 :         put_le32_ptr(reply.data, (uint32_t)file_size);
     396             : 
     397             :         // provide compatibility with old protocol banner download
     398          13 :         if (strncmp((const char *)request.data, "@PARAM/param.pck", 16) == 0) {
     399           2 :             auto *gchan = gcs().chan(reply.chan);
     400           2 :             if (gchan != nullptr) {
     401           2 :                 gchan->send_banner();
     402             :             }
     403             :         }
     404          13 :         break;
     405             :     }
     406          22 :     case FTP_OP::ReadFile:
     407             :     {
     408             :         // must actually be working on a file
     409          22 :         if (fd == -1) {
     410           1 :             GCS_FTP::error(reply, FTP_ERROR::FileNotFound);
     411           1 :             break;
     412             :         }
     413             : 
     414             :         // must have the file in read mode
     415          21 :         if ((mode != FTP_FILE_MODE::Read)) {
     416           1 :             GCS_FTP::error(reply, FTP_ERROR::Fail);
     417           1 :             break;
     418             :         }
     419             : 
     420             :         // seek to requested offset
     421          20 :         if (AP::FS().lseek(fd, request.offset, SEEK_SET) == -1) {
     422           0 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     423           0 :             break;
     424             :         }
     425             : 
     426             :         // fill the buffer
     427          20 :         const ssize_t read_bytes = AP::FS().read(fd, reply.data, MIN(sizeof(reply.data),request.size));
     428          20 :         if (read_bytes == -1) {
     429           0 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     430           0 :             break;
     431             :         }
     432          20 :         if (read_bytes == 0) {
     433           1 :             GCS_FTP::error(reply, FTP_ERROR::EndOfFile);
     434           1 :             break;
     435             :         }
     436             : 
     437          19 :         reply.opcode = FTP_OP::Ack;
     438          19 :         reply.offset = request.offset;
     439          19 :         reply.size = (uint8_t)read_bytes;
     440          19 :         break;
     441             :     }
     442           0 :     case FTP_OP::Ack:
     443             :     case FTP_OP::Nack:
     444             :         // eat these, we just didn't expect them
     445           0 :         return true;
     446             : 
     447           3 :     case FTP_OP::OpenFileWO:
     448             :     case FTP_OP::CreateFile:
     449             :     {
     450             :         // only allow one file to be open per session
     451           3 :         if (fd != -1) {
     452           0 :             GCS_FTP::error(reply, FTP_ERROR::Fail);
     453           0 :             break;
     454             :         }
     455             : 
     456             :         // sanity check that our the request looks well formed
     457           3 :         if (!check_name_len(request)) {
     458           0 :             GCS_FTP::error(reply, FTP_ERROR::InvalidDataSize);
     459           0 :             break;
     460             :         }
     461             : 
     462           3 :         request.data[sizeof(request.data) - 1] = 0; // ensure the path is null terminated
     463             : 
     464             :         // actually open the file
     465           6 :         fd = AP::FS().open((char *)request.data,
     466           3 :                            (request.opcode == FTP_OP::CreateFile) ? O_WRONLY|O_CREAT|O_TRUNC : O_WRONLY|O_CREAT);
     467           3 :         if (fd == -1) {
     468           0 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     469           0 :             break;
     470             :         }
     471           3 :         mode = FTP_FILE_MODE::Write;
     472             : 
     473           3 :         reply.opcode = FTP_OP::Ack;
     474           3 :         break;
     475             :     }
     476           4 :     case FTP_OP::WriteFile:
     477             :     {
     478             :         // must actually be working on a file
     479           4 :         if (fd == -1) {
     480           0 :             GCS_FTP::error(reply, FTP_ERROR::FileNotFound);
     481           0 :             break;
     482             :         }
     483             : 
     484             :         // must have the file in write mode
     485           4 :         if ((mode != FTP_FILE_MODE::Write)) {
     486           0 :             GCS_FTP::error(reply, FTP_ERROR::Fail);
     487           0 :             break;
     488             :         }
     489             : 
     490             :         // seek to requested offset
     491           4 :         if (AP::FS().lseek(fd, request.offset, SEEK_SET) == -1) {
     492           0 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     493           0 :             break;
     494             :         }
     495             : 
     496             :         // fill the buffer
     497           4 :         const ssize_t write_bytes = AP::FS().write(fd, request.data, request.size);
     498           4 :         if (write_bytes == -1) {
     499           0 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     500           0 :             break;
     501             :         }
     502             : 
     503           4 :         reply.opcode = FTP_OP::Ack;
     504           4 :         reply.offset = request.offset;
     505           4 :         break;
     506             :     }
     507           4 :     case FTP_OP::CreateDirectory:
     508             :     {
     509             :         // sanity check that our the request looks well formed
     510           4 :         if (!check_name_len(request)) {
     511           0 :             GCS_FTP::error(reply, FTP_ERROR::InvalidDataSize);
     512           0 :             break;
     513             :         }
     514             : 
     515           4 :         request.data[sizeof(request.data) - 1] = 0; // ensure the path is null terminated
     516             : 
     517             :         // actually make the directory
     518           4 :         if (AP::FS().mkdir((char *)request.data) == -1) {
     519           2 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     520           2 :             break;
     521             :         }
     522             : 
     523           2 :         reply.opcode = FTP_OP::Ack;
     524           2 :         break;
     525             :     }
     526           3 :     case FTP_OP::RemoveDirectory:
     527             :     case FTP_OP::RemoveFile:
     528             :     {
     529             :         // sanity check that our the request looks well formed
     530           3 :         if (!check_name_len(request)) {
     531           0 :             GCS_FTP::error(reply, FTP_ERROR::InvalidDataSize);
     532           0 :             break;
     533             :         }
     534             : 
     535           3 :         request.data[sizeof(request.data) - 1] = 0; // ensure the path is null terminated
     536             : 
     537             :         // remove the file/dir
     538           3 :         if (AP::FS().unlink((char *)request.data) == -1) {
     539           0 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     540           0 :             break;
     541             :         }
     542             : 
     543           3 :         reply.opcode = FTP_OP::Ack;
     544           3 :         break;
     545             :     }
     546           4 :     case FTP_OP::CalcFileCRC32:
     547             :     {
     548             :         // sanity check that our the request looks well formed
     549           4 :         if (!check_name_len(request)) {
     550           1 :             GCS_FTP::error(reply, FTP_ERROR::InvalidDataSize);
     551           1 :             break;
     552             :         }
     553             : 
     554           3 :         request.data[sizeof(request.data) - 1] = 0; // ensure the path is null terminated
     555             : 
     556           3 :         uint32_t checksum = 0;
     557           3 :         if (!AP::FS().crc32((char *)request.data, checksum)) {
     558           1 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     559           1 :             break;
     560             :         }
     561             : 
     562             :         // reset our scratch area so we don't leak data, and can leverage trimming
     563           2 :         memset(reply.data, 0, sizeof(reply.data));
     564           2 :         reply.size = sizeof(uint32_t);
     565           2 :         put_le32_ptr(reply.data, checksum);
     566           2 :         reply.opcode = FTP_OP::Ack;
     567           2 :         break;
     568             :     }
     569          12 :     case FTP_OP::BurstReadFile:
     570             :     {
     571          12 :         const uint16_t max_read = (request.size == 0?sizeof(reply.data):request.size);
     572             :         // must actually be working on a file
     573          12 :         if (fd == -1) {
     574           0 :             GCS_FTP::error(reply, FTP_ERROR::FileNotFound);
     575           0 :             break;
     576             :         }
     577             : 
     578             :         // must have the file in read mode
     579          12 :         if ((mode != FTP_FILE_MODE::Read)) {
     580           0 :             GCS_FTP::error(reply, FTP_ERROR::Fail);
     581           0 :             break;
     582             :         }
     583             : 
     584             :         // seek to requested offset
     585          12 :         if (AP::FS().lseek(fd, request.offset, SEEK_SET) == -1) {
     586           1 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     587           1 :             break;
     588             :         }
     589             : 
     590             :         /*
     591             :           calculate a burst delay so that FTP burst
     592             :           transfer doesn't use more than 1/3 of
     593             :           available bandwidth on links that don't have
     594             :           flow control. This reduces the chance of
     595             :           lost packets a lot, which results in overall
     596             :           faster transfers
     597             :         */
     598          11 :         uint32_t burst_delay_ms = 0;
     599          11 :         if (valid_channel(request.chan)) {
     600          11 :             auto *port = mavlink_comm_port[request.chan];
     601          11 :             if (port != nullptr && port->get_flow_control() != AP_HAL::UARTDriver::FLOW_CONTROL_ENABLE) {
     602           0 :                 const uint32_t bw = port->bw_in_bytes_per_second();
     603           0 :                 const uint16_t pkt_size = PAYLOAD_SIZE(request.chan, FILE_TRANSFER_PROTOCOL) - (sizeof(reply.data) - max_read);
     604           0 :                 burst_delay_ms = 3000 * pkt_size / bw;
     605             :             }
     606             :         }
     607             : 
     608             :         // this transfer size is enough for a full parameter file with max parameters
     609          11 :         const uint32_t transfer_size = 2000;
     610          11 :         reply.offset = request.offset;
     611         657 :         for (uint32_t i = 0; (i < transfer_size); i++) {
     612             :             // fill the buffer
     613         657 :             const ssize_t read_bytes = AP::FS().read(fd, reply.data, MIN(sizeof(reply.data), max_read));
     614         657 :             if (read_bytes == -1) {
     615           0 :                 reply.burst_complete = true;
     616           0 :                 GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     617           0 :                 break;
     618             :             }
     619             : 
     620         657 :             if (read_bytes != sizeof(reply.data)) {
     621             :                 // don't send any old data
     622         408 :                 memset(reply.data + read_bytes, 0, sizeof(reply.data) - read_bytes);
     623             :             }
     624             : 
     625         657 :             if (read_bytes == 0) {
     626          11 :                 reply.burst_complete = true;
     627          11 :                 GCS_FTP::error(reply, FTP_ERROR::EndOfFile);
     628          11 :                 break;
     629             :             }
     630             : 
     631         646 :             reply.opcode = FTP_OP::Ack;
     632             :             // Signal to the client that they need to request another burst read to get more data
     633         646 :             reply.burst_complete = (i == (transfer_size - 1));
     634         646 :             reply.size = (uint8_t)read_bytes;
     635             : 
     636         646 :             push_reply(reply);
     637             : 
     638             :             // update the offset for the next read
     639         646 :             reply.offset += read_bytes;
     640             : 
     641             :             // prep the reply to be used again
     642         646 :             reply.seq_number++;
     643             : 
     644         646 :             if (burst_delay_ms > 0) {
     645           0 :                 hal.scheduler->delay(burst_delay_ms);
     646             :             }
     647             :         }
     648             : 
     649          11 :         if (reply.opcode != FTP_OP::Nack) {
     650             :             // prevent a duplicate packet send for
     651             :             // normal replies of burst reads
     652           0 :             skip_push_reply = true;
     653             :         }
     654          11 :         break;
     655             :     }
     656             : 
     657           5 :     case FTP_OP::Rename: {
     658             :         // sanity check that the request looks well formed
     659           5 :         const char *filename1 = (char*)request.data;
     660           5 :         const size_t len1 = strnlen(filename1, sizeof(request.data)-2);
     661           5 :         const char *filename2 = (char*)&request.data[len1+1];
     662           5 :         const size_t len2 = strnlen(filename2, sizeof(request.data)-(len1+1));
     663           6 :         const bool is_req_size_consider_tnull = (request.size - (len1+len2) == 2 &&
     664           1 :                                                  request.data[sizeof(request.data) - 1] == 0);
     665           5 :         if (filename1[len1] != 0 || ((len1+len2+1 != request.size) && !is_req_size_consider_tnull) || (request.size == 0)) {
     666           1 :             GCS_FTP::error(reply, FTP_ERROR::InvalidDataSize);
     667           1 :             break;
     668             :         }
     669           4 :         request.data[sizeof(request.data) - 1] = 0; // ensure the 2nd path is null terminated
     670             :         // remove the file/dir
     671           4 :         if (AP::FS().rename(filename1, filename2) != 0) {
     672           1 :             GCS_FTP::error(reply, FTP_ERROR::FailErrno);
     673           1 :             break;
     674             :         }
     675           3 :         reply.opcode = FTP_OP::Ack;
     676           3 :         break;
     677             :     }
     678             : 
     679           1 :     case FTP_OP::TruncateFile:
     680             :     default:
     681             :         // we don't implement this opcode. say so specifically, so a client
     682             :         // preferring a newer opcode can tell "never heard of it" from "that
     683             :         // command failed" and fall back to the older one
     684           1 :         GCS_SEND_TEXT(MAV_SEVERITY_DEBUG, "Unsupported FTP: %d", static_cast<int>(request.opcode));
     685           1 :         GCS_FTP::error(reply, FTP_ERROR::UnknownCommand);
     686           1 :         break;
     687             :     }
     688             : 
     689         104 :     return skip_push_reply;
     690             : }
     691             : 
     692             : /*
     693             :   get the time of the last send for a channel
     694             :  */
     695   103052786 : uint32_t GCS_FTP::get_last_send_ms(mavlink_channel_t chan)
     696             : {
     697   103052786 :     if (ftp == nullptr) {
     698   101326675 :         return 0;
     699             :     }
     700     1726111 :     uint32_t ret = 0;
     701    10356666 :     for (const auto &s : ftp->sessions) {
     702             :         // using a comparison will be briefly wrong every 49 days, but
     703             :         // this is non-critical and getting it perfect would be
     704             :         // expensive in a hot path
     705     8630555 :         if (s.chan == chan && s.last_send_ms > ret) {
     706       29953 :             ret = s.last_send_ms;
     707             :         }
     708             :     }
     709     1726111 :     return ret;
     710             : }
     711             : 
     712             : /*
     713             :   fill in a reply with an error code
     714             :  */
     715          29 : void GCS_FTP::error(Transaction &response, FTP_ERROR error)
     716             : {
     717          29 :     response.opcode = FTP_OP::Nack;
     718          29 :     response.data[0] = static_cast<uint8_t>(error);
     719          29 :     response.size = 1;
     720             : 
     721             :     // FIXME: errno's are not thread-local as they should be on ChibiOS
     722          29 :     if (error == FTP_ERROR::FailErrno) {
     723             :         // translate the errno's that we have useful messages for
     724           6 :         switch (errno) {
     725           2 :             case EEXIST:
     726           2 :                 response.data[0] = static_cast<uint8_t>(FTP_ERROR::FileExists);
     727           2 :                 break;
     728           3 :             case ENOENT:
     729           3 :                 response.data[0] = static_cast<uint8_t>(FTP_ERROR::FileNotFound);
     730           3 :                 break;
     731           1 :             default:
     732           1 :                 response.data[1] = static_cast<uint8_t>(errno);
     733           1 :                 response.size = 2;
     734           1 :                 break;
     735             :         }
     736             :     }
     737          29 : }
     738             : 
     739             : /*
     740             :   setup reply packet to reply to the request
     741             : */
     742         124 : void GCS_FTP::setup_reply(const Transaction &request, Transaction &reply)
     743             : {
     744         124 :     memset(&reply, 0, sizeof(reply));
     745         124 :     reply.req_opcode = request.opcode;
     746         124 :     reply.session = request.session;
     747         124 :     reply.seq_number = request.seq_number + 1;
     748         124 :     reply.chan = request.chan;
     749         124 :     reply.sysid = request.sysid;
     750         124 :     reply.compid = request.compid;
     751         124 : }
     752             : 
     753             : /*
     754             :   main FTP thread
     755             :  */
     756           7 : void GCS_FTP::worker(void)
     757             : {
     758             :     Transaction request;
     759           7 :     Transaction reply {};
     760           7 :     reply.session = -1; // flag the reply as invalid for any reuse
     761             : 
     762             :     while (true) {
     763      483879 :         while (!requests.pop(request)) {
     764             :             // nothing to handle, delay ourselves a bit then check again. Ideally we'd use conditional waits here
     765      483754 :             hal.scheduler->delay(2);
     766             : 
     767             :             // kill any dead sessions
     768      483747 :             const uint32_t now = AP_HAL::millis();
     769     2902482 :             for (auto &s : sessions) {
     770     2418735 :                 if (s.last_send_ms != 0 &&
     771       17469 :                     now - s.last_send_ms > FTP_SESSION_KILL_TIMEOUT) {
     772           2 :                     s.close();   // error code ignored
     773             :                 }
     774             :             }
     775             :         }
     776             : 
     777         125 :         if (request.opcode == FTP_OP::ResetSessions) {
     778             :             /*
     779             :               close all sessions for this channel, compid and sysid
     780             :              */
     781         114 :             for (auto &s : sessions) {
     782          95 :                 if (request.sysid == s.sysid &&
     783          16 :                     request.compid == s.compid &&
     784          16 :                     request.chan == s.chan) {
     785             :                     // close this session
     786          16 :                     s.close();   // error code ignored
     787             :                 }
     788             :             }
     789             :             // always ACK, even if no sessions were closed
     790          19 :             setup_reply(request, reply);
     791          19 :             reply.opcode = FTP_OP::Ack;
     792          19 :             send_reply(reply);
     793          19 :             continue;
     794          19 :         }
     795             : 
     796         106 :         Session *session = nullptr;
     797         227 :         for (uint8_t i=0; i<ARRAY_SIZE(sessions); i++) {
     798         213 :             auto &s = sessions[i];
     799         213 :             if (request.sysid == s.sysid &&
     800          98 :                 request.compid == s.compid &&
     801          98 :                 request.chan == s.chan &&
     802          98 :                 request.session == s.session_id) {
     803             :                 // found the session
     804          92 :                 session = &s;
     805          92 :                 break;
     806             :             }
     807             :         }
     808             : 
     809         106 :         if (session == nullptr) {
     810             :             /*
     811             :               find the oldest session to possibly reuse
     812             :              */
     813          14 :             const uint32_t now = AP_HAL::millis();
     814          14 :             session = &sessions[0];
     815          70 :             for (uint8_t i=1; i<ARRAY_SIZE(sessions); i++) {
     816          56 :                 auto &s = sessions[i];
     817          56 :                 if ((now - s.last_send_ms) > (now - session->last_send_ms)) {
     818           1 :                     session = &s;
     819             :                 }
     820             :             }
     821             : 
     822             :             // only reuse the session if it is not active
     823          14 :             auto &s = *session;
     824          14 :             if (s.last_send_ms != 0 &&
     825           0 :                 now - s.last_send_ms < FTP_SESSION_TIMEOUT) {
     826             :                 // the oldest session is still active, reject the request
     827           0 :                 setup_reply(request, reply);
     828           0 :                 error(reply, FTP_ERROR::NoSessionsAvailable);
     829           0 :                 send_reply(reply);
     830           0 :                 continue;
     831             :             }
     832             :             // claim the session
     833          14 :             s.close();   // error code ignored
     834          14 :             s.session_id = request.session;
     835          14 :             s.sysid = request.sysid;
     836          14 :             s.compid = request.compid;
     837          14 :             s.chan = request.chan;
     838             :         }
     839             : 
     840             :         // if it's a rerequest and we still have the last response then send it
     841         106 :         if ((request.sysid == reply.sysid) && (request.compid == reply.compid) &&
     842         100 :             (request.session == reply.session) && (request.seq_number + 1 == reply.seq_number) &&
     843           1 :             reply.data[0] != uint8_t(FTP_ERROR::NoSessionsAvailable)) {
     844           1 :             session->push_reply(reply);
     845           1 :             continue;
     846             :         }
     847             : 
     848         105 :         setup_reply(request, reply);
     849             : 
     850         105 :         bool skip_push_reply = session->handle_request(request, reply);
     851             : 
     852         105 :         if (!skip_push_reply) {
     853         105 :             session->push_reply(reply);
     854             :         }
     855         125 :     }
     856             : }
     857             : 
     858             : #endif  // AP_MAVLINK_FTP_ENABLED

Generated by: LCOV version 1.14