fix(mavftp): harden FTP reply handling and validate transfer settings - #1274
fix(mavftp): harden FTP reply handling and validate transfer settings#1274amilcarlucas wants to merge 29 commits into
Conversation
Capture a MAVFTPReturn failure from a download callback and return it from\nthe reply loop.\n\nThis lets callers of cmd_getparams detect malformed packed parameter data\ninstead of reporting a successful transfer after the callback has rejected\nthe payload. The callback result is cleared for every new download so a\nprior failure cannot affect a later operation. finish termination before returning callback errors return packed-parameter decode failures consume reported callback failures fix(mavftp): skip rejected download output
A successful BurstReadFile reply can contain only part of a download.\n\nReturn success after processing every ACK, including replies that request\nthe next burst. This prevents a valid partial reply from being reported as\na transfer failure on slow telemetry links, while preserving EOF and gap\nrecovery handling. fix(mavftp): safely decode malformed burst nacks
Validate each packed-parameter record before slicing or unpacking it.\n\nMalformed @PARAM responses can end in a partial header or value, or claim a\nshared name prefix longer than the prior parameter name. Report these cases\nas decode failures rather than raising struct errors or producing corrupted\nparameter names.
Validate decoded packed-parameter records against the transmitted num_params\nheader field.\n\ntotal_params describes the controller-wide parameter count and can be larger\nthan a valid subset response. Using num_params accepts those subset downloads\nwhile still rejecting incomplete or overlong payloads.
Mark file handles opened internally by cmd_put as MAVFTP-owned.\n\nThe existing staging-resource cleanup then closes those handles when the FTP\nsession ends, preventing descriptor leaks and file-lock problems on repeated\nuploads. Handles supplied through cmd_put's fh argument remain caller-owned\nand are left open.
Replace the instance-wide operation_complete flag with a completion record containing the request opcode and reply sequence. This prevents delayed or duplicated replies from a previous operation from completing the command currently being awaited. Add a regression test covering a delayed ListDirectory EOF arriving before a RemoveFile acknowledgement.
Adopt the session IDs returned by OpenFileRO and CreateFile before issuing follow-up requests. Accept those allocation ACKs even though their session differs from the request session. Retain in-flight ReadFile, BurstReadFile, and WriteFile requests so timeout retries resend the original request with its original sequence number. Return decoded NACK errors for gap reads and writes instead of reporting success or FileProtected. Terminate active remote file sessions when either download loop times out. Add regression coverage for session allocation, NACK propagation, retransmission sequence reuse, and both timeout cleanup paths.
The parameter decoder imported typing.Tuple and typing.Dict for annotations but called them as constructors at runtime. This broke getparams on supported Python versions after a successful FTP transfer. Use the built-in tuple and dict constructors for sorting and rebuilding decoded parameter mappings.
Treat callback-owned downloads as fully consumed by the callback and skip the generic publish step. This prevents virtual MAVFTP paths such as @PARAM/param.pck?withdefaults=1 from being treated as local files. Add regression coverage for successful callbacks.
Keep a reply result when it matches an in-flight request, including successful replies. A final out-of-order gap ReadFile ACK can complete a download after a later BurstReadFile becomes last_op; previously the data was correct but process_ftp_reply() returned Fail. Add regression coverage for this completion path, stabilize the mocked read-timeout clock, and log the number of remaining gaps correctly.
The callback path already clears publish_result before invoking the callback, making the failure-branch assignment redundant. Remove the dead assignment while preserving callback failure propagation and the guarantee that callback-owned downloads are not published as files. Document the callback success and failure regression coverage, and make the successful callback fixture advertise the exact four-byte payload it consumes.
Keep read_sector() downloads in memory, retain the caller's requested size, and start BurstReadFile at the requested offset. This prevents FUSE reads from downloading the whole remote file, returning an oversized range, or publishing a local file named after the remote path. Add regression coverage for offset reads, returned range length, and absence of local output.
Use integer parameter type IDs in save_params(), matching the values returned by ftp_param_decode() and extract_params(). This makes the getparams datatype-comment option usable for valid parameter files. Add regression coverage for the emitted float datatype comment.
Return a ReadFile failure after terminating a session for a short acknowledgement that does not satisfy an outstanding gap. This prevents the reply loop from reporting a completed download after a file-size race or malformed reply. Add regression coverage for the unexpected short gap-ACK path.
Catch download callback exceptions, retain an FTP failure result, and continue through the normal session cleanup path. This also makes getparams output failures fail the command rather than leaking the active FTP session. Add regression coverage for callback exceptions during download completion.
Reject unsafe command settings before they can violate retry invariants, stall transfer queues, divide by zero, or exceed the MAVFTP payload limit. Defensively validate upload write sizing for callers that set settings directly. Add regressions for invalid command settings and direct invalid upload block sizes.
Validate file listing entries before splitting their name and size fields. Malformed server data now returns InvalidDataSize instead of raising ValueError from the reply-processing loop. Add regression coverage for a file entry without a size separator.
Add an executable MAVFTP integration script for replaying operations against a connected flight controller. The test verifies heartbeat communication and exercises status, configuration, cancellation, listing, upload, CRC, download, rename, removal, directory creation/removal, and parameter retrieval. Temporary remote paths are unique per run and cleaned up on completion or failure.
Complete synchronous range reads as soon as the requested bytes and any gaps are satisfied, even when the reply is a full-sized burst.\n\nAdd a regression covering a small request fulfilled by a full burst so the client terminates instead of requesting data through EOF.
Store synchronous range-read payloads relative to the requested offset so a small read does not allocate a buffer proportional to the remote file offset.\n\nReturn the in-memory range directly from the compact buffer and add a regression covering a one-megabyte offset with a two-byte read.
Store synchronous range-read payloads relative to the requested offset so small reads do not allocate memory proportional to the remote offset. Keep remote and buffer positions distinct while handling burst gaps, retries, and completion, and add a regression for a two-byte read at a one-megabyte offset.
Exercise directory creation/removal and synchronous range reads using the crafted uploaded file, including a small full-burst request and a high-offset request. Reinitialize the MAVFTP connection before rename/delete to avoid late range-read termination replies interfering with subsequent mutations. Document the controller firmware limitation when that handshake remains incomplete.
|
Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Full report: https://uav.tridgell.net/DevCallReviews/2026_09_04/devcall_pr_reviews.html#prpymavlink-1274 Reviewed at head The title is the main findingIt's called "Ruff lint annotations" with a one-line body, and a reviewer skimming it would reasonably treat it as cosmetic. I measured rather than assumed: parsing Six methods added, none removed: None of that is necessarily wrong — but it's a MAVFTP correctness rewrite and deserves to be reviewed as one. Please retitle and describe it, or split the behavioural work out. CI: three deterministic pylint messages, all introduced here
The two Annotations that are wrongSince typing is the stated purpose, these matter on their own terms:
And the three headline aliases are all Related: the new settings validation lives only in One thing I am not blaming on this PRA cross-check pass reported that Compatibility is fine. pymavlink declares no |
Keep the rename destination stored as arg2 so main() forwards both paths correctly, while displaying new_remote_path in help output.
Add explicit annotations for MAVFTP state, operations, parameter decoding, and command interfaces while retaining compatibility with the project's supported Python versions. Apply the accompanying Ruff-driven cleanup to simplify formatting, resource cleanup, and type-safe optional values without changing the FTP protocol contract. Keep generated sources untouched so later behavioral fixes can be reviewed independently.
f0e7942 to
4509d5c
Compare
|
Automated review note — AI-generated (Claude). A correction to my review above — please read this one first. I measured that review against the wrong base, and most of what I attributed to this PR belongs to #1267. GitHub's diff is against master, so for a PR stacked on another it includes the whole base series. I read it as this PR's work. Sorry — that's the second-worst kind of review error after a wrong finding. Measured against this PR's actual stacked base (#1267's head
So my headline was wrong. This is not "a MAVFTP correctness rewrite mislabelled as a lint change" — it is close to what the title says. The What does stand, verified as yours:
The Net: with the base corrected, the remaining ask on this PR is small — move three lines above the imports to below them, and widen one annotation. Details in the follow-up report: https://uav.tridgell.net/DevCallReviews/followups/2026_09_04_2335/devcall_pr_reviews.html |
Previous noteAutomated review note — AI-generated (Claude). Short addendum to the correction directly above, which was already stale when I posted it. You pushed At
So nothing from my review is outstanding. CI on the new head is 31 queued; worth watching that the pylint job comes back clean now that I'll pick this head up properly on the next follow-up pass rather than review it inside an addendum. |
Move MAVFTP transfer-setting constraints into MAVFTPSettings so constructor inputs, direct library assignments, CLI options, and cmd_set() share one validation path. Reject non-finite and out-of-range values, enforce the idle-detection/read-retry relationship, and restore the prior value when an assignment fails validation. CLI validation now happens before opening a MAVLink transport. Add regression coverage for command, constructor, and direct-library entry points to prevent invalid settings from reaching the transfer state machine.
4509d5c to
8794d46
Compare
|
Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Full report: https://uav.tridgell.net/DevCallReviews/2026_09_05/devcall_pr_reviews.html#prpymavlink-1274 Re-reviewed at head Measured against the real stacked base (#1267's head Resolved
The refactor is behaviour-preserving, and my numbers needed correctingAgainst the real base the AST delta with annotations and docstrings stripped is −497/+818 node lines, +2.07%, adding five methods ( One small new thing
|
requires #1267