From d741a6a7e9bff0580f2558ac4795747fa95bf16f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 15 May 2026 13:35:50 +0800 Subject: [PATCH] usbip: serialize darwin device state, pack iso-IN per descriptor Darwin URBs handled in concurrent goroutines mutated BoxUSBHostDevice's pipes/interfaces dictionaries without serialization, so SetConfiguration racing with endpoint IO could corrupt them or hand back stale pipes. Guard the collections with os_unfair_lock; keep the slow IOKit calls outside the lock so unrelated endpoints don't stall. Iso IN responses were flat-copied buffer[:actual], losing offsets for multi-packet transfers. Pack the wire payload per IsoPackets[i].Offset/ ActualLength to match Linux's stub_tx.c, and mirror the scatter on the Darwin client side so a future multi-packet emitter is correct. --- service/usbip/client_darwin.go | 34 +++++++++++++- service/usbip/host_darwin.go | 41 +++++++++++++++- service/usbip/usbhost_darwin.m | 86 +++++++++++++++++++++++++--------- 3 files changed, 138 insertions(+), 23 deletions(-) diff --git a/service/usbip/client_darwin.go b/service/usbip/client_darwin.go index 70e721646..3d221c88f 100644 --- a/service/usbip/client_darwin.go +++ b/service/usbip/client_darwin.go @@ -547,11 +547,43 @@ func (c *darwinVirtualController) completeSubmitInTransfer(ptr unsafe.Pointer, r copyLength = len(response.Buffer) } if copyLength > 0 && ptr != nil { - copy(unsafe.Slice((*byte)(ptr), copyLength), response.Buffer[:copyLength]) + if len(response.IsoPackets) > 0 { + dst := unsafe.Slice((*byte)(ptr), requestLength) + scatterIsoInResponseBuffer(dst, response.Buffer[:copyLength], response.IsoPackets) + } else { + copy(unsafe.Slice((*byte)(ptr), copyLength), response.Buffer[:copyLength]) + } } return response.Status, actualLength } +func scatterIsoInResponseBuffer(dst []byte, payload []byte, packets []IsoPacketDescriptor) { + cursor := 0 + for i := range packets { + length := int(packets[i].ActualLength) + if length <= 0 { + continue + } + if cursor+length > len(payload) { + length = len(payload) - cursor + if length <= 0 { + return + } + } + offset := int(packets[i].Offset) + if offset < 0 || offset >= len(dst) { + cursor += length + continue + } + end := offset + length + if end > len(dst) { + end = len(dst) + } + copy(dst[offset:end], payload[cursor:cursor+(end-offset)]) + cursor += length + } +} + func (c *darwinVirtualController) sendSubmit(command SubmitCommand) (SubmitResponse, error) { seq := c.seq.Add(1) command.Header.SeqNum = seq diff --git a/service/usbip/host_darwin.go b/service/usbip/host_darwin.go index c18d19825..a22297704 100644 --- a/service/usbip/host_darwin.go +++ b/service/usbip/host_darwin.go @@ -525,11 +525,50 @@ func (s *darwinServerDataSession) handleSubmit(command SubmitCommand) SubmitResp } response.ActualLength = actual if command.Header.Direction == USBIPDirIn && actual > 0 { - response.Buffer = buffer[:min(int(actual), len(buffer))] + if command.NumberOfPackets > 0 { + response.Buffer = packIsoInResponseBuffer(buffer, response.IsoPackets) + response.ActualLength = int32(len(response.Buffer)) + } else { + response.Buffer = buffer[:min(int(actual), len(buffer))] + } } return response } +func packIsoInResponseBuffer(buffer []byte, packets []IsoPacketDescriptor) []byte { + var total int + for i := range packets { + length := int(packets[i].ActualLength) + if length <= 0 { + packets[i].ActualLength = 0 + continue + } + offset := int(packets[i].Offset) + if offset < 0 || offset >= len(buffer) { + packets[i].ActualLength = 0 + continue + } + if offset+length > len(buffer) { + length = len(buffer) - offset + packets[i].ActualLength = int32(length) + } + total += length + } + if total == 0 { + return nil + } + packed := make([]byte, 0, total) + for i := range packets { + length := int(packets[i].ActualLength) + if length <= 0 { + continue + } + offset := int(packets[i].Offset) + packed = append(packed, buffer[offset:offset+length]...) + } + return packed +} + func (s *darwinServerDataSession) trackSubmit(seq uint32, endpoint uint8) { s.access.Lock() defer s.access.Unlock() diff --git a/service/usbip/usbhost_darwin.m b/service/usbip/usbhost_darwin.m index 357d09e46..54117b43d 100644 --- a/service/usbip/usbhost_darwin.m +++ b/service/usbip/usbhost_darwin.m @@ -5,16 +5,32 @@ #import #import #import +#import #import #import -@interface BoxUSBHostDevice : NSObject +@interface BoxUSBHostDevice : NSObject { + os_unfair_lock _stateLock; +} @property(nonatomic, strong) IOUSBHostDevice *device; @property(nonatomic, strong) NSMutableArray *interfaces; @property(nonatomic, strong) NSMutableDictionary *pipes; +- (void)withStateLock:(NS_NOESCAPE void (^)(void))block; @end @implementation BoxUSBHostDevice +- (instancetype)init { + self = [super init]; + if (self != nil) { + _stateLock = OS_UNFAIR_LOCK_INIT; + } + return self; +} +- (void)withStateLock:(NS_NOESCAPE void (^)(void))block { + os_unfair_lock_lock(&_stateLock); + block(); + os_unfair_lock_unlock(&_stateLock); +} @end @interface BoxUSBHostController : NSObject @@ -283,20 +299,25 @@ static IOUSBHostPipe *box_find_pipe_for_endpoint(BoxUSBHostDevice *box, uint8_t } static IOUSBHostPipe *box_pipe_for_endpoint(BoxUSBHostDevice *box, uint8_t endpoint) { - NSNumber *key = @(endpoint); - IOUSBHostPipe *cached = box.pipes[key]; - if (cached != nil) { - return cached; - } - IOUSBHostPipe *pipe = box_find_pipe_for_endpoint(box, endpoint); - if (pipe == nil) { - box_load_interfaces(box); - pipe = box_find_pipe_for_endpoint(box, endpoint); - } - if (pipe != nil) { - box.pipes[key] = pipe; - } - return pipe; + __block IOUSBHostPipe *result = nil; + [box withStateLock:^{ + NSNumber *key = @(endpoint); + IOUSBHostPipe *cached = box.pipes[key]; + if (cached != nil) { + result = cached; + return; + } + IOUSBHostPipe *pipe = box_find_pipe_for_endpoint(box, endpoint); + if (pipe == nil) { + box_load_interfaces(box); + pipe = box_find_pipe_for_endpoint(box, endpoint); + } + if (pipe != nil) { + box.pipes[key] = pipe; + } + result = pipe; + }]; + return result; } static IOUSBHostInterface *box_interface_for_number(BoxUSBHostDevice *box, uint8_t interface_number) { @@ -493,18 +514,25 @@ bool box_usbhost_device_control(box_usbhost_device_t *device, const uint8_t setu if (request.bmRequestType == 0 && request.bRequest == kIOUSBDeviceRequestSetConfiguration && request.wIndex == 0 && request.wLength == 0) { ok = [box.device configureWithValue:request.wValue matchInterfaces:YES error:&error]; if (ok) { - box.pipes = [NSMutableDictionary dictionary]; - box_load_interfaces(box); + [box withStateLock:^{ + box.pipes = [NSMutableDictionary dictionary]; + box_load_interfaces(box); + }]; } } else if (request.bmRequestType == kIOUSBDeviceRequestRecipientInterface && request.bRequest == kIOUSBDeviceRequestSetInterface && request.wLength == 0) { - IOUSBHostInterface *interface = box_interface_for_number(box, request.wIndex & 0xff); + __block IOUSBHostInterface *interface = nil; + [box withStateLock:^{ + interface = box_interface_for_number(box, request.wIndex & 0xff); + }]; if (interface == nil) { error = [NSError errorWithDomain:NSMachErrorDomain code:kIOReturnNotFound userInfo:nil]; } else { ok = [interface selectAlternateSetting:request.wValue error:&error]; if (ok) { - box.pipes = [NSMutableDictionary dictionary]; - box_load_interfaces(box); + [box withStateLock:^{ + box.pipes = [NSMutableDictionary dictionary]; + box_load_interfaces(box); + }]; } } } else { @@ -601,7 +629,23 @@ bool box_usbhost_device_iso(box_usbhost_device_t *device, uint8_t endpoint, uint *status_out = ok ? 0 : (int32_t)(error != nil ? error.code : kIOReturnError); } if (ok && (endpoint & kIOUSBEndpointDescriptorDirection) == kIOUSBEndpointDescriptorDirectionIn && data_len > 0 && data != NULL) { - memcpy(data, payload.bytes, actual <= data_len ? actual : data_len); + const uint8_t *src = payload.bytes; + for (size_t i = 0; i < packet_count; i++) { + int32_t signed_offset = packets[i].offset; + int32_t signed_length = packets[i].actual_length; + if (signed_length <= 0 || signed_offset < 0) { + continue; + } + size_t offset = (size_t)signed_offset; + size_t length = (size_t)signed_length; + if (offset >= data_len) { + continue; + } + if (offset + length > data_len) { + length = data_len - offset; + } + memcpy(data + offset, src + offset, length); + } } return true; }