HIDRemoteDeviceServer.m   [plain text]


//
//  HIDRemoeSimpleServer.m
//  RemoteHID
//
//  Created by yg on 1/14/18.
//

#import <Foundation/Foundation.h>
#import "HIDRemoteDeviceServer.h"
#import "HIDRemoteSimpleProtocol.h"
//#import <HID/HID.h>
#import "RemoteHIDPrivate.h"
#import <IOKit/IOKitLib.h>

@interface HIDRemoteDevice ()

@property        dispatch_semaphore_t   semaphore;
@property        NSData *               lastGetReport;
@property        BOOL                   waitForReport;

@end


@implementation HIDRemoteDevice

-(nullable instancetype)initWithProperties:(nonnull NSDictionary *)properties
{
    self = [super initWithProperties:properties];
    if (!self) {
        return self;
    }
    self.semaphore = dispatch_semaphore_create(0);
    return self;
}

-(IOReturn) getReportHandler:(__unused IOHIDReportType) type
                    reportID:(__unused uint8_t) reportID
                      report:( uint8_t * _Nonnull ) report
                reportLength:(NSUInteger) reportLength
{
    IOReturn status = kIOReturnAborted;
    if (self.waitForReport) {
        self.lastGetReport = [NSData dataWithBytes:report length:reportLength];
        dispatch_semaphore_signal(self.semaphore);
        status = kIOReturnSuccess;
    }
    return status;
}

- (NSString *)description {
    uint64_t serviceID = 0;
    IORegistryEntryGetRegistryEntryID(self.service, &serviceID);
    return [NSString stringWithFormat:@"<HIDRemoteHIDUserDevice:%p id:%lld service:%llx device:%@>",(void *) self, self.deviceID, serviceID, [super description]];
}

@end

@interface HIDRemoteDeviceServer ()

@end

@implementation HIDRemoteDeviceServer

-(nullable instancetype) initWithQueue:(dispatch_queue_t) queue
{
    self = [super init];
    if (!self) {
        return self;
    }
    
    self->_queue   = queue;
    self->_devices = [[NSMutableDictionary alloc] init];
    return self;
}


-(BOOL) connectEndpoint:(__nonnull id) endpoint
{
    NSMutableDictionary * endpointDevices = [[NSMutableDictionary alloc] init];
    self.devices [endpoint] = endpointDevices;
    return TRUE;
}

-(void) disconnectEndpoint:(__nonnull id) endpoint
{
     NSMutableDictionary * endpointDevices = self.devices [endpoint];
    
    [endpointDevices enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key __unused, id  _Nonnull obj, BOOL * _Nonnull stop __unused) {
        HIDUserDevice * device = (HIDUserDevice *)obj;
        [device cancel];
    }];
    
    self.devices [endpoint] = nil;
    
    os_log_debug (RemoteHIDLog (), "DisconnectEP devices:%@", self.devices);
}


