summaryrefslogtreecommitdiff
path: root/SmartDeviceLink-iOS/SmartDeviceLink/SDLIAPTransport.m
blob: f45fb7aabbf2746ffa10135958ad5ca5e266c99b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
//  SDLIAPTransport.h
//
//  Copyright (c) 2014 Ford Motor Company. All rights reserved.

@import Foundation;
@import UIKit;

#import "SDLIAPTransport.h"
#import "SDLDebugTool.h"
#import "SDLSiphonServer.h"

#define LEGACY_PROTOCOL_STRING @"com.ford.sync.prot0"
#define CONTROL_PROTOCOL_STRING @"com.smartdevicelink.prot0"

#define IAP_INPUT_BUFFER_SIZE 1024


@interface SDLIAPTransport ()

@property (strong) EASession *session;
@property (strong) EAAccessory *accessory;
@property (strong) NSMutableData *writeData;
@property (assign) BOOL onControlProtocol;
@property (assign) BOOL useLegacyProtocol;
@property (strong) NSString *protocolString;
@property (assign) BOOL isOutputStreamReady;
@property (assign) BOOL isInputStreamReady;
@property (assign) BOOL transportReady;


@property (strong) NSTimer* backgroundedTimer;


@end



@implementation SDLIAPTransport

- (id)init {
    if (self = [super initWithEndpoint:nil endpointParam:nil]) {

        [SDLDebugTool logInfo:@"Init" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(accessoryConnected:) name:EAAccessoryDidConnectNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(accessoryDisconnected:) name:EAAccessoryDidDisconnectNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
        
        [SDLSiphonServer init];
    }
    return self;
}


#pragma mark -
#pragma mark SDLTransport Implementation

- (void)connect {
    [SDLDebugTool logInfo:@"Connect" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
    
    if (!self.session){
        [self checkForValidConnectedAccessory];
        
        if (self.accessory && self.protocolString) {
            [self openSession];
        } else {
            [SDLDebugTool logInfo:@"No Devices Found" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
        }
    } else {
        [SDLDebugTool logInfo:@"Session Already Open" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
    }
}

- (void)disconnect {
    [SDLDebugTool logInfo:@"Disconnect" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
    
    if (self.session) {
        [self closeSession];
        
        if (!self.onControlProtocol) {
            [SDLDebugTool logInfo:@"Transport Not Ready" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
            self.transportReady = NO;
            [self notifyTransportDisconnected];
        }
    }
}

- (void)sendData:(NSData*) data {
    [self writeDataOut:data];
}



#pragma mark -
#pragma mark EAAccessory Notifications

- (void)accessoryConnected:(NSNotification*) notification {
    [SDLDebugTool logInfo:@"Accessory Connected" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
    [self connect];
}

- (void)accessoryDisconnected:(NSNotification*) notification {
    [SDLDebugTool logInfo:@"Accessory Disconnected" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
    [self disconnect];
}

-(void)applicationWillEnterForeground:(NSNotification *)notification {
    [SDLDebugTool logInfo:@"Will Enter Foreground" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
    //TODO:DEBUG
    //    [self.backgroundedTimer invalidate];
    
    [self setupControllerForAccessory:nil withProtocolString:nil];
    [self connect];
}

-(void)applicationDidEnterBackground:(NSNotification *)notification {
    [SDLDebugTool logInfo:@"Did Enter Background" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
    //TODO:DEBUG
    //    self.backgroundedTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(backgroundButAwake:) userInfo: nil repeats: YES];
}


#pragma mark -
#pragma mark Response Timers


- (void)protocolIndexRestart{
    
    //TODO:DEBUG
    [SDLDebugTool logInfo:@"PI Timer" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
    
    if (!self.transportReady) {
        [SDLDebugTool logInfo:@"PI Restart" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
        [self closeSession];
        [self connect];
    }
        
}



#pragma mark -
#pragma mark NSStreamDelegateEventExtensions

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event
{

    switch (event) {
        case NSStreamEventNone:
            break;
        case NSStreamEventOpenCompleted:
        {
            if (stream == [_session outputStream]) {
                self.isOutputStreamReady = YES;
            } else if (stream == [_session inputStream]) {
                self.isInputStreamReady = YES;
            }
            
            if (self.isOutputStreamReady && self.isInputStreamReady) {
                [SDLDebugTool logInfo:@"Streams Event Open" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
                
                if (self.onControlProtocol) {
                    [SDLDebugTool logInfo:@"Waiting For Protocol Index" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
                    
                    //Begin Connection Retry
//                    float randomNumber = (float)arc4random() / UINT_MAX; // between 0 and 1
//                    float randomMinMax = 0.0f + (0.5f-0.0f)*randomNumber; // between Min (0.0) and Max (0.5)
                    
                    //[SDLDebugTool logInfo:[NSString stringWithFormat:@"Wait: %f", 1.5f] withType:SDLDebugType_Transport_iAP];
                    
                    //TODO:DEBUG
//                    [self performSelector:@selector(protocolIndexRestart) withObject:nil afterDelay:2.5f];

                } else {
                    [SDLDebugTool logInfo:@"Transport Ready" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
                    self.transportReady = YES;
                    [self notifyTransportConnected];
                }
                
            }
            break;
        }
        case NSStreamEventHasBytesAvailable:
            [self readDataIn];
            break;
        case NSStreamEventHasSpaceAvailable:
            break;
        case NSStreamEventErrorOccurred:
        {
            NSString *logMessage = [NSString stringWithFormat:@"Stream Error:%@", [[stream streamError] localizedDescription]];
            [SDLDebugTool logInfo:logMessage withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
            break;
        }
        case NSStreamEventEndEncountered:
        {
            if (stream == [_session outputStream]) {
                self.isOutputStreamReady = NO;
            } else if (stream == [_session inputStream]) {
                self.isInputStreamReady = NO;
            }
            
            if (!self.isOutputStreamReady && !self.isInputStreamReady) {
                [SDLDebugTool logInfo:@"Streams Event End" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
                [self disconnect];
                [self connect];
            }
            break;
        }
        default:
            break;
    }
}



#pragma mark -
#pragma mark Class Methods

- (void)setupControllerForAccessory:(EAAccessory *)accessory withProtocolString:(NSString *)protocolString
{
    self.accessory = accessory;
    self.protocolString = protocolString;
}

- (void)checkForValidConnectedAccessory {
    for (EAAccessory* accessory in [[EAAccessoryManager sharedAccessoryManager] connectedAccessories]) {
        
        [SDLDebugTool logInfo:[NSString stringWithFormat:@"Check Accessory: %@", accessory] withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];

        self.useLegacyProtocol = NO;
        
        if (self.forceLegacy) {
            self.useLegacyProtocol = YES;
        }
        else {
            for (NSString *protocolString in [accessory protocolStrings]) {
                if ([protocolString isEqualToString:LEGACY_PROTOCOL_STRING]) {
                    self.useLegacyProtocol = YES;
                }
                
                if ([protocolString isEqualToString:CONTROL_PROTOCOL_STRING]) {
                    [SDLDebugTool logInfo:[NSString stringWithFormat:@"MultiApp Sync @ %@", CONTROL_PROTOCOL_STRING] withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
                    
                    self.useLegacyProtocol = NO;
                    
                    [self setupControllerForAccessory:accessory withProtocolString:CONTROL_PROTOCOL_STRING];
                    return;
                }
            }
        }

        if (self.useLegacyProtocol) {
            [SDLDebugTool logInfo:[NSString stringWithFormat:@"Legacy Sync @ %@", LEGACY_PROTOCOL_STRING] withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
            
            [self setupControllerForAccessory:accessory withProtocolString:LEGACY_PROTOCOL_STRING];
            return;
        }
	}
}

- (void)dealloc {
    [SDLDebugTool logInfo:@"Dealloc" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
    
    [self closeSession];
    [self setupControllerForAccessory:nil withProtocolString:nil];
    
    [[NSNotificationCenter defaultCenter] removeObserver:self name:EAAccessoryDidConnectNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:EAAccessoryDidDisconnectNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
}



#pragma mark Session Control

- (void)openSession {
    if (self.accessory && self.protocolString) {
        
        self.session = [[EASession alloc] initWithAccessory:self.accessory forProtocol:self.protocolString];
        
        if (self.session) {
            [SDLDebugTool logInfo:@"Opening Streams" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
            
            [[self.session inputStream] setDelegate:self];
            [[self.session inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [[self.session inputStream] open];
            
            [[self.session outputStream] setDelegate:self];
            [[self.session outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [[self.session outputStream] open];
            
            if ([self.protocolString isEqualToString:CONTROL_PROTOCOL_STRING]) {
                self.onControlProtocol = YES;
            }
        } else {
            if ([self.protocolString isEqualToString:CONTROL_PROTOCOL_STRING]) {
                [SDLDebugTool logInfo:@"Session Not Opened (Control Protocol)" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
                
                //Begin Connection Retry
                float randomNumber = (float)arc4random() / UINT_MAX; // between 0 and 1
                float randomMinMax = 0.0f + (0.5f-0.0f)*randomNumber; // between Min (0.0) and Max (0.5)
                
                [SDLDebugTool logInfo:[NSString stringWithFormat:@"Wait: %f", randomMinMax] withType:SDLDebugType_Transport_iAP];
                [self performSelector:@selector(openSession) withObject:nil afterDelay:randomNumber];
            } else {
                [SDLDebugTool logInfo:@"Session Not Opened" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
            }
        }
    } else {
        [SDLDebugTool logInfo:@"Accessory Or Protocol String Not Set" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
    }
}

- (void)closeSession {
    if (self.session) {
        [SDLDebugTool logInfo:@"Closing Streams" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
        
        [[self.session inputStream] close];
        [[self.session inputStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [[self.session inputStream] setDelegate:nil];
        
        [[self.session outputStream] close];
        [[self.session outputStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [[self.session outputStream] setDelegate:nil];
        
        self.session = nil;
        self.writeData = nil;
        
        self.isOutputStreamReady = NO;
        self.isInputStreamReady = NO;
    }
}



#pragma mark Low Level Read/Write

// Write data to the accessory while there is space available and data to write
- (void)writeDataOut:(NSData *)dataOut {

    NSMutableData *remainder = dataOut.mutableCopy;

    while (1) {
        if (remainder.length == 0) break;

        if ( [[self.session outputStream] hasSpaceAvailable] ) {
            
            //TODO: Added for debug, issue with module
            //[NSThread sleepForTimeInterval:0.020];
            
            NSInteger bytesWritten = [[self.session outputStream] write:remainder.bytes maxLength:remainder.length];
            if (bytesWritten == -1) {
                NSLog(@"Error: %@", [[self.session outputStream] streamError]);
                break;
            }

            NSString *logMessage = [NSString stringWithFormat:@"Outgoing: (%ld)", (long)bytesWritten];
            [SDLDebugTool logInfo:logMessage
                    andBinaryData:[remainder subdataWithRange:NSMakeRange(0, bytesWritten)]
                         withType:SDLDebugType_Transport_iAP
                         toOutput:SDLDebugOutput_File];

            [remainder replaceBytesInRange:NSMakeRange(0, bytesWritten) withBytes:NULL length:0];
        }
    }

}

// Read data while there is data and space available in the input buffer
- (void)readDataIn {
    uint8_t buf[IAP_INPUT_BUFFER_SIZE];
    while ([[self.session inputStream] hasBytesAvailable])
    {
        NSInteger bytesRead = [[self.session inputStream] read:buf maxLength:IAP_INPUT_BUFFER_SIZE];

        NSData *dataIn = [NSData dataWithBytes:buf length:bytesRead];

        NSString *logMessage = [NSString stringWithFormat:@"Incoming: (%ld)", (long)bytesRead];
        [SDLDebugTool logInfo:logMessage
                andBinaryData:dataIn
                     withType:SDLDebugType_Transport_iAP
                     toOutput:SDLDebugOutput_File];

        if (bytesRead > 0) {
            // TODO: change this to ndsata parameter for consistency
            [self handleBytesReceivedFromTransport:buf length:bytesRead];
        } else {
            break;
        }
    }
}



#pragma mark -
#pragma mark Overridden Methods

- (void)handleBytesReceivedFromTransport:(Byte *)receivedBytes length:(NSInteger)receivedBytesLength {
    
    if (self.onControlProtocol){

        NSNumber *dataProtocol = [NSNumber numberWithUnsignedInt:receivedBytes[0]];
        
        [SDLDebugTool logInfo:[NSString stringWithFormat:@"Moving To Protocol Index: %@", dataProtocol] withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
        
        if ([dataProtocol isEqualToNumber:[NSNumber numberWithInt:255]]) {
            [SDLDebugTool logInfo:@"All Available Protocol Strings Are In Use" withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
            
            //FIXME: Restart but dont call back up to app or connect will keep getting called when busy...
            return;
        }
        else {
            NSString *currentProtocolString = [NSString stringWithFormat:@"com.smartdevicelink.prot%@", dataProtocol];
            
            [self closeSession];
            self.onControlProtocol = NO;

            [self setupControllerForAccessory:self.accessory withProtocolString:currentProtocolString];
            [self openSession];
        }
    }
    else {
        [super handleDataReceivedFromTransport:[NSData dataWithBytes:receivedBytes length:receivedBytesLength]];
    }
}



#pragma mark -
#pragma mark Debug Helpers

-(void) backgroundButAwake:(NSTimer*) t
{
    [SDLDebugTool logInfo:@"Still Awake..." withType:SDLDebugType_Transport_iAP toOutput:SDLDebugOutput_All toGroup:self.debugConsoleGroupName];
}

@end