Files
OpenVixDiskLib/docs/nfc_write.md
T
Lucian PetrutandCursor 4ddf0c015d Add openvixdisklib as an open NBD replacement for VMware VDDK.
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]>
2026-09-07 11:41:01 +00:00

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).