-(BOOL) createRemoteDevice:(__nonnull id) endpoint  deviceID:(uint64_t) deviceID property:( NSMutableDictionary * __nonnull) property
{
    
    HIDRemoteDevice * device = [[HIDRemoteDevice alloc] initWithProperties:property];
    if (!device) {
        os_log_error (RemoteHIDLog (), "HIDUserDevice failed, property:%@", property);
        return NO;
    }
    
    os_log (RemoteHIDLog (), "Create device:%@ for endpoint:%@ property:%@", device, endpoint, property);
    
    __weak HIDRemoteDevice  *weakDevice = device;

    [device setSetReportHandler:^IOReturn(IOHIDReportType type, uint32_t reportID, uint8_t * _Nullable report, NSUInteger reportLength) {
        __strong HIDRemoteDevice * strongDevice = weakDevice;
        if (!strongDevice) {
            return kIOReturnNoDevice;
        }
        IOReturn status = [self remoteDeviceSetReport:strongDevice type:type reportID:reportID report:report reportLength:reportLength];
        return status;
    }];

    [device setGetReportHandler:^IOReturn(IOHIDReportType type, uint32_t reportID, uint8_t * _Nullable report, NSUInteger * reportLength) {
        __strong HIDRemoteDevice * strongDevice = weakDevice;
        if (!strongDevice) {
            return kIOReturnNoDevice;
        }
        
        os_log_debug (RemoteHIDLog (), "[device:%d] getReport type:%d reportID:%d", (int)strongDevice.deviceID, type, reportID);
        
        NSData *    reportData;
        long        semaStatus;
        IOReturn    status;
        
        strongDevice.lastGetReport = nil;
        strongDevice.waitForReport = YES;
        
        status = [self remoteDeviceGetReport:strongDevice type:type reportID:reportID report:report reportLength:reportLength];
        require_action_quiet(status == kIOReturnSuccess, exit, os_log_error (RemoteHIDLog (), "[device:%d] remoteDeviceGetReport:0x%x", (int)strongDevice.deviceID, status));

        semaStatus = dispatch_semaphore_wait(strongDevice.semaphore, dispatch_time(DISPATCH_TIME_NOW, kRemoteHIDDeviceTimeout * NSEC_PER_SEC));
        require_action_quiet(semaStatus == 0, exit, status = kIOReturnTimeout; os_log_error (RemoteHIDLog (), "[device:%d] remoteDeviceGetReport timeout", (int)strongDevice.deviceID));
        
        reportData = strongDevice.lastGetReport;
        require_action_quiet(reportData, exit, status = kIOReturnError; os_log_error (RemoteHIDLog (), "[device:%d] invalid report :%@", (int)strongDevice.deviceID, reportData));
        
        memcpy(report, ((NSData *)reportData).bytes, ((NSData *)reportData).length);
        *reportLength = ((NSData *)reportData).length;
    
    exit:
        
        strongDevice.waitForReport = NO;
        
        return status;
    }];

    [device setDispatchQueue:self.queue];
    
    device.endpoint = endpoint;
    device.deviceID = deviceID;
    
    [device activate];

    NSMutableDictionary * endpointDevices = self.devices [endpoint];
    
    HIDRemoteDevice * tmpDevice = endpointDevices [@(deviceID)];
    if (tmpDevice) {
        [tmpDevice cancel];
    }
    endpointDevices [@(deviceID)] = device;

    return YES;
}

-(IOReturn) remoteDeviceSetReport:(__unused HIDRemoteDevice *) device type:(__unused IOHIDReportType) type reportID:(__unused uint8_t) reportID report:(__unused uint8_t *) report reportLength:(__unused NSUInteger) reportLength
{
    return kIOReturnUnsupported;
}

-(IOReturn) remoteDeviceGetReport:(__unused HIDRemoteDevice *) device type:(__unused IOHIDReportType) type reportID:(__unused uint8_t) reportID report:(__unused uint8_t *) report reportLength:(__unused NSUInteger *) reportLength
{
    return kIOReturnUnsupported;
}

-(void) removeRemoteDevice:(__nonnull id) endpoint deviceID:(uint64_t) deviceID
{
    NSMutableDictionary * endpointDevices = self.devices [endpoint];
    HIDUserDevice * device = endpointDevices [@(deviceID)];
    if (device) {
        os_log (RemoteHIDLog (), "Remove device:%@ for endpoint:%@", device, endpoint);
        [device cancel];
    } else {
         os_log_error (RemoteHIDLog (), "Device:%lld not found for endpoint:%@", deviceID, endpoint);
    }
    endpointDevices [@(deviceID)] = nil;
    
    os_log_debug (RemoteHIDLog (), "Remaining devices:%@", self.devices);
    
}

-(BOOL) remoteDeviceReportHandler:(__nonnull id) endpoint packet:(HIDDeviceReport *) packet
{
    if (packet->header.length <= sizeof(HIDDeviceReport)) {
        os_log_error (RemoteHIDLog (), "Invalid report size:%d", packet->header.length);
        return NO;
    }
    
    NSMutableDictionary * devices = self.devices[endpoint];
    HIDUserDevice * device = devices[@(packet->header.deviceID)];
    if (!device) {
        os_log_error (RemoteHIDLog (), "HID Device for deviceID:%d does not exist", packet->header.deviceID);
        return NO;
    }

    IOReturn status = [device handleReport:&(packet->data[0]) reportLength:(packet->header.length - sizeof(HIDDeviceReport))];
    if (status) {
        os_log_error (RemoteHIDLog (), "handleReport:%x", status);
        return NO;
    }
    return YES;
}

