From 94ef290b656661322c22151d2b7ef5361acfde1c Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Wed, 28 Oct 2026 19:40:18 +0900 Subject: [PATCH 1/2] feat(vmnet): add batched packet reads and writes --- cmd/vzbridgegen/metadata/native.json | 2 +- internal/vzbridge/native.h | 5 +- internal/vzbridge/native_bindings.go | 42 +++-- internal/vzbridge/vmnet_interface.m | 84 ++++++++-- vmnet/batch.go | 181 ++++++++++++++++++++++ vmnet/batch_example_test.go | 83 ++++++++++ vmnet/batch_test.go | 220 +++++++++++++++++++++++++++ vmnet/interface.go | 59 ++----- 8 files changed, 604 insertions(+), 72 deletions(-) create mode 100644 vmnet/batch.go create mode 100644 vmnet/batch_example_test.go create mode 100644 vmnet/batch_test.go diff --git a/cmd/vzbridgegen/metadata/native.json b/cmd/vzbridgegen/metadata/native.json index 9fe164cd..033e1b0f 100644 --- a/cmd/vzbridgegen/metadata/native.json +++ b/cmd/vzbridgegen/metadata/native.json @@ -1 +1 @@ -{"Schema":1,"ExtractorSHA256":"2b13dc4d73aeadc6673989f31f4220a469c8f26d232def0bdb52c5bb2a717788","HeaderSHA256":"c86fc72b963c659883ce258c8a129cc9ad899bea75b042ff7cc2012ff257f51d","Bindings":[{"Name":"ObjectQueue","Symbol":"vz_objectQueue","Result":"unsafe.Pointer","Parameters":[{"Name":"object","Type":"objc.NSObject"}],"Queue":false,"ObjectQueue":false},{"Name":"SetObjectQueue","Symbol":"vz_setObjectQueue","Result":"","Parameters":[{"Name":"object","Type":"unsafe.Pointer"},{"Name":"queue","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"InheritObjectQueue","Symbol":"vz_inheritObjectQueue","Result":"","Parameters":[{"Name":"object","Type":"unsafe.Pointer"},{"Name":"source","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"AttachDeviceVZUSBController","Symbol":"vz_attachDeviceVZUSBController","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"objc.NSObject"},{"Name":"p2","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"CancelInstallVZMacOSInstaller","Symbol":"vz_cancelInstallVZMacOSInstaller","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":true},{"Name":"DetachDeviceVZUSBController","Symbol":"vz_detachDeviceVZUSBController","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"objc.NSObject"},{"Name":"p2","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"DispatchQueuePointer","Symbol":"vz_dispatchQueuePointer","Result":"unsafe.Pointer","Parameters":null,"Queue":false,"ObjectQueue":false},{"Name":"Drain","Symbol":"vz_drain","Result":"","Parameters":null,"Queue":false,"ObjectQueue":false},{"Name":"FetchLatestSupportedMacOSRestoreImageWithCompletionHandler","Symbol":"vz_fetchLatestSupportedMacOSRestoreImageWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"uint64"}],"Queue":true,"ObjectQueue":false},{"Name":"GetUUIDUSBDevice","Symbol":"vz_getUUIDUSBDevice","Result":"*objc.Pointer","Parameters":[{"Name":"p0","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":true},{"Name":"InstallByVZMacOSInstaller","Symbol":"vz_installByVZMacOSInstaller","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"},{"Name":"p2","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"InvalidateVZVirtioSocketListener","Symbol":"vz_invalidateVZVirtioSocketListener","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":true},{"Name":"LinuxInstallRosetta","Symbol":"vz_linuxInstallRosetta","Result":"","Parameters":[{"Name":"p0","Type":"uint64"}],"Queue":true,"ObjectQueue":false},{"Name":"LoadMacOSRestoreImageFile","Symbol":"vz_loadMacOSRestoreImageFile","Result":"","Parameters":[{"Name":"p0","Type":"string"},{"Name":"p1","Type":"uint64"}],"Queue":true,"ObjectQueue":false},{"Name":"MacOSRestoreImageMajorVersion","Symbol":"vz_macOSRestoreImageMajorVersion","Result":"int64","Parameters":[{"Name":"p0","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"MacOSRestoreImageMinorVersion","Symbol":"vz_macOSRestoreImageMinorVersion","Result":"int64","Parameters":[{"Name":"p0","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"MacOSRestoreImagePatchVersion","Symbol":"vz_macOSRestoreImagePatchVersion","Result":"int64","Parameters":[{"Name":"p0","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"SetInvalidDiskModeError","Symbol":"vz_setInvalidDiskModeError","Result":"","Parameters":[{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVZDiskBlockDeviceStorageDeviceAttachment","Symbol":"vz_newVZDiskBlockDeviceStorageDeviceAttachment","Result":"*objc.Pointer","Parameters":[{"Name":"p0","Type":"int32"},{"Name":"p1","Type":"bool"},{"Name":"p2","Type":"int32"},{"Name":"p3","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVZFileHandleNetworkDeviceAttachment","Symbol":"vz_newVZFileHandleNetworkDeviceAttachment","Result":"*objc.Pointer","Parameters":[{"Name":"p0","Type":"int32"},{"Name":"p1","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVZFileHandleSerialPortAttachment","Symbol":"vz_newVZFileHandleSerialPortAttachment","Result":"*objc.Pointer","Parameters":[{"Name":"readFD","Type":"int32"},{"Name":"hasRead","Type":"bool"},{"Name":"writeFD","Type":"int32"},{"Name":"hasWrite","Type":"bool"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVZMacOSInstaller","Symbol":"vz_newVZMacOSInstaller","Result":"*objc.Pointer","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"string"}],"Queue":true,"ObjectQueue":true},{"Name":"NewVZNetworkBlockDeviceStorageDeviceAttachment","Symbol":"vz_newVZNetworkBlockDeviceStorageDeviceAttachment","Result":"*objc.Pointer","Parameters":[{"Name":"p0","Type":"string"},{"Name":"p1","Type":"float64"},{"Name":"p2","Type":"bool"},{"Name":"p3","Type":"int32"},{"Name":"p4","Type":"*unsafe.Pointer"},{"Name":"p5","Type":"uint64"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVZVirtioSocketListener","Symbol":"vz_newVZVirtioSocketListener","Result":"*objc.Pointer","Parameters":[{"Name":"device","Type":"objc.NSObject"},{"Name":"context","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"NewVZVirtualMachineWithDispatchQueue","Symbol":"vz_newVZVirtualMachineWithDispatchQueue","Result":"*objc.Pointer","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"},{"Name":"p2","Type":"uint64"}],"Queue":false,"ObjectQueue":false},{"Name":"PauseWithCompletionHandler","Symbol":"vz_pauseWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"ReleaseObject","Symbol":"vz_releaseObject","Result":"","Parameters":[{"Name":"p0","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"RestoreMachineStateFromURLWithCompletionHandler","Symbol":"vz_restoreMachineStateFromURLWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"},{"Name":"p2","Type":"string"}],"Queue":true,"ObjectQueue":true},{"Name":"ResumeWithCompletionHandler","Symbol":"vz_resumeWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"SaveMachineStateToURLWithCompletionHandler","Symbol":"vz_saveMachineStateToURLWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"},{"Name":"p2","Type":"string"}],"Queue":true,"ObjectQueue":true},{"Name":"SetCallback","Symbol":"vz_setCallback","Result":"","Parameters":[{"Name":"p0","Type":"uint64"}],"Queue":true,"ObjectQueue":false},{"Name":"SocketConnectionDuplicatedFileDescriptor","Symbol":"vz_socketConnectionDuplicatedFileDescriptor","Result":"int32","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":true},{"Name":"NewGraphicApplication","Symbol":"vz_newGraphicApplication","Result":"unsafe.Pointer","Parameters":null,"Queue":false,"ObjectQueue":false},{"Name":"AddGraphicWindow","Symbol":"vz_addGraphicWindow","Result":"","Parameters":[{"Name":"application","Type":"unsafe.Pointer"},{"Name":"machine","Type":"objc.NSObject"},{"Name":"width","Type":"float64"},{"Name":"height","Type":"float64"},{"Name":"title","Type":"string"},{"Name":"enableController","Type":"bool"}],"Queue":false,"ObjectQueue":false},{"Name":"FreeGraphicApplication","Symbol":"vz_freeGraphicApplication","Result":"","Parameters":[{"Name":"application","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"StartWithCompletionHandler","Symbol":"vz_startWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"StartWithOptionsCompletionHandler","Symbol":"vz_startWithOptionsCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"objc.NSObject"},{"Name":"p2","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"StopWithCompletionHandler","Symbol":"vz_stopWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"VZVirtioSocketDevice_connectToPort","Symbol":"vz_VZVirtioSocketDevice_connectToPort","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint32"},{"Name":"p2","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"NewVmnetNetworkConfiguration","Symbol":"vz_newVmnetNetworkConfiguration","Result":"unsafe.Pointer","Parameters":[{"Name":"mode","Type":"int32"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVmnetNetwork","Symbol":"vz_newVmnetNetwork","Result":"unsafe.Pointer","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"DisableVmnetDHCP","Symbol":"vz_disableVmnetDHCP","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"DisableVmnetDNSProxy","Symbol":"vz_disableVmnetDNSProxy","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"DisableVmnetNAT44","Symbol":"vz_disableVmnetNAT44","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"DisableVmnetNAT66","Symbol":"vz_disableVmnetNAT66","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"DisableVmnetRouterAdvertisement","Symbol":"vz_disableVmnetRouterAdvertisement","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"SetVmnetExternalInterface","Symbol":"vz_setVmnetExternalInterface","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"name","Type":"string"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"SetVmnetMTU","Symbol":"vz_setVmnetMTU","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"mtu","Type":"uint32"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"SetVmnetIPv4Subnet","Symbol":"vz_setVmnetIPv4Subnet","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"address","Type":"uint32"},{"Name":"mask","Type":"uint32"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"SetVmnetIPv6Prefix","Symbol":"vz_setVmnetIPv6Prefix","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"address","Type":"unsafe.Pointer"},{"Name":"prefixLength","Type":"uint8"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"AddVmnetDHCPReservation","Symbol":"vz_addVmnetDHCPReservation","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"mac","Type":"unsafe.Pointer"},{"Name":"address","Type":"uint32"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"AddVmnetPortForwarding","Symbol":"vz_addVmnetPortForwarding","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"udp","Type":"bool"},{"Name":"ipv6","Type":"bool"},{"Name":"externalPort","Type":"uint16"},{"Name":"internalPort","Type":"uint16"},{"Name":"address","Type":"unsafe.Pointer"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"VmnetIPv4Subnet","Symbol":"vz_vmnetIPv4Subnet","Result":"uint64","Parameters":[{"Name":"network","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"VmnetIPv6Prefix","Symbol":"vz_vmnetIPv6Prefix","Result":"uint8","Parameters":[{"Name":"network","Type":"objc.NSObject"},{"Name":"address","Type":"unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVmnetNetworkDeviceAttachment","Symbol":"vz_newVmnetNetworkDeviceAttachment","Result":"*objc.Pointer","Parameters":[{"Name":"network","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"VmnetNetworkDeviceAttachmentNetwork","Symbol":"vz_vmnetNetworkDeviceAttachmentNetwork","Result":"unsafe.Pointer","Parameters":[{"Name":"attachment","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"ReleaseCFObject","Symbol":"vz_releaseCFObject","Result":"","Parameters":[{"Name":"object","Type":"unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"StartVmnetInterface","Symbol":"vz_startVmnetInterface","Result":"unsafe.Pointer","Parameters":[{"Name":"network","Type":"objc.NSObject"},{"Name":"name","Type":"string"},{"Name":"options","Type":"uint32"},{"Name":"callback","Type":"uint64"},{"Name":"context","Type":"uint64"},{"Name":"maxPacketSize","Type":"unsafe.Pointer"},{"Name":"mac","Type":"unsafe.Pointer"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"CopyVmnetBridgedInterfaces","Symbol":"vz_copyVmnetBridgedInterfaces","Result":"*objc.Pointer","Parameters":[{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"StopVmnetInterface","Symbol":"vz_stopVmnetInterface","Result":"","Parameters":[{"Name":"session","Type":"unsafe.Pointer"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"ReadVmnetPacket","Symbol":"vz_readVmnetPacket","Result":"int32","Parameters":[{"Name":"session","Type":"unsafe.Pointer"},{"Name":"buffer","Type":"unsafe.Pointer"},{"Name":"size","Type":"uint64"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"WriteVmnetPacket","Symbol":"vz_writeVmnetPacket","Result":"","Parameters":[{"Name":"session","Type":"unsafe.Pointer"},{"Name":"buffer","Type":"unsafe.Pointer"},{"Name":"size","Type":"uint64"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"CopyVmnetNetworkSerialization","Symbol":"vz_copyVmnetNetworkSerialization","Result":"unsafe.Pointer","Parameters":[{"Name":"network","Type":"objc.NSObject"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVmnetNetworkWithSerialization","Symbol":"vz_newVmnetNetworkWithSerialization","Result":"unsafe.Pointer","Parameters":[{"Name":"serialization","Type":"objc.NSObject"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"ReleaseXPCObject","Symbol":"vz_releaseXPCObject","Result":"","Parameters":[{"Name":"object","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"ListenVmnetNetwork","Symbol":"vz_listenVmnetNetwork","Result":"unsafe.Pointer","Parameters":[{"Name":"name","Type":"string"},{"Name":"serialization","Type":"objc.NSObject"},{"Name":"requirement","Type":"string"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"CloseVmnetNetworkService","Symbol":"vz_closeVmnetNetworkService","Result":"","Parameters":[{"Name":"service","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"RequestVmnetNetwork","Symbol":"vz_requestVmnetNetwork","Result":"unsafe.Pointer","Parameters":[{"Name":"name","Type":"string"},{"Name":"requirement","Type":"string"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"CancelVmnetNetworkRequest","Symbol":"vz_cancelVmnetNetworkRequest","Result":"","Parameters":[{"Name":"request","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"WaitVmnetNetworkRequest","Symbol":"vz_waitVmnetNetworkRequest","Result":"unsafe.Pointer","Parameters":[{"Name":"request","Type":"unsafe.Pointer"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"ReleaseVmnetNetworkRequest","Symbol":"vz_releaseVmnetNetworkRequest","Result":"","Parameters":[{"Name":"request","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"GraphicsDisplaySize","Symbol":"vz_graphicsDisplaySize","Result":"","Parameters":[{"Name":"display","Type":"objc.NSObject"},{"Name":"size","Type":"unsafe.Pointer"}],"Queue":true,"ObjectQueue":true},{"Name":"ReconfigureGraphicsDisplaySize","Symbol":"vz_reconfigureGraphicsDisplaySize","Result":"","Parameters":[{"Name":"display","Type":"objc.NSObject"},{"Name":"width","Type":"float64"},{"Name":"height","Type":"float64"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":true},{"Name":"NewEFICertificate","Symbol":"vz_newEFICertificate","Result":"*objc.Pointer","Parameters":[{"Name":"data","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"EfiCertificateData","Symbol":"vz_efiCertificateData","Result":"*objc.Pointer","Parameters":[{"Name":"signature","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"IsEFICertificate","Symbol":"vz_isEFICertificate","Result":"bool","Parameters":[{"Name":"signature","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"EnableEFISecureBoot","Symbol":"vz_enableEFISecureBoot","Result":"","Parameters":[{"Name":"store","Type":"objc.NSObject"},{"Name":"signature","Type":"objc.NSObject"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"RegisterUSBAccessories","Symbol":"vz_registerUSBAccessories","Result":"*objc.Pointer","Parameters":[{"Name":"context","Type":"uint64"}],"Queue":false,"ObjectQueue":false},{"Name":"UnregisterUSBAccessories","Symbol":"vz_unregisterUSBAccessories","Result":"","Parameters":[{"Name":"listener","Type":"objc.NSObject"},{"Name":"context","Type":"uint64"}],"Queue":false,"ObjectQueue":false},{"Name":"USBAccessoryRegistryID","Symbol":"vz_USBAccessoryRegistryID","Result":"uint64","Parameters":[{"Name":"accessory","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"USBAccessoryDeviceDescriptor","Symbol":"vz_USBAccessoryDeviceDescriptor","Result":"*objc.Pointer","Parameters":[{"Name":"accessory","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"USBAccessoryConfigurationDescriptor","Symbol":"vz_USBAccessoryConfigurationDescriptor","Result":"*objc.Pointer","Parameters":[{"Name":"accessory","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"ObserveUSBDisconnect","Symbol":"vz_observeUSBDisconnect","Result":"*objc.Pointer","Parameters":[{"Name":"controller","Type":"objc.NSObject"},{"Name":"context","Type":"uint64"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":true},{"Name":"StopObservingUSBDisconnect","Symbol":"vz_stopObservingUSBDisconnect","Result":"","Parameters":[{"Name":"controller","Type":"objc.NSObject"},{"Name":"observer","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":true},{"Name":"EfiSecureBootEnabled","Symbol":"vz_efiSecureBootEnabled","Result":"bool","Parameters":[{"Name":"store","Type":"objc.NSObject"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false}]} +{"Schema":1,"ExtractorSHA256":"2b13dc4d73aeadc6673989f31f4220a469c8f26d232def0bdb52c5bb2a717788","HeaderSHA256":"5d0da7a8f3b9ec8ae3bb3d4d9b0555fd9ab7f13d6e16a63d478b6a770276cffa","Bindings":[{"Name":"ObjectQueue","Symbol":"vz_objectQueue","Result":"unsafe.Pointer","Parameters":[{"Name":"object","Type":"objc.NSObject"}],"Queue":false,"ObjectQueue":false},{"Name":"SetObjectQueue","Symbol":"vz_setObjectQueue","Result":"","Parameters":[{"Name":"object","Type":"unsafe.Pointer"},{"Name":"queue","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"InheritObjectQueue","Symbol":"vz_inheritObjectQueue","Result":"","Parameters":[{"Name":"object","Type":"unsafe.Pointer"},{"Name":"source","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"AttachDeviceVZUSBController","Symbol":"vz_attachDeviceVZUSBController","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"objc.NSObject"},{"Name":"p2","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"CancelInstallVZMacOSInstaller","Symbol":"vz_cancelInstallVZMacOSInstaller","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":true},{"Name":"DetachDeviceVZUSBController","Symbol":"vz_detachDeviceVZUSBController","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"objc.NSObject"},{"Name":"p2","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"DispatchQueuePointer","Symbol":"vz_dispatchQueuePointer","Result":"unsafe.Pointer","Parameters":null,"Queue":false,"ObjectQueue":false},{"Name":"Drain","Symbol":"vz_drain","Result":"","Parameters":null,"Queue":false,"ObjectQueue":false},{"Name":"FetchLatestSupportedMacOSRestoreImageWithCompletionHandler","Symbol":"vz_fetchLatestSupportedMacOSRestoreImageWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"uint64"}],"Queue":true,"ObjectQueue":false},{"Name":"GetUUIDUSBDevice","Symbol":"vz_getUUIDUSBDevice","Result":"*objc.Pointer","Parameters":[{"Name":"p0","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":true},{"Name":"InstallByVZMacOSInstaller","Symbol":"vz_installByVZMacOSInstaller","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"},{"Name":"p2","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"InvalidateVZVirtioSocketListener","Symbol":"vz_invalidateVZVirtioSocketListener","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":true},{"Name":"LinuxInstallRosetta","Symbol":"vz_linuxInstallRosetta","Result":"","Parameters":[{"Name":"p0","Type":"uint64"}],"Queue":true,"ObjectQueue":false},{"Name":"LoadMacOSRestoreImageFile","Symbol":"vz_loadMacOSRestoreImageFile","Result":"","Parameters":[{"Name":"p0","Type":"string"},{"Name":"p1","Type":"uint64"}],"Queue":true,"ObjectQueue":false},{"Name":"MacOSRestoreImageMajorVersion","Symbol":"vz_macOSRestoreImageMajorVersion","Result":"int64","Parameters":[{"Name":"p0","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"MacOSRestoreImageMinorVersion","Symbol":"vz_macOSRestoreImageMinorVersion","Result":"int64","Parameters":[{"Name":"p0","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"MacOSRestoreImagePatchVersion","Symbol":"vz_macOSRestoreImagePatchVersion","Result":"int64","Parameters":[{"Name":"p0","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"SetInvalidDiskModeError","Symbol":"vz_setInvalidDiskModeError","Result":"","Parameters":[{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVZDiskBlockDeviceStorageDeviceAttachment","Symbol":"vz_newVZDiskBlockDeviceStorageDeviceAttachment","Result":"*objc.Pointer","Parameters":[{"Name":"p0","Type":"int32"},{"Name":"p1","Type":"bool"},{"Name":"p2","Type":"int32"},{"Name":"p3","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVZFileHandleNetworkDeviceAttachment","Symbol":"vz_newVZFileHandleNetworkDeviceAttachment","Result":"*objc.Pointer","Parameters":[{"Name":"p0","Type":"int32"},{"Name":"p1","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVZFileHandleSerialPortAttachment","Symbol":"vz_newVZFileHandleSerialPortAttachment","Result":"*objc.Pointer","Parameters":[{"Name":"readFD","Type":"int32"},{"Name":"hasRead","Type":"bool"},{"Name":"writeFD","Type":"int32"},{"Name":"hasWrite","Type":"bool"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVZMacOSInstaller","Symbol":"vz_newVZMacOSInstaller","Result":"*objc.Pointer","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"string"}],"Queue":true,"ObjectQueue":true},{"Name":"NewVZNetworkBlockDeviceStorageDeviceAttachment","Symbol":"vz_newVZNetworkBlockDeviceStorageDeviceAttachment","Result":"*objc.Pointer","Parameters":[{"Name":"p0","Type":"string"},{"Name":"p1","Type":"float64"},{"Name":"p2","Type":"bool"},{"Name":"p3","Type":"int32"},{"Name":"p4","Type":"*unsafe.Pointer"},{"Name":"p5","Type":"uint64"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVZVirtioSocketListener","Symbol":"vz_newVZVirtioSocketListener","Result":"*objc.Pointer","Parameters":[{"Name":"device","Type":"objc.NSObject"},{"Name":"context","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"NewVZVirtualMachineWithDispatchQueue","Symbol":"vz_newVZVirtualMachineWithDispatchQueue","Result":"*objc.Pointer","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"},{"Name":"p2","Type":"uint64"}],"Queue":false,"ObjectQueue":false},{"Name":"PauseWithCompletionHandler","Symbol":"vz_pauseWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"ReleaseObject","Symbol":"vz_releaseObject","Result":"","Parameters":[{"Name":"p0","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"RestoreMachineStateFromURLWithCompletionHandler","Symbol":"vz_restoreMachineStateFromURLWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"},{"Name":"p2","Type":"string"}],"Queue":true,"ObjectQueue":true},{"Name":"ResumeWithCompletionHandler","Symbol":"vz_resumeWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"SaveMachineStateToURLWithCompletionHandler","Symbol":"vz_saveMachineStateToURLWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"},{"Name":"p2","Type":"string"}],"Queue":true,"ObjectQueue":true},{"Name":"SetCallback","Symbol":"vz_setCallback","Result":"","Parameters":[{"Name":"p0","Type":"uint64"}],"Queue":true,"ObjectQueue":false},{"Name":"SocketConnectionDuplicatedFileDescriptor","Symbol":"vz_socketConnectionDuplicatedFileDescriptor","Result":"int32","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":true},{"Name":"NewGraphicApplication","Symbol":"vz_newGraphicApplication","Result":"unsafe.Pointer","Parameters":null,"Queue":false,"ObjectQueue":false},{"Name":"AddGraphicWindow","Symbol":"vz_addGraphicWindow","Result":"","Parameters":[{"Name":"application","Type":"unsafe.Pointer"},{"Name":"machine","Type":"objc.NSObject"},{"Name":"width","Type":"float64"},{"Name":"height","Type":"float64"},{"Name":"title","Type":"string"},{"Name":"enableController","Type":"bool"}],"Queue":false,"ObjectQueue":false},{"Name":"FreeGraphicApplication","Symbol":"vz_freeGraphicApplication","Result":"","Parameters":[{"Name":"application","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"StartWithCompletionHandler","Symbol":"vz_startWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"StartWithOptionsCompletionHandler","Symbol":"vz_startWithOptionsCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"objc.NSObject"},{"Name":"p2","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"StopWithCompletionHandler","Symbol":"vz_stopWithCompletionHandler","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"VZVirtioSocketDevice_connectToPort","Symbol":"vz_VZVirtioSocketDevice_connectToPort","Result":"","Parameters":[{"Name":"p0","Type":"objc.NSObject"},{"Name":"p1","Type":"uint32"},{"Name":"p2","Type":"uint64"}],"Queue":true,"ObjectQueue":true},{"Name":"NewVmnetNetworkConfiguration","Symbol":"vz_newVmnetNetworkConfiguration","Result":"unsafe.Pointer","Parameters":[{"Name":"mode","Type":"int32"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVmnetNetwork","Symbol":"vz_newVmnetNetwork","Result":"unsafe.Pointer","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"DisableVmnetDHCP","Symbol":"vz_disableVmnetDHCP","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"DisableVmnetDNSProxy","Symbol":"vz_disableVmnetDNSProxy","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"DisableVmnetNAT44","Symbol":"vz_disableVmnetNAT44","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"DisableVmnetNAT66","Symbol":"vz_disableVmnetNAT66","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"DisableVmnetRouterAdvertisement","Symbol":"vz_disableVmnetRouterAdvertisement","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"SetVmnetExternalInterface","Symbol":"vz_setVmnetExternalInterface","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"name","Type":"string"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"SetVmnetMTU","Symbol":"vz_setVmnetMTU","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"mtu","Type":"uint32"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"SetVmnetIPv4Subnet","Symbol":"vz_setVmnetIPv4Subnet","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"address","Type":"uint32"},{"Name":"mask","Type":"uint32"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"SetVmnetIPv6Prefix","Symbol":"vz_setVmnetIPv6Prefix","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"address","Type":"unsafe.Pointer"},{"Name":"prefixLength","Type":"uint8"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"AddVmnetDHCPReservation","Symbol":"vz_addVmnetDHCPReservation","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"mac","Type":"unsafe.Pointer"},{"Name":"address","Type":"uint32"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"AddVmnetPortForwarding","Symbol":"vz_addVmnetPortForwarding","Result":"","Parameters":[{"Name":"configuration","Type":"objc.NSObject"},{"Name":"udp","Type":"bool"},{"Name":"ipv6","Type":"bool"},{"Name":"externalPort","Type":"uint16"},{"Name":"internalPort","Type":"uint16"},{"Name":"address","Type":"unsafe.Pointer"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"VmnetIPv4Subnet","Symbol":"vz_vmnetIPv4Subnet","Result":"uint64","Parameters":[{"Name":"network","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"VmnetIPv6Prefix","Symbol":"vz_vmnetIPv6Prefix","Result":"uint8","Parameters":[{"Name":"network","Type":"objc.NSObject"},{"Name":"address","Type":"unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVmnetNetworkDeviceAttachment","Symbol":"vz_newVmnetNetworkDeviceAttachment","Result":"*objc.Pointer","Parameters":[{"Name":"network","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"VmnetNetworkDeviceAttachmentNetwork","Symbol":"vz_vmnetNetworkDeviceAttachmentNetwork","Result":"unsafe.Pointer","Parameters":[{"Name":"attachment","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"ReleaseCFObject","Symbol":"vz_releaseCFObject","Result":"","Parameters":[{"Name":"object","Type":"unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"StartVmnetInterface","Symbol":"vz_startVmnetInterface","Result":"unsafe.Pointer","Parameters":[{"Name":"network","Type":"objc.NSObject"},{"Name":"name","Type":"string"},{"Name":"options","Type":"uint32"},{"Name":"callback","Type":"uint64"},{"Name":"context","Type":"uint64"},{"Name":"maxPacketSize","Type":"unsafe.Pointer"},{"Name":"mac","Type":"unsafe.Pointer"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"CopyVmnetBridgedInterfaces","Symbol":"vz_copyVmnetBridgedInterfaces","Result":"*objc.Pointer","Parameters":[{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"StopVmnetInterface","Symbol":"vz_stopVmnetInterface","Result":"","Parameters":[{"Name":"session","Type":"unsafe.Pointer"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"WriteVmnetPacket","Symbol":"vz_writeVmnetPacket","Result":"int32","Parameters":[{"Name":"session","Type":"unsafe.Pointer"},{"Name":"buffer","Type":"unsafe.Pointer"},{"Name":"size","Type":"uint64"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"ReadVmnetPackets","Symbol":"vz_readVmnetPackets","Result":"int32","Parameters":[{"Name":"session","Type":"unsafe.Pointer"},{"Name":"buffers","Type":"unsafe.Pointer"},{"Name":"sizes","Type":"unsafe.Pointer"},{"Name":"count","Type":"int32"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"WriteVmnetPackets","Symbol":"vz_writeVmnetPackets","Result":"int32","Parameters":[{"Name":"session","Type":"unsafe.Pointer"},{"Name":"buffers","Type":"unsafe.Pointer"},{"Name":"sizes","Type":"unsafe.Pointer"},{"Name":"count","Type":"int32"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"CopyVmnetNetworkSerialization","Symbol":"vz_copyVmnetNetworkSerialization","Result":"unsafe.Pointer","Parameters":[{"Name":"network","Type":"objc.NSObject"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"NewVmnetNetworkWithSerialization","Symbol":"vz_newVmnetNetworkWithSerialization","Result":"unsafe.Pointer","Parameters":[{"Name":"serialization","Type":"objc.NSObject"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"ReleaseXPCObject","Symbol":"vz_releaseXPCObject","Result":"","Parameters":[{"Name":"object","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"ListenVmnetNetwork","Symbol":"vz_listenVmnetNetwork","Result":"unsafe.Pointer","Parameters":[{"Name":"name","Type":"string"},{"Name":"serialization","Type":"objc.NSObject"},{"Name":"requirement","Type":"string"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"CloseVmnetNetworkService","Symbol":"vz_closeVmnetNetworkService","Result":"","Parameters":[{"Name":"service","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"RequestVmnetNetwork","Symbol":"vz_requestVmnetNetwork","Result":"unsafe.Pointer","Parameters":[{"Name":"name","Type":"string"},{"Name":"requirement","Type":"string"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"CancelVmnetNetworkRequest","Symbol":"vz_cancelVmnetNetworkRequest","Result":"","Parameters":[{"Name":"request","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"WaitVmnetNetworkRequest","Symbol":"vz_waitVmnetNetworkRequest","Result":"unsafe.Pointer","Parameters":[{"Name":"request","Type":"unsafe.Pointer"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"ReleaseVmnetNetworkRequest","Symbol":"vz_releaseVmnetNetworkRequest","Result":"","Parameters":[{"Name":"request","Type":"unsafe.Pointer"}],"Queue":false,"ObjectQueue":false},{"Name":"GraphicsDisplaySize","Symbol":"vz_graphicsDisplaySize","Result":"","Parameters":[{"Name":"display","Type":"objc.NSObject"},{"Name":"size","Type":"unsafe.Pointer"}],"Queue":true,"ObjectQueue":true},{"Name":"ReconfigureGraphicsDisplaySize","Symbol":"vz_reconfigureGraphicsDisplaySize","Result":"","Parameters":[{"Name":"display","Type":"objc.NSObject"},{"Name":"width","Type":"float64"},{"Name":"height","Type":"float64"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":true},{"Name":"NewEFICertificate","Symbol":"vz_newEFICertificate","Result":"*objc.Pointer","Parameters":[{"Name":"data","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"EfiCertificateData","Symbol":"vz_efiCertificateData","Result":"*objc.Pointer","Parameters":[{"Name":"signature","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"IsEFICertificate","Symbol":"vz_isEFICertificate","Result":"bool","Parameters":[{"Name":"signature","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"EnableEFISecureBoot","Symbol":"vz_enableEFISecureBoot","Result":"","Parameters":[{"Name":"store","Type":"objc.NSObject"},{"Name":"signature","Type":"objc.NSObject"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false},{"Name":"RegisterUSBAccessories","Symbol":"vz_registerUSBAccessories","Result":"*objc.Pointer","Parameters":[{"Name":"context","Type":"uint64"}],"Queue":false,"ObjectQueue":false},{"Name":"UnregisterUSBAccessories","Symbol":"vz_unregisterUSBAccessories","Result":"","Parameters":[{"Name":"listener","Type":"objc.NSObject"},{"Name":"context","Type":"uint64"}],"Queue":false,"ObjectQueue":false},{"Name":"USBAccessoryRegistryID","Symbol":"vz_USBAccessoryRegistryID","Result":"uint64","Parameters":[{"Name":"accessory","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"USBAccessoryDeviceDescriptor","Symbol":"vz_USBAccessoryDeviceDescriptor","Result":"*objc.Pointer","Parameters":[{"Name":"accessory","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"USBAccessoryConfigurationDescriptor","Symbol":"vz_USBAccessoryConfigurationDescriptor","Result":"*objc.Pointer","Parameters":[{"Name":"accessory","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":false},{"Name":"ObserveUSBDisconnect","Symbol":"vz_observeUSBDisconnect","Result":"*objc.Pointer","Parameters":[{"Name":"controller","Type":"objc.NSObject"},{"Name":"context","Type":"uint64"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":true},{"Name":"StopObservingUSBDisconnect","Symbol":"vz_stopObservingUSBDisconnect","Result":"","Parameters":[{"Name":"controller","Type":"objc.NSObject"},{"Name":"observer","Type":"objc.NSObject"}],"Queue":true,"ObjectQueue":true},{"Name":"EfiSecureBootEnabled","Symbol":"vz_efiSecureBootEnabled","Result":"bool","Parameters":[{"Name":"store","Type":"objc.NSObject"},{"Name":"errorOut","Type":"*unsafe.Pointer"}],"Queue":true,"ObjectQueue":false}]} diff --git a/internal/vzbridge/native.h b/internal/vzbridge/native.h index eb9fd250..36c8f623 100644 --- a/internal/vzbridge/native.h +++ b/internal/vzbridge/native.h @@ -102,8 +102,9 @@ void vz_releaseCFObject(void *object); void *vz_startVmnetInterface(VZBridgeObject network, const char *name, uint32_t options, uint64_t callback, uint64_t context, void *maxPacketSize, void *mac, VZBridgeErrorOut errorOut) __attribute__((annotate("vzbridge:noqueue"))); VZBridgeOwnedObject vz_copyVmnetBridgedInterfaces(VZBridgeErrorOut errorOut) __attribute__((annotate("vzbridge:noqueue"))); void vz_stopVmnetInterface(void *session, VZBridgeErrorOut errorOut) __attribute__((annotate("vzbridge:noqueue"))); -int32_t vz_readVmnetPacket(void *session, void *buffer, uint64_t size, VZBridgeErrorOut errorOut) __attribute__((annotate("vzbridge:noqueue"))); -void vz_writeVmnetPacket(void *session, const void *buffer, uint64_t size, VZBridgeErrorOut errorOut) __attribute__((annotate("vzbridge:noqueue"))); +int32_t vz_writeVmnetPacket(void *session, const void *buffer, uint64_t size, VZBridgeErrorOut errorOut) __attribute__((annotate("vzbridge:noqueue"))); +int32_t vz_readVmnetPackets(void *session, void *buffers, void *sizes, int32_t count, VZBridgeErrorOut errorOut) __attribute__((annotate("vzbridge:noqueue"))); +int32_t vz_writeVmnetPackets(void *session, void *buffers, void *sizes, int32_t count, VZBridgeErrorOut errorOut) __attribute__((annotate("vzbridge:noqueue"))); void *vz_copyVmnetNetworkSerialization(VZBridgeObject network, VZBridgeErrorOut errorOut); void *vz_newVmnetNetworkWithSerialization(VZBridgeObject serialization, VZBridgeErrorOut errorOut); void vz_releaseXPCObject(void *object) __attribute__((annotate("vzbridge:noqueue"))); diff --git a/internal/vzbridge/native_bindings.go b/internal/vzbridge/native_bindings.go index 31d13041..bdca99d6 100644 --- a/internal/vzbridge/native_bindings.go +++ b/internal/vzbridge/native_bindings.go @@ -895,12 +895,12 @@ func StopVmnetInterface(session unsafe.Pointer, errorOut *unsafe.Pointer) { runtime.KeepAlive(errorOut) } -var callReadVmnetPacket lazyBinding[func(unsafe.Pointer, unsafe.Pointer, uint64, *unsafe.Pointer) int32] +var callWriteVmnetPacket lazyBinding[func(unsafe.Pointer, unsafe.Pointer, uint64, *unsafe.Pointer) int32] -func ReadVmnetPacket(session unsafe.Pointer, buffer unsafe.Pointer, size uint64, errorOut *unsafe.Pointer) int32 { - call := callReadVmnetPacket.get(func() func(unsafe.Pointer, unsafe.Pointer, uint64, *unsafe.Pointer) int32 { +func WriteVmnetPacket(session unsafe.Pointer, buffer unsafe.Pointer, size uint64, errorOut *unsafe.Pointer) int32 { + call := callWriteVmnetPacket.get(func() func(unsafe.Pointer, unsafe.Pointer, uint64, *unsafe.Pointer) int32 { var call func(unsafe.Pointer, unsafe.Pointer, uint64, *unsafe.Pointer) int32 - purego.RegisterFunc(&call, uintptr(C.vz_readVmnetPacket)) + purego.RegisterFunc(&call, uintptr(C.vz_writeVmnetPacket)) return call }) var result int32 @@ -911,18 +911,38 @@ func ReadVmnetPacket(session unsafe.Pointer, buffer unsafe.Pointer, size uint64, return result } -var callWriteVmnetPacket lazyBinding[func(unsafe.Pointer, unsafe.Pointer, uint64, *unsafe.Pointer)] +var callReadVmnetPackets lazyBinding[func(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, int32, *unsafe.Pointer) int32] -func WriteVmnetPacket(session unsafe.Pointer, buffer unsafe.Pointer, size uint64, errorOut *unsafe.Pointer) { - call := callWriteVmnetPacket.get(func() func(unsafe.Pointer, unsafe.Pointer, uint64, *unsafe.Pointer) { - var call func(unsafe.Pointer, unsafe.Pointer, uint64, *unsafe.Pointer) - purego.RegisterFunc(&call, uintptr(C.vz_writeVmnetPacket)) +func ReadVmnetPackets(session unsafe.Pointer, buffers unsafe.Pointer, sizes unsafe.Pointer, count int32, errorOut *unsafe.Pointer) int32 { + call := callReadVmnetPackets.get(func() func(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, int32, *unsafe.Pointer) int32 { + var call func(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, int32, *unsafe.Pointer) int32 + purego.RegisterFunc(&call, uintptr(C.vz_readVmnetPackets)) return call }) - call(session, buffer, size, errorOut) + var result int32 + result = call(session, buffers, sizes, count, errorOut) runtime.KeepAlive(session) - runtime.KeepAlive(buffer) + runtime.KeepAlive(buffers) + runtime.KeepAlive(sizes) + runtime.KeepAlive(errorOut) + return result +} + +var callWriteVmnetPackets lazyBinding[func(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, int32, *unsafe.Pointer) int32] + +func WriteVmnetPackets(session unsafe.Pointer, buffers unsafe.Pointer, sizes unsafe.Pointer, count int32, errorOut *unsafe.Pointer) int32 { + call := callWriteVmnetPackets.get(func() func(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, int32, *unsafe.Pointer) int32 { + var call func(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, int32, *unsafe.Pointer) int32 + purego.RegisterFunc(&call, uintptr(C.vz_writeVmnetPackets)) + return call + }) + var result int32 + result = call(session, buffers, sizes, count, errorOut) + runtime.KeepAlive(session) + runtime.KeepAlive(buffers) + runtime.KeepAlive(sizes) runtime.KeepAlive(errorOut) + return result } var callCopyVmnetNetworkSerialization lazyBinding[func(unsafe.Pointer, *unsafe.Pointer) unsafe.Pointer] diff --git a/internal/vzbridge/vmnet_interface.m b/internal/vzbridge/vmnet_interface.m index 836a7373..34a3e540 100644 --- a/internal/vzbridge/vmnet_interface.m +++ b/internal/vzbridge/vmnet_interface.m @@ -7,6 +7,7 @@ interface_ref interface; dispatch_queue_t events; dispatch_block_t disableCallback; + uint64_t readMaxPackets, writeMaxPackets; }; static void interfaceError(vmnet_return_t status, NSString *operation, void **output) { @@ -43,6 +44,8 @@ static void interfaceError(vmnet_return_t status, NSString *operation, void **ou result = status; if (status == VMNET_SUCCESS && parameters) { *(uint64_t *)maxPacketSize = xpc_dictionary_get_uint64(parameters, vmnet_max_packet_size_key); + session->readMaxPackets = xpc_dictionary_get_uint64(parameters, vmnet_read_max_packets_key); + session->writeMaxPackets = xpc_dictionary_get_uint64(parameters, vmnet_write_max_packets_key); const char *address = xpc_dictionary_get_string(parameters, vmnet_mac_address_key); unsigned int bytes[6]; if (address && sscanf(address, "%x:%x:%x:%x:%x:%x", &bytes[0], &bytes[1], &bytes[2], &bytes[3], &bytes[4], &bytes[5]) == 6) @@ -137,30 +140,83 @@ void vz_stopVmnetInterface(void *pointer, void **errorOut) } #endif } -int32_t vz_readVmnetPacket(void *pointer, void *buffer, uint64_t size, void **errorOut) +int32_t vz_writeVmnetPacket(void *pointer, const void *buffer, uint64_t size, void **errorOut) { #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000 if (@available(macOS 26, *)) { struct VmnetSession *session = pointer; - struct iovec vec = { .iov_base = buffer, .iov_len = size }; - struct vmpktdesc packet = { .vm_pkt_size = size, .vm_pkt_iov = &vec, .vm_pkt_iovcnt = 1, .vm_flags = 0 }; + struct iovec vector = { .iov_base = (void *)buffer, .iov_len = size }; + struct vmpktdesc packet = { .vm_pkt_size = size, .vm_pkt_iov = &vector, .vm_pkt_iovcnt = 1, .vm_flags = 0 }; int count = 1; - vmnet_return_t result = vmnet_read(session->interface, &packet, &count); - interfaceError(result, @"vmnet_read", errorOut); - return result == VMNET_SUCCESS && count ? (int32_t)packet.vm_pkt_size : 0; + vmnet_return_t result = vmnet_write(session->interface, &packet, &count); + interfaceError(result, @"vmnet_write", errorOut); + return result == VMNET_SUCCESS ? count : 0; } #endif return 0; } -void vz_writeVmnetPacket(void *pointer, const void *buffer, uint64_t size, void **errorOut) -{ + +// vmnet limits each call to 256 KB. Use decimal KB conservatively. #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000 - if (@available(macOS 26, *)) { - struct VmnetSession *session = pointer; - struct iovec vec = { .iov_base = (void *)buffer, .iov_len = size }; - struct vmpktdesc packet = { .vm_pkt_size = size, .vm_pkt_iov = &vec, .vm_pkt_iovcnt = 1, .vm_flags = 0 }; - int count = 1; - interfaceError(vmnet_write(session->interface, &packet, &count), @"vmnet_write", errorOut); +static int32_t transferPackets(void *pointer, void *buffers, void *sizes, int32_t requested, bool reading, void **errorOut) +{ + struct VmnetSession *session = pointer; + uint64_t limit = reading ? session->readMaxPackets : session->writeMaxPackets; + if (!limit) + limit = 1; + if (requested < 1 || requested > 32) { + interfaceError(VMNET_INVALID_ARGUMENT, @"packet batch count", errorOut); + return 0; } + struct iovec vectors[32]; + struct vmpktdesc packets[32]; + uint64_t *lengths = sizes; + uint64_t remaining = 256000; + int submitted = 0; + for (; submitted < requested && submitted < limit; submitted++) { + uint64_t size = lengths[submitted]; + if (!size || size > remaining) + break; + remaining -= size; + vectors[submitted] = (struct iovec) { .iov_base = ((void **)buffers)[submitted], .iov_len = size }; + packets[submitted] = (struct vmpktdesc) { .vm_pkt_size = size, .vm_pkt_iov = &vectors[submitted], .vm_pkt_iovcnt = 1, .vm_flags = 0 }; + } + if (!submitted) { + interfaceError(VMNET_INVALID_ARGUMENT, @"packet batch size", errorOut); + return 0; + } + int count = submitted; + vmnet_return_t result = reading ? vmnet_read(session->interface, packets, &count) + : vmnet_write(session->interface, packets, &count); + interfaceError(result, reading ? @"vmnet_read" : @"vmnet_write", errorOut); + // An error may leave count unchanged. Only successful calls report progress. + if (result != VMNET_SUCCESS) + return 0; + if (count < 0 || count > submitted) { + interfaceError(VMNET_FAILURE, @"packet batch result count", errorOut); + return 0; + } + if (reading) + for (int i = 0; i < count; i++) + lengths[i] = packets[i].vm_pkt_size; + return count; +} #endif + +int32_t vz_readVmnetPackets(void *pointer, void *buffers, void *sizes, int32_t count, void **errorOut) +{ +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000 + if (@available(macOS 26, *)) + return transferPackets(pointer, buffers, sizes, count, true, errorOut); +#endif + return 0; +} + +int32_t vz_writeVmnetPackets(void *pointer, void *buffers, void *sizes, int32_t count, void **errorOut) +{ +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000 + if (@available(macOS 26, *)) + return transferPackets(pointer, buffers, sizes, count, false, errorOut); +#endif + return 0; } diff --git a/vmnet/batch.go b/vmnet/batch.go new file mode 100644 index 00000000..813d64ce --- /dev/null +++ b/vmnet/batch.go @@ -0,0 +1,181 @@ +package vmnet + +import ( + "context" + "fmt" + "io" + "net" + "runtime" + "unsafe" + + "github.com/Code-Hex/vz/v4/internal/vzbridge" +) + +const packetBatchLimit = 32 +const packetBatchBytes = 256000 + +// ReadPackets reads Ethernet frames into frames and returns the number read. +// It waits for at least one frame, but does not wait to fill the batch. +// A read may return fewer frames than fit, even when more frames are queued. +// +// Each buffer must have capacity for [Interface.MaxPacketSize] bytes. On success, +// ReadPackets sets the lengths of frames[:n] to the received frame lengths. +// The same buffers can be passed to the next call without resetting their lengths. +// The buffers must not overlap. The caller must not use frames or its buffers +// during the call, and ReadPackets does not retain them after it returns. +// +// Canceling ctx or calling [Interface.Stop] interrupts a blocked read, including +// a read waiting for another reader to finish. On an open interface, an empty +// batch returns (0, nil). +// +// See https://developer.apple.com/documentation/vmnet/vmnet_read(_:_:_:)?language=objc +func (i *Interface) ReadPackets(ctx context.Context, frames [][]byte) (int, error) { + if i == nil || i.owner == nil { + return 0, fmt.Errorf("vmnet interface is nil or uninitialized") + } + defer runtime.KeepAlive(i) + r := i.owner.resource + select { + case <-r.done: + return 0, net.ErrClosed + default: + } + for _, frame := range frames { + if cap(frame) < r.maxSize { + return 0, io.ErrShortBuffer + } + } + if len(frames) == 0 { + return 0, nil + } + if r.maxSize > packetBatchBytes { + return 0, fmt.Errorf("vmnet maximum packet size %d exceeds batch byte limit %d", r.maxSize, packetBatchBytes) + } + select { + case r.readers <- struct{}{}: + case <-ctx.Done(): + return 0, ctx.Err() + case <-r.done: + return 0, net.ErrClosed + } + defer func() { <-r.readers }() + frames = frames[:min(len(frames), packetBatchLimit, packetBatchBytes/r.maxSize)] + for { + if err := ctx.Err(); err != nil { + return 0, err + } + r.mu.Lock() + if r.stopping { + r.mu.Unlock() + return 0, net.ErrClosed + } + n, err := r.transferPackets(frames, true) + r.mu.Unlock() + if err != nil || n > 0 { + return n, err + } + // Events are wake hints, not packet counts. Drain native packets before waiting. + select { + case <-r.wake: + case <-ctx.Done(): + return 0, ctx.Err() + case <-r.done: + return 0, net.ErrClosed + } + } +} + +// WritePackets sends Ethernet frames and returns the number sent. +// It checks every frame before sending any, then splits the frames into native +// batches as needed. Each frame must contain between 1 and +// min(MaxPacketSize(), 256000) bytes. +// +// On error, n counts only frames confirmed by successful native calls. The failed +// call may have sent additional frames, so retrying frames[n:] can send duplicates. +// Concurrent writers may interleave their batches. +// +// The caller must not change frames or its buffers during the call. +// WritePackets does not retain them after it returns. On an open interface, +// an empty batch returns (0, nil). +// +// See https://developer.apple.com/documentation/vmnet/vmnet_write(_:_:_:)?language=objc +func (i *Interface) WritePackets(frames [][]byte) (int, error) { + if i == nil || i.owner == nil { + return 0, fmt.Errorf("vmnet interface is nil or uninitialized") + } + defer runtime.KeepAlive(i) + r := i.owner.resource + select { + case <-r.done: + return 0, net.ErrClosed + default: + } + for _, frame := range frames { + if len(frame) == 0 || len(frame) > min(r.maxSize, packetBatchBytes) { + return 0, fmt.Errorf("invalid vmnet frame length %d, maximum %d", len(frame), min(r.maxSize, packetBatchBytes)) + } + } + sent := 0 + for sent < len(frames) { + r.mu.Lock() + if r.stopping { + r.mu.Unlock() + return sent, net.ErrClosed + } + n, err := r.transferPackets(frames[sent:min(len(frames), sent+packetBatchLimit)], false) + r.mu.Unlock() + if err != nil { + return sent, err + } + if n == 0 { + return sent, io.ErrShortWrite + } + sent += n + } + return sent, nil +} + +// The resource mutex protects the session throughout this synchronous call. +func (r *interfaceResource) transferPackets(frames [][]byte, reading bool) (int, error) { + var buffers [packetBatchLimit]unsafe.Pointer + var sizes [packetBatchLimit]uint64 + var pinned runtime.Pinner + defer pinned.Unpin() + for j, frame := range frames { + size := len(frame) + if reading { + size = r.maxSize + frame = frame[:size] + } + buffers[j] = unsafe.Pointer(&frame[0]) + sizes[j] = uint64(size) + pinned.Pin(buffers[j]) + } + pinned.Pin(&buffers[0]) + pinned.Pin(&sizes[0]) + var errorPtr unsafe.Pointer + var count int32 + if reading { + count = vzbridge.ReadVmnetPackets(r.session, unsafe.Pointer(&buffers[0]), unsafe.Pointer(&sizes[0]), int32(len(frames)), &errorPtr) + } else { + count = vzbridge.WriteVmnetPackets(r.session, unsafe.Pointer(&buffers[0]), unsafe.Pointer(&sizes[0]), int32(len(frames)), &errorPtr) + } + if err := vmnetError(errorPtr); err != nil { + return 0, err + } + n := int(count) + if n < 0 || n > len(frames) { + return 0, fmt.Errorf("vmnet returned invalid packet count %d", n) + } + if reading { + for j := range n { + if sizes[j] == 0 || sizes[j] > uint64(r.maxSize) { + return 0, fmt.Errorf("vmnet returned invalid frame length %d", sizes[j]) + } + } + for j := range n { + frames[j] = frames[j][:int(sizes[j])] + } + } + return n, nil +} diff --git a/vmnet/batch_example_test.go b/vmnet/batch_example_test.go new file mode 100644 index 00000000..c3bb90e7 --- /dev/null +++ b/vmnet/batch_example_test.go @@ -0,0 +1,83 @@ +package vmnet_test + +import ( + "context" + "encoding/binary" + "fmt" + "time" + + "github.com/Code-Hex/vz/v4/vmnet" +) + +func ExampleInterface_ReadPackets() { + config, err := vmnet.NewNetworkConfiguration(vmnet.ModeHost) + if err != nil { + fmt.Println(err) + return + } + network, err := vmnet.NewNetwork(config) + if err != nil { + fmt.Println(err) + return + } + iface, err := vmnet.StartInterfaceWithNetwork(network) + if err != nil { + fmt.Println(err) + return + } + defer iface.Stop() + + frames := make([][]byte, 32) + for j := range frames { + frames[j] = make([]byte, 0, iface.MaxPacketSize()) + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + for { + n, err := iface.ReadPackets(ctx, frames) + if err != nil { + fmt.Println(err) + return + } + for _, frame := range frames[:n] { + fmt.Printf("received %d bytes\n", len(frame)) + } + // Copy any frames you need to keep before the next read reuses the buffers. + } +} + +func ExampleInterface_WritePackets() { + config, err := vmnet.NewNetworkConfiguration(vmnet.ModeHost) + if err != nil { + fmt.Println(err) + return + } + network, err := vmnet.NewNetwork(config) + if err != nil { + fmt.Println(err) + return + } + iface, err := vmnet.StartInterfaceWithNetwork(network) + if err != nil { + fmt.Println(err) + return + } + defer iface.Stop() + + frames := make([][]byte, 2) + for j := range frames { + frame := make([]byte, 60) + copy(frame[:6], []byte{255, 255, 255, 255, 255, 255}) + copy(frame[6:12], iface.MACAddress()) + // EtherType 0x88b5 is reserved for local experimental use. + binary.BigEndian.PutUint16(frame[12:14], 0x88b5) + copy(frame[14:], fmt.Sprintf("packet %d", j)) + frames[j] = frame + } + if n, err := iface.WritePackets(frames); err != nil { + // The failed native call may have sent more than n frames. Do not retry + // frames[n:] unless the protocol can handle duplicates. + fmt.Printf("sent at least %d frames: %v\n", n, err) + return + } +} diff --git a/vmnet/batch_test.go b/vmnet/batch_test.go new file mode 100644 index 00000000..038dca7a --- /dev/null +++ b/vmnet/batch_test.go @@ -0,0 +1,220 @@ +package vmnet + +import ( + "bytes" + "context" + "encoding/binary" + "errors" + "io" + "net" + "runtime" + "testing" + "time" + + "github.com/Code-Hex/vz/v4/internal/objc" +) + +func batchInterfaces(t *testing.T) (*Interface, *Interface) { + t.Helper() + if Available(26) { + t.Skip("requires macOS 26 SDK") + } + c, err := NewNetworkConfiguration(ModeHost) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { objc.Release(c) }) + network, err := NewNetwork(c) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { objc.Release((*objc.Pointer)(network)) }) + start := func() *Interface { + i, err := StartInterfaceWithNetwork(network) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := i.Stop(); err != nil { + t.Error(err) + } + }) + return i + } + return start(), start() +} + +func batchFrame(source net.HardwareAddr, index, size int) []byte { + frame := bytes.Repeat([]byte{byte(index)}, size) + copy(frame, []byte{255, 255, 255, 255, 255, 255}) + copy(frame[6:], source) + binary.BigEndian.PutUint16(frame[12:], 0x88b5) + binary.BigEndian.PutUint32(frame[14:], uint32(index)) + return frame +} + +func TestInterfacePacketBatches(t *testing.T) { + sender, receiver := batchInterfaces(t) + frames := make([][]byte, 70) + for j := range frames { + size := 60 + if j >= 7 { + size = 1500 + } + frames[j] = batchFrame(sender.MACAddress(), j, size) + } + if n, err := sender.WritePackets(frames); err != nil || n != len(frames) { + t.Fatalf("write burst = %d, %v", n, err) + } + buffers := make([][]byte, 7) + for j := range buffers { + buffers[j] = make([]byte, 0, receiver.MaxPacketSize()) + } + ctx, cancel := context.WithTimeout(t.Context(), 3*time.Second) + defer cancel() + received := 0 + for received < len(frames) { + // Notifications coalesce. Already queued packets must not need another wake. + select { + case <-receiver.owner.resource.wake: + default: + } + n, err := receiver.ReadPackets(ctx, buffers) + if err != nil { + t.Fatalf("read after %d packets: %v", received, err) + } + if n < 1 || n > len(buffers) { + t.Fatalf("read count = %d", n) + } + for _, frame := range buffers[:n] { + if len(frame) < 18 || binary.BigEndian.Uint16(frame[12:]) != 0x88b5 { + continue + } + if received >= len(frames) || !bytes.Equal(frame, frames[received]) { + t.Fatalf("frame %d differs: length %d", received, len(frame)) + } + received++ + } + } +} + +func TestInterfaceBatchValidation(t *testing.T) { + sender, receiver := batchInterfaces(t) + for _, iface := range []*Interface{nil, {}} { + if _, err := iface.ReadPackets(t.Context(), nil); err == nil { + t.Fatal("invalid interface accepted") + } + if _, err := iface.WritePackets(nil); err == nil { + t.Fatal("invalid interface accepted") + } + } + if n, err := sender.WritePackets(nil); n != 0 || err != nil { + t.Fatalf("empty write = %d, %v", n, err) + } + if n, err := receiver.ReadPackets(t.Context(), nil); n != 0 || err != nil { + t.Fatalf("empty read = %d, %v", n, err) + } + frame := batchFrame(sender.MACAddress(), 0, 60) + for _, invalid := range [][]byte{nil, make([]byte, sender.MaxPacketSize()+1)} { + frames := make([][]byte, 40) + for j := range frames { + frames[j] = frame + } + frames[len(frames)-1] = invalid + if n, err := sender.WritePackets(frames); n != 0 || err == nil { + t.Fatalf("invalid write = %d, %v", n, err) + } + } + ctx, cancel := context.WithTimeout(t.Context(), 100*time.Millisecond) + defer cancel() + buffers := [][]byte{make([]byte, receiver.MaxPacketSize())} + for { + n, err := receiver.ReadPackets(ctx, buffers) + if errors.Is(err, context.DeadlineExceeded) { + break + } + if err != nil { + t.Fatal(err) + } + for _, got := range buffers[:n] { + if bytes.Equal(got, frame) { + t.Fatal("invalid batch sent a prefix") + } + } + } + if err := sender.WritePacket(frame); err != nil { + t.Fatal(err) + } + buffers = append(buffers, make([]byte, receiver.MaxPacketSize()-1)) + if n, err := receiver.ReadPackets(t.Context(), buffers); n != 0 || !errors.Is(err, io.ErrShortBuffer) { + t.Fatalf("short batch = %d, %v", n, err) + } + if _, err := receiver.ReadPacket(t.Context(), buffers[0][:0]); !errors.Is(err, io.ErrShortBuffer) { + t.Fatalf("single read lost len contract: %v", err) + } + ctx2, cancel2 := context.WithTimeout(t.Context(), 3*time.Second) + defer cancel2() + for { + n, err := receiver.ReadPackets(ctx2, buffers[:1]) + if err != nil { + t.Fatal(err) + } + if n == 1 && bytes.Equal(buffers[0], frame) { + break + } + } +} + +func TestInterfaceBatchWaitCancellationAndStop(t *testing.T) { + _, receiver := batchInterfaces(t) + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + first := make(chan error, 1) + read := func(ctx context.Context) error { + buffers := [][]byte{make([]byte, receiver.MaxPacketSize())} + for { + if _, err := receiver.ReadPackets(ctx, buffers); err != nil { + return err + } + } + } + go func() { first <- read(ctx) }() + deadline := time.After(3 * time.Second) + for len(receiver.owner.resource.readers) == 0 { + select { + case <-deadline: + t.Fatal("first reader did not acquire token") + default: + runtime.Gosched() + } + } + secondCtx, cancelSecond := context.WithCancel(t.Context()) + second := make(chan error, 1) + go func() { second <- read(secondCtx) }() + cancelSecond() + select { + case err := <-second: + if !errors.Is(err, context.Canceled) { + t.Fatal(err) + } + case <-time.After(3 * time.Second): + t.Fatal("queued reader did not cancel") + } + go func() { second <- read(t.Context()) }() + if err := receiver.Stop(); err != nil { + t.Fatal(err) + } + for _, result := range []<-chan error{first, second} { + select { + case err := <-result: + if !errors.Is(err, net.ErrClosed) { + t.Fatal(err) + } + case <-time.After(3 * time.Second): + t.Fatal("Stop did not interrupt readers") + } + } + if _, err := receiver.WritePackets([][]byte{{1}}); !errors.Is(err, net.ErrClosed) { + t.Fatalf("write after stop: %v", err) + } +} diff --git a/vmnet/interface.go b/vmnet/interface.go index df95273d..c2fde2f1 100644 --- a/vmnet/interface.go +++ b/vmnet/interface.go @@ -226,47 +226,12 @@ func (i *Interface) ReadPacket(ctx context.Context, frame []byte) (int, error) { if len(frame) < r.maxSize { return 0, io.ErrShortBuffer } - select { - case r.readers <- struct{}{}: - case <-ctx.Done(): - return 0, ctx.Err() - case <-r.done: - return 0, net.ErrClosed - } - defer func() { <-r.readers }() - var pinned runtime.Pinner - pinned.Pin(&frame[0]) - defer pinned.Unpin() - for { - if err := ctx.Err(); err != nil { - return 0, err - } - r.mu.Lock() - if r.stopping { - r.mu.Unlock() - return 0, net.ErrClosed - } - var errorPtr unsafe.Pointer - n := int(vzbridge.ReadVmnetPacket(r.session, unsafe.Pointer(&frame[0]), uint64(len(frame)), &errorPtr)) - err := vmnetError(errorPtr) - r.mu.Unlock() - if err != nil { - return 0, err - } - if n > 0 { - if n > len(frame) { - return 0, fmt.Errorf("vmnet returned oversized frame %d", n) - } - return n, nil - } - select { - case <-r.wake: - case <-ctx.Done(): - return 0, ctx.Err() - case <-r.done: - return 0, net.ErrClosed - } + frames := [][]byte{frame} + n, err := i.ReadPackets(ctx, frames) + if err != nil || n == 0 { + return 0, err } + return len(frames[0]), nil } // WritePacket synchronously sends a frame and does not retain its buffer. @@ -281,15 +246,21 @@ func (i *Interface) WritePacket(frame []byte) error { if r.stopping { return net.ErrClosed } - if len(frame) == 0 || len(frame) > r.maxSize { - return fmt.Errorf("invalid vmnet frame length %d, maximum %d", len(frame), r.maxSize) + if len(frame) == 0 || len(frame) > min(r.maxSize, packetBatchBytes) { + return fmt.Errorf("invalid vmnet frame length %d, maximum %d", len(frame), min(r.maxSize, packetBatchBytes)) } var pinned runtime.Pinner pinned.Pin(&frame[0]) defer pinned.Unpin() var errorPtr unsafe.Pointer - vzbridge.WriteVmnetPacket(r.session, unsafe.Pointer(&frame[0]), uint64(len(frame)), &errorPtr) - return vmnetError(errorPtr) + n := vzbridge.WriteVmnetPacket(r.session, unsafe.Pointer(&frame[0]), uint64(len(frame)), &errorPtr) + if err := vmnetError(errorPtr); err != nil { + return err + } + if n != 1 { + return io.ErrShortWrite + } + return nil } // Stop interrupts readers and stops the interface. From a936293909887303047762c6d79b829ae58bacbb Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Wed, 28 Oct 2026 19:40:30 +0900 Subject: [PATCH 2/2] perf(vmnetsock): forward received frames in batches --- vmnetsock/adapter.go | 27 +++++++------ vmnetsock/adapter_test.go | 80 +++++++++++++++++++++++++++++++++++++++ vmnetsock/framing.go | 29 +++++++------- vmnetsock/framing_test.go | 57 ++++++++++++++++++++++++++-- 4 files changed, 162 insertions(+), 31 deletions(-) diff --git a/vmnetsock/adapter.go b/vmnetsock/adapter.go index 12196a1e..28e23990 100644 --- a/vmnetsock/adapter.go +++ b/vmnetsock/adapter.go @@ -146,25 +146,30 @@ func (s *adapterState) forwardToInterface(iface *vmnet.Interface, transport Tran } func (s *adapterState) forwardToPeer(ctx context.Context, iface *vmnet.Interface, transport Transport) error { - frame := make([]byte, iface.MaxPacketSize()) - + size := iface.MaxPacketSize() + count := min(32, max(1, 256000/size)) + frames := make([][]byte, count) + for j := range frames { + frames[j] = make([]byte, size) + } + headers := make([][4]byte, count) + vectors := make(net.Buffers, 2*count) for { - n, err := iface.ReadPacket(ctx, frame) + n, err := iface.ReadPackets(ctx, frames) if err != nil { return err } - if transport == Stream { - err = writeStreamFrame(s.conn, frame[:n]) + if err := writeStreamFrames(s.conn, frames[:n], headers, vectors); err != nil { + return err + } } else { - err = writeDatagramFrame(s.conn, frame[:n]) - if errors.Is(err, unix.ENOBUFS) { - continue + for _, frame := range frames[:n] { + if err := writeDatagramFrame(s.conn, frame); err != nil && !errors.Is(err, unix.ENOBUFS) { + return err + } } } - if err != nil { - return err - } } } diff --git a/vmnetsock/adapter_test.go b/vmnetsock/adapter_test.go index df0a6925..6f5aa4e3 100644 --- a/vmnetsock/adapter_test.go +++ b/vmnetsock/adapter_test.go @@ -13,6 +13,7 @@ import ( "time" "github.com/Code-Hex/vz/v4/internal/macos" + "github.com/Code-Hex/vz/v4/internal/objc" "github.com/Code-Hex/vz/v4/internal/vzbridge" "github.com/Code-Hex/vz/v4/vmnet" "github.com/Code-Hex/vz/v4/vmnetsock" @@ -247,3 +248,82 @@ func TestAdapterStreamTermination(t *testing.T) { }) } } + +func TestAdapterQueuedBurst(t *testing.T) { + if macos.Available(26, vzbridge.SDKVersion) != nil { + t.Skip("requires macOS 26 SDK") + } + for _, transport := range []vmnetsock.Transport{vmnetsock.Stream, vmnetsock.Datagram} { + t.Run(string(rune('0'+transport)), func(t *testing.T) { + c, err := vmnet.NewNetworkConfiguration(vmnet.ModeHost) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { objc.Release(c) }) + network, err := vmnet.NewNetwork(c) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { objc.Release((*objc.Pointer)(network)) }) + sender, receiver := newInterface(t, network), newInterface(t, network) + frames := make([][]byte, 70) + for j := range frames { + frame := bytes.Repeat([]byte{byte(j)}, 60+j) + copy(frame, []byte{255, 255, 255, 255, 255, 255}) + copy(frame[6:], sender.MACAddress()) + binary.BigEndian.PutUint16(frame[12:], 0x88b5) + frames[j] = frame + } + if n, err := sender.WritePackets(frames); n != len(frames) || err != nil { + t.Fatalf("burst write = %d, %v", n, err) + } + adapter, err := vmnetsock.New(t.Context(), receiver, transport) + if err != nil { + t.Fatal(err) + } + defer adapter.Close() + file := duplicate(t, adapter) + peer, err := net.FileConn(file) + if err != nil { + t.Fatal(err) + } + defer peer.Close() + if err := peer.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil { + t.Fatal(err) + } + buffer := make([]byte, receiver.MaxPacketSize()) + for received := 0; received < len(frames); { + var size int + if transport == vmnetsock.Stream { + var header [4]byte + if _, err := io.ReadFull(peer, header[:]); err != nil { + t.Fatal(err) + } + size = int(binary.BigEndian.Uint32(header[:])) + if size == 0 || size > len(buffer) { + t.Fatalf("invalid frame size %d", size) + } + _, err = io.ReadFull(peer, buffer[:size]) + } else { + size, err = peer.Read(buffer) + } + if err != nil { + t.Fatalf("frame %d: %v", received, err) + } + if size < 14 || binary.BigEndian.Uint16(buffer[12:]) != 0x88b5 { + continue + } + if !bytes.Equal(buffer[:size], frames[received]) { + t.Fatalf("frame %d differs, length %d", received, size) + } + received++ + } + if err := adapter.Close(); err != nil { + t.Fatal(err) + } + if err := receiver.WritePacket(frames[0]); err != nil { + t.Fatalf("borrowed interface stopped: %v", err) + } + }) + } +} diff --git a/vmnetsock/framing.go b/vmnetsock/framing.go index 3ff5c9ab..a398da45 100644 --- a/vmnetsock/framing.go +++ b/vmnetsock/framing.go @@ -27,23 +27,20 @@ func readStreamFrame(reader io.Reader, frame []byte) (int, error) { return int(size), err } -func writeStreamFrame(writer io.Writer, frame []byte) error { - var header [4]byte - binary.BigEndian.PutUint32(header[:], uint32(len(frame))) - - for _, data := range [][]byte{header[:], frame} { - for len(data) > 0 { - n, err := writer.Write(data) - if err != nil { - return err - } - if n == 0 { - return io.ErrShortWrite - } - data = data[n:] - } +func writeStreamFrames(writer io.Writer, frames [][]byte, headers [][4]byte, vectors net.Buffers) error { + vectors = vectors[:2*len(frames)] + var expected int64 + for j, frame := range frames { + binary.BigEndian.PutUint32(headers[j][:], uint32(len(frame))) + vectors[2*j] = headers[j][:] + vectors[2*j+1] = frame + expected += int64(4 + len(frame)) } - return nil + n, err := vectors.WriteTo(writer) + if err == nil && n != expected { + return io.ErrShortWrite + } + return err } func readDatagramFrame(conn *net.UnixConn, frame []byte) (int, error) { diff --git a/vmnetsock/framing_test.go b/vmnetsock/framing_test.go index 357f48ac..ee33bfe6 100644 --- a/vmnetsock/framing_test.go +++ b/vmnetsock/framing_test.go @@ -78,8 +78,8 @@ func TestStreamFrames(t *testing.T) { } }) } - writer := &shortWriter{} - if err := writeStreamFrame(writer, []byte("abc")); err != nil { + writer := &bytes.Buffer{} + if err := writeStreamFrames(writer, [][]byte{[]byte("abc")}, make([][4]byte, 1), make(net.Buffers, 2)); err != nil { t.Fatal(err) } if !bytes.Equal(writer.Bytes(), []byte{0, 0, 0, 3, 'a', 'b', 'c'}) { @@ -143,8 +143,12 @@ func TestSocketBackpressureClose(t *testing.T) { done := make(chan error, 1) go func() { - _, err := conn.Write(frame) - done <- err + if transport == Stream { + done <- writeStreamFrames(conn, [][]byte{frame}, make([][4]byte, 1), make(net.Buffers, 2)) + } else { + _, err := conn.Write(frame) + done <- err + } }() if err := conn.Close(); err != nil { t.Fatal(err) @@ -183,3 +187,48 @@ func saturateSocket(t *testing.T, conn *net.UnixConn, frame []byte) { t.Fatalf("socket did not reach backpressure: %v", fillErr) } } + +func TestStreamBatchFrames(t *testing.T) { + conn, file, err := socketPair(Stream, 1500) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + defer file.Close() + peer, err := net.FileConn(file) + if err != nil { + t.Fatal(err) + } + defer peer.Close() + deadline := time.Now().Add(3 * time.Second) + if err := conn.SetWriteDeadline(deadline); err != nil { + t.Fatal(err) + } + if err := peer.SetReadDeadline(deadline); err != nil { + t.Fatal(err) + } + frames := [][]byte{[]byte("abc"), []byte("d"), bytes.Repeat([]byte{42}, 1500)} + headers := make([][4]byte, len(frames)) + vectors := make(net.Buffers, 2*len(frames)) + var want bytes.Buffer + for _, frame := range frames { + binary.Write(&want, binary.BigEndian, uint32(len(frame))) + want.Write(frame) + } + for range 2 { + if err := writeStreamFrames(conn, frames, headers, vectors); err != nil { + t.Fatal(err) + } + got := make([]byte, want.Len()) + if _, err := io.ReadFull(peer, got); err != nil { + t.Fatal(err) + } + if !bytes.Equal(got, want.Bytes()) { + t.Fatal("stream batch changed framing or order") + } + } + writer := &shortWriter{} + if err := writeStreamFrames(writer, frames, headers, vectors); !errors.Is(err, io.ErrShortWrite) { + t.Fatalf("short writer = %v", err) + } +}