1//======== Copyright (c) 2017 Valve Corporation, All rights reserved. =========
2//
3// Purpose: HID device abstraction temporary stub
4//
5//=============================================================================
6#include "../../SDL_internal.h"
7
8#ifdef SDL_JOYSTICK_HIDAPI
9
10#include <CoreBluetooth/CoreBluetooth.h>
11#include <QuartzCore/QuartzCore.h>
12#import <UIKit/UIKit.h>
13#import <mach/mach_time.h>
14#include <pthread.h>
15#include <sys/time.h>
16#include <unistd.h>
17#include "../hidapi/hidapi.h"
18
19#define VALVE_USB_VID       0x28DE
20#define D0G_BLE2_PID        0x1106
21
22typedef uint32_t uint32;
23typedef uint64_t uint64;
24
25// enables detailed NSLog logging of feature reports
26#define FEATURE_REPORT_LOGGING	0
27
28#define REPORT_SEGMENT_DATA_FLAG	0x80
29#define REPORT_SEGMENT_LAST_FLAG	0x40
30
31#define VALVE_SERVICE		@"100F6C32-1735-4313-B402-38567131E5F3"
32
33// (READ/NOTIFICATIONS)
34#define VALVE_INPUT_CHAR	@"100F6C33-1735-4313-B402-38567131E5F3"
35
36//  (READ/WRITE)
37#define VALVE_REPORT_CHAR	@"100F6C34-1735-4313-B402-38567131E5F3"
38
39// TODO: create CBUUID's in __attribute__((constructor)) rather than doing [CBUUID UUIDWithString:...] everywhere
40
41#pragma pack(push,1)
42
43typedef struct
44{
45	uint8_t		segmentHeader;
46	uint8_t		featureReportMessageID;
47	uint8_t		length;
48	uint8_t		settingIdentifier;
49	union {
50		uint16_t	usPayload;
51		uint32_t	uPayload;
52		uint64_t	ulPayload;
53		uint8_t		ucPayload[15];
54	};
55} bluetoothSegment;
56
57typedef struct {
58	uint8_t		id;
59	union {
60		bluetoothSegment segment;
61		struct {
62			uint8_t		segmentHeader;
63			uint8_t		featureReportMessageID;
64			uint8_t		length;
65			uint8_t		settingIdentifier;
66			union {
67				uint16_t	usPayload;
68				uint32_t	uPayload;
69				uint64_t	ulPayload;
70				uint8_t		ucPayload[15];
71			};
72		};
73	};
74} hidFeatureReport;
75
76#pragma pack(pop)
77
78size_t GetBluetoothSegmentSize(bluetoothSegment *segment)
79{
80    return segment->length + 3;
81}
82
83#define RingBuffer_cbElem   19
84#define RingBuffer_nElem    4096
85
86typedef struct {
87	int _first, _last;
88	uint8_t _data[ ( RingBuffer_nElem * RingBuffer_cbElem ) ];
89	pthread_mutex_t accessLock;
90} RingBuffer;
91
92static void RingBuffer_init( RingBuffer *this )
93{
94    this->_first = -1;
95    this->_last = 0;
96    pthread_mutex_init( &this->accessLock, 0 );
97}
98
99static bool RingBuffer_write( RingBuffer *this, const uint8_t *src )
100{
101    pthread_mutex_lock( &this->accessLock );
102    memcpy( &this->_data[ this->_last ], src, RingBuffer_cbElem );
103    if ( this->_first == -1 )
104    {
105        this->_first = this->_last;
106    }
107    this->_last = ( this->_last + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
108    if ( this->_last == this->_first )
109    {
110        this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
111        pthread_mutex_unlock( &this->accessLock );
112        return false;
113    }
114    pthread_mutex_unlock( &this->accessLock );
115    return true;
116}
117
118static bool RingBuffer_read( RingBuffer *this, uint8_t *dst )
119{
120    pthread_mutex_lock( &this->accessLock );
121    if ( this->_first == -1 )
122    {
123        pthread_mutex_unlock( &this->accessLock );
124        return false;
125    }
126    memcpy( dst, &this->_data[ this->_first ], RingBuffer_cbElem );
127    this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
128    if ( this->_first == this->_last )
129    {
130        this->_first = -1;
131    }
132    pthread_mutex_unlock( &this->accessLock );
133    return true;
134}
135
136
137#pragma mark HIDBLEDevice Definition
138
139typedef enum
140{
141	BLEDeviceWaitState_None,
142	BLEDeviceWaitState_Waiting,
143	BLEDeviceWaitState_Complete,
144	BLEDeviceWaitState_Error
145} BLEDeviceWaitState;
146
147@interface HIDBLEDevice : NSObject <CBPeripheralDelegate>
148{
149	RingBuffer _inputReports;
150	uint8_t	_featureReport[20];
151	BLEDeviceWaitState	_waitStateForReadFeatureReport;
152	BLEDeviceWaitState	_waitStateForWriteFeatureReport;
153}
154
155@property (nonatomic, readwrite) bool connected;
156@property (nonatomic, readwrite) bool ready;
157
158@property (nonatomic, strong) CBPeripheral     *bleSteamController;
159@property (nonatomic, strong) CBCharacteristic *bleCharacteristicInput;
160@property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport;
161
162- (id)initWithPeripheral:(CBPeripheral *)peripheral;
163
164@end
165
166
167@interface HIDBLEManager : NSObject <CBCentralManagerDelegate>
168
169@property (nonatomic) int nPendingScans;
170@property (nonatomic) int nPendingPairs;
171@property (nonatomic, strong) CBCentralManager *centralManager;
172@property (nonatomic, strong) NSMapTable<CBPeripheral *, HIDBLEDevice *> *deviceMap;
173@property (nonatomic, retain) dispatch_queue_t bleSerialQueue;
174
175+ (instancetype)sharedInstance;
176- (void)startScan:(int)duration;
177- (void)stopScan;
178- (int)updateConnectedSteamControllers:(BOOL) bForce;
179- (void)appWillResignActiveNotification:(NSNotification *)note;
180- (void)appDidBecomeActiveNotification:(NSNotification *)note;
181
182@end
183
184
185// singleton class - access using HIDBLEManager.sharedInstance
186@implementation HIDBLEManager
187
188+ (instancetype)sharedInstance
189{
190	static HIDBLEManager *sharedInstance = nil;
191	static dispatch_once_t onceToken;
192	dispatch_once(&onceToken, ^{
193		sharedInstance = [HIDBLEManager new];
194		sharedInstance.nPendingScans = 0;
195		sharedInstance.nPendingPairs = 0;
196
197		[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil];
198		[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
199
200		// receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical
201		// race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means
202		// that we can still screw this up.
203		// most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really
204		// listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY
205		// DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery.
206		// see also the error-handling states in the peripheral delegate to re-open the device if it gets closed
207		sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL );
208		dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) );
209
210		// creating a CBCentralManager will always trigger a future centralManagerDidUpdateState:
211		// where any scanning gets started or connecting to existing peripherals happens, it's never already in a
212		// powered-on state for a newly launched application.
213		sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue];
214		sharedInstance.deviceMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:4];
215	});
216	return sharedInstance;
217}
218
219// called for NSNotification UIApplicationWillResignActiveNotification
220- (void)appWillResignActiveNotification:(NSNotification *)note
221{
222	// we'll get resign-active notification if pairing is happening.
223	if ( self.nPendingPairs > 0 )
224		return;
225
226	for ( CBPeripheral *peripheral in self.deviceMap )
227	{
228		HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
229		if ( steamController )
230		{
231			steamController.connected = NO;
232			steamController.ready = NO;
233			[self.centralManager cancelPeripheralConnection:peripheral];
234		}
235	}
236	[self.deviceMap removeAllObjects];
237}
238
239// called for NSNotification UIApplicationDidBecomeActiveNotification
240//  whenever the application comes back from being inactive, trigger a 20s pairing scan and reconnect
241//  any devices that may have paired while we were inactive.
242- (void)appDidBecomeActiveNotification:(NSNotification *)note
243{
244	[self updateConnectedSteamControllers:true];
245	[self startScan:20];
246}
247
248- (int)updateConnectedSteamControllers:(BOOL) bForce
249{
250	static uint64_t s_unLastUpdateTick = 0;
251	static mach_timebase_info_data_t s_timebase_info;
252
253	if (s_timebase_info.denom == 0)
254	{
255		mach_timebase_info( &s_timebase_info );
256	}
257
258	uint64_t ticksNow = mach_approximate_time();
259	if ( !bForce && ( ( (ticksNow - s_unLastUpdateTick) * s_timebase_info.numer ) / s_timebase_info.denom ) < (5ull * NSEC_PER_SEC) )
260		return (int)self.deviceMap.count;
261
262	// we can see previously connected BLE peripherals but can't connect until the CBCentralManager
263	// is fully powered up - only do work when we are in that state
264	if ( self.centralManager.state != CBManagerStatePoweredOn )
265		return (int)self.deviceMap.count;
266
267	// only update our last-check-time if we actually did work, otherwise there can be a long delay during initial power-up
268	s_unLastUpdateTick = mach_approximate_time();
269
270	// if a pair is in-flight, the central manager may still give it back via retrieveConnected... and
271	// cause the SDL layer to attempt to initialize it while some of its endpoints haven't yet been established
272	if ( self.nPendingPairs > 0 )
273		return (int)self.deviceMap.count;
274
275	NSArray<CBPeripheral *> *peripherals = [self.centralManager retrieveConnectedPeripheralsWithServices: @[ [CBUUID UUIDWithString:@"180A"]]];
276	for ( CBPeripheral *peripheral in peripherals )
277	{
278		// we already know this peripheral
279		if ( [self.deviceMap objectForKey: peripheral] != nil )
280			continue;
281
282		NSLog( @"connected peripheral: %@", peripheral );
283		if ( [peripheral.name isEqualToString:@"SteamController"] )
284		{
285			self.nPendingPairs += 1;
286			HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
287			[self.deviceMap setObject:steamController forKey:peripheral];
288			[self.centralManager connectPeripheral:peripheral options:nil];
289		}
290	}
291
292	return (int)self.deviceMap.count;
293}
294
295// manual API for folks to start & stop scanning
296- (void)startScan:(int)duration
297{
298	NSLog( @"BLE: requesting scan for %d seconds", duration );
299	@synchronized (self)
300	{
301		if ( _nPendingScans++ == 0 )
302		{
303			[self.centralManager scanForPeripheralsWithServices:nil options:nil];
304		}
305	}
306
307	if ( duration != 0 )
308	{
309		dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
310			[self stopScan];
311		});
312	}
313}
314
315- (void)stopScan
316{
317	NSLog( @"BLE: stopping scan" );
318	@synchronized (self)
319	{
320		if ( --_nPendingScans <= 0 )
321		{
322			_nPendingScans = 0;
323			[self.centralManager stopScan];
324		}
325	}
326}
327
328
329#pragma mark CBCentralManagerDelegate Implementation
330
331// called whenever the BLE hardware state changes.
332- (void)centralManagerDidUpdateState:(CBCentralManager *)central
333{
334	switch ( central.state )
335	{
336		case CBCentralManagerStatePoweredOn:
337		{
338			NSLog( @"CoreBluetooth BLE hardware is powered on and ready" );
339
340			// at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices,
341			// otherwise callers should occaisionally do additional scans. we don't want to continuously be
342			// scanning because it drains battery, causes other nearby people to have a hard time pairing their
343			// Steam Controllers, and may also trigger firmware weirdness when a device attempts to start
344			// the pairing sequence multiple times concurrently
345			if ( [self updateConnectedSteamControllers:false] == 0 )
346			{
347				// TODO: we could limit our scan to only peripherals supporting the SteamController service, but
348				//  that service doesn't currently fit in the base advertising packet, we'd need to put it into an
349				//  extended scan packet. Useful optimization downstream, but not currently necessary
350				//	NSArray *services = @[[CBUUID UUIDWithString:VALVE_SERVICE]];
351				[self startScan:20];
352			}
353			break;
354		}
355
356		case CBCentralManagerStatePoweredOff:
357			NSLog( @"CoreBluetooth BLE hardware is powered off" );
358			break;
359
360		case CBCentralManagerStateUnauthorized:
361			NSLog( @"CoreBluetooth BLE state is unauthorized" );
362			break;
363
364		case CBCentralManagerStateUnknown:
365			NSLog( @"CoreBluetooth BLE state is unknown" );
366			break;
367
368		case CBCentralManagerStateUnsupported:
369			NSLog( @"CoreBluetooth BLE hardware is unsupported on this platform" );
370			break;
371
372		case CBCentralManagerStateResetting:
373			NSLog( @"CoreBluetooth BLE manager is resetting" );
374			break;
375	}
376}
377
378- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
379{
380	HIDBLEDevice *steamController = [_deviceMap objectForKey:peripheral];
381	steamController.connected = YES;
382	self.nPendingPairs -= 1;
383}
384
385- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
386{
387	NSLog( @"Failed to connect: %@", error );
388	[_deviceMap removeObjectForKey:peripheral];
389	self.nPendingPairs -= 1;
390}
391
392- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
393{
394	NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
395	NSString *log = [NSString stringWithFormat:@"Found '%@'", localName];
396
397	if ( [localName isEqualToString:@"SteamController"] )
398	{
399		NSLog( @"%@ : %@ - %@", log, peripheral, advertisementData );
400		self.nPendingPairs += 1;
401		HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
402		[self.deviceMap setObject:steamController forKey:peripheral];
403		[self.centralManager connectPeripheral:peripheral options:nil];
404	}
405}
406
407- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
408{
409	HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
410	if ( steamController )
411	{
412		steamController.connected = NO;
413		steamController.ready = NO;
414		[self.deviceMap removeObjectForKey:peripheral];
415	}
416}
417
418@end
419
420
421// Core Bluetooth devices calling back on event boundaries of their run-loops. so annoying.
422static void process_pending_events()
423{
424	CFRunLoopRunResult res;
425	do
426	{
427		res = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0.001, FALSE );
428	}
429	while( res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut );
430}
431
432@implementation HIDBLEDevice
433
434- (id)init
435{
436	if ( self = [super init] )
437	{
438        RingBuffer_init( &_inputReports );
439		self.bleSteamController = nil;
440		self.bleCharacteristicInput = nil;
441		self.bleCharacteristicReport = nil;
442		_connected = NO;
443		_ready = NO;
444	}
445	return self;
446}
447
448- (id)initWithPeripheral:(CBPeripheral *)peripheral
449{
450	if ( self = [super init] )
451	{
452        RingBuffer_init( &_inputReports );
453		_connected = NO;
454		_ready = NO;
455		self.bleSteamController = peripheral;
456		if ( peripheral )
457		{
458			peripheral.delegate = self;
459		}
460		self.bleCharacteristicInput = nil;
461		self.bleCharacteristicReport = nil;
462	}
463	return self;
464}
465
466- (void)setConnected:(bool)connected
467{
468	_connected = connected;
469	if ( _connected )
470	{
471		[_bleSteamController discoverServices:nil];
472	}
473	else
474	{
475		NSLog( @"Disconnected" );
476	}
477}
478
479- (size_t)read_input_report:(uint8_t *)dst
480{
481	if ( RingBuffer_read( &_inputReports, dst+1 ) )
482	{
483		*dst = 0x03;
484		return 20;
485	}
486	return 0;
487}
488
489- (int)send_report:(const uint8_t *)data length:(size_t)length
490{
491	[_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
492	return (int)length;
493}
494
495- (int)send_feature_report:(hidFeatureReport *)report
496{
497#if FEATURE_REPORT_LOGGING
498	uint8_t *reportBytes = (uint8_t *)report;
499
500	NSLog( @"HIDBLE:send_feature_report (%02zu/19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", GetBluetoothSegmentSize( report->segment ),
501		  reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6],
502		  reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12],
503		  reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18],
504		  reportBytes[19] );
505#endif
506
507	int sendSize = (int)GetBluetoothSegmentSize( &report->segment );
508	if ( sendSize > 20 )
509		sendSize = 20;
510
511#if 1
512	// fire-and-forget - we are going to not wait for the response here because all Steam Controller BLE send_feature_report's are ignored,
513	//  except errors.
514	[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
515
516	// pretend we received a result anybody cares about
517	return 19;
518
519#else
520	// this is technically the correct send_feature_report logic if you want to make sure it gets through and is
521	// acknowledged or errors out
522	_waitStateForWriteFeatureReport = BLEDeviceWaitState_Waiting;
523	[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize
524									 ] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
525
526	while ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting )
527	{
528		process_pending_events();
529	}
530
531	if ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error )
532	{
533		_waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
534		return -1;
535	}
536
537	_waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
538	return 19;
539#endif
540}
541
542- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer
543{
544	_waitStateForReadFeatureReport = BLEDeviceWaitState_Waiting;
545	[_bleSteamController readValueForCharacteristic:_bleCharacteristicReport];
546
547	while ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting )
548		process_pending_events();
549
550	if ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Error )
551	{
552		_waitStateForReadFeatureReport = BLEDeviceWaitState_None;
553		return -1;
554	}
555
556	memcpy( buffer, _featureReport, sizeof(_featureReport) );
557
558	_waitStateForReadFeatureReport = BLEDeviceWaitState_None;
559
560#if FEATURE_REPORT_LOGGING
561	NSLog( @"HIDBLE:get_feature_report (19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]",
562		  buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6],
563		  buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12],
564		  buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18],
565		  buffer[19] );
566#endif
567
568	return 19;
569}
570
571#pragma mark CBPeripheralDelegate Implementation
572
573- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
574{
575	for (CBService *service in peripheral.services)
576	{
577		NSLog( @"Found Service: %@", service );
578		if ( [service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]] )
579		{
580			[peripheral discoverCharacteristics:nil forService:service];
581		}
582	}
583}
584
585- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
586{
587	// nothing yet needed here, enable for logging
588	if ( /* DISABLES CODE */ (0) )
589	{
590		for ( CBDescriptor *descriptor in characteristic.descriptors )
591		{
592			NSLog( @" - Descriptor '%@'", descriptor );
593		}
594	}
595}
596
597- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
598{
599	if ([service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]])
600	{
601		for (CBCharacteristic *aChar in service.characteristics)
602		{
603			NSLog( @"Found Characteristic %@", aChar );
604
605			if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR]] )
606			{
607				self.bleCharacteristicInput = aChar;
608			}
609			else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
610			{
611				self.bleCharacteristicReport = aChar;
612				[self.bleSteamController discoverDescriptorsForCharacteristic: aChar];
613			}
614		}
615	}
616}
617
618- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
619{
620	static uint64_t s_ticksLastOverflowReport = 0;
621
622	// receiving an input report is the final indicator that the user accepted a pairing
623	// request and that we successfully established notification. CoreBluetooth has no
624	// notification of the pairing acknowledgement, which is a bad oversight.
625	if ( self.ready == NO )
626	{
627		self.ready = YES;
628		HIDBLEManager.sharedInstance.nPendingPairs -= 1;
629	}
630
631	if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] )
632	{
633		NSData *data = [characteristic value];
634		if ( data.length != 19 )
635		{
636			NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 19", (unsigned long)data.length );
637		}
638		if ( !RingBuffer_write( &_inputReports, (const uint8_t *)data.bytes ) )
639		{
640			uint64_t ticksNow = mach_approximate_time();
641			if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) )
642			{
643				NSLog( @"HIDBLE: input report buffer overflow" );
644				s_ticksLastOverflowReport = ticksNow;
645			}
646		}
647	}
648	else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] )
649	{
650		memset( _featureReport, 0, sizeof(_featureReport) );
651
652		if ( error != nil )
653		{
654			NSLog( @"HIDBLE: get_feature_report error: %@", error );
655			_waitStateForReadFeatureReport = BLEDeviceWaitState_Error;
656		}
657		else
658		{
659			NSData *data = [characteristic value];
660			if ( data.length != 20 )
661			{
662				NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 20", (unsigned long)data.length );
663			}
664			memcpy( _featureReport, data.bytes, MIN( data.length, sizeof(_featureReport) ) );
665			_waitStateForReadFeatureReport = BLEDeviceWaitState_Complete;
666		}
667	}
668}
669
670- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
671{
672	if ( [characteristic.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
673	{
674		if ( error != nil )
675		{
676			NSLog( @"HIDBLE: write_feature_report error: %@", error );
677			_waitStateForWriteFeatureReport = BLEDeviceWaitState_Error;
678		}
679		else
680		{
681			_waitStateForWriteFeatureReport = BLEDeviceWaitState_Complete;
682		}
683	}
684}
685
686- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
687{
688	NSLog( @"didUpdateNotifcationStateForCharacteristic %@ (%@)", characteristic, error );
689}
690
691@end
692
693
694#pragma mark hid_api implementation
695
696struct hid_device_ {
697	void *device_handle;
698	int blocking;
699	hid_device *next;
700};
701
702int HID_API_EXPORT HID_API_CALL hid_init(void)
703{
704	return ( HIDBLEManager.sharedInstance == nil ) ? -1 : 0;
705}
706
707int HID_API_EXPORT HID_API_CALL hid_exit(void)
708{
709	return 0;
710}
711
712void HID_API_EXPORT HID_API_CALL hid_ble_scan( bool bStart )
713{
714	HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
715	if ( bStart )
716	{
717		[bleManager startScan:0];
718	}
719	else
720	{
721		[bleManager stopScan];
722	}
723}
724
725hid_device * HID_API_EXPORT hid_open_path( const char *path, int bExclusive /* = false */ )
726{
727	hid_device *result = NULL;
728	NSString *nssPath = [NSString stringWithUTF8String:path];
729	HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
730	NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
731
732	for ( HIDBLEDevice *device in devices )
733	{
734		// we have the device but it hasn't found its service or characteristics until it is connected
735		if ( !device.ready || !device.connected || !device.bleCharacteristicInput )
736			continue;
737
738		if ( [device.bleSteamController.identifier.UUIDString isEqualToString:nssPath] )
739		{
740			result = (hid_device *)malloc( sizeof( hid_device ) );
741			memset( result, 0, sizeof( hid_device ) );
742			result->device_handle = (void*)CFBridgingRetain( device );
743			result->blocking = NO;
744			// enable reporting input events on the characteristic
745			[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
746			return result;
747		}
748	}
749	return result;
750}
751
752void  HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
753{
754	/* This function is identical to the Linux version. Platform independent. */
755	struct hid_device_info *d = devs;
756	while (d) {
757		struct hid_device_info *next = d->next;
758		free(d->path);
759		free(d->serial_number);
760		free(d->manufacturer_string);
761		free(d->product_string);
762		free(d);
763		d = next;
764	}
765}
766
767int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
768{
769	/* All Nonblocking operation is handled by the library. */
770	dev->blocking = !nonblock;
771
772	return 0;
773}
774
775struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
776{ @autoreleasepool {
777	struct hid_device_info *root = NULL;
778
779	if ( ( vendor_id == 0 && product_id == 0 ) ||
780		 ( vendor_id == VALVE_USB_VID && product_id == D0G_BLE2_PID ) )
781	{
782		HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
783		[bleManager updateConnectedSteamControllers:false];
784		NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
785		for ( HIDBLEDevice *device in devices )
786		{
787			// there are several brief windows in connecting to an already paired device and
788			// one long window waiting for users to confirm pairing where we don't want
789			// to consider a device ready - if we hand it back to SDL or another
790			// Steam Controller consumer, their additional SC setup work will fail
791			// in unusual/silent ways and we can actually corrupt the BLE stack for
792			// the entire system and kill the appletv remote's Menu button (!)
793			if ( device.bleSteamController.state != CBPeripheralStateConnected ||
794				 device.connected == NO || device.ready == NO )
795			{
796				if ( device.ready == NO && device.bleCharacteristicInput != nil )
797				{
798					// attempt to register for input reports. this call will silently fail
799					// until the pairing finalizes with user acceptance. oh, apple.
800					[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
801				}
802				continue;
803			}
804			struct hid_device_info *device_info = (struct hid_device_info *)malloc( sizeof(struct hid_device_info) );
805			memset( device_info, 0, sizeof(struct hid_device_info) );
806			device_info->next = root;
807			root = device_info;
808			device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String );
809			device_info->vendor_id = VALVE_USB_VID;
810			device_info->product_id = D0G_BLE2_PID;
811			device_info->product_string = wcsdup( L"Steam Controller" );
812			device_info->manufacturer_string = wcsdup( L"Valve Corporation" );
813		}
814	}
815	return root;
816}}
817
818int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
819{
820	static wchar_t s_wszManufacturer[] = L"Valve Corporation";
821	wcsncpy( string, s_wszManufacturer, sizeof(s_wszManufacturer)/sizeof(s_wszManufacturer[0]) );
822	return 0;
823}
824
825int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
826{
827	static wchar_t s_wszProduct[] = L"Steam Controller";
828	wcsncpy( string, s_wszProduct, sizeof(s_wszProduct)/sizeof(s_wszProduct[0]) );
829	return 0;
830}
831
832int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
833{
834	static wchar_t s_wszSerial[] = L"12345";
835	wcsncpy( string, s_wszSerial, sizeof(s_wszSerial)/sizeof(s_wszSerial[0]) );
836	return 0;
837}
838
839int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
840{
841    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
842
843	if ( !device_handle.connected )
844		return -1;
845
846	return [device_handle send_report:data length:length];
847}
848
849void HID_API_EXPORT hid_close(hid_device *dev)
850{
851    HIDBLEDevice *device_handle = CFBridgingRelease( dev->device_handle );
852
853	// disable reporting input events on the characteristic
854	if ( device_handle.connected ) {
855		[device_handle.bleSteamController setNotifyValue:NO forCharacteristic:device_handle.bleCharacteristicInput];
856	}
857
858	free( dev );
859}
860
861int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
862{
863    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
864
865	if ( !device_handle.connected )
866		return -1;
867
868	return [device_handle send_feature_report:(hidFeatureReport *)(void *)data];
869}
870
871int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
872{
873    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
874
875	if ( !device_handle.connected )
876		return -1;
877
878	size_t written = [device_handle get_feature_report:data[0] into:data];
879
880	return written == length-1 ? (int)length : (int)written;
881}
882
883int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
884{
885    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
886
887	if ( !device_handle.connected )
888		return -1;
889
890	return hid_read_timeout(dev, data, length, 0);
891}
892
893int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
894{
895    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
896
897	if ( !device_handle.connected )
898		return -1;
899
900	if ( milliseconds != 0 )
901	{
902		NSLog( @"hid_read_timeout with non-zero wait" );
903	}
904	int result = (int)[device_handle read_input_report:data];
905#if FEATURE_REPORT_LOGGING
906	NSLog( @"HIDBLE:hid_read_timeout (%d) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", result,
907		  data[1], data[2], data[3], data[4], data[5], data[6],
908		  data[7], data[8], data[9], data[10], data[11], data[12],
909		  data[13], data[14], data[15], data[16], data[17], data[18],
910		  data[19] );
911#endif
912	return result;
913}
914
915#endif /* SDL_JOYSTICK_HIDAPI */
916