-(BOOL) remoteDeviceGetReportHandler:(__nonnull id) endpoint packet:(HIDDeviceReport *) packet
{
    if (packet->header.length <= sizeof(HIDDeviceReport)) {
        os_log_error (RemoteHIDLog (), "Invalid report size:%d", packet->header.length);
        return NO;
    }
    
    NSMutableDictionary * devices = self.devices[endpoint];
    HIDRemoteDevice * device = devices[@(packet->header.deviceID)];
    if (!device) {
        os_log_error (RemoteHIDLog (), "HID Device for deviceID:%d does not exist", packet->header.deviceID);
        return NO;
    }
    
    return [device getReportHandler:packet->reportType reportID:packet->data[0] report:packet->data reportLength:(packet->header.length - sizeof(HIDDeviceReport))] == kIOReturnSuccess;
}

-(void) remoteDeviceConnectHandler:(__nonnull id) endpoint packet:(HIDDeviceControl *) packet
{
    NSData * deviceDescriptionData = [NSData dataWithBytes:&packet->data length:packet->header.length];
    NSError * err = nil;
    id deviceDescription = [NSPropertyListSerialization propertyListWithData:deviceDescriptionData options:NSPropertyListMutableContainersAndLeaves format:NULL error:&err];
    if (err) {
        os_log_error (RemoteHIDLog (), "HIDPacketDevice de-serialization error:%@", err);
        os_log_debug (RemoteHIDLog (), "HIDPacketDevice config data:%@",  [[NSString alloc] initWithData:deviceDescriptionData encoding:NSASCIIStringEncoding]);
        return;
    }
    
    [self createRemoteDevice: endpoint  deviceID:packet->header.deviceID property:deviceDescription];
    
}


-(void) endpointMessageHandler:(__nonnull id) endpoint data:(uint8_t*) data size:(size_t) dataSize
{

    if (!_devices[endpoint]) {
        os_log_info (RemoteHIDLog (), "Unknown accesory:%@", endpoint);
        return;
    }

    require_quiet(dataSize >= (sizeof(HIDTransportHeader) + sizeof(HIDDeviceHeader)), error);
    
    HIDDeviceHeader * packet =  (HIDDeviceHeader *) (data + sizeof(HIDTransportHeader));

    do {
        require_quiet((((uint8_t *)packet + sizeof(HIDDeviceHeader)) <= (data + dataSize)) &&
                      (packet->length >= sizeof (HIDDeviceHeader)) &&
                      (((uint8_t *)packet + packet->length) <= (data + dataSize)) , error);
        
        [self endpointPacketHandler: endpoint packet:packet];
        packet = (HIDDeviceHeader *)((uint8_t *)packet + packet->length);
    } while ((uint8_t *)packet < (data + dataSize));

    return;

error:

    os_log_error (RemoteHIDLog (), "Invalid message length:%zu data:%@", dataSize ,  [NSData dataWithBytes:data length:dataSize]);
}

-(void) endpointPacketHandler:(__nonnull id) endpoint packet:(HIDDeviceHeader *) packet
{
    
    os_log_debug (RemoteHIDLog (), "endpointPacketHandler deviceid:%u type:%d size:%u", packet->deviceID, packet->packetType, packet->length);

    switch (packet->packetType) {
        case HIDPacketTypeDeviceConnect:
            [self remoteDeviceConnectHandler:endpoint packet:(HIDDeviceControl*) packet];
            break;
        case HIDPacketTypeDeviceDisconnect:
            [self removeRemoteDevice:endpoint deviceID:packet->deviceID];
            break;
        case HIDPacketTypeHandleReport:
            [self remoteDeviceReportHandler:endpoint packet:(HIDDeviceReport *)packet];
            break;
        case HIDPacketTypeGetReport:
            [self remoteDeviceGetReportHandler:endpoint packet:(HIDDeviceReport *)packet];
            break;
        default:
            os_log_error (RemoteHIDLog (), "Unsupported packet type:%u", packet->packetType);
            break;
    }
}

-(void) activate
{
}

-(void) cancel
{
    
}

-(void) disconnectAll
{
    [self.devices removeAllObjects];
}
@end