# File Transfer Protocol (FTP)

The File Transfer Protocol (FTP) enables file transfer over MAVLink.
It supports common FTP operations like: reading, truncating, writing, removing and creating files, listing and removing directories.

::: info
MAVLink FTP implementation closely follows the design of the original internet [FTP protocol](https://en.wikipedia.org/wiki/File_Transfer_Protocol) in terms of the message structure, sequences, and the supported opcodes/operations.
Developers can read the Internet protocol RFCs to understand MAVLink FTP.
:::

The protocol follows a client-server pattern, where all commands are sent by the GCS (client),
and the Drone (server) responds either with an ACK containing the requested information, or a NAK containing an error.
The GCS sets a timeout after most commands, and may resend the command if it is triggered.
The drone must re-send its response if a request with the same sequence number is received.

All messages (commands, ACK, NAK) are exchanged inside [FILE_TRANSFER_PROTOCOL](#FILE_TRANSFER_PROTOCOL) messages.
This message type definition is minimal, with fields for specifying the target network, system and component, and for an "arbitrary" variable-length payload.

The different commands and other information required to implement the protocol are encoded _within_ in the `FILE_TRANSFER_PROTOCOL` payload.
This topic explains the encoding, packing format, commands and errors, and the order in which the commands are sent to implement the core FTP functionality.

## Message/Enum Summary

| Message                                                                                                   | Description                                  |
| --------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| <a id="FILE_TRANSFER_PROTOCOL"></a>[FILE_TRANSFER_PROTOCOL](../messages/common.md#FILE_TRANSFER_PROTOCOL) | FTP message. Encodes commands, ACK and NACK. |

| Enum                                                                              | Description                    |
| --------------------------------------------------------------------------------- | ------------------------------ |
| <a id="MAV_FTP_OPCODE"></a>[MAV_FTP_OPCODE](../messages/common.md#MAV_FTP_OPCODE) | FTP opcodes. Used in Commands. |
| <a id="MAV_FTP_ERR"></a>[MAV_FTP_ERR](../messages/common.md#MAV_FTP_ERR)          | FTP errors. Used in NAK.       |

## Protocol Discovery

FTP (v1) is supported if the [AUTOPILOT_VERSION.capability](../messages/common.md#AUTOPILOT_VERSION) field has the [MAV_PROTOCOL_CAPABILITY_FTP](../messages/common.md#MAV_PROTOCOL_CAPABILITY_FTP) flag set.

This flag should only be set by a MAVLink component that supports the specific version of the protocol defined in this document.

::: info
The encoding and content of the `FILE_TRANSFER_PROTOCOL` payload field are not mandated by the specification, and other encoding schemes might be used, for example, in private networks.
If you have implemented a private encoding or different version you **must not** set the [MAV_PROTOCOL_CAPABILITY_FTP](../messages/common.md#MAV_PROTOCOL_CAPABILITY_FTP) flag.
:::

## Payload Format {#payload}

The `FILE_TRANSFER_PROTOCOL` payload is encoded with the information required for the various FTP messages.
This includes fields for holding the command that is being sent, the sequence number of the current FTP message (for multi-message data transfers),
the size of information in the data part of the message etc.

::: tip
Readers will note that the FTP payload format is very similar to the packet format used for serializing MAVLink itself.
:::

Below is the over-the-wire format for the payload part of the [FILE_TRANSFER_PROTOCOL](../messages/common.md#FILE_TRANSFER_PROTOCOL) message on PX4/_QGroundControl_.

![FILE_TRANSFER_PROTOCOL Payload format - QGC](../../assets/packets/ftp_transfer_payload_data_qgc.jpg)

| Byte Index      | C version                | Content                     | Value               | Explanation                                                                                                                                                                                                                                                                                                                                                                                             |
| --------------- | ------------------------ | --------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0 to 1          | `uint16_t seq_number`    | Sequence number for message | 0&nbsp;-&nbsp;65535 | All _new_ messages between the GCS and drone iterate this number. Re-sent commands/ACK/NAK should use the previous response's sequence number.                                                                                                                                                                                                                                                          |
| 2               | `uint8_t session`        | Session id                  | 0 - 255             | Session id for read/write operations (the server may use this to reference the file handle and information about the progress of read/write operations).                                                                                                                                                                                                                                                |
| 3               | `uint8_t opcode`         | [OpCode](#opcodes) (id)     | 0 - 255             | Ids for particular commands and ACK/NAK messages.                                                                                                                                                                                                                                                                                                                                                       |
| 4               | `uint8_t size`           | Size                        | 0 - 255             | Depends on [OpCode](#opcodes). For Reads/Writes this is the size of the `data` transported. For NAK it is the number of bytes used for [error information](#error_codes) (1 or 2).                                                                                                                                                                                                                      |
| 5               | `uint8_t req_opcode`     | Request [OpCode](#opcodes)  | 0 - 255             | OpCode (of original message) returned in an ACK or NAK response.                                                                                                                                                                                                                                                                                                                                        |
| 6               | `uint8_t burst_complete` | Burst complete              | 0, 1                | Code to indicate if a burst is complete. 1: set of burst packets complete, 0: More burst packets coming.<br>- Only used if `req_opcode` is [BurstReadFile](#BurstReadFile).                                                                                                                                                                                                                             |
| 7               | `uint8_t padding`        | Padding                     |                     | 32 bit alignment padding.                                                                                                                                                                                                                                                                                                                                                                               |
| 8 to 11         | `uint32_t offset`        | Content offset              |                     | Offsets into data to be sent for [ListDirectory](#ListDirectory) and [ReadFile](#ReadFile) commands.                                                                                                                                                                                                                                                                                                    |
| 12 to (max) 251 | `uint8_t data[]`         | Data                        |                     | Command/response data. Varies by [OpCode](#opcodes). This contains the `path` for operations that act on a file or directory. For an ACK for a read or write this is the requested information. For an ACK for a `OpenFileRO` operation this is the size of the file that was opened. For a NAK the first byte is the [error code](#error_codes) and the (optional) second byte may be an error number. |

## OpCodes/Command {#opcodes}

The opcodes that may be sent by the GCS (client) to the drone (server) are enumerated in [MAV_FTP_ERR](#MAV_FTP_ERR), and listed below for convenience.

<!--  uint8_t enum Opcode: https://github.com/PX4/Firmware/blob/master/src/modules/mavlink/mavlink_ftp.h -->

| Opcode                                | Name                                               | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| ------------------------------------- | -------------------------------------------------- |   |
| 0                                     | None                                               | Ignored, always ACKed                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| <a id="TerminateSession"></a> 1       | TerminateSession                                   | Terminates open Read `session`.<br>- Closes the file associated with (`session`) and frees the session ID for re-use.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| <a id="ResetSessions"></a> 2          | ResetSessions                                      | Terminates _all_ open read sessions.<br>- Clears all state held by the drone (server); closes all open files, etc.<br>- Sends an ACK reply with no data. <!-- Note, is same as Terminate, but does not check if file session exists -->                                                                                                                                                                                                                                                                                                                                                                                                                 |
| <a id="ListDirectory"></a> 3          | [ListDirectory](#list_directory)                   | List directory entry information (files, folders etc.) in `<path>`, starting from a specified entry index (`<offset>`).<br>- Response is an ACK packet with one or more entries on success, otherwise a NAK packet with an error code.<br>- Completion is indicated by a NACK with EOF in response to a requested index (`offset`) beyond the list of entries.<br>- The directory is closed after the operation, so this leaves no state on the server.                                                                                                                                                                                                 |
| <a id="OpenFileRO"></a> 4             | [OpenFileRO](#reading-a-file)                      | Opens file at `<path>` for reading, returns `<session>`<br>- The `path` is stored in the [payload](#payload) `data`. The drone opens the file (`path`) and allocates a _session number_. The file must exist.<br>- An ACK packet must include the allocated `session` and the data size of the file to be opened (`size`)<br>- A NAK packet must contain [error information](#error_codes) . Typical error codes for this command are `NoSessionsAvailable`, `FileExists`. <br>- The file remains open after the operation, and must eventually be closed by `Reset` or `Terminate`.                                                                    |
| <a id="ReadFile"></a> 5               | [ReadFile](#reading-a-file-readfile)               | Reads `<size>` bytes from `<offset>` in `<session>`.<br>- Seeks to (`offset`) in the file opened in (session) and reads (`size`) bytes into the result buffer.<br>- Sends an ACK packet with the result buffer on success, otherwise a NAK packet with an error code. For short reads or reads beyond the end of a file, the (`size`) field in the ACK packet will indicate the actual number of bytes read.<br>- Reads can be issued to any offset in the file for any number of bytes, so reconstructing portions of the file to deal with lost packets should be easy.<br>- For best download performance, try to keep two `Read` packets in flight. |
| <a id="CreateFile"></a> 6             | `CreateFile`                                       | Creates file at `<path>` for writing, returns `<session>`.<br>- Creates the file (path) and allocates a _session number_. All parent directories must exist. If the file already existed, then this call will truncate it. Equivalent UNIX flags: (O_CREAT &#124; O_TRUNC &#124; O_WRONLY)<br>- Sends an ACK packet with the allocated session number on success, or a NAK packet with an error code on error (i.e. [FileExists](#FileExists) if the `path` already exists).<br>- The file remains open after the operation, and must eventually be closed by `Reset` or `Terminate`.                                                                   |
| <a id="WriteFile"></a> 7              | `WriteFile`                                        | Writes `<size>` bytes to `<offset>` in `<session>`.<br>- Sends an ACK reply with no data on success, otherwise a NAK packet with an error code.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| <a id="RemoveFile"></a> 8             | [RemoveFile](#remove-file)                         | Remove file at `<path>`.<br>- ACK reply with no data on success.<br>- NAK packet with [error information](#error_codes) on failure.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| <a id="CreateDirectory"></a> 9        | [CreateDirectory](#create-directory)               | Creates directory at `<path>`.<br>- Sends an ACK reply with no data on success, otherwise a NAK packet with an error code.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| <a id="RemoveDirectory"></a> 10       | [RemoveDirectory](#remove-directory)               | Removes directory at `<path>`. The directory must be empty. <br>- Sends an ACK reply with no data on success, otherwise a NAK packet with an error code.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| <a id="OpenFileWO"></a> 11            | OpenFileWO                                         | Opens file at `<path>` for writing, returns `<session>`. <br>- Opens the file (`path`) and allocates a _session number_. The file will be created if it does not exist. Equivalent UNIX flags: (O*CREAT &#124; O_WRONLY)<br>- Sends an ACK packet with the allocated \_session number* on success, otherwise a NAK packet with an error code.<br>- The file remains open after the operation, and must eventually be closed by `Reset` or `Terminate`.                                                                                                                                                                                                  |
| <a id="TruncateFile"></a> 12          | [TruncateFile](#truncate-file)                     | Truncate file at `<path>` to `<offset>` length.<br>- Sends an ACK reply with no data on success, otherwise a NAK packet with an error code.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| <a id="Rename"></a> 13                | Rename                                             | Rename `<path1>` to `<path2>`.<br>- Sends an ACK reply the no data on success, otherwise a NAK packet with an error code (i.e. if the source path does not exist).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| <a id="CalcFileCRC32"></a> 14         | CalcFileCRC32                                      | Calculate CRC32 for file at `<path>`.<br>- Sends an ACK reply with the checksum on success, otherwise a NAK packet with an error code.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| <a id="BurstReadFile"></a> 15         | [BurstReadFile](#reading-a-file-burstreadfile)     | Burst-read parts of a file. Messages in the burst are streamed (without ACK) until the burst is complete (as indicated by the field `burst_complete` being set to `1`). Parts of a burst that are dropped may be fetched using [ReadFile](#ReadFile).                                                                                                                                                                                                                                                                                                                                                                                                   |
| <a id="ListDirectoryWithTime"></a> 16 | [ListDirectoryWithTime](#list_directory_with_time) | List directory entry information as for [ListDirectory](#ListDirectory), but with each entry additionally including its last-modification time. Lets a GCS fetch timestamps without downloading file contents.<br>- A server that does not implement this command NAKs with [UnknownCommand](#UnknownCommand); clients should then fall back to [ListDirectory](#ListDirectory).                                                                                                                                                                                                                                                                        |

The drone (server) will respond with/send the following opcodes for any of the above messages (ACK response on success or a NAK in the event of an error).

| Opcode | Name | Description   |
| ------ | ---- | ------------- |
| 128    | ACK  | ACK response. |
| 129    | NAK  | NAK response. |

Notes:

- An ACK response may additionally return requested data in the payload (e.g. `OpenFileRO` returns the session and file size, `ReadFile` returns the requested file data, etc.).
- The NAK response includes [error information](#error_codes) in the payload `data`.

## NAK Error Information {#error_codes}

NAK responses must include one of the errors codes enumerated in [MAV_FTP_ERR](#MAV_FTP_ERR) in the [payload](#payload) `data[0]` field.
These are listed below for convenience.

An appropriate error code must be used if one is defined.
If no appropriate error code exists, the Drone (server) may respond with [Fail](#Fail) or [FailErrno](#FailErrno).

If the error code is `FailErrno`, then `data[1]` must additionally contain an error number.
This error number is a file-system specific error code (understood by the server).

The payload `size` field must be set to either 1 or 2, depending on whether or not `FailErrno` is specified.

::: info
These are **errors**. Normally if the GCS receives an error it should not attempt to continue the FTP operation, but instead return to an idle state.
:::

<!--  uint8_t enum ErrorCode: https://github.com/PX4/Firmware/blob/master/src/modules/mavlink/mavlink_ftp.h -->

| Error                             | Name                | Description                                                                                                                                    |
| --------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| <a id="None"></a>0                | None                | No error                                                                                                                                       |
| <a id="Fail"></a>1                | Fail                | Unknown failure                                                                                                                                |
| <a id="FailErrno"></a>2           | FailErrno           | Command failed, Err number sent back in `PayloadHeader.data[1]`. This is a file-system error number understood by the server operating system. |
| <a id="InvalidDataSize"></a>3     | InvalidDataSize     | Payload `size` is invalid                                                                                                                      |
| <a id="InvalidSession"></a>4      | InvalidSession      | Session is not currently open                                                                                                                  |
| <a id="NoSessionsAvailable"></a>5 | NoSessionsAvailable | All available sessions are already in use.                                                                                                     |
| <a id="EOF"></a>6                 | EOF                 | Offset past end of file for `ListDirectory` and `ReadFile` commands.                                                                           |
| <a id="UnknownCommand"></a>7      | UnknownCommand      | Unknown command / opcode                                                                                                                       |
| <a id="FileExists"></a>8          | FileExists          | File/directory already exists                                                                                                                  |
| <a id="FileProtected"></a>9       | FileProtected       | File/directory is write protected                                                                                                              |
| <a id="FileNotFound"></a>10       | FileNotFound        | File/directory not found                                                                                                                       |

## Timeouts/Resending {#timeouts}

The GCS (client) starts a timeout after most commands are sent (these are cleared if an ACK/NAK is received).

::: info
Timeouts may not be set for some messages. For example, a timeout need not set for [ResetSessions](#ResetSessions) as the message should always succeed.
:::

If a timeout activates either the command or its response is assumed to have been lost,
and the command should be re-sent with the same sequence number etc.
A number of retries are allowed, after which the GCS should fail the whole download and reset to an idle state.

If the drone (client) receives a message with the same sequence number then it assumes that its ACK/NAK response was lost.
In this case it should resend the response (the sequence number is not iterated, because it is as though the previous response was not sent).

The drone has no timeout mechanism; it only ever responds to commands and does not expect any responses.

GCS recommended settings:

- ACK/NAK timeout: 50 milliseconds
- Command retries: 6

## FTP Operations

### Reading a File (`ReadFile`)

After opening a file session, [ReadFile](#ReadFile) is called to request a message sized chunk of the file, which is then returned to the client in an `ACK` message.
The process is repeated at different offsets until the whole file has been retrieved.
The file session is then closed.

::: info
[Burst reading a file](#reading-a-file-burstreadfile) is a (generally) faster alternative to this approach.
:::

The sequence of operations for downloading (reading) a file using [ReadFile] is shown below.
This assumes that there are no timeouts and all operations/requests succeed.

[![Mermaid Sequence: Reading a File](https://mermaid. )](https://mermaid-js.github.io/mermaid-live-editor/# )

<!-- Original diagram
sequenceDiagram;
    participant GCS
    participant Drone
    Note right of GCS: Open file
    GCS->>Drone:  OpenFileRO( data[0]=path, size=len(path) )
    Drone-- >>GCS: ACK( session, size=4, data=len(file) )
    Note right of GCS: Read file in chunks<br>(call at offset)
    GCS->>Drone:  ReadFile(session, size, offset)
    Drone-- >>GCS: ACK(session, size=len(buffer), data[0]=buffer)
    Note right of GCS: Continue until NAK<br> with EOF
    Drone-- >>GCS: NAK(session, size=1, data=EOF)
    Note right of GCS: Close session
    GCS->>Drone:  TerminateSession(session)
    Drone-- >>GCS: ACK()
-->

The sequence of operations is:

1. GCS (client) sends [OpenFileRO](#OpenFileRO) command specifying the file path to open.
   - The payload must specify: `data[0]`= file path string, `size`=length of file path string.
1. Drone (server) responds with either
   - ACK on success. The [payload](#payload) must specify fields: `session` = file session id, `size` = 4, `data` = length of file that has been opened.
   - NAK with [error information](#error_codes), e.g. `NoSessionsAvailable`, `FileExists`.
     The GCS may cancel the operation, depending on the error.
1. GCS sends [ReadFile](#ReadFile) commands to download a chunk of data from the file.
   - The payload must specify: `session`=current session, `size`=size of data to read, `offset`= position in data to start reading
1. Drone responds to each message with either
   - ACK on success. The [payload](#payload) fields are: `data` = data chunk requested, `size` = size of data in the `data` field.
   - NAK on failure with [error information](#error_codes).
1. The ReadFile/ACK sequence above is repeated at different offsets to download the whole file.
   - Eventually the GCS will (must) request an offset past the end of the file.
   - The Drone will return a NAK with error code EOF. The GCS uses this message to recognise the download is complete.
1. GCS sends [TerminateSession](#TerminateSession) to close the file.
   The drone should send an ACK/NAK, but this may (generally speaking) be ignored by the GCS.

The GCS should create a timeout after `OpenFileRO` and `ReadFile` commands are sent and resend the messages as needed (and [described above](#timeouts)).
A timeout is not set for `TerminateSession` (the server may ignore failure of the command or the ACK).

### Reading a File (`BurstReadFile`)

After opening a file for reading, it is read in "bursts".
Each burst delivers a part of the file as a stream of messages.
The last message in the burst is indicated by setting `burst_complete=1` (without any ACKs).

The client tracks the received chunks.
On completion of the burst (or the file), if there are any missing parts of the file it can request them using either another burst or using [ReadFile](#reading-a-file-readfile).

::: info
Burst read is a (generally) faster alternative to using [ReadFile](#ReadFile) to [read a file](#reading-a-file-readfile).
This is because fewer messages are sent and need to be waited on.
:::

The sequence of operations for a burst read is shown below (assuming there are no timeouts and all operations/requests succeed).

[![Mermaid Sequence: Burst read file](https://mermaid.ink/img/pako: )](pako: )

<!-- Original Diagram
sequenceDiagram;
    participant Client
    participant Server
    Note over Client,Server: Open file (for burst reading)
    Note right of Client: Request open file
    Client->>Server:  OpenFileRO( data[0]=path, size=len(path) )
    Server->>Client: ACK( session, size=4, data=len(file) )
    Note over Client,Server: Read the file in bursts (repeat sequence below at new offsets)
    Note right of Client: Request burst read part of the file (in this case at offset=0)
    Client->>Server:  BurstReadFile( session, offset=0, size=data_per_burst )
    Note left of Server: Server streams data in chunks at increasing offsets with burst_complete=0
    Server->>Client: ACK(session, burst_complete=0, offset=0, size=len(buffer), data[0]=buffer)
    Server->>Client: ACK(session, burst_complete=0, offset=<current_offset>, size=len(buffer), data[0]=buffer)
    Server->>Client: ...
    Note left of Server: For last message in burst set burst_complete=1
    Server->>Client: ACK(session, burst_complete=1, offset=<offset_of_last_burst_chunk>, size=len(buffer), data[0]=buffer)
    Note over Client,Server: Read any missing parts using BurstReadFile (sequence above) or ReadFile
    Note over Client,Server: Close session when whole file collected
    Client->>Server:  TerminateSession(session)
    Server->>Client: ACK()
-->

The sequence of operations is:

1. GCS (client) sends [OpenFileRO](#OpenFileRO) command specifying the file path to open.
   - The payload must specify: `data[0]`= file path string, `size`=length of file path string.
1. Drone (server) responds with either
   - ACK on success. The [payload](#payload) must specify fields: `session` = file session id, `size` = 4, `data` = length of file that has been opened.
   - NAK with [error information](#error_codes), e.g. `NoSessionsAvailable`, `FileExists`.
     The GCS may cancel the operation, depending on the error.
1. Client (i.e. GCS) sends [BurstReadFile](#BurstReadFile) command specifying the part of the file that it wants to get from an offset to the end, along with the default size of each burst payload.
   - The payload must specify: `session`: the current session id, `offset` = offset in file of start of burst, `size`= default length of payload in burst responses, `data` is ignored.
1. Server (drone) responds with either
   - ACK on success. The [payload](#payload) must specify fields: `session` = file session id, `size` = 4, `data` = length of file in burst.
   - NAK with [error information](#error_codes). The client may cancel the operation, depending on the error.
1. Server sends stream of [BurstReadFile](#BurstReadFile) data to client (without ACK) until the whole burst is sent, or a server-defined burst size limit is reached.
   - The payload must specify: `session`=current session, `size`=size of data to read per burst message (max equal to payload size = 239), `offset`= position in original data of current chunk.
   - The payload must specify `burst_complete=0` for all chunks, except the last one, which must set `burst_complete=1`.
1. The client must maintain its own record of the received (and missing) chunks.
   It may request any missing chunks at either the end of a burst or at the end of the file.
   Missing chunks can be requested using `ReadFile`.
1. Client sends [TerminateSession](#TerminateSession) to close the file once all the chunks have been downloaded
   The server should send an ACK/NAK, but this may (generally speaking) be ignored by the client.

The client should create a timeout while waiting for a new `BurstReadFile`, and on expiry may request missing parts of the file using either `BurstReadFile` or `ReadFile`
A timeout is not set for `TerminateSession` (the server may ignore failure of the command or the ACK).

### Uploading a File

The sequence of operations for uploading a file to the drone, assuming there are no timeouts and all operations/requests succeed, is shown below.

[![Mermaid Sequence: Uploading a file](https://mermaid. )](https://mermaid-js.github.io/mermaid-live-editor/# )

<!-- Original Sequence
sequenceDiagram;
    participant GCS
    participant Drone
    Note right of GCS: Create file on Drone
    GCS->>Drone:  CreateFile( data[0]=path, size=len(path) )
    Drone-- >>GCS: ACK( session , size=0 )
    Note right of GCS: Write data in chunks<br>until complete
    GCS->>Drone:  WriteFile(session, size=len(buffer), offset, data=buffer)
    Drone-- >>GCS: ACK( session, size=0 )
    Note right of GCS: Close session
    GCS->>Drone:  TerminateSession(session)
    Drone-- >>GCS: ACK()
-->

The sequence of operations is:

1. GCS (client) sends [CreateFile](#CreateFile) command specifying the file path where the file is to be uploaded.
   - The payload must specify: `data[0]`= target file path string, `size`=length of file path string.
1. Drone (server) attempts to create the file, and responds with either
   - ACK on success. The [payload](#payload) must specify fields: `session` = new file session id, `size` = 0.
   - NAK with [error information](#error_codes).
     - The GCS should cancel the whole operation on error.
     - If there is a sequence error at this stage the GCS should send a command to `ResetSessions`
1. GCS sends [WriteFile](#WriteFile) commands to upload a chunk of data to the Drone.
   - The payload must specify: `session`=current session id, `data`=file chunk,`size`=length of `data`, `offset`= offset of data to write
1. Drone responds to each message with either
   - ACK on success. The [payload](#payload) fields are: `size` = 0.
   - NAK on failure with [error information](#error_codes).
     - The GCS should cancel the whole upload operation by sending a command to `ResetSessions` if there is an NAK.
1. The WriteFile/ACK sequence above is repeated at different offsets to upload the whole file.
   Once the GCS determines that the upload is complete it moves to the next step.
1. GCS sends [TerminateSession](#TerminateSession) to close the file.
   The drone should send an ACK/NAK, but this may (generally speaking) be ignored by the GCS.

The GCS should create a timeout after `CreateFile` and `WriteFile` commands are sent, and resend the messages as needed (and [described above](#timeouts)).
A timeout is not set for `TerminateSession` (the server may ignore failure of the command or the ACK).

::: warning
PX4 and _QGroundControl_ implement this slightly differently than outlined above.
The implementation only has a single session (id=0) so only a single operation can be active at a time.
As a result, this operation should only be started if no other operation is active.
The drone expects that the session id will be set to zero by the sender of `CreateFile`.
Last of all, the GCS sends `ResetSessions` rather than `TerminateSession`.
While you can send either if talking to PX4, if the protocol is implemented elsewhere calling `ResetSessions` may break other communications.
:::

### Remove File

::: info
`RemoveFile` handling is implemented in PX4 but not in _QGroundControl_.
GCS behaviour is therefore not fully defined/tested.
:::

The sequence of operations for removing a file is shown below (assuming there are no timeouts and all operations/requests succeed).

[![Mermaid Sequence: Removing a file](https://mermaid.ink/img/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtO1xuICAgIHBhcnRpY2lwYW50IEdDU1xuICAgIHBhcnRpY2lwYW50IERyb25lXG4gICAgR0NTLT4-RHJvbmU6ICBSZW1vdmVGaWxlKCBkYXRhWzBdPXBhdGgsIHNpemU9bGVuKHBhdGgpIClcbiAgICBEcm9uZS0tPj5HQ1M6IEFDSyhzaXplPTApIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtO1xuICAgIHBhcnRpY2lwYW50IEdDU1xuICAgIHBhcnRpY2lwYW50IERyb25lXG4gICAgR0NTLT4-RHJvbmU6ICBSZW1vdmVGaWxlKCBkYXRhWzBdPXBhdGgsIHNpemU9bGVuKHBhdGgpIClcbiAgICBEcm9uZS0tPj5HQ1M6IEFDSyhzaXplPTApIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)

<!-- Original Sequence
sequenceDiagram;
    participant GCS
    participant Drone
    GCS->>Drone:  RemoveFile( data[0]=path, size=len(path) )
    Drone-- >>GCS: ACK(size=0)
-->

The sequence of operations is:

1. GCS sends [RemoveFile](#RemoveFile) command specifying the full path of the file to be deleted.
   - The payload must specify: `data[0]`= file path string, `size`=length of file path string.
1. Drone attempts to delete file, and responds to the message with either:
   - ACK on success, containing payload `size`=0 (i.e. no data).
   - NAK on failure, with [error information](#error_codes).
   - The drone must clean up any resources associated with the request after sending the response.

The GCS should create a timeout after the `RemoveFile` command is sent and resend the message as needed (and [described above](#timeouts)).

### Truncate File

The sequence of operations for truncating a file is shown below (assuming there are no timeouts and all operations/requests succeed).

::: info
`TruncateFile` handling is implemented in PX4 but not in _QGroundControl_.
GCS behaviour is therefore not fully defined/tested.
:::

[![Mermaid Sequence: Truncate file](https://mermaid.ink/img/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtO1xuICAgIHBhcnRpY2lwYW50IEdDU1xuICAgIHBhcnRpY2lwYW50IERyb25lXG4gICAgR0NTLT4-RHJvbmU6ICBUcnVuY2F0ZUZpbGUgPGJyPiggZGF0YVswXT1wYXRoLCBzaXplPWxlbihwYXRoKSwgb2Zmc2V0PW9mZnNldCB0byB0cnVuY2F0ZSApXG4gICAgRHJvbmUtLT4-R0NTOiBBQ0soc2l6ZT0wKSIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtO1xuICAgIHBhcnRpY2lwYW50IEdDU1xuICAgIHBhcnRpY2lwYW50IERyb25lXG4gICAgR0NTLT4-RHJvbmU6ICBUcnVuY2F0ZUZpbGUgPGJyPiggZGF0YVswXT1wYXRoLCBzaXplPWxlbihwYXRoKSwgb2Zmc2V0PW9mZnNldCB0byB0cnVuY2F0ZSApXG4gICAgRHJvbmUtLT4-R0NTOiBBQ0soc2l6ZT0wKSIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)

<!-- Original sequence
sequenceDiagram;
    participant GCS
    participant Drone
    GCS->>Drone:  TruncateFile ( data[0]=path, size=len(path), offset=offset to truncate )
    Drone-- >>GCS: ACK(size=0)
-->

The sequence of operations is:

1. GCS sends [TruncateFile](#TruncateFile) command specifying file to truncate and the offset for truncation.
   - The payload must specify: `data[0]`= file path string, `size` = length of file path string, `offset` = truncation point in file (amount of data to keep).
1. Drone attempts to truncate file, and responds to the message with either:
   - ACK on success, containing payload `size`=0 (i.e. no data).
     - The request should succeed if the offset is the same as the file size, and may be attempted if the offset is zero (i.e. truncate whole file).
   - NAK on failure, with [error information](#error_codes).
     - The request should fail if the offset is 0 (truncate whole file) and for normal file system errors.
   - The drone must clean up any resources associated with the request after sending the response.

The GCS should create a timeout after the `TruncateFile` command is sent and resend the message as needed (and [described above](#timeouts)).

### List Directory {#list_directory}

The sequence of operations for getting a directory listing is shown below (assuming there are no timeouts and all operations/requests succeed).

[![Mermaid Sequence: List Directory](https://mermaid. )](https://mermaid-js.github.io/mermaid-live-editor/# )

<!-- original sequence
sequenceDiagram;
    participant GCS
    participant Drone
    Note over GCS,Drone: Request entries from index (offset) 0.<br>One or more entries returned in ACK.
    GCS->>Drone: ListDirectory( data[0]=path, size=len(path), offset=0 )
    Drone->>GCS: ACK(size, data=entries_at_offset_0)
    Note over GCS,Drone: Repeat request in cycle to get all<br>entries (each time set offset to<br>entry index just after last one<br>received).
    GCS->>Drone: ListDirectory( data[0]=path, size=len(path), offset=...)
    Drone->>GCS: ACK(size, data=entries_at_offset)
    Note over GCS,Drone: Drone NACK with EOF when all<br>entries returned<br>(e.g. request with:<br>offset >= number of entries).
    GCS->>Drone:  ListDirectory( data[0]=path, size=len(path), offset=too_big )
    Drone->>GCS: NACK(size=1, data[0]=EOF)
-->

The sequence of operations is:

1. GCS sends [ListDirectory](#ListDirectory) command specifying a directory path and the **index** of an entry.
   - The [payload](#payload) must specify:
     - `data[0]` = file path
     - `size` = length of path string
     - `offset` = The index of the first entry to get (0 for first entry, 1 for second, etc.).
1. Drone responds with an ACK containing **one or more entries** (the first entry is the one specified in request `offset` field).
   - The payload must specify:
     - `data[0]` = Information for one or more (sequential) entries, starting at the requested entry index (`offset`).
       Each entry is separated with a null terminator (`\0`), and has the following format (where `type` is one of the letters **F**(ile), **D**(irectory), **S**(skip))

       ```txt
       <type><file_or_folder_name>\t<file_size_in_bytes>\0
       ```

       For example, given five files named _TestFile1.xml_ to _TestFile5.xml_, the entries returned at offset 2 might look like: `FTestFile3.xml\t223\0FTestFile4.xml\t755568\0FTestFile5.xml\t11111\0`

     - `size` = The size of the `data`.

1. The operation is then repeated at different offsets to download the whole directory listing.

   ::: info
   The offset for each request will depend on how many entries were returned by the previous request(s).
   :::

1. The operation completes when the GCS requests an entry index (`offset`) greater than or equal to the number of entries.
   In this case the drone responds with a [NAK](#error_codes) containing [EOF](#EOF) (end of file).

The GCS should create a timeout after the `ListDirectory` command is sent and resend the message as needed (and [described above](#timeouts)).

The drone may also [NAK](#error_codes) with an unexpected error.
Generally errors are unrecoverable, and the drone must clean up all resources (i.e. close file handles) associated with the request after sending the NAK.

### List Directory with Timestamps {#list_directory_with_time}

[ListDirectoryWithTime](#ListDirectoryWithTime) works exactly like [ListDirectory](#list_directory), but each returned entry additionally includes its last-modification time.
This allows a GCS to display or compare timestamps (e.g. to show the most recent log, or to detect which files have changed), without having to download file contents.

::: info
This is a separate opcode rather than a change to [ListDirectory](#list_directory) so that existing clients and servers keep working.
A client that needs timestamps uses `ListDirectoryWithTime`, and falls back to [ListDirectory](#ListDirectory) if the server NAKs with [UnknownCommand](#UnknownCommand).
:::

The sequence of operations, offset-based paging, and [EOF](#EOF) termination are identical to [List Directory](#list_directory).
The only difference is the per-entry format returned in the ACK payload `data`.

Each entry is separated with a null terminator (`\0`), and has the following format (where `type` is one of the letters **F**(ile), **D**(irectory), **S**(kip)):

```txt
<type><file_or_folder_name>\t<file_size_in_bytes>\t<modification_time>\0
```

The `modification_time` is the entry's last-modification time as a decimal ASCII string, in **seconds since the UNIX epoch (UTC)**.
If the modification time is not known — for example the file system does not store it, or the component has no real-time clock — the server must send `0`.

For example, given five files named _TestFile1.xml_ to _TestFile5.xml_, the entries returned at offset 2 might look like:

`FTestFile3.xml\t223\t1747459200\0FTestFile4.xml\t755568\t1746940800\0FTestFile5.xml\t11111\t0\0`

(here the modification time of _TestFile5.xml_ is unknown).

::: info
Only modification time is reported (not creation time).
POSIX `struct stat` — the interface exposed by most flight-stack file systems (e.g. NuttX) — has no portable creation-time field, so modification time is the only timestamp that can be reliably provided across backends.
:::

The GCS should create a timeout after the `ListDirectoryWithTime` command is sent and resend the message as needed (and [described above](#timeouts)).

### Create Directory

The sequence of operations for creating a directory is shown below (assuming there are no timeouts and all operations/requests succeed).
Note that this operation will fail if the directory is not empty.

[![Mermaid Sequence: Create Directory](https://mermaid.ink/img/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtO1xuICAgIHBhcnRpY2lwYW50IEdDU1xuICAgIHBhcnRpY2lwYW50IERyb25lXG4gICAgR0NTLT4-RHJvbmU6ICBDcmVhdGVEaXJlY3RvcnkoIGRhdGFbMF09cGF0aCwgc2l6ZT1sZW4ocGF0aCkgKVxuICAgIERyb25lLS0-PkdDUzogQUNLKHNpemU9MCkiLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtO1xuICAgIHBhcnRpY2lwYW50IEdDU1xuICAgIHBhcnRpY2lwYW50IERyb25lXG4gICAgR0NTLT4-RHJvbmU6ICBDcmVhdGVEaXJlY3RvcnkoIGRhdGFbMF09cGF0aCwgc2l6ZT1sZW4ocGF0aCkgKVxuICAgIERyb25lLS0-PkdDUzogQUNLKHNpemU9MCkiLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)

<!--
sequenceDiagram;
    participant GCS
    participant Drone
    GCS->>Drone:  CreateDirectory( data[0]=path, size=len(path) )
    Drone-- >>GCS: ACK(size=0)
-->

The sequence of operations is:

1. GCS sends [CreateDirectory](#CreateDirectory) command specifying the full path of the directory to be created.
   - The payload must specify: `data[0]`= directory path string, `size`=length of directory path string.
1. Drone attempts to create directory, and responds to the message with either:
   - ACK on success, containing payload `size`=0 (i.e. no data).
   - NAK on failure, with [error information](#error_codes).
   - The drone must clean up any resources associated with the request after sending the response.

The GCS should not create timeouts or handle the NAK case (other than to report an error to the user).

### Remove Directory

::: info
`RemoveDirectory` handling is implemented in PX4 but not in _QGroundControl_.
GCS behaviour is therefore not fully defined/tested.
:::

The sequence of operations for removing a directory is shown below (assuming there are no timeouts and all operations/requests succeed).
Note that this operation will fail if the directory is not empty.

[![Mermaid Sequence: Remove directory](https://mermaid.ink/img/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtO1xuICAgIHBhcnRpY2lwYW50IEdDU1xuICAgIHBhcnRpY2lwYW50IERyb25lXG4gICAgR0NTLT4-RHJvbmU6ICBSZW1vdmVEaXJlY3RvcnkoIGRhdGFbMF09cGF0aCwgc2l6ZT1sZW4ocGF0aCkgKVxuICAgIERyb25lLS0-PkdDUzogQUNLKHNpemU9MCkiLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtO1xuICAgIHBhcnRpY2lwYW50IEdDU1xuICAgIHBhcnRpY2lwYW50IERyb25lXG4gICAgR0NTLT4-RHJvbmU6ICBSZW1vdmVEaXJlY3RvcnkoIGRhdGFbMF09cGF0aCwgc2l6ZT1sZW4ocGF0aCkgKVxuICAgIERyb25lLS0-PkdDUzogQUNLKHNpemU9MCkiLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)

<!-- Original Diagram
sequenceDiagram;
    participant GCS
    participant Drone
    GCS->>Drone:  RemoveDirectory( data[0]=path, size=len(path) )
    Drone-- >>GCS: ACK(size=0)
-->

The sequence of operations is:

1. GCS sends [RemoveDirectory](#RemoveDirectory) command specifying the full path of the directory to be deleted.
   - The payload must specify: `data[0]`= directory path string, `size`=length of directory path string.
1. Drone attempts to delete directory, and responds to the message with either:
   - ACK on success, containing payload `size`=0 (i.e. no data).
   - NAK on failure, with [error information](#error_codes).
   - The drone must clean up any resources associated with the request after sending the response.

The GCS should create a timeout after the `RemoveDirectory` command is sent and resend the message as needed (and [described above](#timeouts)).

## Virtual Directory Entries (Directory Alias)

MAVFTP supports the concept of "standard" virtual directories for storing particular types of data in a flight-stack-independent and file-system-independent location.
This allows a GCS to provision or fetch data without having to know how or where it is stored in the target component.

::: info
MAVFTP represents storage on a remote component as a single entity accessible via a directory structure that the protocol exposes.
In order to represent multiple physical drives supported by the component, such as "c" or "d" drives, these can be abstracted to this structure.
Similarly, in order to support virtual directories, components must abstract the syntax that indicates their location, and map it to their physical file system(s).
:::

In the [MAVLink FTP URL Scheme](#mavlink-ftp-url-scheme) a virtual directory is specified using the `@<directory>` prefix, such as `@MAV_LOG` for log files.
When encoded in the [FILE_TRANSFER_PROTOCOL](#FILE_TRANSFER_PROTOCOL) this prefix should be prepended when a path is being specified.
For example, when [Listing a directory](#list_directory) the request might encode `data[0]` as `@MAV_LOG/path_in_log/`, and the recipient would be expected to map this to the underlying file system.
If the full path including virtual directory is not known, the recipient would NAK with [FileNotFound](#error_codes) (this is just another "not found" error case).

The following standard directory locations are defined:

- `@MAV_LOG`- Log files

## Implementations

The FTP v1 Protocol has been implemented (at least) in PX4, ArduPilot, QGroundControl and MAVSDK.
Those implementations can be used in your own code within the terms of their software licenses.

PX4 Implementation:

- [src/modules/mavlink/mavlink_ftp.cpp](https://github.com/PX4/Firmware/blob/master/src/modules/mavlink/mavlink_ftp.cpp)
- [src/modules/mavlink/mavlink_ftp.h](https://github.com/PX4/Firmware/blob/master/src/modules/mavlink/mavlink_ftp.h)

_QGroundControl_ implementation:

- [src/uas/FileManager.cc](https://github.com/mavlink/qgroundcontrol/blob/master/src/Vehicle/FTPManager.cc)
- [/src/uas/FileManager.h](https://github.com/mavlink/qgroundcontrol/blob/master/src/Vehicle/FTPManager.h)

Everything is run by the client (QGC in this case); the server simply responds to packets in order as they arrive.
There's buffering in the server for a little overlap (two packets in the queue at a time). This is a tradeoff between memory and link latency which may need to be reconsidered at some point.

The MAVLink receiver thread copies an incoming request verbatim from the MAVLink buffer into a request queue, and queues a low-priority work item to handle the packet. This avoids trying to do file I/O on the MAVLink receiver thread, as well as avoiding yet another worker thread. The worker is responsible for directly queuing replies, which are sent with the same sequence number as the request.

The implementation on PX4 only supports a single session.

## CRC32 Implementation

The CRC32 algorithm used by MAVLink FTP is described in [MAVLink CRCs](../guide/crc.md).

## MAVLink FTP URL Scheme

Resources to be downloaded using MAVLink FTP can be referenced using the following URL-like format:

```txt
mftp://[;comp=<id>][/@<directory>]/<path>
```

Where:

- `path`: the location of the resource on the target component and/or in the virtual directory.
- `id`: target _component ID_ of the component hosting the resource.
  The `;comp=<id>` part is optional (if omitted, the resource is downloaded from the current component).
  It should be specified if the request must be redirected
- `directory`: A [virtual directory](#virtual-directory-entries-directory-alias) on the target source.
  The `@<directory>` part is optional (if omitted, the resource is downloaded from the "normal" directory path).

For example:

- A GCS connected to an autopilot might download a file using the following URL:

  ```txt
  ## FTP resource 'camera.xml' from current component
  mftp:///camera.xml
  ```

- A GCS connected to an autopilot through a companion computer might host the metadata on the companion (e.g. due to lack of flash space, faster download or if there's a central MAVFTP server on the vehicle), so it would need to specify the component ID of the component running on the companion (e.g. 100 for the camera), so that the request is redirected:

  ```txt
  ## FTP resource '/info/version.json' from component with id 100
  mftp://;comp=100/info/version.json
  ```

- A GCS wanting to download a log might use

  ```txt
  ## FTP resource '2024.log' from @MAV_LOG virtual directory
  mftp:///@MAV_LOG/2024.log
  ```
