VDDK is no longer publicly distributed, so this library reverse-engineers the vSphere NFC path and exposes ConnectEx, Open, Read, and Write without the proprietary SDK. Co-authored-by: Cursor <[email protected]>
4.1 KiB
VDDK NFC disk write
This document records how NfcDisk.write in
openvixdisklib/nfc_open.py implements VixDiskLib_Write over NFC AIO.
The request layout matches the captured VixDiskLib_Read IO message in
docs/nfc_read.md. Open flags and the IO direction field were taken
from a strace of VDDK 8 writing one sector to a temporary 10 GiB
disk (docs/reverse_engineering_procedure.md).
The public VixDiskLib_Write prototype is in .vddk/vixDiskLib.h:
VixError VixDiskLib_Write(VixDiskLibHandle diskHandle,
VixDiskLibSectorType startSector,
VixDiskLibSectorType numSectors,
const uint8 *writeBuffer);
ConnectEx(..., Bool readOnly, ...) with readOnly=FALSE and Open
without VIXDISKLIB_FLAG_OPEN_READ_ONLY (that flag is 1 << 2 in the
same header) is what produces the writable NFC open below. The ticket
must be NfcRandomAccessOpenDisk (docs/nfc_auth.md); flags 0x1a
on a NfcGetVmFiles ticket are rejected as VIX_E_FILE_READ_ONLY.
Mapping from VDDK
Units on the wire are bytes, as for reads:
offset = startSector * sectorSize
length = numSectors * sectorSize
| VDDK call | Wire effect |
|---|---|
Open without VIXDISKLIB_FLAG_OPEN_READ_ONLY |
OPEN_FILE flags 0x1a |
Open with VIXDISKLIB_FLAG_OPEN_READ_ONLY |
OPEN_FILE flags 0x1e (read-only) |
VixDiskLib_Write(h, start, n, buf) |
IO opcode 0, then n * 512 data bytes |
VixDiskLib_Read(h, start, n, buf) |
IO opcode 1 |
0x1e vs 0x1a is bit 0x04, the same value as
VIXDISKLIB_FLAG_OPEN_READ_ONLY. Writable opens clear that bit.
VDDK also issues several DDB_GET queries and a type-10
GET_FILE_INFO (longContentID) before the first write. They are not
required to write or read sectors.
Request (44 bytes + data)
Little-endian, after the usual 16-byte AIO header
(magic 0xA100DA7A, type 7, size 44, monotonic opId):
| Offset | Type | Write(start, n) |
|---|---|---|
| 0 | uint64 |
File handle from OPEN_FILE |
| 8 | uint64 |
0 (NFC_AIO_IO_WRITE; read uses 1) |
| 16 | uint64 |
Byte offset |
| 24 | uint64 |
Byte length |
| 32 | uint32 |
Byte length (same value) |
| 36 | uint32 |
Byte length (same value) |
| 40 | uint32 |
0 |
Sector bytes follow the 44-byte payload and are not counted in AIO
size. VDDK sends header + payload + data in one write(). The
replacement may split that into two sendalls; TCP does not care.
The server replies with a type-7 header and a 44-byte payload for that
opId. There is no extra data on the write reply (unlike reads).
A 1-sector VDDK write was 572 bytes on the wire: 16 + 44 + 512.
Client-side split
NfcAioInitSession advertises a 64 KiB buffer. VDDK splits writes
larger than that into 64 KiB chunks (VDDK programming guide). The
Python client does the same: several IO requests of at most
NFC_AIO_BUFFER_SIZE bytes, each with its own opId.
Python replacement
NfcDisk.write(start_sector, num_sectors, data) in
openvixdisklib/nfc_open.py. open_disk(..., read_only=False) selects
flags 0x1a. The drop-in handle exposes the same shape as VDDK:
connect(read_only=False), open without
VIXDISKLIB_FLAG_OPEN_READ_ONLY, then write.
Integration tests create an empty 10 GiB disk, write known patterns,
and read them back (tests/integration/test_nfc_read_write.py,
tests/integration/test_openvixdisklib.py). Cross-check tests write
with VDDK and with the replacement and read with both
(tests/integration/test_crosscheck.py).