Home | History | Annotate | Download | only in WALT
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #import "WALTClient.h"
     18 
     19 #include <ctype.h>
     20 #include <dispatch/dispatch.h>
     21 #include <mach/clock.h>
     22 #include <mach/mach.h>
     23 #include <mach/mach_host.h>
     24 #include <stdlib.h>
     25 #include <time.h>
     26 
     27 #import "MIDIEndpoint.h"
     28 #import "MIDIMessage.h"
     29 
     30 NSString * const WALTClientErrorDomain = @"WALTClientErrorDomain";
     31 
     32 static NSString * const kWALTVersion = @"v 4";
     33 
     34 static const MIDIChannel kWALTChannel = 1;
     35 static const MIDIByte kWALTSerialOverMIDIProgram = 1;
     36 static const MIDIMessageType kWALTCommandType = MIDIMessageChannelPressure;
     37 
     38 const NSTimeInterval kWALTReadTimeout = 0.2;
     39 static const NSTimeInterval kWALTDuplicateTimeout = 0.01;
     40 
     41 static const int kWALTSyncIterations = 7;
     42 #define kWALTSyncDigitMax 9  // #define to avoid variable length array warnings.
     43 
     44 /** Similar to atoll(), but only reads a maximum of n characters from s. */
     45 static unsigned long long antoull(const char *s, size_t n) {
     46   unsigned long long result = 0;
     47   while (s && n-- && *s && isdigit(*s)) {
     48     result = result * 10 + (*s - '0');
     49     ++s;
     50   }
     51   return result;
     52 }
     53 
     54 /** Converts a mach_timespec_t to its equivalent number of microseconds. */
     55 static int64_t TimespecToMicroseconds(const mach_timespec_t ts) {
     56   return ((int64_t)ts.tv_sec) * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC;
     57 }
     58 
     59 /** Returns the current time (in microseconds) on a clock. */
     60 static int64_t CurrentTime(clock_serv_t clock) {
     61   mach_timespec_t time = {0};
     62   clock_get_time(clock, &time);
     63   return TimespecToMicroseconds(time);
     64 }
     65 
     66 /** Sleeps the current thread for us microseconds. */
     67 static void Sleep(int64_t us) {
     68   const struct timespec ts = {
     69     .tv_sec = (long)(us / USEC_PER_SEC),
     70     .tv_nsec = (us % USEC_PER_SEC) * NSEC_PER_USEC,
     71   };
     72   nanosleep(&ts, NULL);
     73 }
     74 
     75 @interface WALTClient ()
     76 @property (readwrite, nonatomic, getter=isConnected) BOOL connected;
     77 
     78 - (void)drainResponseQueue;
     79 - (BOOL)improveSyncBoundsWithError:(NSError **)error;
     80 - (BOOL)improveMinBoundWithError:(NSError **)error;
     81 - (BOOL)improveMaxBoundWithError:(NSError **)error;
     82 - (BOOL)readRemoteTimestamps:(uint64_t[kWALTSyncDigitMax])times error:(NSError **)error;
     83 - (WALTTrigger)readTrigger:(NSData *)response;
     84 @end
     85 
     86 @implementation WALTClient {
     87   MIDIClient *_client;
     88 
     89   // Responses from the MIDIClient are queued up here with a signal to the semaphore.
     90   NSMutableArray<NSData *> *_responseQueue;  // TODO(pquinn): Lock-free circular buffer?
     91   dispatch_semaphore_t _responseSemaphore;
     92 
     93   BOOL _syncCompleted;
     94 
     95   clock_serv_t _clock;
     96 
     97   NSData *_lastData;
     98   NSTimeInterval _lastDataTimestamp;
     99 
    100   struct {
    101     // All microseconds.
    102     int64_t base;
    103     int64_t minError;
    104     int64_t maxError;
    105   } _sync;
    106 }
    107 
    108 - (instancetype)initWithError:(NSError **)error {
    109   if ((self = [super init])) {
    110     _responseQueue = [[NSMutableArray<NSData *> alloc] init];
    111     _responseSemaphore = dispatch_semaphore_create(0);
    112 
    113     // NB: It's important that this is the same clock used as the base for UIEvent's -timestamp.
    114     kern_return_t result = host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &_clock);
    115 
    116     if (result != KERN_SUCCESS || ![self checkConnectionWithError:error]) {
    117       self = nil;
    118     }
    119   }
    120   return self;
    121 }
    122 
    123 - (void)dealloc {
    124   [self drainResponseQueue];
    125   mach_port_deallocate(mach_task_self(), _clock);
    126 }
    127 
    128 // Ensure only one KVO notification is sent when the connection state is changed.
    129 + (BOOL)automaticallyNotifiesObserversOfConnected {
    130   return NO;
    131 }
    132 
    133 - (void)setConnected:(BOOL)connected {
    134   if (_connected != connected) {
    135     [self willChangeValueForKey:@"connected"];
    136     _connected = connected;
    137     [self didChangeValueForKey:@"connected"];
    138   }
    139 }
    140 
    141 - (BOOL)checkConnectionWithError:(NSError **)error {
    142   if (_client.source.isOnline && _client.destination.isOnline && _syncCompleted) {
    143     self.connected = YES;
    144     return YES;  // Everything's fine.
    145   }
    146 
    147   _syncCompleted = NO;  // Reset the sync state.
    148   [self drainResponseQueue];
    149 
    150   // Create a new client.
    151   // This probably isn't strictly necessary, but solves some of the flakiness on iOS.
    152   _client.delegate = nil;
    153   _client = [[MIDIClient alloc] initWithName:@"WALT" error:error];
    154   _client.delegate = self;
    155   if (!_client) {
    156     self.connected = NO;
    157     return NO;
    158   }
    159 
    160   if (!_client.source.isOnline) {
    161     // Try to connect to the first available input source.
    162     // TODO(pquinn): Make this user-configurable.
    163     NSArray<MIDISource *> *sources = [MIDISource allSources];
    164     if (sources.count) {
    165       if (![_client connectToSource:sources.firstObject error:error]) {
    166         self.connected = NO;
    167         return NO;
    168       }
    169     }
    170   }
    171 
    172   if (!_client.destination.isOnline) {
    173     // Try to connect to the first available input source.
    174     // TODO(pquinn): Make this user-configurable.
    175     NSArray<MIDIDestination *> *destinations = [MIDIDestination allDestinations];
    176     if (destinations.count) {
    177       if (![_client connectToDestination:destinations.firstObject error:error]) {
    178         self.connected = NO;
    179         return NO;
    180       }
    181     }
    182 
    183     if (_client.destination.isOnline) {
    184       // Switch to Serial-over-MIDI mode.
    185       NSData *message = MIDIMessageCreateSimple1(MIDIMessageProgramChange,
    186                                                  kWALTChannel,
    187                                                  kWALTSerialOverMIDIProgram);
    188       if (![_client sendData:message error:error]) {
    189         self.connected = NO;
    190         return NO;
    191       }
    192 
    193       // Make sure it's using a known protocol version.
    194       message = MIDIMessageCreateSimple1(kWALTCommandType, kWALTChannel, WALTVersionCommand);
    195       if (![_client sendData:message error:error]) {
    196         self.connected = NO;
    197         return NO;
    198       }
    199 
    200       NSData *response = [self readResponseWithTimeout:kWALTReadTimeout];
    201       NSString *versionString = [[NSString alloc] initWithData:response
    202                                                       encoding:NSASCIIStringEncoding];
    203       if (![versionString isEqualToString:kWALTVersion]) {
    204         if (error) {
    205           *error = [NSError errorWithDomain:WALTClientErrorDomain
    206                                        code:0
    207                                    userInfo:@{NSLocalizedDescriptionKey:
    208                                                 [@"Unknown WALT version: "
    209                                                   stringByAppendingString:versionString]}];
    210         }
    211         self.connected = NO;
    212         return NO;
    213       }
    214 
    215       if (![self syncClocksWithError:error]) {
    216         self.connected = NO;
    217         return NO;
    218       }
    219 
    220       _syncCompleted = YES;
    221     }
    222   }
    223 
    224   self.connected = (_client.source.isOnline && _client.destination.isOnline && _syncCompleted);
    225   return YES;
    226 }
    227 
    228 #pragma mark - Clock Synchronisation
    229 
    230 - (BOOL)syncClocksWithError:(NSError **)error {
    231   _sync.base = CurrentTime(_clock);
    232 
    233   if (![self sendCommand:WALTZeroSyncCommand error:error]) {
    234     return NO;
    235   }
    236 
    237   NSData *data = [self readResponseWithTimeout:kWALTReadTimeout];
    238   if (![self checkResponse:data forCommand:WALTZeroSyncCommand]) {
    239     if (error) {
    240       *error = [NSError errorWithDomain:WALTClientErrorDomain
    241                                    code:0
    242                                userInfo:@{NSLocalizedDescriptionKey:
    243           [NSString stringWithFormat:@"Bad acknowledgement for WALTZeroSyncCommand: %@", data]}];
    244     }
    245     return NO;
    246   }
    247 
    248   _sync.maxError = CurrentTime(_clock) - _sync.base;
    249   _sync.minError = 0;
    250 
    251   for (int i = 0; i < kWALTSyncIterations; ++i) {
    252     if (![self improveSyncBoundsWithError:error]) {
    253       return NO;
    254     }
    255   }
    256 
    257   // Shift the time base so minError == 0
    258   _sync.base += _sync.minError;
    259   _sync.maxError -= _sync.minError;
    260   _sync.minError = 0;
    261   return YES;
    262 }
    263 
    264 - (BOOL)updateSyncBoundsWithError:(NSError **)error {
    265   // Reset the bounds to unrealistic values
    266   _sync.minError = -1e7;
    267   _sync.maxError =  1e7;
    268 
    269   for (int i = 0; i < kWALTSyncIterations; ++i) {
    270     if (![self improveSyncBoundsWithError:error]) {
    271       return NO;
    272     }
    273   }
    274 
    275   return YES;
    276 }
    277 
    278 - (int64_t)minError {
    279   return _sync.minError;
    280 }
    281 
    282 - (int64_t)maxError {
    283   return _sync.maxError;
    284 }
    285 
    286 - (BOOL)improveSyncBoundsWithError:(NSError **)error {
    287   return ([self improveMinBoundWithError:error] && [self improveMaxBoundWithError:error]);
    288 }
    289 
    290 - (BOOL)improveMinBoundWithError:(NSError **)error {
    291   if (![self sendCommand:WALTResetCommand error:error]) {
    292     return NO;
    293   }
    294 
    295   NSData *data = [self readResponseWithTimeout:kWALTReadTimeout];
    296   if (![self checkResponse:data forCommand:WALTResetCommand]) {
    297     if (error) {
    298       *error = [NSError errorWithDomain:WALTClientErrorDomain
    299                                    code:0
    300                                userInfo:@{NSLocalizedDescriptionKey:
    301           [NSString stringWithFormat:@"Bad acknowledgement for WALTResetCommand: %@", data]}];
    302     }
    303     return NO;
    304   }
    305 
    306   const uint64_t kMaxSleepTime = 700; // s
    307   const uint64_t kMinSleepTime = 70;  // s
    308   const uint64_t kSleepTimeDivider = 10;
    309 
    310   uint64_t sleepTime = (_sync.maxError - _sync.minError) / kSleepTimeDivider;
    311   if (sleepTime > kMaxSleepTime) { sleepTime = kMaxSleepTime; }
    312   if (sleepTime < kMinSleepTime) { sleepTime = kMinSleepTime; }
    313 
    314   struct {
    315     uint64_t local[kWALTSyncDigitMax];
    316     uint64_t remote[kWALTSyncDigitMax];
    317   } digitTimes = {0};
    318 
    319   // Send the digits 1 through 9 and record the times they were sent in digitTimes.local.
    320   for (int i = 0; i < kWALTSyncDigitMax; ++i) {
    321     digitTimes.local[i] = CurrentTime(_clock) - _sync.base;
    322 
    323     char c = '1' + i;
    324     if (![self sendCommand:c error:error]) {
    325       if (error) {
    326         *error = [NSError errorWithDomain:WALTClientErrorDomain
    327                                      code:0
    328                                  userInfo:@{NSLocalizedDescriptionKey:
    329             [NSString stringWithFormat:@"Error sending digit %d", i + 1],
    330                                             NSUnderlyingErrorKey: *error}];
    331       }
    332       return NO;
    333     }
    334     // Sleep between digits
    335     Sleep(sleepTime);
    336   }
    337 
    338   if (![self readRemoteTimestamps:digitTimes.remote error:error]) {
    339     return NO;
    340   }
    341 
    342   // Adjust minError to be the largest delta between local and remote.
    343   for (int i = 0; i < kWALTSyncDigitMax; ++i) {
    344     int64_t delta = digitTimes.local[i] - digitTimes.remote[i];
    345     if (digitTimes.local[i] != 0 && digitTimes.remote[i] != 0 && delta > _sync.minError) {
    346       _sync.minError = delta;
    347     }
    348   }
    349   return YES;
    350 }
    351 
    352 - (BOOL)improveMaxBoundWithError:(NSError **)error {
    353   struct {
    354     uint64_t local[kWALTSyncDigitMax];
    355     uint64_t remote[kWALTSyncDigitMax];
    356   } digitTimes = {0};
    357 
    358   // Ask the WALT to send the digits 1 through 9, and record the times they are received in
    359   // digitTimes.local.
    360   if (![self sendCommand:WALTSendSyncCommand error:error]) {
    361     return NO;
    362   }
    363 
    364   for (int i = 0; i < kWALTSyncDigitMax; ++i) {
    365     NSData *data = [self readResponseWithTimeout:kWALTReadTimeout];
    366     if (data.length != 1) {
    367       if (error) {
    368         *error = [NSError errorWithDomain:WALTClientErrorDomain
    369                                      code:0
    370                                  userInfo:@{NSLocalizedDescriptionKey:
    371             [NSString stringWithFormat:@"Error receiving digit %d: %@", i + 1, data]}];
    372       }
    373       return NO;
    374     }
    375 
    376     char c = ((const char *)data.bytes)[0];
    377     if (!isdigit(c)) {
    378       if (error) {
    379         *error = [NSError errorWithDomain:WALTClientErrorDomain
    380                                      code:0
    381                                  userInfo:@{NSLocalizedDescriptionKey:
    382             [NSString stringWithFormat:@"Error parsing digit response: %c", c]}];
    383       }
    384       return NO;
    385     }
    386 
    387     int digit = c - '0';
    388     digitTimes.local[digit - 1] = CurrentTime(_clock) - _sync.base;
    389   }
    390 
    391   if (![self readRemoteTimestamps:digitTimes.remote error:error]) {
    392     return NO;
    393   }
    394 
    395   // Adjust maxError to be the smallest delta between local and remote
    396   for (int i = 0; i < kWALTSyncDigitMax; ++i) {
    397     int64_t delta = digitTimes.local[i] - digitTimes.remote[i];
    398     if (digitTimes.local[i] != 0 && digitTimes.remote[i] != 0 && delta < _sync.maxError) {
    399       _sync.maxError = delta;
    400     }
    401   }
    402   return YES;
    403 }
    404 
    405 - (BOOL)readRemoteTimestamps:(uint64_t [9])times error:(NSError **)error {
    406   for (int i = 0; i < kWALTSyncDigitMax; ++i) {
    407     // Ask the WALT for each digit's recorded timestamp
    408     if (![self sendCommand:WALTReadoutSyncCommand error:error]) {
    409       return NO;
    410     }
    411 
    412     NSData *data = [self readResponseWithTimeout:kWALTReadTimeout];
    413     if (data.length < 3) {
    414       if (error) {
    415         *error = [NSError errorWithDomain:WALTClientErrorDomain
    416                                      code:0
    417                                  userInfo:@{NSLocalizedDescriptionKey:
    418             [NSString stringWithFormat:@"Error receiving sync digit %d: %@", i + 1, data]}];
    419       }
    420       return NO;
    421     }
    422 
    423     // The reply data is formatted as n:xxxx, where n is a digit between 1 and 9, and xxxx
    424     // is a microsecond timestamp.
    425     int digit = (int)antoull(data.bytes, 1);
    426     uint64_t timestamp = antoull(((const char *)data.bytes) + 2, data.length - 2);
    427 
    428     if (digit != (i + 1) || timestamp == 0) {
    429       if (error) {
    430         *error = [NSError errorWithDomain:WALTClientErrorDomain
    431                                      code:0
    432                                  userInfo:@{NSLocalizedDescriptionKey:
    433             [NSString stringWithFormat:@"Error parsing remote time response for %d: %@", i, data]}];
    434       }
    435       return NO;
    436     }
    437     times[digit - 1] = timestamp;
    438   }
    439   return YES;
    440 }
    441 
    442 #pragma mark - MIDIClient Delegate
    443 
    444 // TODO(pquinn): Errors from these callbacks aren't propoagated anywhere.
    445 
    446 - (void)MIDIClientEndpointAdded:(MIDIClient *)client {
    447   [self performSelectorOnMainThread:@selector(checkConnectionWithError:)
    448                          withObject:nil
    449                       waitUntilDone:NO];
    450 }
    451 
    452 - (void)MIDIClientEndpointRemoved:(MIDIClient *)client {
    453   [self performSelectorOnMainThread:@selector(checkConnectionWithError:)
    454                          withObject:nil
    455                       waitUntilDone:NO];
    456 }
    457 
    458 - (void)MIDIClientConfigurationChanged:(MIDIClient *)client {
    459   [self performSelectorOnMainThread:@selector(checkConnectionWithError:)
    460                          withObject:nil
    461                       waitUntilDone:NO];
    462 }
    463 
    464 - (void)MIDIClient:(MIDIClient *)client receivedError:(NSError *)error {
    465   // TODO(pquinn): What's the scope of these errors?
    466   NSLog(@"WALTClient received unhandled error: %@", error);
    467 }
    468 
    469 - (void)MIDIClient:(MIDIClient *)client receivedData:(NSData *)message {
    470   NSData *body = MIDIMessageBody(message);
    471   @synchronized (_responseQueue) {
    472     // Sometimes a message will be received twice in quick succession. It's not clear where the bug
    473     // is (the WALT, CoreMIDI, or this application), and it cannot be reliably reproduced. As a
    474     // hack, simply ignore messages that appear to be duplicates and arrive within
    475     // kWALTDuplicateTimeout seconds.
    476     if (self.currentTime - _lastDataTimestamp <= kWALTDuplicateTimeout &&
    477         [body isEqualToData:_lastData]) {
    478       NSLog(@"Ignoring duplicate response within kWALTDuplicateTimeout: %@", message);
    479       return;
    480     }
    481 
    482     [_responseQueue addObject:body];
    483     _lastData = body;
    484     _lastDataTimestamp = self.currentTime;
    485   }
    486   dispatch_semaphore_signal(_responseSemaphore);
    487 }
    488 
    489 #pragma mark - Send/Receive
    490 
    491 - (void)drainResponseQueue {
    492   @synchronized (_responseQueue) {
    493     // Drain out any stale responses or the semaphore destructor will assert.
    494     while (_responseQueue.count) {
    495       dispatch_semaphore_wait(_responseSemaphore, DISPATCH_TIME_FOREVER);
    496       [_responseQueue removeObjectAtIndex:0];
    497     }
    498   }
    499 }
    500 
    501 - (NSData *)readResponseWithTimeout:(NSTimeInterval)timeout {
    502   if (dispatch_semaphore_wait(_responseSemaphore,
    503                               dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC))) {
    504     return nil;
    505   }
    506 
    507   @synchronized (_responseQueue) {
    508     NSAssert(_responseQueue.count > 0, @"_responseQueue is empty!");
    509     NSData *response = _responseQueue.firstObject;
    510     [_responseQueue removeObjectAtIndex:0];
    511     return response;
    512   }
    513 }
    514 
    515 - (BOOL)sendCommand:(WALTCommand)command error:(NSError **)error {
    516   NSData *message = MIDIMessageCreateSimple1(kWALTCommandType, kWALTChannel, command);
    517   [self drainResponseQueue];
    518   return [_client sendData:message error:error];
    519 }
    520 
    521 - (BOOL)checkResponse:(NSData *)response forCommand:(WALTCommand)command {
    522   const WALTCommand flipped = isupper(command) ? tolower(command) : toupper(command);
    523   if (response.length < 1 || ((const char *)response.bytes)[0] != flipped) {
    524     return NO;
    525   } else {
    526     return YES;
    527   }
    528 }
    529 
    530 #pragma mark - Specific Commands
    531 
    532 - (NSTimeInterval)lastShockTimeWithError:(NSError **)error {
    533   if (![self sendCommand:WALTGShockCommand error:error]) {
    534     return -1;
    535   }
    536 
    537   NSData *response = [self readResponseWithTimeout:kWALTReadTimeout];
    538   if (!response) {
    539     if (error) {
    540       *error = [NSError errorWithDomain:WALTClientErrorDomain
    541                                    code:0
    542                                userInfo:@{NSLocalizedDescriptionKey:
    543                                             @"Error receiving shock response."}];
    544     }
    545     return -1;
    546   }
    547 
    548   uint64_t microseconds = antoull(response.bytes, response.length);
    549   return ((NSTimeInterval)microseconds + _sync.base) / USEC_PER_SEC;
    550 }
    551 
    552 - (WALTTrigger)readTrigger:(NSData *)response {
    553   NSString *responseString =
    554       [[NSString alloc] initWithData:response encoding:NSASCIIStringEncoding];
    555   NSArray<NSString *> *components = [responseString componentsSeparatedByString:@" "];
    556 
    557   WALTTrigger result = {0};
    558 
    559   if (components.count != 5 ||
    560       ![[components objectAtIndex:0] isEqualToString:@"G"] ||
    561       [components objectAtIndex:1].length != 1) {
    562     return result;
    563   }
    564 
    565   result.tag = [[components objectAtIndex:1] characterAtIndex:0];
    566 
    567   uint64_t microseconds = atoll([components objectAtIndex:2].UTF8String);
    568   result.t = ((NSTimeInterval)microseconds + _sync.base) / USEC_PER_SEC;
    569   result.value = (int)atoll([components objectAtIndex:3].UTF8String);
    570   result.count = (unsigned int)atoll([components objectAtIndex:4].UTF8String);
    571   return result;
    572 }
    573 
    574 - (WALTTrigger)readTriggerWithTimeout:(NSTimeInterval)timeout {
    575   return [self readTrigger:[self readResponseWithTimeout:timeout]];
    576 }
    577 
    578 #pragma mark - Time
    579 
    580 - (NSTimeInterval)baseTime {
    581   return ((NSTimeInterval)_sync.base) / USEC_PER_SEC;
    582 }
    583 
    584 - (NSTimeInterval)currentTime {
    585   return ((NSTimeInterval)CurrentTime(_clock)) / USEC_PER_SEC;
    586 }
    587 @end
    588