TestVirtualService.m   [plain text]


//
//  TestVirtualService.m
//  IOHIDFamily
//
//  Created by yg on 1/23/17.
//
//

#import  <XCTest/XCTest.h>
#include <IOKit/hid/IOHIDEventSystemClient.h>
#include <IOKit/hid/IOHIDEvent.h>
#include <mach/mach_time.h>
#include "IOHIDUnitTestUtility.h"
#include "IOHIDPrivateKeys.h"
#import  "IOHIDEventSystemTestController.h"
#import  "IOHIDUserDeviceTestController.h"
#import  "IOHIDDeviceTestController.h"
#import  "IOHIDUnitTestDescriptors.h"


static void  __IOHIDVirtualServiceNotifyCallback ( void * target, void *  context,  IOHIDServiceClientRef service, uint32_t type, CFDictionaryRef property);

static bool  __IOHIDVirtualServiceSetPropertyCallback (void * _Nullable target, void * _Nullable context, IOHIDServiceClientRef service, CFStringRef key, CFTypeRef value);

static CFTypeRef  __IOHIDVirtualServiceCopyPropertyCallback (void * _Nullable target, void * _Nullable context, IOHIDServiceClientRef service,  CFStringRef key);

static IOHIDEventRef  __IOHIDVirtualServiceCopyEvent (void * _Nullable target, void * _Nullable context, IOHIDServiceClientRef service, IOHIDEventType type, IOHIDEventRef matching, IOOptionBits options);

static IOReturn    __IOHIDVirtualServiceSetOutputEvent (void * _Nullable target, void * _Nullable context, IOHIDServiceClientRef service, IOHIDEventRef event);

@interface TestVirtualService : XCTestCase

@property       IOHIDEventSystemClientRef       virtualServiceSystemClient;
@property       dispatch_queue_t                virtualServiceQueue;
@property       dispatch_queue_t                rootQueue;
@property       IOHIDServiceClientRef           virtualService;
@property       NSMutableDictionary             *virtualServicePropertyCache;
@property       dispatch_queue_t                eventControllerQueue;
@property       IOHIDEventSystemTestController *eventController;
@property       NSInteger                       virtualServiceOutputEventCount;

@end

@implementation TestVirtualService

- (void)setUp {
    self.virtualServiceSystemClient =  IOHIDEventSystemClientCreateWithType (kCFAllocatorDefault, kIOHIDEventSystemClientTypeSimple, NULL);
    XCTAssert (self.virtualServiceSystemClient != NULL);
    
    self.rootQueue = IOHIDUnitTestCreateRootQueue(63, 2);
    XCTAssert(self.rootQueue != NULL) ;
    
    self.virtualServiceQueue = dispatch_queue_create_with_target ("IOHIDVirtualService", DISPATCH_QUEUE_SERIAL, self.rootQueue);
    XCTAssert(self.virtualServiceQueue != NULL);

    self.virtualServicePropertyCache = [[NSMutableDictionary alloc] init];
    self.virtualServicePropertyCache[@kIOHIDPrimaryUsagePageKey] = @(kHIDPage_GenericDesktop);
    self.virtualServicePropertyCache[@kIOHIDPrimaryUsageKey] = @(kHIDUsage_GD_Mouse);
    self.virtualServicePropertyCache[@kIOHIDVendorIDKey] = @(0xff00);
    self.virtualServicePropertyCache[@kIOHIDProductIDKey] = @(0xff00);
    self.virtualServicePropertyCache[@kIOHIDDeviceUsagePairsKey] =  @[
                                                                        @{
                                                                           @kIOHIDDeviceUsagePageKey : @(kHIDPage_GenericDesktop),
                                                                           @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_Pointer)
                                                                        },
                                                                        @{
                                                                            @kIOHIDDeviceUsagePageKey : @(kHIDPage_AppleVendor),
                                                                            @kIOHIDDeviceUsageKey: @(kHIDPage_AppleVendor)
                                                                        }
                                                                    ];
    
    self.virtualServicePropertyCache[@kIOHIDTransportKey] = @"Virtual";
    
    NSString * uniqueID = [[[NSUUID alloc] init] UUIDString];

    self.virtualServicePropertyCache[@kIOHIDPhysicalDeviceUniqueIDKey] = uniqueID;
    
    IOHIDEventSystemClientScheduleWithDispatchQueue(self.virtualServiceSystemClient, self.virtualServiceQueue);
    
    
    dispatch_sync (self.virtualServiceQueue, ^() {
        IOHIDVirtualServiceClientCallbacks callbacks = {
            __IOHIDVirtualServiceNotifyCallback,
            __IOHIDVirtualServiceSetPropertyCallback,
            __IOHIDVirtualServiceCopyPropertyCallback,
            __IOHIDVirtualServiceCopyEvent,
            __IOHIDVirtualServiceSetOutputEvent
        };
        self.virtualService = IOHIDVirtualServiceClientCreate (self.virtualServiceSystemClient, NULL, callbacks,  (__bridge void * _Nullable)(self), NULL);
    });
    
    HIDXCTAssertAndThrowTrue (self.virtualService != NULL);

    sleep (2);

    self.eventControllerQueue = dispatch_queue_create_with_target ("IOHIDEventSystemTestController", DISPATCH_QUEUE_SERIAL, self.rootQueue);
    HIDXCTAssertAndThrowTrue(self.eventControllerQueue != nil);
    
    self.eventController = [[IOHIDEventSystemTestController alloc] initWithDeviceUniqueID:uniqueID AndQueue:self.eventControllerQueue];
    HIDXCTAssertAndThrowTrue(self.eventController != nil);

}

