Properly handle out of order read fragments

This commit is contained in:
Lucian Petrut
2026-09-07 12:10:03 +00:00
parent 1e9c2816ef
commit c590089866
3 changed files with 33 additions and 18 deletions
+11 -8
View File
@@ -67,27 +67,30 @@ Reply payload (handle is zeroed; lengths describe this fragment):
| 8 | `uint64` | `1` (read) |
| 16 | `uint64` | Byte offset of the **request** |
| 24 | `uint32` | Total request length |
| 28 | `uint32` | Fragment index (`0`, `1`, …) |
| 28 | `uint32` | Byte offset of this fragment (`0`, `65536`, …) |
| 32 | `uint32` | This fragment’s byte length |
| 36 | `uint32` | Same as offset 32 |
| 40 | `uint32` | `0` |
When there is a single fragment, offsets 24–31 look like a `uint64`
length (index is 0). The 129-sector capture shows why they are two
`uint32`s: fragment 0 has `(66048, 0)` then chunk 65536; fragment 1
has `(66048, 1)` then chunk 512.
length (the fragment offset is 0). The 129-sector capture shows why
they are two `uint32`s: fragment 0 has `(66048, 0)` then chunk 65536;
fragment 1 has `(66048, 65536)` then chunk 512. `0x00010000` at offset
28 is the byte offset, not a 0-based index.
Read loop: receive fragments with that `opId` until the concatenated
data length equals the request. Use the `uint32` at payload offset 32
as the extra-data size for that fragment. Do not treat extra data as
part of AIO `size` (that field stays 44).
as the extra-data size for that fragment, and copy it to the byte
offset at payload offset 28 — fragments are not always delivered in
order. Do not treat extra data as part of AIO `size` (that field stays
44).
129-sector example (one client request, two server fragments):
```
C: type=7 opId=18 size=44 offset=0 length=66048
S: type=7 opId=18 size=44 index=0 chunk=65536 + 65536 data
S: type=7 opId=18 size=44 index=1 chunk=512 + 512 data
S: type=7 opId=18 size=44 dest=0 chunk=65536 + 65536 data
S: type=7 opId=18 size=44 dest=65536 chunk=512 + 512 data
```
## Lab check
+2 -1
View File
@@ -227,7 +227,8 @@ What that comparison showed:
VDDK repeats the byte length there.
- Zeros on the wire are real transferred zeros, not a sparse skip.
Replay: `NfcDisk.read` loops on fragments until `length` bytes arrive.
Replay: `NfcDisk.read` places fragments at the byte offset in the
reply (they may arrive out of order) until `length` bytes are filled.
Proof: `tests/integration/test_nfc_read_write.py` writes a known pattern
(including a 129-sector read that must assemble two fragments) and
checks the bytes that came back.