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