- (void)tearDown {
    @autoreleasepool {
        self.eventController = nil;
    }
    
    dispatch_sync (self.virtualServiceQueue, ^() {
        if (self.virtualService) {
            IOHIDVirtualServiceClientRemove (self.virtualService);
             CFRelease(self.virtualService);
        }
        if (self.virtualServiceSystemClient) {
            IOHIDEventSystemClientUnscheduleFromDispatchQueue (self.virtualServiceSystemClient, self.virtualServiceQueue);
            CFRelease (self.virtualServiceSystemClient);
        }
    });

    [super tearDown];
}

- (void)testVirtualServiceDispatchEvent {
    
    uint8_t buffer[] = {1,2,3};
    IOHIDEventRef event = IOHIDEventCreateVendorDefinedEvent (kCFAllocatorDefault, mach_absolute_time(), 0,0,0, buffer, sizeof(buffer), 0);
    XCTAssert (event != NULL);
    
    for (NSInteger index = 0 ; index < 10; index++) {
        XCTAssert(IOHIDVirtualServiceClientDispatchEvent (self.virtualService, event) == TRUE);
    }
    
    // Allow event to be dispatched
    usleep (kDefaultReportDispatchCompletionTime);

    // Make copy
    NSArray *events = nil;
    @synchronized (self.eventController.events) {
        events = [self.eventController.events copy];
    }
    
    EVENTS_STATS stats =  [IOHIDEventSystemTestController getEventsStats:events];

    HIDTestEventLatency(stats);

    XCTAssert(stats.counts[kIOHIDEventTypeVendorDefined] == 10);
    

}

- (void)testVirtualServiceCopyEvent {
    
    IOHIDEventRef event = IOHIDServiceClientCopyEvent (self.eventController.eventService, 0, NULL, 0);
    XCTAssert(event != NULL);
}

- (void)testVirtualServiceSetOutputEvent {
    IOReturn  status;
    
    status = IOHIDServiceClientSetElementValue(self.eventController.eventService, kHIDPage_LEDs, kHIDUsage_LED_CapsLock, 1);
    XCTAssert (status == kIOReturnSuccess, "IOHIDServiceClientSetElementValue:%x",status);
    sleep (1);
    XCTAssert (self.virtualServiceOutputEventCount == 1);
    status = IOHIDServiceClientSetElementValue(self.eventController.eventService, kHIDPage_LEDs, kHIDUsage_LED_CapsLock, 0);
    XCTAssert (status == kIOReturnSuccess, "IOHIDServiceClientSetElementValue:%x",status);
    sleep (1);
    XCTAssert (self.virtualServiceOutputEventCount == 2);
}


- (id) serviceCopyProperty: (IOHIDServiceClientRef) __unused service   : (NSString *) key {
    return self.virtualServicePropertyCache[key];
}

- (Boolean) serviceSetProperty: (IOHIDServiceClientRef) __unused service  : (NSString *) key  : (id) value {
    self.virtualServicePropertyCache[key] = value;
    return true;
}

- (IOHIDEventRef) serviceCopyEvent: (IOHIDServiceClientRef) __unused service  : (IOHIDEventType) __unused type  : (IOHIDEventRef) __unused matching :  (IOOptionBits) __unused options {
    uint8_t buffer[] = {1,2,3};
    IOHIDEventRef event = IOHIDEventCreateVendorDefinedEvent (kCFAllocatorDefault, mach_absolute_time(), 0,0,0, buffer, sizeof(buffer), 0);
    XCTAssert (event != NULL);
    return event;
}


@end

void  __IOHIDVirtualServiceNotifyCallback ( void * __unused target, void *  __unused context,  IOHIDServiceClientRef __unused service, uint32_t type, CFDictionaryRef property) {

    NSLog(@"__IOHIDVirtualServiceNotifyCallback: type:%d property:%@", type, property);

}

bool  __IOHIDVirtualServiceSetPropertyCallback (void * target, void * __unused context, IOHIDServiceClientRef service, CFStringRef key, CFTypeRef value) {
    TestVirtualService *self = (__bridge TestVirtualService *)target;

    NSLog(@"__IOHIDVirtualServiceSetPropertyCallback Key:%@ Value:%@", key , value);

    return [self serviceSetProperty: service : (__bridge NSString *)(key) : (__bridge id)(value)];
}

CFTypeRef  __IOHIDVirtualServiceCopyPropertyCallback (void * target, void * __unused context, IOHIDServiceClientRef service,  CFStringRef key) {
    TestVirtualService *self = (__bridge TestVirtualService *)target;
    
    CFTypeRef value =  (__bridge CFTypeRef)([self serviceCopyProperty: service : (__bridge NSString *)(key)]);
    
    NSLog(@"__IOHIDVirtualServiceCopyPropertyCallback Key:%@ Value:%@", key, value);
    
    return value ? CFRetain(value) : NULL;
}

IOHIDEventRef  __IOHIDVirtualServiceCopyEvent (void * target, void * __unused context, IOHIDServiceClientRef service, IOHIDEventType type, IOHIDEventRef matching, IOOptionBits options) {
    TestVirtualService *self = (__bridge TestVirtualService *)target;

    NSLog(@"__IOHIDVirtualServiceCopyEvent");
    
    return [self serviceCopyEvent:service :type :matching :options];;
}

static IOReturn    __IOHIDVirtualServiceSetOutputEvent (void * target, void * __unused context, IOHIDServiceClientRef __unused service, IOHIDEventRef __unused event) {
    TestVirtualService *self = (__bridge TestVirtualService *)target;

    NSLog(@"__IOHIDVirtualServiceSetOutputEvent");

    self.virtualServiceOutputEventCount += 1;
    return kIOReturnSuccess;
}