//
// TestHIDDevice.m
// IOHIDFamily
//
// Created by yg on 1/5/17.
//
//
#import <XCTest/XCTest.h>
#import "IOHIDUnitTestUtility.h"
#import "IOHIDEventSystemTestController.h"
#import "IOHIDUserDeviceTestController.h"
#import "IOHIDDeviceTestController.h"
#import "IOHIDUnitTestDescriptors.h"
static uint8_t descriptorForKeyboard[] = {
HIDKeyboardDescriptor
};
static IOReturn UserDeviceHandleReportAsyncCallback (void * _Nullable refcon, IOReturn result);
static uint8_t descriptorForElements[] = {
HIDVendorDescriptor
};
static void GetReportAsync (
void * _Nullable context,
IOReturn result,
void * _Nullable sender,
IOHIDReportType type,
uint32_t reportID,
uint8_t * report,
CFIndex reportLength);
static void SetReportAsync (
void * _Nullable context,
IOReturn result,
void * _Nullable sender,
IOHIDReportType type,
uint32_t reportID,
uint8_t * report,
CFIndex reportLength);
@interface TestHIDDevice : XCTestCase <IOHIDUserDeviceObserver>
@property dispatch_queue_t deviceControllerQueue;
@property dispatch_queue_t sourceControllerQueue;
@property dispatch_queue_t rootQueue;
@property NSString * elementUniqueID;
@property NSString * keyboardUniqueID;
@property IOHIDUserDeviceTestController * elementController;
@property IOHIDUserDeviceTestController * keyboardController;
@property IOHIDDeviceTestController * keyboardDeviceController;
@property IOHIDDeviceTestController * elementDeviceController;
@end
@implementation TestHIDDevice {
NSInteger setReportCount[4];
NSInteger getReportCount[4];
NSInteger getAsyncReportCount[4];
NSInteger setAsyncReportCount[4];
HIDVendorDescriptorFeatureReport featureReport;
CFIndex featureReportLength;
uint64_t timestamp;
CFIndex handleAsyncReportCount;
}
- (void)setUp {
[super setUp];
self.rootQueue = IOHIDUnitTestCreateRootQueue(31, 2);
self.deviceControllerQueue = dispatch_queue_create_with_target ("IOHIDDeviceTestController", DISPATCH_QUEUE_SERIAL, self.rootQueue);
HIDXCTAssertAndThrowTrue(self.deviceControllerQueue != nil);
self.sourceControllerQueue = dispatch_queue_create_with_target ("IOHIDUserDeviceTestController", DISPATCH_QUEUE_SERIAL, self.rootQueue);
HIDXCTAssertAndThrowTrue(self.sourceControllerQueue != nil);
self.keyboardUniqueID = [[[NSUUID alloc] init] UUIDString];
NSData * descriptorData = [[NSData alloc] initWithBytes:descriptorForKeyboard length:sizeof(descriptorForKeyboard)];
self.keyboardController = [[IOHIDUserDeviceTestController alloc] initWithDescriptor:descriptorData DeviceUniqueID:self.keyboardUniqueID andQueue:self.sourceControllerQueue];
HIDXCTAssertAndThrowTrue(self.keyboardController != nil);
self.keyboardDeviceController = [[IOHIDDeviceTestController alloc] initWithDeviceUniqueID:self.keyboardUniqueID :CFRunLoopGetCurrent()];
HIDXCTAssertAndThrowTrue(self.keyboardDeviceController != nil);
self.elementUniqueID = [[[NSUUID alloc] init] UUIDString];
descriptorData = [[NSData alloc] initWithBytes:descriptorForElements length:sizeof(descriptorForElements)];
self.elementController = [[IOHIDUserDeviceTestController alloc] initWithDescriptor:descriptorData DeviceUniqueID:self.elementUniqueID andQueue:self.sourceControllerQueue];
HIDXCTAssertAndThrowTrue(self.elementController != nil);
self.elementDeviceController = [[IOHIDDeviceTestController alloc] initWithDeviceUniqueID:self.elementUniqueID :CFRunLoopGetCurrent()];
HIDXCTAssertAndThrowTrue(self.elementDeviceController != nil);
}
- (void)tearDown {
[self.keyboardController invalidate];
[self.elementController invalidate];
[self.elementDeviceController invalidate];
[self.keyboardDeviceController invalidate];
@autoreleasepool {
self.keyboardController = nil;
self.elementController = nil;
self.elementDeviceController = nil;
self.keyboardDeviceController = nil;
}
[super tearDown];
}
- (void)testOpenByEventDriver {
dispatch_queue_t systemControllerQueue = dispatch_queue_create_with_target ("IOHIDEventSystemTestController", DISPATCH_QUEUE_SERIAL, self.rootQueue);
HIDXCTAssertAndThrowTrue(systemControllerQueue != nil);
IOHIDEventSystemTestController * eventController = [[IOHIDEventSystemTestController alloc] initWithDeviceUniqueID:self.keyboardUniqueID AndQueue:systemControllerQueue];
HIDXCTAssertAndThrowTrue(eventController != nil);
CFTypeRef prop = IOHIDDeviceGetProperty(self.keyboardDeviceController.device, CFSTR(kIOHIDDeviceOpenedByEventSystemKey));
XCTAssertTrue(prop != NULL, "kIOHIDDeviceOpenedByEventSystemKey does not exist");
if (prop) {
CFRelease(prop);
}
[eventController invalidate];
@autoreleasepool {
eventController = nil;
}
}
- (void)testIOHIDUserDeviceCopyService {
io_service_t service = IOHIDUserDeviceCopyService (self.keyboardController.userDevice);
XCTAssertTrue (service != IO_OBJECT_NULL, "IOHIDUserDeviceGetService:
CFTypeRef phisicalUUID = IORegistryEntryCreateCFProperty(service, CFSTR (kIOHIDPhysicalDeviceUniqueIDKey) , kCFAllocatorDefault, 0);
XCTAssertTrue(phisicalUUID && CFEqual(phisicalUUID, (__bridge CFStringRef)self.keyboardController.userDeviceUniqueID), "UUID does not match");
if (service) {
IOObjectRelease(service);
}
}
- (void)testHIDUserDeviceHandleReport {
__block IOReturn status;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
HIDVendorDescriptorInputReport report;
NSData * reportData;
report.VEN_VendorDefined0081 = 0x11111111;
reportData = [[NSData alloc] initWithBytes: &report length:sizeof (report)];
status |= [self.elementController handleReport: reportData withInterval: 2000];
XCTAssert (status == kIOReturnSuccess, "handleReport: 0x
report.VEN_VendorDefined0081 = 0x22222222;
reportData = [[NSData alloc] initWithBytes: &report length:sizeof (report)];
status |= [self.elementController handleReportAsync: reportData Callback:UserDeviceHandleReportAsyncCallback Context:(__bridge void *)(self)];
XCTAssert (status == kIOReturnSuccess, "handleReportAsync: 0x });
XCTAssert (status == kIOReturnSuccess, "handleReportAsync: 0x
CFRunLoopRunInMode (kCFRunLoopDefaultMode, 2.0, false);
XCTAssert(handleAsyncReportCount == 1);
NSArray *values = [self.elementDeviceController.values copy];
XCTAssert(values && values.count == 2, "Invalid reports
}
- (void)testHIDUserDeviceHandleReportWithTimestamp {
__block IOReturn status = kIOReturnSuccess;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
HIDVendorDescriptorInputReport report;
NSData * reprortData;
timestamp = mach_absolute_time ();
report.VEN_VendorDefined0081 = 0x11111111;
reprortData = [[NSData alloc] initWithBytes: &report length:sizeof (report)];
status |= [self.elementController handleReport: reprortData withTimestamp: timestamp];
report.VEN_VendorDefined0081 = 0x22222222;
reprortData = [[NSData alloc] initWithBytes: &report length:sizeof (report)];
status |= [self.elementController handleReportAsync: reprortData withTimestamp: timestamp Callback:UserDeviceHandleReportAsyncCallback Context:(__bridge void *)(self)];
});
CFRunLoopRunInMode (kCFRunLoopDefaultMode, 2.0, false);
XCTAssert(handleAsyncReportCount == 1);
NSArray *values = [self.elementDeviceController.values copy];
XCTAssert(values && values.count == 2, "Invalid reports
}
- (void)testHIDDevice {
IOReturn status;
NSDictionary * matching;
NSArray * elements;
IOHIDElementRef element;
IOHIDValueRef elementValue;
HIDVendorDescriptorInputReport report;
NSData * reportData;
report.VEN_VendorDefined0081 = 0x11111111;
reportData = [[NSData alloc] initWithBytes: &report length:sizeof (report)];
status = [self.elementController handleReport: reportData withInterval: 2000];
XCTAssert (status == kIOReturnSuccess, "handleReport: 0x
XCTAssert(IOHIDDeviceConformsTo (self.elementDeviceController.device, kHIDPage_AppleVendor, 0x80) == true);
CFTypeRef value = IOHIDDeviceGetProperty (self.elementDeviceController.device, CFSTR(kIOHIDPhysicalDeviceUniqueIDKey));
XCTAssert(value && CFEqual (value, (CFStringRef)self.elementUniqueID) == true);
CFStringRef testPropertyKey = CFSTR("TestDeviceProperty");
IOHIDDeviceSetProperty (self.elementDeviceController.device, testPropertyKey, testPropertyKey);
value = IOHIDDeviceGetProperty (self.elementDeviceController.device, testPropertyKey);
XCTAssert(value && CFEqual (value, testPropertyKey) == true);
matching = @{@kIOHIDElementUsagePageKey:@(kHIDPage_AppleVendor), @kIOHIDElementUsageKey:@(0x81)};
elements = (NSArray *)CFBridgingRelease(IOHIDDeviceCopyMatchingElements (self.elementDeviceController.device, (CFDictionaryRef)matching, 0));
XCTAssert(elements && elements.count == 1);
elementValue = NULL;
element = (__bridge IOHIDElementRef)elements[0];
status = IOHIDDeviceGetValue (self.elementDeviceController.device, element, &elementValue);
XCTAssert(status == kIOReturnSuccess && elementValue != NULL, "IOHIDDeviceGetValue: CFIndex elementValueInteger = IOHIDValueGetIntegerValue (elementValue);
XCTAssert(elementValueInteger == 0x11111111);
matching = @{@kIOHIDElementUsagePageKey:@(kHIDPage_AppleVendor), @kIOHIDElementUsageKey:@(0x83)};
elements = (NSArray *)CFBridgingRelease(IOHIDDeviceCopyMatchingElements (self.elementDeviceController.device, (CFDictionaryRef)matching, 0));
XCTAssert(elements && elements.count == 1);
element = (__bridge IOHIDElementRef)elements[0];
XCTAssert(IOHIDElementGetUsagePage (element) == kHIDPage_AppleVendor);
XCTAssert(IOHIDElementGetUsage (element) == 0x83);
XCTAssert(IOHIDElementGetType (element) == kIOHIDElementTypeFeature);
IOHIDElementRef tempElement = IOHIDElementGetParent (element);
XCTAssert (tempElement != NULL);
XCTAssert(IOHIDElementGetChildren(tempElement) != NULL);
XCTAssert(IOHIDElementGetDevice (element) == self.elementDeviceController.device);
XCTAssert(IOHIDElementGetCollectionType (tempElement) == kIOHIDElementCollectionTypeApplication);
elementValue = IOHIDValueCreateWithIntegerValue (kCFAllocatorDefault, element, mach_absolute_time(), 0x55555555);
XCTAssert (elementValue != NULL);
status = IOHIDDeviceSetValue(self.elementDeviceController.device, element, elementValue);
XCTAssert(status == kIOReturnSuccess || status == kIOReturnUnsupported, "IOHIDDeviceSetValue:
}
- (void)testHIDDeviceReport {
IOReturn status;
CFIndex reportLength;
self.elementController.userDeviceObserver = self;
HIDVendorDescriptorOutputReport outputReport;
for (int index = 0; index < 10; index++) {
status = IOHIDDeviceSetReport (self.elementDeviceController.device, kIOHIDReportTypeOutput, 0, (uint8_t*)&outputReport, sizeof(outputReport));
XCTAssert (status == kIOReturnSuccess, "IOHIDDeviceSetReport : 0x }
sleep (1);
XCTAssert (self->setReportCount[kIOHIDReportTypeOutput] == 10);
for (int index = 0; index < 10; index++) {
status = IOHIDDeviceSetReport (self.elementDeviceController.device, kIOHIDReportTypeFeature, 0, (uint8_t*)&featureReport, sizeof (featureReport));
XCTAssert (status == kIOReturnSuccess, "IOHIDDeviceSetReport : 0x }
sleep (1);
XCTAssert (self->setReportCount[kIOHIDReportTypeFeature] == 10);
featureReportLength = sizeof (featureReport);
for (int index = 0; index < 10; index++) {
status = IOHIDDeviceGetReport (self.elementDeviceController.device, kIOHIDReportTypeFeature, 0, (uint8_t*)&featureReport, &featureReportLength);
XCTAssert (status == kIOReturnSuccess, "IOHIDDeviceGetReport (kIOHIDReportTypeFeature) : 0x }
XCTAssert (self->setReportCount[kIOHIDReportTypeFeature] == 10);
HIDVendorDescriptorInputReport inputReport;
reportLength = sizeof (inputReport);
for (int index = 0; index < 10; index++) {
status = IOHIDDeviceGetReport (self.elementDeviceController.device, kIOHIDReportTypeInput, 0, (uint8_t*)&inputReport, &reportLength);
XCTAssert (status == kIOReturnSuccess, "IOHIDDeviceGetReport (kIOHIDReportTypeInput) : 0x }
XCTAssert (self->setReportCount[kIOHIDReportTypeFeature] == 10);
featureReportLength = sizeof (featureReport);
status = kIOReturnSuccess;
for (int index = 0; index < 10 && status == kIOReturnSuccess; index++) {
status = IOHIDDeviceGetReportWithCallback (self.elementDeviceController.device, kIOHIDReportTypeFeature, 0, (uint8_t*)&featureReport, &featureReportLength, 1.0, GetReportAsync, (__bridge void * _Nonnull)(self));
}
sleep (1);
XCTAssert(status == kIOReturnUnsupported || (status == kIOReturnSuccess && getAsyncReportCount[kIOHIDReportTypeFeature] == 10),
"IOHIDDeviceGetReportWithCallback status: );
featureReportLength = sizeof (featureReport);
status = kIOReturnSuccess;
for (int index = 0; index < 10 && status == kIOReturnSuccess; index++) {
status = IOHIDDeviceSetReportWithCallback (self.elementDeviceController.device, kIOHIDReportTypeFeature, 0, (uint8_t*)&featureReport, featureReportLength, 1.0, SetReportAsync, (__bridge void * _Nonnull)(self));
}
sleep (1);
XCTAssert(status == kIOReturnUnsupported || (status == kIOReturnSuccess && getAsyncReportCount[kIOHIDReportTypeFeature] == 10),
"IOHIDDeviceGetReportWithCallback status: );
}
-(IOReturn) GetReportCallback: (IOHIDReportType) type : (uint32_t __unused) reportID : (uint8_t * __unused) report : (CFIndex * __unused) reportLength {
++self->getReportCount[type];
return kIOReturnSuccess;
}
-(IOReturn) SetReportCallback: (IOHIDReportType) type : (uint32_t __unused) reportID : (uint8_t * __unused) report : (CFIndex __unused) reportLength {
XCTAssert (type == kIOHIDReportTypeOutput || type == kIOHIDReportTypeFeature );
XCTAssert (reportID == 0);
++self->setReportCount[type];
return kIOReturnSuccess;
}
void GetReportAsync (void * _Nullable context,
IOReturn result,
void * _Nullable sender __unused,
IOHIDReportType type,
uint32_t reportID __unused,
uint8_t * report __unused,
CFIndex reportLength __unused
) {
TestHIDDevice *self = (__bridge TestHIDDevice *)context;
XCTAssert(result==kIOReturnSuccess);
++self->getAsyncReportCount[type];
}
void SetReportAsync (void * _Nullable context,
IOReturn result,
void * _Nullable sender __unused,
IOHIDReportType type,
uint32_t reportID __unused,
uint8_t * report __unused,
CFIndex reportLength __unused
) {
TestHIDDevice *self = (__bridge TestHIDDevice *)context;
XCTAssert(result==kIOReturnSuccess);
++self->setAsyncReportCount[type];
}
IOReturn UserDeviceHandleReportAsyncCallback (void * _Nullable refcon, IOReturn result) {
TestHIDDevice *self = (__bridge TestHIDDevice *)refcon;
XCTAssert(result==kIOReturnSuccess);
++self->handleAsyncReportCount;
return result;
}
@end